add pizza bot
parent
8bc0f8cce0
commit
6d0335b8d9
@ -0,0 +1,21 @@
|
||||
package eu.jonahbauer.chat.bot.test;
|
||||
|
||||
import eu.jonahbauer.chat.bot.api.ChatBot;
|
||||
import eu.jonahbauer.chat.bot.config.BotConfig;
|
||||
import eu.jonahbauer.chat.bot.impl.ChatBotFactory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public final class ChatBotFactoryAccess {
|
||||
|
||||
private ChatBotFactoryAccess() {}
|
||||
|
||||
public static <T extends ChatBot> T create(@NotNull BotConfig config, @NotNull Supplier<T> supplier) {
|
||||
return ScopedValue.where(ChatBotFactory.BOT_CONFIG, config).get(supplier);
|
||||
}
|
||||
|
||||
public static <T extends ChatBot> T create(@NotNull Supplier<T> supplier) {
|
||||
return create(BotConfig.EMPTY, supplier);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package eu.jonahbauer.chat.bot.test;
|
||||
|
||||
import eu.jonahbauer.chat.bot.api.Chat;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MockChat implements Chat {
|
||||
private final @NotNull List<@NotNull Message> messages = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public boolean send(@NotNull String channel, @NotNull String name, @NotNull String message, boolean bottag, boolean publicId) {
|
||||
messages.add(new Message(channel, name, message, bottag, publicId));
|
||||
return true;
|
||||
}
|
||||
|
||||
public @NotNull List<@NotNull Message> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public record Message(@NotNull String channel, @NotNull String name, @NotNull String message, boolean bottag, boolean publicId) {
|
||||
public Message(@NotNull String channel, @NotNull String name, @NotNull String message) {
|
||||
this(channel, name, message, true, true);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
module eu.jonahbauer.chat.bot.api.fixtures {
|
||||
exports eu.jonahbauer.chat.bot.test;
|
||||
|
||||
requires eu.jonahbauer.chat.bot.api;
|
||||
requires eu.jonahbauer.chat.bot.config;
|
||||
requires static transitive org.jetbrains.annotations;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
plugins {
|
||||
id("chat-bot.bot-conventions")
|
||||
}
|
||||
|
||||
group = "eu.jonahbauer.chat.bots"
|
||||
|
||||
dependencies {
|
||||
implementation(project(":bot-database"))
|
||||
testImplementation(testFixtures(project(":bot-api")))
|
||||
}
|
@ -0,0 +1,692 @@
|
||||
package eu.jonahbauer.chat.bot.pizza;
|
||||
|
||||
import eu.jonahbauer.chat.bot.api.ChatBot;
|
||||
import eu.jonahbauer.chat.bot.api.Message.Post;
|
||||
import eu.jonahbauer.chat.bot.config.BotConfig;
|
||||
import eu.jonahbauer.chat.bot.database.Database;
|
||||
import eu.jonahbauer.chat.bot.database.NonUniqueResultException;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.*;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.table.Order;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.table.OrderItem;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.table.Pizza;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.table.User;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.view.OrderDetail;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.view.OrderItemDetail;
|
||||
import eu.jonahbauer.chat.bot.pizza.util.ArgumentParser;
|
||||
import eu.jonahbauer.chat.bot.pizza.util.Pair;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static eu.jonahbauer.chat.bot.pizza.util.ArgumentParser.*;
|
||||
import static eu.jonahbauer.chat.bot.pizza.util.FormatUtils.*;
|
||||
import static java.util.FormatProcessor.FMT;
|
||||
|
||||
@Slf4j
|
||||
public class PizzaBot extends ChatBot {
|
||||
private static final @NotNull ScopedValue<Double> ANNOYANCE = ScopedValue.newInstance();
|
||||
private static final @NotNull ScopedValue<User> USER = ScopedValue.newInstance();
|
||||
|
||||
private static final @NotNull BotConfig.Key.OfStringArray PRIMARY_CHANNELS = BotConfig.Key.ofStringArray("primary_channels");
|
||||
private static final @NotNull BotConfig.Key.OfString DATABASE = BotConfig.Key.ofString("database");
|
||||
|
||||
private static final @NotNull DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("HH:mm")
|
||||
.withZone(ZoneId.of("Europe/Berlin"))
|
||||
.withLocale(Locale.GERMANY);
|
||||
private static final @NotNull List<@NotNull String> ANNOYED_RESPONSES = List.of(
|
||||
"Es ist so dunkel, ich kann dich nicht hören.",
|
||||
"Jetzt schrei doch nicht so!",
|
||||
"Fresse!"
|
||||
);
|
||||
|
||||
private final @Nullable Set<@NotNull String> primaryChannels;
|
||||
private final @NotNull PizzaService service;
|
||||
|
||||
public PizzaBot() {
|
||||
super("Marek");
|
||||
this.primaryChannels = getConfig().get(PRIMARY_CHANNELS).map(Set::copyOf).orElse(null);
|
||||
this.service = new PizzaService(new Database(getConfig().require(DATABASE)));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
PizzaBot(@Nullable Set<@NotNull String> primaryChannels, @NotNull Database database) {
|
||||
super("Marek");
|
||||
this.primaryChannels = primaryChannels;
|
||||
this.service = new PizzaService(database);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMessage(@NotNull Post post) {
|
||||
var message = post.message();
|
||||
if (message.length() < 6) return;
|
||||
|
||||
var command = message.substring(0, 6);
|
||||
|
||||
var runnable = (Runnable) () -> {
|
||||
try {
|
||||
if ("!marek".equals(command) || "!Marek".equals(command)) {
|
||||
handle(post);
|
||||
} else if ("!MAREK".equals(command) && !ANNOYED_RESPONSES.isEmpty()) {
|
||||
handleAnnoyed();
|
||||
} else if ("!marek".equalsIgnoreCase(command)) {
|
||||
var uppercase = command.chars().filter(chr -> 'A' <= chr && chr <= 'Z').count();
|
||||
ScopedValue.runWhere(ANNOYANCE, uppercase / 5d, () -> handle(post));
|
||||
}
|
||||
} catch (PizzaBotException ex) {
|
||||
post(ex.getMessage());
|
||||
}
|
||||
};
|
||||
|
||||
if (post.userId() != null && post.userName() != null) {
|
||||
ScopedValue.runWhere(USER, new User(post.userId(), post.userName()), runnable);
|
||||
} else {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
private void handle(@NotNull Post post) {
|
||||
if (!isPrimaryChannel(post.channel())) {
|
||||
handleNonPrimaryChannel();
|
||||
} else {
|
||||
try {
|
||||
dispatch(ArgumentParser.parse(post.message().substring(6)));
|
||||
} catch (SQLException ex) {
|
||||
log.error("Fehler beim Verarbeiten von {}.", post, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleAnnoyed() {
|
||||
assert !ANNOYED_RESPONSES.isEmpty();
|
||||
|
||||
var idx = (int) (Math.random() * ANNOYED_RESPONSES.size());
|
||||
post(ANNOYED_RESPONSES.get(idx));
|
||||
}
|
||||
|
||||
private void handleNonPrimaryChannel() {
|
||||
assert primaryChannels != null;
|
||||
|
||||
if (primaryChannels.size() == 1) {
|
||||
post(STR."Bitte benutze den Channel \{primaryChannels.iterator().next()}.");
|
||||
} else {
|
||||
var channels = new StringBuilder();
|
||||
var it = primaryChannels.iterator();
|
||||
while (it.hasNext()) {
|
||||
var channel = it.next();
|
||||
if (!it.hasNext()) {
|
||||
channels.append(" oder ");
|
||||
} else if (!channels.isEmpty()) {
|
||||
channels.append(", ");
|
||||
}
|
||||
channels.append(channel);
|
||||
}
|
||||
post(STR."Bitte benutze einen der Channel \{channels.toString()}.");
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatch(@NotNull List<@NotNull String> args) throws SQLException {
|
||||
if (args.isEmpty()) {
|
||||
helpUser();
|
||||
return;
|
||||
}
|
||||
|
||||
var argc = args.size();
|
||||
var command = args.getFirst();
|
||||
switch (command) {
|
||||
case "--user", "-u" -> runAs(args);
|
||||
case "help" -> {
|
||||
if (argc > 1 && "--all".equals(args.get(1))) {
|
||||
helpAdmin();
|
||||
} else {
|
||||
helpUser();
|
||||
}
|
||||
}
|
||||
case "info" -> info();
|
||||
case "order" -> {
|
||||
if (argc == 1) throw new PizzaBotException("too few arguments");
|
||||
switch (args.get(1)) {
|
||||
case "revoke" -> {
|
||||
if (argc == 2) orderRevoke();
|
||||
else if (argc != 3) throw new PizzaBotException("too many arguments");
|
||||
else switch (args.get(2)) {
|
||||
case "--all" -> orderRevokeAll();
|
||||
case String s when isLong(s) -> orderRevoke(toLong(s));
|
||||
case String name -> orderRevoke(name);
|
||||
}
|
||||
}
|
||||
case "list" -> {
|
||||
if (argc == 2) orderList();
|
||||
// else if (argc == 3 && "--all".equals(args.get(2))) orderListAll();
|
||||
else throw new PizzaBotException("invalid command");
|
||||
}
|
||||
case "add" -> {
|
||||
if (argc < 4) throw new PizzaBotException("too few arguments");
|
||||
else if (argc > 4) throw new PizzaBotException("too many arguments");
|
||||
orderAdd(args.get(2), args.get(3));
|
||||
}
|
||||
case String name when argc == 2 -> order(name, null);
|
||||
case String name -> order(name, String.join(" ", args.subList(2, args.size())));
|
||||
}
|
||||
}
|
||||
case "summary" -> {
|
||||
if (argc == 1) summary();
|
||||
else if (argc == 2 && "--all".equals(args.get(1))) summaryAll();
|
||||
else if (argc == 2) summary(args.get(1));
|
||||
else throw new PizzaBotException("invalid command");
|
||||
}
|
||||
case "check" -> {
|
||||
if (argc == 1) check();
|
||||
else if (argc == 2 && "--all".equals(args.get(1))) checkAll();
|
||||
else if (argc == 2) check(args.get(1));
|
||||
else throw new PizzaBotException("invalid command");
|
||||
}
|
||||
case "pay" -> {
|
||||
if (argc == 1) throw new PizzaBotException("missing argument");
|
||||
else if (argc == 2) pay(toDouble(args.get(1)));
|
||||
else if (argc == 3) pay(args.get(1), toDouble(args.get(2)));
|
||||
else throw new PizzaBotException("too many arguments");
|
||||
}
|
||||
case "payment" -> {
|
||||
if (argc == 1) throw new PizzaBotException("too few arguments");
|
||||
switch (args.get(1)) {
|
||||
case "confirm" -> {
|
||||
if (argc == 2) throw new PizzaBotException("too few arguments");
|
||||
else if (argc == 3 && "--all".equals(args.get(2))) throw new PizzaBotException("missing arguments");
|
||||
else if (argc == 3) paymentConfirm(toLong(args.get(2)), null);
|
||||
else if (argc == 4 && "--all".equals(args.get(2))) paymentConfirmAll(args.get(3));
|
||||
else if (argc == 4) paymentConfirm(toLong(args.get(2)), args.get(3));
|
||||
else throw new PizzaBotException("invalid command");
|
||||
}
|
||||
case "void" -> {
|
||||
if (argc == 2) throw new PizzaBotException("too few arguments");
|
||||
else if (argc == 3) paymentVoid(toLong(args.get(2)), null);
|
||||
else if (argc == 4) paymentVoid(toLong(args.get(2)), args.get(3));
|
||||
else throw new PizzaBotException("invalid command");
|
||||
}
|
||||
default -> throw new PizzaBotException("invalid command");
|
||||
}
|
||||
}
|
||||
default -> throw new PizzaBotException("Unbekannter Befehl.");
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull User getUser() {
|
||||
if (USER.isBound()) {
|
||||
return USER.get();
|
||||
} else {
|
||||
throw new PizzaBotException("Diese Aktion kann nur mit öffentlicher ID ausgeführt werden.");
|
||||
}
|
||||
}
|
||||
|
||||
//<editor-fold desc="Admin" defaultstate="collapsed">
|
||||
private void runAs(@NotNull List<String> args) throws SQLException {
|
||||
// TODO
|
||||
if (true) throw new PizzaBotException("nicht implementiert");
|
||||
|
||||
if (args.size() < 2) throw new PizzaBotException("missing argument");
|
||||
|
||||
var username = args.get(1);
|
||||
// var user = service.getUserByEventAndName(, username);
|
||||
//
|
||||
// ScopedValue.runWhere(USER, user, () -> {
|
||||
// try {
|
||||
// dispatch(args.subList(2, args.size()));
|
||||
// } catch (SQLException ex) {
|
||||
// throw Lombok.sneakyThrow(ex);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// if (false) throw new SQLException();
|
||||
}
|
||||
|
||||
private void orderAdd(@NotNull String targetOrder, @NotNull String newUserName) throws SQLException {
|
||||
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||
var newUser = service.getUserByName(newUserName);
|
||||
|
||||
OrderItem item;
|
||||
Pizza pizza;
|
||||
List<UserWithPayment> users;
|
||||
try {
|
||||
var targetId = Long.parseLong(targetOrder);
|
||||
var detail = service.getItemById(targetId)
|
||||
.orElseThrow(() -> new PizzaBotException(STR."Die Bestellung mit Nummer \{targetId} existiert nicht."));
|
||||
item = detail.item();
|
||||
pizza = detail.pizza();
|
||||
users = detail.users();
|
||||
} catch (NumberFormatException _) {
|
||||
var user = service.getUserByEventAndName(order.eventId(), targetOrder);
|
||||
try {
|
||||
item = service.getItemByUser(order.id(), user.id())
|
||||
.orElseThrow(() -> new PizzaBotException(STR."\{user.username()} hat keine Bestellung aufgegeben."));
|
||||
pizza = service.getPizzaById(item.pizzaId()).orElseThrow();
|
||||
users = service.getUsersByItem(item);
|
||||
} catch (NonUniqueResultException ex) {
|
||||
throw new PizzaBotException(STR."\{user.username()} hat mehr als eine Bestellung aufgegeben. Bitte gibt die Positionsnummer an.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
var payed = users.stream().filter(UserWithPayment::payed).map(UserWithPayment::username).toList();
|
||||
if (!payed.isEmpty()) {
|
||||
throw new PizzaBotException(STR."\{join(payed)} \{payed.size() == 1 ? "hat" : "haben"} schon bezahlt.");
|
||||
}
|
||||
|
||||
if (users.stream().anyMatch(u -> u.id() == newUser.id())) {
|
||||
throw new PizzaBotException(STR."\{newUser.username()} nimmt bereits an dieser Bestellung teil.");
|
||||
}
|
||||
|
||||
service.add(item, newUser, false);
|
||||
post(STR."\{newUser.username()} wurde zur Bestellung \{item.id()} (\{pizza.name()}) von \{join(users, UserWithPayment::username)} hinzugefügt.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt eine Übersicht alle Bestellungen während der Veranstaltung.
|
||||
*/
|
||||
private void summaryAll() throws SQLException {
|
||||
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||
summary0(order.eventName(), service.getItemDetailByEvent(order.eventId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt eine Übersicht der Positionen der laufenden Bestellung.
|
||||
*/
|
||||
private void summary() throws SQLException {
|
||||
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||
summary0(order.name(), service.getItemDetailByOrder(order.id()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt eine Übersicht der Positionen der Bestellung mit dem angegebenen Namen.
|
||||
*/
|
||||
private void summary(@NotNull String name) throws SQLException {
|
||||
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||
var other = service.getOrderByEventAndName(order.eventId(), name);
|
||||
summary0(other.name(), service.getItemDetailByOrder(other.id()));
|
||||
}
|
||||
|
||||
private void summary0(@NotNull String name, @NotNull Collection<OrderItemDetail> details) {
|
||||
var items = details.stream().collect(Collectors.groupingBy(
|
||||
detail -> Pair.of(detail.pizza(), detail.notes()),
|
||||
() -> new TreeMap<>(Comparator.comparing(Pair::first)),
|
||||
Collectors.collectingAndThen(
|
||||
Collectors.groupingBy(
|
||||
OrderItemDetail::id,
|
||||
Collectors.mapping(OrderItemDetail::userName, Collectors.joining("+"))
|
||||
),
|
||||
Map::values
|
||||
)
|
||||
));
|
||||
var lines = new ArrayList<String>(items.size() + 2);
|
||||
lines.add("Übersicht " + name);
|
||||
lines.add("");
|
||||
if (items.isEmpty()) {
|
||||
lines.add("(keine Einträge)");
|
||||
} else {
|
||||
items.forEach((item, users) -> {
|
||||
var counts = users.stream().collect(Collectors.groupingBy(
|
||||
Function.identity(),
|
||||
Collectors.counting()
|
||||
));
|
||||
var usersString = counts.entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.map(entry -> entry.getValue() == 1 ? entry.getKey() : entry.getValue() + "x " + entry.getKey())
|
||||
.collect(Collectors.joining(", "));
|
||||
var pizza = item.first().name().toUpperCase(Locale.ROOT);
|
||||
var notes = item.second() != null ? " " + item.second() : "";
|
||||
lines.add(STR."\{tabular(users.size())}x \{pizza}\{notes} (\{usersString})");
|
||||
});
|
||||
}
|
||||
post(String.join("\n", lines));
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt die zusammengefasste Rechnung für alle Bestellungen während der Veranstaltung.
|
||||
*/
|
||||
private void checkAll() throws SQLException {
|
||||
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||
var details = service.getItemDetailByEvent(order.eventId());
|
||||
|
||||
var tips = details.stream().collect(Collectors.groupingBy(
|
||||
OrderItemDetail::order,
|
||||
Collectors.collectingAndThen(Collectors.toList(), list -> calculateTip(list.getFirst().orderPayment(), list))
|
||||
));
|
||||
|
||||
var items = details.stream().collect(Collectors.groupingBy(
|
||||
OrderItemDetail::user,
|
||||
Collectors.reducing(Receipt.empty(), item -> item.receipt(tips.get(item.order())), Receipt::sum)
|
||||
));
|
||||
check0(order.eventName(), tips.values().stream().anyMatch(tip -> tip > 0), items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt die Rechnung für der laufenden Bestellung.
|
||||
*/
|
||||
private void check() throws SQLException {
|
||||
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||
check0(order.order());
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt die Rechnung für die Bestellung mit dem angegebenen Namen.
|
||||
*/
|
||||
private void check(@NotNull String name) throws SQLException {
|
||||
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||
var other = service.getOrderByEventAndName(order.eventId(), name);
|
||||
check0(other);
|
||||
}
|
||||
|
||||
private static double calculateTip(@Nullable Double payment, List<@NotNull OrderItemDetail> details) {
|
||||
var total = details.stream().mapToDouble(OrderItemDetail::userPrice).sum();
|
||||
return payment == null ? 0 : payment / total;
|
||||
}
|
||||
|
||||
private void check0(@NotNull Order order) throws SQLException {
|
||||
var details = service.getItemDetailByOrder(order.id());
|
||||
|
||||
var tip = calculateTip(order.payment(), details);
|
||||
var items = details.stream().collect(Collectors.groupingBy(
|
||||
OrderItemDetail::user,
|
||||
Collectors.reducing(Receipt.empty(), item -> item.receipt(tip), Receipt::sum)
|
||||
));
|
||||
check0(order.name(), order.payment() != null, items);
|
||||
}
|
||||
|
||||
private void check0(@NotNull String name, boolean tipped, @NotNull Map<User, @NotNull Receipt> items) {
|
||||
var lines = new ArrayList<String>();
|
||||
lines.add("Rechnung für " + name + (tipped ? " (mit Trinkgeld)" : ""));
|
||||
lines.add("alle Angaben ohne Gewähr");
|
||||
lines.add("");
|
||||
if (items.isEmpty()) {
|
||||
lines.add("(keine Einträge)");
|
||||
} else {
|
||||
var toString = (BiConsumer<String, Receipt>) (user, receipt) -> {
|
||||
if (receipt.payed()) {
|
||||
lines.add(strikethrough(user + ": " + receipt.totalAsString()));
|
||||
} else if (receipt.unpayed()) {
|
||||
lines.add(user + ": " + receipt.totalAsString());
|
||||
} else {
|
||||
lines.add(user + ": " + strikethrough(receipt.totalAsString()) + " " + receipt.pendingAsString());
|
||||
}
|
||||
};
|
||||
items.forEach((user, receipt) -> toString.accept(user.username(), receipt));
|
||||
lines.add("");
|
||||
toString.accept("Gesamt", items.values().stream().reduce(Receipt::sum).orElseThrow());
|
||||
}
|
||||
post(String.join("\n", lines));
|
||||
}
|
||||
|
||||
private void pay(double amount) throws SQLException {
|
||||
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||
service.save(order.order().withPayment(amount));
|
||||
post(FMT."Zahlung von %.2f\{amount} für \{order.name()} wurde gespeichert");
|
||||
}
|
||||
|
||||
private void pay(@NotNull String name, double amount) throws SQLException {
|
||||
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||
var other = service.getOrderByEventAndName(order.eventId(), name);
|
||||
service.save(other.withPayment(amount));
|
||||
post(FMT."Zahlung von %.2f\{amount} für \{other.name()} wurde gespeichert");
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Beginnt eine neue Bestellung.
|
||||
// * @param restaurant die Restaurant-ID
|
||||
// */
|
||||
// private void orderStart(@NotNull String restaurant, @NotNull String deadline) {
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Beginnt eine neue Bestellung.
|
||||
// * @param restaurantId die Restaurant-ID
|
||||
// */
|
||||
// private void orderStart(long restaurantId, @NotNull String deadline) {
|
||||
// var restaurant = restaurantRepository.getById(restaurantId)
|
||||
// .orElseThrow(() -> new BreakException(STR."Das Restaurant mit der ID \{restaurantId} existiert nicht."));
|
||||
// var order = new Order(-1, , restaurantId, , , null);
|
||||
// orderRepository.save(order);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Beendet eine Bestellung.
|
||||
// * @param payment der gezahlte Betrag inkl. Trinkgeld
|
||||
// */
|
||||
// private void orderFinish(double payment) {
|
||||
// var order = service.getCurrentOrderAsAdmin();
|
||||
// orderRepository.save(order.withPayment(payment));
|
||||
// check(order.id());
|
||||
// }
|
||||
|
||||
/**
|
||||
* Bestätigt den Zahlungseingang für alle ausstehenden Zahlungen der laufenden Veranstaltung eines Benutzers.
|
||||
*/
|
||||
private void paymentConfirmAll(@NotNull String name) throws SQLException {
|
||||
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||
var user = service.getUserByEventAndName(order.eventId(), name);
|
||||
var count = service.paymentByEventAndUser(order.eventId(), user.id(), true);
|
||||
post(STR."\{count} ausstehenden Zahlungen für \{user.username()} wurden bestätigt.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Bestätigt den Zahlungseingang für eine Bestellung.
|
||||
*/
|
||||
private void paymentConfirm(long itemId, @Nullable String username) throws SQLException {
|
||||
payment0(itemId, username, true, (pizza, user) -> STR."Zahlungseingang für \{pizza} (\{user}) wurde bestätigt.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Widerruft die Bestätigung eines Zahlungseingangs.
|
||||
*/
|
||||
private void paymentVoid(long itemId, @Nullable String username) throws SQLException {
|
||||
payment0(itemId, username, false, (pizza, user) -> STR."Zahlungseingang für \{pizza} (\{user}) wurde bestätigt.");
|
||||
}
|
||||
|
||||
private void payment0(long itemId, @Nullable String username, boolean payed, @NotNull BinaryOperator<String> message) throws SQLException {
|
||||
var _ = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||
var summary = service.getItemById(itemId)
|
||||
.orElseThrow(() -> new PizzaBotException(STR."Die Bestellung mit Nummer \{itemId} existiert nicht."));
|
||||
if (username != null) {
|
||||
var users = summary.users().stream().filter(u -> u.username().startsWith(username)).toList();
|
||||
if (users.isEmpty()) {
|
||||
throw new PizzaBotException("Der Benutzer existiert nicht oder hat nicht an der Bestellung teilgenommen.");
|
||||
} else if (users.size() > 1) {
|
||||
throw new PizzaBotException("Der Benutzer ist nicht eindeutig.");
|
||||
} else {
|
||||
var user = users.getFirst();
|
||||
service.paymentByItemAndUser(itemId, user.id(), true);
|
||||
post(message.apply(summary.pizza().name(), user.username()));
|
||||
}
|
||||
} else {
|
||||
service.paymentByItem(summary.item().id(), payed);
|
||||
post(message.apply(summary.pizza().name(), join(" + ", summary.users(), UserWithPayment::username)));
|
||||
}
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
//<editor-fold desc="User" defaultstate="collapsed">
|
||||
/**
|
||||
* Zeigt Informationen zur aktuell laufenden Bestellung.
|
||||
*/
|
||||
private void info() throws SQLException {
|
||||
var order = service.getCurrentOrder();
|
||||
var result = service.getRestaurantWithPizzas(order.restaurantId());
|
||||
var restaurant = result.first();
|
||||
var pizzas = result.second();
|
||||
|
||||
var lines = new ArrayList<String>();
|
||||
lines.add(STR."Pizzabestellung bei \{restaurant.name()} in \{restaurant.city()}");
|
||||
lines.add(STR."Bestellannahmeschluss: \{DATE_FORMATTER.format(order.deadline())}");
|
||||
pizzas.stream().map(Pizza::toPrettyString).forEach(lines::add);
|
||||
post(String.join("\n", lines));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listet die eigenen Bestellungen auf.
|
||||
*/
|
||||
private void orderList() throws SQLException {
|
||||
var user = getUser();
|
||||
var current = service.getCurrentOrder();
|
||||
|
||||
var itemsByOrder = service.getItemDetailByEventAndUser(current.eventId(), user.id()).stream().collect(Collectors.groupingBy(
|
||||
OrderItemDetail::order,
|
||||
TreeMap::new,
|
||||
Collectors.toUnmodifiableList()
|
||||
));
|
||||
|
||||
var lines = new ArrayList<String>();
|
||||
lines.add("Bestellungen von " + user.username());
|
||||
if (itemsByOrder.isEmpty()) lines.add("(keine Einträge)");
|
||||
itemsByOrder.forEach((order, items) -> {
|
||||
lines.add("");
|
||||
lines.add(order.name());
|
||||
items.stream().map(OrderItemDetail::toPrettyString).forEach(lines::add);
|
||||
});
|
||||
post(String.join("\n", lines));
|
||||
}
|
||||
|
||||
/**
|
||||
* Storniert die einzige eigene Bestellung.
|
||||
*/
|
||||
private void orderRevoke() throws SQLException {
|
||||
var order = service.getCurrentOrder().checkDeadline();
|
||||
var user = getUser();
|
||||
|
||||
try {
|
||||
var item = service.getItemByUser(order.id(), user.id());
|
||||
if (item.isEmpty()) {
|
||||
post("Du hast keine Bestellung aufgegeben.");
|
||||
} else {
|
||||
orderRevoke0(item.get());
|
||||
}
|
||||
} catch (NonUniqueResultException ex) {
|
||||
post("Du hast mehr als eine Bestellung aufgegeben. Bitte gib die Positionsnummer oder den Namen der Pizza an.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Storniert die angegebene, eigene Bestellung.
|
||||
*/
|
||||
private void orderRevoke(long itemId) throws SQLException {
|
||||
var order = service.getCurrentOrder().checkDeadline();
|
||||
var item = service.getItemById(itemId);
|
||||
if (item.isEmpty() || item.get().item().orderId() != order.id()) {
|
||||
post(STR."Die Position mit Nummer \{itemId} existiert nicht.");
|
||||
} else {
|
||||
orderRevoke0(item.get().checkUserAccess(getUser()).item());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Storniert die eigene Bestellung der angegebene Pizza.
|
||||
*/
|
||||
private void orderRevoke(@NotNull String name) throws SQLException {
|
||||
var order = service.getCurrentOrder().checkDeadline();
|
||||
var user = getUser();
|
||||
try {
|
||||
var item = service.getItemByPizzaName(order.id(), user.id(), name);
|
||||
if (item.isEmpty()) {
|
||||
post("Du hast keine solche Pizza bestellt.");
|
||||
} else {
|
||||
orderRevoke0(item.get());
|
||||
}
|
||||
} catch (NonUniqueResultException ex) {
|
||||
post("Du hast mehr als eine solche Pizza bestellt. Bitte gib die Positionsnummer an.");
|
||||
}
|
||||
}
|
||||
|
||||
private void orderRevoke0(@NotNull OrderItem item) throws SQLException {
|
||||
var pizza = service.getPizzaById(item.pizzaId()).orElseThrow();
|
||||
service.delete(item);
|
||||
post(STR."Die Position \{item.id()} (\{pizza.name()}) wurde storniert.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Storniert alle eigenen Bestellungen.
|
||||
*/
|
||||
private void orderRevokeAll() throws SQLException {
|
||||
var order = service.getCurrentOrder().checkDeadline();
|
||||
var user = getUser();
|
||||
var items = service.getItemsByUser(order.id(), user.id());
|
||||
if (items.isEmpty()) {
|
||||
post("Du hast keine Bestellung aufgegeben.");
|
||||
} else {
|
||||
for (var item : items) {
|
||||
service.delete(item);
|
||||
}
|
||||
post("Alle deine Bestellungen wurden storniert.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bestellt die Pizza mit dem angegebenen Namen.
|
||||
*/
|
||||
private void order(@NotNull String nameOrNummer, @Nullable String notes) throws SQLException {
|
||||
var order = service.getCurrentOrder().checkDeadline();
|
||||
var pizza = service.getPizza(order.restaurantId(), nameOrNummer);
|
||||
order0(order, pizza, notes);
|
||||
}
|
||||
|
||||
private void order0(@NotNull OrderDetail order, @NotNull Pizza pizza, @Nullable String notes) throws SQLException {
|
||||
var id = service.order(order, getUser(), pizza, notes);
|
||||
post(STR."Pizza \{pizza.name()} wurde zur Bestellung hinzugefügt (Positionsnummer \{id}).");
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
private void helpUser() {
|
||||
post("""
|
||||
Marek is back!
|
||||
!marek info - Menü anzeigen
|
||||
|
||||
Bestellung
|
||||
!marek order (NUMMER | NAME) [NOTIZEN] - Pizza bestellen
|
||||
!marek order revoke [NUMMER | NAME | --all] - Bestellung(en) stornieren
|
||||
!marek order list - Bestellungen auflisten
|
||||
""");
|
||||
}
|
||||
|
||||
private void helpAdmin() {
|
||||
post("""
|
||||
!marek --user USER order (NUMMER | NAME) [NOTIZEN] - Pizza bestellen
|
||||
!marek --user USER order revoke [NUMMER | NAME | --all] - Bestellung(en) stornieren
|
||||
!marek --user USER order list - Bestellungen auflisten
|
||||
|
||||
!marek summary [NAME | --all] - Bestellübersicht
|
||||
!marek check [NAME | --all] - Rechnung anzeigen
|
||||
!marek pay [NAME] AMOUNT - Rechnungsbetrag angeben
|
||||
!marek order list --all - Alle Bestellungen auflisten
|
||||
!marek order add (NUMMER | NAME) USER - Fügt USER zur Bestellung mit Nummer NUMMER / von NAME hinzu
|
||||
|
||||
!marek payment confirm NUMMER [USER] - Zahlungseingang bestätigen
|
||||
!marek payment confirm --all USER - Zahlungseingang bestätigen
|
||||
!marek payment void NUMMER [USER] - Widerruf der Bestätigung des Zahlungseingangs
|
||||
""");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void post(@NotNull String message, @Nullable String name, @Nullable Boolean bottag, @Nullable Boolean publicId) {
|
||||
var annoyance = ANNOYANCE.orElse(0d);
|
||||
if (annoyance == 0) {
|
||||
super.post(message, name, bottag, publicId);
|
||||
} else if (annoyance == 1) {
|
||||
super.post(message.toUpperCase(Locale.ROOT), name, bottag, publicId);
|
||||
} else {
|
||||
var infuriatedMessage = message.codePoints()
|
||||
.map(chr -> Math.random() < annoyance ? Character.toUpperCase(chr) : chr)
|
||||
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
|
||||
.toString();
|
||||
super.post(infuriatedMessage, name, bottag, publicId);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPrimaryChannel(@NotNull String channel) {
|
||||
return primaryChannels == null || primaryChannels.contains(channel);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package eu.jonahbauer.chat.bot.pizza;
|
||||
|
||||
import lombok.experimental.StandardException;
|
||||
|
||||
@StandardException
|
||||
public class PizzaBotException extends RuntimeException {
|
||||
}
|
@ -0,0 +1,298 @@
|
||||
package eu.jonahbauer.chat.bot.pizza;
|
||||
|
||||
import eu.jonahbauer.chat.bot.database.Database;
|
||||
import eu.jonahbauer.chat.bot.database.NonUniqueResultException;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.*;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.table.*;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.view.OrderDetail;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.view.OrderItemDetail;
|
||||
import eu.jonahbauer.chat.bot.pizza.util.Pair;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class PizzaService {
|
||||
private final @NotNull Database database;
|
||||
|
||||
public PizzaService(@NotNull Database database) {
|
||||
this.database = Objects.requireNonNull(database);
|
||||
}
|
||||
|
||||
public @NotNull OrderDetail getCurrentOrder() throws SQLException {
|
||||
@Language("SQL")
|
||||
var query = "SELECT * FROM order_detail WHERE active = TRUE ORDER BY deadline DESC LIMIT 1";
|
||||
return database.executeUniqueQuery(OrderDetail.class, query)
|
||||
.orElseThrow(() -> new PizzaBotException("Aktuell läuft keine Pizzabestellung."));
|
||||
}
|
||||
|
||||
public @NotNull Pair<@NotNull Restaurant, @NotNull List<@NotNull Pizza>> getRestaurantWithPizzas(long restaurantId) throws SQLException {
|
||||
return database.transaction(() -> {
|
||||
var restaurant = database.findById(Restaurant.class, restaurantId).orElseThrow();
|
||||
var pizzas = database.findAllWhere(Pizza.class, Pizza.Fields.restaurantId, restaurantId);
|
||||
return Pair.of(restaurant, pizzas);
|
||||
});
|
||||
}
|
||||
|
||||
// Payment
|
||||
|
||||
public void save(@NotNull Order order) throws SQLException {
|
||||
database.update(order);
|
||||
}
|
||||
|
||||
public @NotNull User getUserByName(@NotNull String name) throws SQLException {
|
||||
try {
|
||||
@Language("SQL")
|
||||
var query = """
|
||||
SELECT "user".* FROM "user"
|
||||
WHERE "user"."username" LIKE ?
|
||||
""";
|
||||
return database.executeUniqueQuery(User.class, query, stmt -> stmt.setString(1, name + "%"))
|
||||
.orElseThrow(() -> new PizzaBotException("Der Benutzer existiert nicht."));
|
||||
} catch (NonUniqueResultException _) {
|
||||
throw new PizzaBotException("Der Benutzer ist nicht eindeutig.");
|
||||
}
|
||||
}
|
||||
|
||||
public @NotNull User getUserByEventAndName(long eventId, @NotNull String name) throws SQLException {
|
||||
try {
|
||||
@Language("SQL")
|
||||
var query = """
|
||||
SELECT "user".* FROM "user"
|
||||
WHERE "user"."username" LIKE ?
|
||||
AND EXISTS(
|
||||
SELECT * FROM "order_item_to_user"
|
||||
JOIN "order_item" ON "order_item_to_user"."order_item_id" = "order_item"."id"
|
||||
JOIN "order" ON "order_item"."order_id" = "order"."id"
|
||||
WHERE "order_item_to_user"."user_id" = "user"."id"
|
||||
AND "order"."event_id" = ?
|
||||
)
|
||||
""";
|
||||
return database.executeUniqueQuery(User.class, query, stmt -> {
|
||||
stmt.setString(1, name + "%");
|
||||
stmt.setLong(2, eventId);
|
||||
}).orElseThrow(() -> new PizzaBotException("Der Benutzer existiert nicht oder hat an keiner Pizzabestellung teilgenommen."));
|
||||
} catch (NonUniqueResultException _) {
|
||||
throw new PizzaBotException("Der Benutzer ist nicht eindeutig.");
|
||||
}
|
||||
}
|
||||
|
||||
public void paymentByItem(long itemId, boolean payed) throws SQLException {
|
||||
database.executeUpdate("UPDATE order_item_to_user SET payed = ? WHERE order_item_id = ?", stmt -> {
|
||||
stmt.setBoolean(1, payed);
|
||||
stmt.setLong(2, itemId);
|
||||
});
|
||||
}
|
||||
|
||||
public int paymentByEventAndUser(long eventId, long userId, boolean payed) throws SQLException {
|
||||
@Language("SQL")
|
||||
var query = """
|
||||
UPDATE "order_item_to_user"
|
||||
SET "payed" = ?
|
||||
WHERE "order_item_to_user"."user_id" = ?
|
||||
AND EXISTS(
|
||||
SELECT *
|
||||
FROM "order_item"
|
||||
JOIN "order" ON "order_item"."order_id" = "order"."id"
|
||||
WHERE "order"."event_id" = ? AND "order_item"."id" = "order_item_to_user"."order_item_id"
|
||||
)
|
||||
""";
|
||||
return database.executeUpdate(query, stmt -> {
|
||||
stmt.setBoolean(1, payed);
|
||||
stmt.setLong(2, userId);
|
||||
stmt.setLong(3, eventId);
|
||||
});
|
||||
}
|
||||
|
||||
public void paymentByItemAndUser(long itemId, long userId, boolean payed) throws SQLException {
|
||||
database.executeUpdate("UPDATE order_item_to_user SET payed = ? WHERE order_item_id = ? AND user_id = ?", stmt -> {
|
||||
stmt.setBoolean(1, payed);
|
||||
stmt.setLong(2, itemId);
|
||||
stmt.setLong(3, userId);
|
||||
});
|
||||
}
|
||||
|
||||
// Order
|
||||
|
||||
public @NotNull Optional<Pizza> getPizzaById(long id) throws SQLException {
|
||||
return database.findById(Pizza.class, id);
|
||||
}
|
||||
|
||||
public @NotNull Pizza getPizza(long restaurantId, @NotNull String nameOrNummer) throws SQLException {
|
||||
try {
|
||||
var nummer = Integer.parseInt(nameOrNummer);
|
||||
return getPizzaByNummer(restaurantId, nummer);
|
||||
} catch (NumberFormatException _) {
|
||||
return getPizzaByName(restaurantId, nameOrNummer);
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull Pizza getPizzaByNummer(long restaurantId, int nummer) throws SQLException {
|
||||
@Language("SQL")
|
||||
var query = "SELECT * FROM pizza WHERE restaurant_id = ? AND number = ?";
|
||||
return database.executeUniqueQuery(Pizza.class, query, stmt -> {
|
||||
stmt.setLong(1, restaurantId);
|
||||
stmt.setInt(2, nummer);
|
||||
}).orElseThrow(() -> new PizzaBotException("Diese Pizza kenne ich nicht."));
|
||||
}
|
||||
|
||||
private @NotNull Pizza getPizzaByName(long restaurantId, @NotNull String name) throws SQLException {
|
||||
try {
|
||||
@Language("SQL")
|
||||
var query = "SELECT * FROM pizza WHERE restaurant_id = ? AND name LIKE ?";
|
||||
return database.executeUniqueQuery(Pizza.class, query, stmt -> {
|
||||
stmt.setLong(1, restaurantId);
|
||||
stmt.setString(2, name + "%");
|
||||
}).orElseThrow(() -> new PizzaBotException("Diese Pizza kenne ich nicht."));
|
||||
} catch (NonUniqueResultException _) {
|
||||
throw new PizzaBotException("Diese Pizza kenne ich nicht.");
|
||||
}
|
||||
}
|
||||
|
||||
public long order(@NotNull OrderDetail order, @NotNull User user, @NotNull Pizza pizza, @Nullable String notes) throws SQLException {
|
||||
return database.transaction(() -> {
|
||||
try {
|
||||
database.insert(user);
|
||||
} catch (SQLException _) {}
|
||||
|
||||
var item = new OrderItem(-1, order.id(), pizza.id(), notes);
|
||||
var itemId = database.insert(item);
|
||||
|
||||
var mapping = new OrderItemToUser(itemId, user.id(), false);
|
||||
database.insert(mapping);
|
||||
|
||||
return itemId;
|
||||
});
|
||||
}
|
||||
|
||||
public void add(@NotNull OrderItem item, @NotNull User user, boolean payed) throws SQLException {
|
||||
database.insert(new OrderItemToUser(item.id(), user.id(), payed));
|
||||
}
|
||||
|
||||
public @NotNull List<@NotNull UserWithPayment> getUsersByItem(@NotNull OrderItem item) throws SQLException {
|
||||
@Language("SQL")
|
||||
var query = """
|
||||
SELECT
|
||||
"user"."id" AS "id",
|
||||
"user"."username" AS "username",
|
||||
"order_item_to_user"."order_item_id" AS "order_item_id",
|
||||
"order_item_to_user"."payed" AS "payed"
|
||||
FROM "user"
|
||||
JOIN "order_item_to_user" ON "user"."id" = "order_item_to_user"."user_id"
|
||||
WHERE "order_item_to_user"."order_item_id" = ?
|
||||
""";
|
||||
return database.executeQuery(UserWithPayment.class, query, stmt -> stmt.setLong(1, item.id()));
|
||||
}
|
||||
|
||||
// Revoke
|
||||
|
||||
public @NotNull Optional<OrderItemSummary> getItemById(long itemId) throws SQLException {
|
||||
return database.transaction(() -> {
|
||||
var item = database.findById(OrderItem.class, itemId);
|
||||
if (item.isEmpty()) return Optional.empty();
|
||||
|
||||
var pizza = database.findById(Pizza.class, item.get().pizzaId()).orElseThrow();
|
||||
var users = getUsersByItem(item.get());
|
||||
|
||||
return Optional.of(new OrderItemSummary(item.get(), pizza, users));
|
||||
});
|
||||
}
|
||||
|
||||
public @NotNull Optional<OrderItem> getItemByPizzaName(long orderId, long userId, @NotNull String name) throws SQLException {
|
||||
@Language("SQL")
|
||||
var query = """
|
||||
SELECT "order_item".*
|
||||
FROM "order_item"
|
||||
JOIN "pizza" ON "order_item"."pizza_id" = "pizza"."id"
|
||||
JOIN "order_item_to_user" ON "order_item"."id" = "order_item_to_user"."order_item_id"
|
||||
WHERE "order_item"."order_id" = ? AND "order_item_to_user"."user_id" = ? AND "pizza"."name" LIKE ?
|
||||
""";
|
||||
return database.executeUniqueQuery(OrderItem.class, query, stmt -> {
|
||||
stmt.setLong(1, orderId);
|
||||
stmt.setLong(2, userId);
|
||||
stmt.setString(3, name + "%");
|
||||
});
|
||||
}
|
||||
|
||||
public @NotNull Optional<OrderItem> getItemByUser(long orderId, long userId) throws SQLException {
|
||||
@Language("SQL")
|
||||
var query = """
|
||||
SELECT "order_item".*
|
||||
FROM "order_item"
|
||||
JOIN "order_item_to_user" ON "order_item"."id" = "order_item_to_user"."order_item_id"
|
||||
WHERE "order_item"."order_id" = ? AND "order_item_to_user"."user_id" = ?
|
||||
""";
|
||||
return database.executeUniqueQuery(OrderItem.class, query, stmt -> {
|
||||
stmt.setLong(1, orderId);
|
||||
stmt.setLong(2, userId);
|
||||
});
|
||||
}
|
||||
|
||||
public @NotNull List<@NotNull OrderItem> getItemsByUser(long orderId, long userId) throws SQLException {
|
||||
@Language("SQL")
|
||||
var query = """
|
||||
SELECT "order_item".*
|
||||
FROM "order_item"
|
||||
JOIN "order_item_to_user" ON "order_item"."id" = "order_item_to_user"."order_item_id"
|
||||
WHERE "order_item"."order_id" = ? AND "order_item_to_user"."user_id" = ?
|
||||
""";
|
||||
return database.executeQuery(OrderItem.class, query, stmt -> {
|
||||
stmt.setLong(1, orderId);
|
||||
stmt.setLong(2, userId);
|
||||
});
|
||||
}
|
||||
|
||||
public boolean delete(@NotNull OrderItem item) throws SQLException {
|
||||
return database.delete(OrderItem.class, item.id());
|
||||
}
|
||||
|
||||
// Summary
|
||||
|
||||
public @NotNull Order getOrderByEventAndName(long eventId, @NotNull String name) throws SQLException {
|
||||
try {
|
||||
@Language("SQL")
|
||||
var query = """
|
||||
SELECT "order".*
|
||||
FROM "order"
|
||||
WHERE "order"."event_id" = ? AND "order"."name" LIKE ?
|
||||
""";
|
||||
|
||||
return database.executeUniqueQuery(Order.class, query, stmt -> {
|
||||
stmt.setLong(1, eventId);
|
||||
stmt.setString(2, name + "%");
|
||||
}).orElseThrow(() -> new PizzaBotException("Diese Bestellung kenne ich nicht."));
|
||||
} catch (NonUniqueResultException ex) {
|
||||
throw new PizzaBotException("Die Bestellung ist nicht eindeutig.");
|
||||
}
|
||||
}
|
||||
|
||||
// Order List
|
||||
|
||||
public @NotNull List<@NotNull OrderItemDetail> getItemDetailByEvent(long eventId) throws SQLException {
|
||||
return database.findAllWhere(OrderItemDetail.class, OrderItemDetail.Fields.eventId, eventId);
|
||||
}
|
||||
|
||||
public @NotNull List<@NotNull OrderItemDetail> getItemDetailByOrder(long orderId) throws SQLException {
|
||||
return database.findAllWhere(OrderItemDetail.class, OrderItemDetail.Fields.orderId, orderId);
|
||||
}
|
||||
|
||||
public @NotNull List<@NotNull OrderItemDetail> getItemDetailByEventAndUser(long eventId, long userId) throws SQLException {
|
||||
@Language("SQL")
|
||||
var query = """
|
||||
SELECT *
|
||||
FROM order_item_detail
|
||||
WHERE event_id = ? AND EXISTS(
|
||||
SELECT * FROM "order_item_to_user"
|
||||
WHERE "user_id" = ? AND "order_item_id" = "order_item_detail"."id"
|
||||
)
|
||||
""";
|
||||
return database.executeQuery(OrderItemDetail.class, query, stmt -> {
|
||||
stmt.setLong(1, eventId);
|
||||
stmt.setLong(2, userId);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package eu.jonahbauer.chat.bot.pizza.model;
|
||||
|
||||
import eu.jonahbauer.chat.bot.pizza.PizzaBotException;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.table.OrderItem;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.table.Pizza;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.table.User;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record OrderItemSummary(@NotNull OrderItem item, @NotNull Pizza pizza, @NotNull List<UserWithPayment> users) {
|
||||
public @NotNull OrderItemSummary checkUserAccess(@NotNull User user) {
|
||||
if (users.stream().noneMatch(u -> u.id() == user.id())) {
|
||||
throw new PizzaBotException("Du bist nicht berechtigt, diese Bestellung zu bearbeiten.");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package eu.jonahbauer.chat.bot.pizza.model;
|
||||
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
|
||||
@FieldNameConstants
|
||||
public record OrderItemToUser(long orderItemId, long userId, long parts, boolean payed) {
|
||||
public OrderItemToUser(long orderItemId, long userId, boolean payed) {
|
||||
this(orderItemId, userId, 1, payed);
|
||||
}
|
||||
|
||||
public OrderItemToUser withPayed(boolean payed) {
|
||||
return new OrderItemToUser(orderItemId, userId, parts, payed);
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package eu.jonahbauer.chat.bot.pizza.model;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static eu.jonahbauer.chat.bot.pizza.util.FormatUtils.tabular;
|
||||
|
||||
public record Receipt(double total, double tipped, double pending, double pendingTipped) {
|
||||
public Receipt(double total, boolean payed, double tip) {
|
||||
this(total, total * tip, payed ? 0 : total, payed ? 0 : total * tip);
|
||||
}
|
||||
|
||||
public boolean payed() {
|
||||
return pending == 0;
|
||||
}
|
||||
|
||||
public boolean unpayed() {
|
||||
return total == pending;
|
||||
}
|
||||
|
||||
public @NotNull String totalAsString() {
|
||||
return tabular(total, 0) + " €" + (tipped != 0 ? " (" + tabular(tipped, 0) + " €)" : "");
|
||||
}
|
||||
|
||||
public @NotNull String pendingAsString() {
|
||||
return tabular(pending, 0) + " €" + (pendingTipped != 0 ? " (" + tabular(pendingTipped, 0) + " €)" : "");
|
||||
}
|
||||
|
||||
public static @NotNull Receipt empty() {
|
||||
return new Receipt(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
public static @NotNull Receipt sum(@NotNull Receipt first, @NotNull Receipt second) {
|
||||
return new Receipt(
|
||||
first.total + second.total,
|
||||
first.tipped + second.tipped,
|
||||
first.pending + second.pending,
|
||||
first.pendingTipped + second.pendingTipped
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package eu.jonahbauer.chat.bot.pizza.model;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public record UserWithPayment(long id, @NotNull String username, long orderItemId, boolean payed) {
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package eu.jonahbauer.chat.bot.pizza.model.table;
|
||||
|
||||
import eu.jonahbauer.chat.bot.database.Entity;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@FieldNameConstants
|
||||
public record Event(long id, @NotNull String name, long adminId, boolean active) implements Entity<Event> {
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package eu.jonahbauer.chat.bot.pizza.model.table;
|
||||
|
||||
import eu.jonahbauer.chat.bot.database.Entity;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
@FieldNameConstants
|
||||
public record Order(
|
||||
long id,
|
||||
long eventId,
|
||||
long restaurantId,
|
||||
@NotNull String name,
|
||||
@NotNull Instant deadline,
|
||||
@Nullable Double payment
|
||||
) implements Entity<Order>, Comparable<Order> {
|
||||
|
||||
public Order {
|
||||
Objects.requireNonNull(deadline);
|
||||
}
|
||||
|
||||
public @NotNull Order withPayment(@Nullable Double payment) {
|
||||
return new Order(id, eventId, restaurantId, name, deadline, payment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Order o) {
|
||||
return this.deadline.compareTo(o.deadline);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package eu.jonahbauer.chat.bot.pizza.model.table;
|
||||
|
||||
import eu.jonahbauer.chat.bot.database.Entity;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@FieldNameConstants
|
||||
public record OrderItem(
|
||||
long id,
|
||||
long orderId,
|
||||
long pizzaId,
|
||||
@Nullable String notes
|
||||
) implements Entity<OrderItem> {
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package eu.jonahbauer.chat.bot.pizza.model.table;
|
||||
|
||||
import eu.jonahbauer.chat.bot.database.Entity;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static eu.jonahbauer.chat.bot.pizza.util.FormatUtils.tabular;
|
||||
|
||||
@FieldNameConstants
|
||||
public record Pizza(
|
||||
long id,
|
||||
long restaurantId,
|
||||
int number,
|
||||
@NotNull String name,
|
||||
@NotNull String ingredients,
|
||||
double price
|
||||
) implements Entity<Pizza>, Comparable<Pizza> {
|
||||
|
||||
public @NotNull String toPrettyString() {
|
||||
var number = tabular(number());
|
||||
var price = tabular(price(), 5);
|
||||
var name = name().toUpperCase(Locale.ROOT);
|
||||
var ingredients = ingredients();
|
||||
return STR."\{number} | \{price} € | \{name} \{ingredients}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Pizza o) {
|
||||
return Integer.compare(number, o.number);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package eu.jonahbauer.chat.bot.pizza.model.table;
|
||||
|
||||
import eu.jonahbauer.chat.bot.database.Entity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record Restaurant(
|
||||
long id,
|
||||
@NotNull String name,
|
||||
@NotNull String city,
|
||||
@Nullable String phone,
|
||||
@Nullable String notes
|
||||
) implements Entity<Restaurant> {
|
||||
public Restaurant {
|
||||
Objects.requireNonNull(name);
|
||||
Objects.requireNonNull(city);
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package eu.jonahbauer.chat.bot.pizza.model.table;
|
||||
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@FieldNameConstants
|
||||
public record User(long id, @NotNull String username) {
|
||||
public User {
|
||||
Objects.requireNonNull(username);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package eu.jonahbauer.chat.bot.pizza.model.view;
|
||||
|
||||
import eu.jonahbauer.chat.bot.pizza.PizzaBotException;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.table.Order;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.table.User;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public record OrderDetail(
|
||||
// Order
|
||||
long id,
|
||||
@NotNull String name,
|
||||
@NotNull Instant deadline,
|
||||
@Nullable Double payment,
|
||||
// Event
|
||||
long eventId,
|
||||
@NotNull String eventName,
|
||||
long adminId,
|
||||
// Restaurant
|
||||
long restaurantId,
|
||||
@NotNull String restaurantName
|
||||
) {
|
||||
public @NotNull Order order() {
|
||||
return new Order(id, eventId, restaurantId, name, deadline, payment);
|
||||
}
|
||||
|
||||
public @NotNull OrderDetail checkDeadline() {
|
||||
if (Instant.now().isAfter(deadline())) {
|
||||
throw new PizzaBotException("Du kannst diese Bestellung nicht mehr bearbeiten, da der Bestellannahmeschluss schon vorbei ist.");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NotNull OrderDetail checkAdminAccess(@NotNull User user) {
|
||||
if (adminId() != user.id()) {
|
||||
throw new PizzaBotException("Du bist nicht berechtigt, diese Aktion durchzuführen.");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package eu.jonahbauer.chat.bot.pizza.model.view;
|
||||
|
||||
import eu.jonahbauer.chat.bot.pizza.model.Receipt;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.table.Order;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.table.Pizza;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.table.User;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Locale;
|
||||
|
||||
import static eu.jonahbauer.chat.bot.pizza.util.FormatUtils.strikethrough;
|
||||
import static eu.jonahbauer.chat.bot.pizza.util.FormatUtils.tabular;
|
||||
|
||||
@FieldNameConstants
|
||||
public record OrderItemDetail(
|
||||
// OrderItem
|
||||
long id,
|
||||
@Nullable String notes,
|
||||
// Order
|
||||
long orderId,
|
||||
@NotNull String orderName,
|
||||
@NotNull Instant orderDeadline,
|
||||
@Nullable Double orderPayment,
|
||||
// Event
|
||||
long eventId,
|
||||
// Restaurant
|
||||
long restaurantId,
|
||||
@NotNull String restaurantName,
|
||||
// Pizza
|
||||
long pizzaId,
|
||||
int pizzaNumber,
|
||||
@NotNull String pizzaName,
|
||||
double pizzaPrice,
|
||||
// User
|
||||
long userId,
|
||||
@NotNull String userName,
|
||||
long userParts,
|
||||
boolean userPayed,
|
||||
long userCount
|
||||
) {
|
||||
public @NotNull Order order() {
|
||||
return new Order(orderId, eventId, restaurantId, orderName, orderDeadline, orderPayment);
|
||||
}
|
||||
|
||||
public @NotNull Pizza pizza() {
|
||||
return new Pizza(pizzaId, restaurantId, pizzaNumber, pizzaName, "N/A", pizzaPrice);
|
||||
}
|
||||
|
||||
public @NotNull User user() {
|
||||
return new User(userId, userName);
|
||||
}
|
||||
|
||||
public @NotNull Receipt receipt(double tip) {
|
||||
return new Receipt(userPrice(), userPayed(), tip);
|
||||
}
|
||||
|
||||
public double userPrice() {
|
||||
return userParts() * pizzaPrice() / userCount();
|
||||
}
|
||||
|
||||
public @NotNull String toPrettyString() {
|
||||
String price;
|
||||
if (userCount() == 1) {
|
||||
price = tabular(pizzaPrice(), 0) + " €";
|
||||
} else {
|
||||
price = tabular(pizzaPrice(), 0) + " € / " + tabular(userCount(), 0) + " = " + tabular(userPrice(), 0) + " €";
|
||||
}
|
||||
if (userPayed()) price = strikethrough(price);
|
||||
|
||||
if (notes() != null) {
|
||||
return STR."\{id()} | \{pizzaName().toUpperCase(Locale.ROOT)} \{notes()} | \{price}";
|
||||
} else {
|
||||
return STR."\{id()} | \{pizzaName().toUpperCase(Locale.ROOT)} | \{price}";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package eu.jonahbauer.chat.bot.pizza.util;
|
||||
|
||||
import eu.jonahbauer.chat.bot.pizza.PizzaBotException;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@UtilityClass
|
||||
public class ArgumentParser {
|
||||
public static @NotNull List<@NotNull String> parse(@NotNull String string) {
|
||||
var list = new ArrayList<String>();
|
||||
var quote = (char) 0;
|
||||
var escaped = false;
|
||||
|
||||
var current = new StringBuilder();
|
||||
var it = string.chars().iterator();
|
||||
while (it.hasNext()) {
|
||||
var chr = (char) it.nextInt();
|
||||
if (escaped) {
|
||||
if (chr != '\n') current.append(chr);
|
||||
} else if (chr == '\\' && quote != '\'') {
|
||||
escaped = true;
|
||||
} else if (quote == 0 && chr == '"') {
|
||||
quote = '"';
|
||||
} else if (quote == 0 && chr == '\'') {
|
||||
quote = '\'';
|
||||
} else if (quote == chr) {
|
||||
quote = 0;
|
||||
} else if (chr == ' ' || chr == '\t') {
|
||||
if (!current.isEmpty()) {
|
||||
list.add(current.toString());
|
||||
current.setLength(0);
|
||||
}
|
||||
} else if (chr == '\n') {
|
||||
break;
|
||||
} else {
|
||||
current.append(chr);
|
||||
}
|
||||
}
|
||||
|
||||
if (escaped) {
|
||||
throw new IllegalArgumentException("incomplete escape sequence");
|
||||
} else if (quote != 0) {
|
||||
throw new IllegalArgumentException("incomplete quote");
|
||||
}
|
||||
|
||||
if (!current.isEmpty()) {
|
||||
list.add(current.toString());
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(list);
|
||||
}
|
||||
|
||||
public static boolean isLong(@NotNull String string) {
|
||||
try {
|
||||
Long.parseLong(string);
|
||||
return true;
|
||||
} catch (NumberFormatException _) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static long toLong(@NotNull String string) {
|
||||
try {
|
||||
return Long.parseLong(string);
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new PizzaBotException(string + " ist keine gültige Zahl.");
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDouble(@NotNull String string) {
|
||||
try {
|
||||
Double.parseDouble(string);
|
||||
return true;
|
||||
} catch (NumberFormatException _) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static double toDouble(@NotNull String string) {
|
||||
try {
|
||||
return Double.parseDouble(string);
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new PizzaBotException(string + " ist keine gültige Zahl.");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package eu.jonahbauer.chat.bot.pizza.util;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@UtilityClass
|
||||
public class FormatUtils {
|
||||
public static @NotNull String tabular(long number) {
|
||||
return tabular(number, 2);
|
||||
}
|
||||
|
||||
public static @NotNull String tabular(long number, int width) {
|
||||
return tabular(String.format(Locale.ROOT, "%" + (width == 0 ? "" : width) + "d", number));
|
||||
}
|
||||
|
||||
public static @NotNull String tabular(double number) {
|
||||
return tabular(number, 6);
|
||||
}
|
||||
|
||||
public static @NotNull String tabular(double number, int width) {
|
||||
return tabular(String.format(Locale.ROOT, "%" + (width == 0 ? "" : width) + ".2f", number));
|
||||
}
|
||||
|
||||
private static @NotNull String tabular(@NotNull String string) {
|
||||
var out = new StringBuilder();
|
||||
for (int i = 0, length = string.length(); i < length; i++) {
|
||||
var chr = string.charAt(i);
|
||||
if (chr == ' ') {
|
||||
out.append('\u2007'); // figure space
|
||||
} else {
|
||||
out.append('\u2060'); // word-joiner to prevent kerning
|
||||
out.append(chr);
|
||||
}
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnnecessaryUnicodeEscape")
|
||||
public static @NotNull String strikethrough(@NotNull String string) {
|
||||
var out = new StringBuilder();
|
||||
for (int i = 0, length = string.length(); i < length; i++) {
|
||||
out.append(string.charAt(i)).append('\u0336');
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
@SuppressWarnings("UnnecessaryUnicodeEscape")
|
||||
public static @NotNull String strip(@NotNull String string) {
|
||||
var out = new StringBuilder();
|
||||
for (int i = 0, length = string.length(); i < length; i++) {
|
||||
var chr = string.charAt(i);
|
||||
if (chr != '\u0336' && chr != '\u0335' && chr != '\u2007' && chr != '\u2060') {
|
||||
out.append(chr);
|
||||
}
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
public static @NotNull String join(@NotNull List<@NotNull String> strings) {
|
||||
return switch (strings.size()) {
|
||||
case 0 -> "";
|
||||
case 1 -> strings.getFirst();
|
||||
default -> String.join(", ", strings.subList(0, strings.size() - 1)) + " und " + strings.getLast();
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> @NotNull String join(@NotNull List<@NotNull T> items, @NotNull Function<? super T, String> toString) {
|
||||
return join(items.stream().map(toString).toList());
|
||||
}
|
||||
|
||||
public static <T> @NotNull String join(@NotNull String separator, @NotNull List<@NotNull T> items, @NotNull Function<? super T, String> toString) {
|
||||
return items.stream().map(toString).collect(Collectors.joining(separator));
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package eu.jonahbauer.chat.bot.pizza.util;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public record Pair<F, S>(F first, S second) {
|
||||
public static <F, S> @NotNull Pair<F, S> of(F first, S second) {
|
||||
return new Pair<>(first, second);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import eu.jonahbauer.chat.bot.api.ChatBot;
|
||||
import eu.jonahbauer.chat.bot.pizza.PizzaBot;
|
||||
|
||||
module eu.jonahbauer.chat.bot.pizza {
|
||||
exports eu.jonahbauer.chat.bot.pizza.model.table to eu.jonahbauer.chat.bot.database;
|
||||
exports eu.jonahbauer.chat.bot.pizza.model.view to eu.jonahbauer.chat.bot.database;
|
||||
exports eu.jonahbauer.chat.bot.pizza.model to eu.jonahbauer.chat.bot.database;
|
||||
|
||||
requires eu.jonahbauer.chat.bot.api;
|
||||
requires eu.jonahbauer.chat.bot.database;
|
||||
|
||||
requires org.slf4j;
|
||||
requires static lombok;
|
||||
|
||||
provides ChatBot with PizzaBot;
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
CREATE TABLE "user"
|
||||
(
|
||||
"id" INTEGER PRIMARY KEY,
|
||||
"username" VARCHAR(255) NOT NULL,
|
||||
CONSTRAINT "uq_username" UNIQUE ("username")
|
||||
);
|
||||
|
||||
CREATE TABLE "restaurant"
|
||||
(
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"name" VARCHAR(255) NOT NULL,
|
||||
"city" VARCHAR(255) NOT NULL,
|
||||
"phone" VARCHAR(255) NULL DEFAULT NULL,
|
||||
"notes" VARCHAR(255) NULL DEFAULT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "pizza"
|
||||
(
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"restaurant_id" INTEGER NOT NULL,
|
||||
"number" INTEGER NOT NULL,
|
||||
"name" VARCHAR(255) NOT NULL,
|
||||
"ingredients" VARCHAR(255) NOT NULL,
|
||||
"price" VARCHAR(255) NOT NULL,
|
||||
CONSTRAINT "fk_pizza_restaurant" FOREIGN KEY ("restaurant_id") REFERENCES "restaurant" ("id") ON DELETE CASCADE,
|
||||
CONSTRAINT "uq_pizza_number" UNIQUE ("restaurant_id", "number")
|
||||
);
|
||||
|
||||
CREATE TABLE "event"
|
||||
(
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"name" VARCHAR(255) NOT NULL,
|
||||
"admin_id" INTEGER NOT NULL,
|
||||
"active" BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
CONSTRAINT "fk_event_admin" FOREIGN KEY ("admin_id") REFERENCES "user" ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "uq_event_active" ON "event" ("active")
|
||||
WHERE "active" = TRUE;
|
||||
|
||||
CREATE TABLE "order"
|
||||
(
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"event_id" INTEGER NOT NULL,
|
||||
"restaurant_id" INTEGER NOT NULL,
|
||||
"name" VARCHAR(255) NOT NULL,
|
||||
"deadline" TIMESTAMP NOT NULL,
|
||||
"payment" DOUBLE NULL,
|
||||
CONSTRAINT "fk_order_event" FOREIGN KEY ("event_id") REFERENCES "event" ("id"),
|
||||
CONSTRAINT "fk_order_restaurant" FOREIGN KEY ("restaurant_id") REFERENCES "restaurant" ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "order_item"
|
||||
(
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"order_id" INTEGER NOT NULL,
|
||||
"pizza_id" INTEGER NOT NULL,
|
||||
"notes" VARCHAR(255),
|
||||
CONSTRAINT "fk_order_item_order" FOREIGN KEY ("order_id") REFERENCES "order" ("id") ON DELETE CASCADE,
|
||||
CONSTRAINT "fk_order_item_pizza" FOREIGN KEY ("pizza_id") REFERENCES "pizza" ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "order_item_to_user"
|
||||
(
|
||||
"order_item_id" INTEGER NOT NULL,
|
||||
"user_id" INTEGER NOT NULL,
|
||||
"parts" INTEGER NOT NULL DEFAULT 1,
|
||||
"payed" BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
PRIMARY KEY ("order_item_id", "user_id"),
|
||||
CONSTRAINT "fk_order_item_to_user_order_item" FOREIGN KEY ("order_item_id") REFERENCES "order_item" ("id") ON DELETE CASCADE,
|
||||
CONSTRAINT "fk_order_item_to_user_user_id" FOREIGN KEY ("user_id") REFERENCES "user" ("id")
|
||||
);
|
||||
|
||||
CREATE VIEW "order_item_detail"
|
||||
AS
|
||||
SELECT "order_item"."id",
|
||||
"order_item"."notes",
|
||||
"order"."id" AS "order_id",
|
||||
"order"."name" AS "order_name",
|
||||
"order"."deadline" AS "order_deadline",
|
||||
"order"."payment" AS "order_payment",
|
||||
"order"."event_id" AS "event_id",
|
||||
"restaurant"."id" AS "restaurant_id",
|
||||
"restaurant"."name" AS "restaurant_name",
|
||||
"pizza"."id" AS "pizza_id",
|
||||
"pizza"."number" AS "pizza_number",
|
||||
"pizza"."name" AS "pizza_name",
|
||||
"pizza"."price" AS "pizza_price",
|
||||
"order_item_to_user"."user_id" AS "user_id",
|
||||
"order_item_to_user"."payed" AS "user_payed",
|
||||
"order_item_to_user"."parts" AS "user_parts",
|
||||
"user"."username" AS "user_name",
|
||||
(SELECT SUM("parts") FROM "order_item_to_user" WHERE "order_item_id" = "order_item"."id") AS "user_count"
|
||||
FROM "order"
|
||||
JOIN "restaurant" ON "order"."restaurant_id" = "restaurant"."id"
|
||||
JOIN "order_item" ON "order"."id" = "order_item"."order_id"
|
||||
JOIN "pizza" ON "order_item"."pizza_id" = "pizza"."id"
|
||||
JOIN "order_item_to_user" ON "order_item"."id" = "order_item_to_user"."order_item_id"
|
||||
JOIN "user" ON "order_item_to_user"."user_id" = "user"."id";
|
||||
|
||||
CREATE VIEW "order_detail"
|
||||
AS
|
||||
SELECT "order"."id",
|
||||
"order"."name",
|
||||
"order"."deadline",
|
||||
"order"."payment",
|
||||
"event"."id" AS "event_id",
|
||||
"event"."name" AS "event_name",
|
||||
"event"."admin_id",
|
||||
"event"."active",
|
||||
"restaurant"."id" AS "restaurant_id",
|
||||
"restaurant"."name" AS "restaurant_name"
|
||||
FROM "event"
|
||||
JOIN "order" ON "event"."id" = "order"."event_id"
|
||||
JOIN "restaurant" ON "order"."restaurant_id" = "restaurant"."id";
|
@ -0,0 +1,38 @@
|
||||
INSERT INTO "restaurant"("name", "city", "phone", "notes")
|
||||
VALUES ('Hell''s Pizza', 'Sonthofen', '08321 7881930', '2023');
|
||||
|
||||
WITH temp(id) AS (SELECT last_insert_rowid())
|
||||
INSERT INTO "pizza"("restaurant_id", "number", "name", "ingredients", "price")
|
||||
SELECT * FROM temp CROSS JOIN (
|
||||
VALUES (10, 'Pizzabrot', 'mit Tomaten & Knoblauch', 6.50),
|
||||
(11, 'Margherita', 'mit Tomaten, Mozzarella & Basilikum', 9.50),
|
||||
(12, 'Salami', 'mit Tomaten, Mozzarella & Salami (gegen Aufpreis mit Rindersalami)', 10.00),
|
||||
(13, 'Prosciutto', 'mit Tomaten, Mozzarella & Schinken', 10.00),
|
||||
(14, 'Salsiccia', 'mit Tomaten, Mozzarella & scharfer Salami', 9.50),
|
||||
(15, 'Funghi', 'mit Tomaten, Mozzarella & Champignons', 9.50),
|
||||
(16, 'Romana', 'mit Tomaten, Mozzarella, Salami & Champignons', 10.50),
|
||||
(17, 'Hawaii', 'mit Tomaten, Mozzarella, Schinken & Ananas', 10.50),
|
||||
(18, 'Regina', 'mit Tomaten, Mozzarella, Schinken & Champignons', 10.50),
|
||||
(19, 'Toscana', 'mit Tomaten, Mozzarella, Salami, Champignons & Peperoni', 11.00),
|
||||
(20, 'Tonno', 'mit Tomaten, Mozzarella, Thunfisch, Zwiebeln & Oliven', 11.00),
|
||||
(21, 'Quattro Formaggi', 'mit Tomaten & vier Käsesorten', 10.50),
|
||||
(22, 'Calzone', 'mit Tomaten, Mozzarella, Schinken & Champignons', 10.50),
|
||||
(24, 'Diavolo', 'mit Tomaten, Mozzarella, scharfer Salami, Peperoni & Oliven', 11.00),
|
||||
(25, 'Rucoletta', 'mit Tomaten, Mozzarella, Rucola, Tomatenstücken & Parmesan', 11.00),
|
||||
(26, 'Quattro Stagioni', 'mit Tomaten, Mozzarella, Schinken, Salami, Champignons, Artischocken, Sardellen & Oliven', 11.00),
|
||||
(27, 'Vegana', 'mit Tomaten & verschiedenem Gemüse', 10.50),
|
||||
(28, 'Siciliana', 'mit Tomaten, Mozzarella, Peperoni, Kapern, Sardellen, Oliven & Knoblauch', 10.00),
|
||||
(29, 'Rustica', 'mit Tomaten, Mozzarella, Speck, Champignons, Gorgonzola & Knoblauch', 11.00),
|
||||
(30, 'Caprese', 'mit Tomaten, Mozzarella, Tomatenscheiben & Basilikum', 11.00),
|
||||
(31, 'Calabria', 'mit Tomaten, Mozzarella, scharfer Salami, Zwiebeln & original Schafskäse', 12.00),
|
||||
(32, 'Verdura', 'mit Tomaten, Mozzarella, gegrilltem Gemüse & Knoblauch', 12.00),
|
||||
(33, 'Mare', 'mit Tomaten, Meeresfrüchten & Knoblauch', 13.00),
|
||||
(35, 'Mafiosa', 'mit Tomaten, Mozzarella, Speck, scharfer Salami, Champignons, Spinat & Peperoni', 12.00),
|
||||
(36, 'Reale', 'mit Tomaten, Mozzarella, original Parmaschinken, Rucola & Parmesan', 13.90),
|
||||
(38, 'Mediterranea', 'mit Tomatenscheiben, Mozzarella, Thunfisch, Kapern & Rucola', 12.00),
|
||||
(39, 'Tricolore', 'mit Büffel-Mozzarella, Tomatenscheiben & Rucola-Pesto', 13.00),
|
||||
(40, 'Alfio Spezial', 'mit Mozzarella, Tomatenstücken, Salsiccia, Zwiebeln, Parmesan, Oliven & Basilikum', 12.00),
|
||||
(41, 'Allgäuer', 'mit Tomaten, Mozzarella, Speck, Bergkäse, Röstzwiebeln & Knoblauch', 11.50),
|
||||
(42, 'Buon Gustaio', 'mit Tomaten, Mozzarella, Spinat, Gorgonzola & Knoblauch', 11.00),
|
||||
(44, 'Mista', 'mit Tomaten, Mozzarella, Schinken, Salami, Pilzen & Thunfisch', 11.00)
|
||||
);
|
@ -0,0 +1,46 @@
|
||||
-- Hell's Pizza Sonthofen
|
||||
-- Stand: Oktober 2023
|
||||
INSERT INTO "restaurant"("name", "city", "phone", "notes")
|
||||
VALUES ('Hell''s Pizza', 'Sonthofen', '08321 7881930', '2024');
|
||||
|
||||
WITH temp(id) AS (SELECT last_insert_rowid())
|
||||
INSERT INTO "pizza"("restaurant_id", "number", "name", "ingredients", "price")
|
||||
SELECT * FROM temp CROSS JOIN (
|
||||
VALUES (10, 'Pizzabrot', 'mit Tomaten & Knoblauch', 7.00),
|
||||
(11, 'Margherita', 'mit Tomaten, Mozzarella & Basilikum', 10.00),
|
||||
(12, 'Salami', 'mit Tomaten, Mozzarella & Salami (gegen Aufpreis mit Rindersalami)', 10.50),
|
||||
(13, 'Prosciutto', 'mit Tomaten, Mozzarella & Schinken', 10.50),
|
||||
(14, 'Salsiccia', 'mit Tomaten, Mozzarella & scharfer Salami', 10.50),
|
||||
(15, 'Funghi', 'mit Tomaten, Mozzarella & Champignons', 10.00),
|
||||
(16, 'Romana', 'mit Tomaten, Mozzarella, Salami & Champignons', 11.00),
|
||||
(17, 'Hawaii', 'mit Tomaten, Mozzarella, Schinken & Ananas', 11.00),
|
||||
(18, 'Regina', 'mit Tomaten, Mozzarella, Schinken & Champignons', 11.00),
|
||||
(19, 'Toscana', 'mit Tomaten, Mozzarella, Salami, Champignons & Peperoni', 11.50),
|
||||
(20, 'Tonno', 'mit Tomaten, Mozzarella, Thunfisch, Zwiebeln & Oliven', 11.50),
|
||||
(21, 'Quattro Formaggi', 'mit Tomaten & vier Käsesorten', 11.00),
|
||||
(22, 'Calzone', 'mit Tomaten, Mozzarella, Schinken & Champignons', 11.00),
|
||||
(23, 'Capriccio', 'mit Tomaten, Mozzarella, Schinken, Champignons, Artischocken, Eier', 13.00),
|
||||
(24, 'Diavolo', 'mit Tomaten, Mozzarella, scharfer Salami, Peperoni & Oliven', 11.50),
|
||||
(25, 'Rucoletta', 'mit Tomaten, Mozzarella, Rucola, Tomatenstücken & Parmesan', 12.00),
|
||||
(26, 'Quattro Stagioni', 'mit Tomaten, Mozzarella, Schinken, Salami, Champignons, Artischocken, Sardellen & Oliven', 12.90),
|
||||
(27, 'Vegana', 'mit Tomaten & verschiedenem Gemüse', 11.00),
|
||||
(28, 'Siciliana', 'mit Tomaten, Mozzarella, Peperoni, Kapern, Sardellen, Oliven & Knoblauch', 10.50),
|
||||
(29, 'Rustica', 'mit Tomaten, Mozzarella, Speck, Champignons, Gorgonzola & Knoblauch', 11.50),
|
||||
(30, 'Caprese', 'mit Tomaten, Mozzarella, Tomatenscheiben & Basilikum', 11.50),
|
||||
(31, 'Calabria', 'mit Tomaten, Mozzarella, scharfer Salami, Zwiebeln & original Schafskäse', 12.00),
|
||||
(32, 'Verdura', 'mit Tomaten, Mozzarella, gegrilltem Gemüse & Knoblauch', 12.00),
|
||||
(33, 'Mare', 'mit Tomaten, Meeresfrüchten & Knoblauch', 13.50),
|
||||
(34, 'Contadina', 'mit Büffelmozzarella, Parmaschinken Julienne, Champignons & Rucola', 13.90),
|
||||
(35, 'Mafiosa', 'mit Tomaten, Mozzarella, Speck, scharfer Salami, Champignons, Spinat & Peperoni', 12.90),
|
||||
(36, 'Reale', 'mit Tomaten, Mozzarella, original Parmaschinken, Rucola & Parmesan', 14.90),
|
||||
(38, 'Mediterranea', 'mit Tomatenscheiben, Mozzarella, Thunfisch, Kapern & Rucola', 12.90),
|
||||
(39, 'Tricolore', 'mit Büffel-Mozzarella, Tomatenscheiben & Rucola-Pesto', 13.90),
|
||||
(40, 'Alfio Spezial', 'mit Mozzarella, Tomatenstücken, Salsiccia, Zwiebeln, Parmesan, Oliven & Basilikum', 12.90),
|
||||
(41, 'Allgäuer', 'mit Tomaten, Mozzarella, Speck, Bergkäse, Röstzwiebeln & Knoblauch DAS ORIGINAL', 12.90),
|
||||
(42, 'Buon Gustaio', 'mit Tomaten, Mozzarella, Spinat, Gorgonzola & Knoblauch', 11.90),
|
||||
(44, 'Mista', 'mit Tomaten, Mozzarella, Schinken, Salami, Pilzen & Thunfisch', 11.90),
|
||||
(48, 'Cudduruni', 'Tomatenstücken, Zwiebeln, Parmesan, Oliven, Basilikum, Chili-Olivenöl NEU', 13.90),
|
||||
(49, 'Bufalina', 'mit Tomaten, Büffelmozzarella, frischer Basilikum und Olivenöl NEU', 14.90)
|
||||
);
|
||||
|
||||
|
@ -0,0 +1,629 @@
|
||||
package eu.jonahbauer.chat.bot.pizza;
|
||||
|
||||
import eu.jonahbauer.chat.bot.api.Message.Post;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.*;
|
||||
import eu.jonahbauer.chat.bot.pizza.model.table.*;
|
||||
import eu.jonahbauer.chat.bot.test.MockChat;
|
||||
import eu.jonahbauer.chat.bot.database.Database;
|
||||
import eu.jonahbauer.chat.bot.test.ChatBotFactoryAccess;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import static eu.jonahbauer.chat.bot.pizza.util.FormatUtils.*;
|
||||
import static java.util.FormatProcessor.FMT;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class PizzaBotTest {
|
||||
@TempDir
|
||||
static Path temp;
|
||||
|
||||
Database database;
|
||||
MockChat chat;
|
||||
PizzaBot bot;
|
||||
|
||||
User admin;
|
||||
User user;
|
||||
User user2;
|
||||
|
||||
Event event;
|
||||
Order order0; // alte bestellung (bestellannahmeschluss vorbei)
|
||||
Order order1; // alte bestellung
|
||||
Order order; // laufende bestellung
|
||||
|
||||
Pizza salami;
|
||||
Pizza margherita;
|
||||
|
||||
@BeforeEach
|
||||
void init() throws IOException, SQLException {
|
||||
var file = Files.createTempFile(temp, "database", ".db");
|
||||
database = new Database("jdbc:sqlite:" + file.toAbsolutePath());
|
||||
run("init.sql");
|
||||
run("restaurants/sonthofen_2023.sql");
|
||||
|
||||
chat = new MockChat();
|
||||
bot = ChatBotFactoryAccess.create(() -> new PizzaBot(Collections.singleton("marek"), database));
|
||||
|
||||
admin = database.save(new User(0, "Admin"));
|
||||
user = database.save(new User(1337, "User1"));
|
||||
user2 = database.save(new User(42, "User2"));
|
||||
|
||||
event = database.save(new Event(0, "Veranstaltung", admin.id(), true));
|
||||
order0 = database.save(new Order(0, event.id(), 1, "28.03.2024", Instant.ofEpochMilli(System.currentTimeMillis() - 3_600_000), 90.0));
|
||||
order1 = database.save(new Order(0, event.id(), 1, "29.03.2024", Instant.ofEpochMilli(System.currentTimeMillis() + 3_600_000), null));
|
||||
order = database.save(new Order(0, event.id(), 1, "30.03.2024", Instant.ofEpochMilli(System.currentTimeMillis() + 7_200_000), null));
|
||||
|
||||
salami = database.findWhere(Pizza.class, Pizza.Fields.name, "Salami").orElseThrow();
|
||||
margherita = database.findWhere(Pizza.class, Pizza.Fields.name, "Margherita").orElseThrow();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void print() {
|
||||
for (var message : chat.getMessages()) {
|
||||
System.out.println("\n" + message.name() + "\n" + message.message());
|
||||
}
|
||||
}
|
||||
|
||||
private void run(@NotNull String script) throws SQLException, IOException {
|
||||
try (var conn = database.getConnection()) {
|
||||
var runner = new ScriptRunner(conn, false, true);
|
||||
runner.setLogWriter(null);
|
||||
try (var stream = Objects.requireNonNull(PizzaBot.class.getResourceAsStream(script))) {
|
||||
try (var reader = new InputStreamReader(stream, StandardCharsets.UTF_8)) {
|
||||
runner.runScript(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void deadline() throws SQLException {
|
||||
database.delete(order);
|
||||
database.delete(order1);
|
||||
order = order0;
|
||||
}
|
||||
|
||||
private void post(@NotNull User user, @NotNull String message) {
|
||||
var post = new Post(0, user.username(), message, "marek", LocalDateTime.now(), 0L, user.id(), user.username(), "ffffff", 0);
|
||||
bot.onMessage(chat, post);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class OrderPizza {
|
||||
|
||||
@Test
|
||||
void orderByPizzaName() throws SQLException {
|
||||
post(user, "!marek order SALAMI");
|
||||
|
||||
var items = database.findAll(OrderItem.class);
|
||||
assertEquals(1, items.size());
|
||||
|
||||
var item = items.getFirst();
|
||||
assertNull(item.notes());
|
||||
assertEquals(order.id(), item.orderId());
|
||||
assertEquals(salami.id(), item.pizzaId());
|
||||
|
||||
var mappings = database.findAll(OrderItemToUser.class);
|
||||
assertEquals(List.of(new OrderItemToUser(item.id(), user.id(), false)), mappings);
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderByPizzaNameWithNotes() throws SQLException {
|
||||
post(user, "!marek order SALAMI Hello World");
|
||||
|
||||
var items = database.findAll(OrderItem.class);
|
||||
assertEquals(1, items.size());
|
||||
|
||||
var item = items.getFirst();
|
||||
assertEquals("Hello World", item.notes());
|
||||
assertEquals(order.id(), item.orderId());
|
||||
assertEquals(salami.id(), item.pizzaId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderByPizzaNameFailsAfterDeadline() throws SQLException {
|
||||
deadline();
|
||||
post(user, "!marek order SALAMI");
|
||||
|
||||
var items = database.findAll(OrderItem.class);
|
||||
assertEquals(0, items.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderByPizzaNumber() throws SQLException {
|
||||
post(user, "!marek order 13");
|
||||
|
||||
var items = database.findAll(OrderItem.class);
|
||||
assertEquals(1, items.size());
|
||||
|
||||
var item = items.getFirst();
|
||||
assertNull(item.notes());
|
||||
assertEquals(order.id(), item.orderId());
|
||||
|
||||
var pizza = database.findById(Pizza.class, item.pizzaId());
|
||||
assertTrue(pizza.isPresent());
|
||||
assertEquals("Prosciutto", pizza.get().name());
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderAddById() throws SQLException {
|
||||
var item = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||
|
||||
post(admin, STR."!marek order add \{item.id()} \{user2.username()}");
|
||||
|
||||
assertEquals(
|
||||
List.of(
|
||||
new OrderItemToUser(item.id(), user.id(), false),
|
||||
new OrderItemToUser(item.id(), user2.id(), false)
|
||||
),
|
||||
database.findAll(OrderItemToUser.class)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderAddByName() throws SQLException {
|
||||
var item = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||
|
||||
post(admin, STR."!marek order add \{user.username()} \{user2.username()}");
|
||||
|
||||
assertEquals(
|
||||
List.of(
|
||||
new OrderItemToUser(item.id(), user.id(), false),
|
||||
new OrderItemToUser(item.id(), user2.id(), false)
|
||||
),
|
||||
database.findAll(OrderItemToUser.class)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderAddByNameFailsWhenNotUnique() throws SQLException {
|
||||
var item1 = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||
database.insert(new OrderItemToUser(item1.id(), user.id(), false));
|
||||
var item2 = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||
database.insert(new OrderItemToUser(item2.id(), user.id(), false));
|
||||
|
||||
post(admin, STR."!marek order add \{user.username()} \{user2.username()}");
|
||||
|
||||
assertEquals(
|
||||
List.of(
|
||||
new OrderItemToUser(item1.id(), user.id(), false),
|
||||
new OrderItemToUser(item2.id(), user.id(), false)
|
||||
),
|
||||
database.findAll(OrderItemToUser.class)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderAddByIdFailsWhenAlreadyPayed() throws SQLException {
|
||||
var item = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||
database.insert(new OrderItemToUser(item.id(), user2.id(), true));
|
||||
|
||||
post(admin, STR."!marek order add \{item.id()} \{admin.username()}");
|
||||
|
||||
assertEquals(
|
||||
List.of(
|
||||
new OrderItemToUser(item.id(), user.id(), false),
|
||||
new OrderItemToUser(item.id(), user2.id(), true)
|
||||
),
|
||||
database.findAll(OrderItemToUser.class)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Revoke {
|
||||
|
||||
@Test
|
||||
void orderRevoke() throws SQLException {
|
||||
var item1 = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||
database.insert(new OrderItemToUser(item1.id(), user.id(), false));
|
||||
var item2 = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||
database.insert(new OrderItemToUser(item2.id(), user2.id(), false));
|
||||
|
||||
post(user, "!marek order revoke");
|
||||
|
||||
var items = database.findAll(OrderItem.class);
|
||||
assertEquals(List.of(item2), items);
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderRevokeFailsAfterDeadline() throws SQLException {
|
||||
deadline();
|
||||
|
||||
var item = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||
|
||||
post(user, "!marek order revoke");
|
||||
|
||||
var items = database.findAll(OrderItem.class);
|
||||
assertEquals(List.of(item), items);
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderRevokeFailsWithMultipleItems() throws SQLException {
|
||||
var item1 = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||
database.insert(new OrderItemToUser(item1.id(), user.id(), false));
|
||||
var item2 = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||
database.insert(new OrderItemToUser(item2.id(), user.id(), false));
|
||||
|
||||
post(user, "!marek order revoke");
|
||||
|
||||
var items = database.findAll(OrderItem.class);
|
||||
assertEquals(2, items.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderRevokeFailsWhenItemBelongsToOtherOrder() throws SQLException {
|
||||
var item = database.save(new OrderItem(0, order1.id(), margherita.id(), null));
|
||||
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||
|
||||
post(user, "!marek order revoke");
|
||||
|
||||
var items = database.findAll(OrderItem.class);
|
||||
assertEquals(List.of(item), items);
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderRevokeByNameWithMultipleItems() throws SQLException {
|
||||
var item1 = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||
database.insert(new OrderItemToUser(item1.id(), user.id(), false));
|
||||
var item2 = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||
database.insert(new OrderItemToUser(item2.id(), user.id(), false));
|
||||
|
||||
post(user, "!marek order revoke SALAMI");
|
||||
|
||||
var items = database.findAll(OrderItem.class);
|
||||
assertEquals(List.of(item1), items);
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderRevokeByNameFailsWhenItemBelongsToOtherUser() throws SQLException {
|
||||
var item = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||
|
||||
post(user2, "!marek order revoke MARGHERITA");
|
||||
|
||||
var items = database.findAll(OrderItem.class);
|
||||
assertEquals(List.of(item), items);
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderRevokeByNameFailsWhenItemBelongsToOtherOrder() throws SQLException {
|
||||
var item = database.save(new OrderItem(0, order1.id(), margherita.id(), null));
|
||||
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||
|
||||
post(user, "!marek order revoke MARGHERITA");
|
||||
|
||||
var items = database.findAll(OrderItem.class);
|
||||
assertEquals(List.of(item), items);
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderRevokeById() throws SQLException {
|
||||
var item = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||
|
||||
post(user, "!marek order revoke " + item.id());
|
||||
|
||||
var items = database.findAll(OrderItem.class);
|
||||
assertEquals(List.of(), items);
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderRevokeByIdFailsWhenItemBelongsToOtherUser() throws SQLException {
|
||||
var item = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||
|
||||
post(user2, "!marek order revoke " + item.id());
|
||||
|
||||
var items = database.findAll(OrderItem.class);
|
||||
assertEquals(List.of(item), items);
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderRevokeByIdFailsWhenItemBelongsToOtherOrder() throws SQLException {
|
||||
var item = database.save(new OrderItem(0, order1.id(), margherita.id(), null));
|
||||
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||
|
||||
post(user, "!marek order revoke " + item.id());
|
||||
|
||||
var items = database.findAll(OrderItem.class);
|
||||
assertEquals(List.of(item), items);
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderRevokeAll() throws SQLException {
|
||||
var item1 = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||
database.insert(new OrderItemToUser(item1.id(), user.id(), false));
|
||||
var item2 = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||
database.insert(new OrderItemToUser(item2.id(), user.id(), false));
|
||||
var item3 = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||
database.insert(new OrderItemToUser(item3.id(), user2.id(), false));
|
||||
|
||||
post(user, "!marek order revoke --all");
|
||||
|
||||
var items = database.findAll(OrderItem.class);
|
||||
assertEquals(List.of(item3), items);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Payment {
|
||||
OrderItem item;
|
||||
OrderItemToUser itemToUser;
|
||||
OrderItemToUser itemToUser2;
|
||||
|
||||
@BeforeEach
|
||||
void init() throws SQLException {
|
||||
item = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||
itemToUser = database.save(new OrderItemToUser(item.id(), user.id(), false));
|
||||
itemToUser2 = database.save(new OrderItemToUser(item.id(), user2.id(), false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void paymentConfirmById() throws SQLException {
|
||||
post(admin, "!marek payment confirm " + item.id());
|
||||
|
||||
var mappings = database.findAll(OrderItemToUser.class);
|
||||
assertEquals(List.of(itemToUser.withPayed(true), itemToUser2.withPayed(true)), mappings);
|
||||
}
|
||||
|
||||
@Test
|
||||
void paymentConfirmByIdFailsWhenNotAdmin() throws SQLException {
|
||||
post(user, "!marek payment confirm " + item.id());
|
||||
|
||||
var mappings = database.findAll(OrderItemToUser.class);
|
||||
assertEquals(List.of(itemToUser, itemToUser2), mappings);
|
||||
}
|
||||
|
||||
@Test
|
||||
void paymentConfirmByIdAndUser() throws SQLException {
|
||||
post(admin, "!marek payment confirm " + item.id() + " " + user.username());
|
||||
|
||||
var mappings = database.findAll(OrderItemToUser.class);
|
||||
assertEquals(List.of(itemToUser.withPayed(true), itemToUser2), mappings);
|
||||
}
|
||||
|
||||
@Test
|
||||
void paymentConfirmByIdAndUserFailsWhenNotAdmin() throws SQLException {
|
||||
post(user, "!marek payment confirm " + item.id() + " " + user.username());
|
||||
|
||||
var mappings = database.findAll(OrderItemToUser.class);
|
||||
assertEquals(List.of(itemToUser, itemToUser2), mappings);
|
||||
}
|
||||
|
||||
@Test
|
||||
void paymentConfirmByUser() throws SQLException {
|
||||
var item2 = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||
var item2ToUser = database.save(new OrderItemToUser(item2.id(), user.id(), false));
|
||||
var item2ToUser2 = database.save(new OrderItemToUser(item2.id(), user2.id(), false));
|
||||
|
||||
post(admin, "!marek payment confirm --all " + user.username());
|
||||
|
||||
var mappings = database.findAll(OrderItemToUser.class);
|
||||
assertEquals(
|
||||
List.of(itemToUser.withPayed(true), itemToUser2, item2ToUser.withPayed(true), item2ToUser2),
|
||||
mappings
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Pay {
|
||||
@Test
|
||||
void pay() throws SQLException {
|
||||
post(admin, "!marek pay 42.5");
|
||||
|
||||
var order = database.findById(Order.class, PizzaBotTest.this.order.id());
|
||||
assertTrue(order.isPresent());
|
||||
assertEquals(42.5, order.get().payment());
|
||||
}
|
||||
|
||||
@Test
|
||||
void payFailsWhenNotAdmin() throws SQLException {
|
||||
post(user, "!marek pay 42.5");
|
||||
|
||||
var order = database.findById(Order.class, PizzaBotTest.this.order.id());
|
||||
assertTrue(order.isPresent());
|
||||
assertEquals(PizzaBotTest.this.order, order.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void payWithOrderName() throws SQLException {
|
||||
post(admin, "!marek pay " + order0.name() + " 42.5");
|
||||
|
||||
var order0 = database.findById(Order.class, PizzaBotTest.this.order0.id());
|
||||
assertTrue(order0.isPresent());
|
||||
assertEquals(42.5, order0.get().payment());
|
||||
|
||||
var order = database.findById(Order.class, PizzaBotTest.this.order.id());
|
||||
assertTrue(order.isPresent());
|
||||
assertNull(order.get().payment());
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class CheckAndSummary {
|
||||
OrderItem item1;
|
||||
OrderItemToUser item1ToUser;
|
||||
|
||||
OrderItem item2;
|
||||
OrderItemToUser item2ToUser;
|
||||
|
||||
OrderItem item3;
|
||||
OrderItemToUser item3ToUser;
|
||||
|
||||
OrderItem item4;
|
||||
OrderItemToUser item4ToUser2;
|
||||
|
||||
OrderItem item5;
|
||||
OrderItemToUser item5ToUser;
|
||||
OrderItemToUser item5ToUser2;
|
||||
|
||||
@BeforeEach
|
||||
void init() throws SQLException {
|
||||
item1 = database.save(new OrderItem(0, order0.id(), margherita.id(), null));
|
||||
item1ToUser = database.save(new OrderItemToUser(item1.id(), user.id(), false));
|
||||
|
||||
item2 = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||
item2ToUser = database.save(new OrderItemToUser(item2.id(), user.id(), false));
|
||||
|
||||
item3 = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||
item3ToUser = database.save(new OrderItemToUser(item3.id(), user.id(), false));
|
||||
|
||||
item4 = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||
item4ToUser2 = database.save(new OrderItemToUser(item4.id(), user2.id(), false));
|
||||
|
||||
item5 = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||
item5ToUser = database.save(new OrderItemToUser(item5.id(), user.id(), false));
|
||||
item5ToUser2 = database.save(new OrderItemToUser(item5.id(), user2.id(), false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void check() {
|
||||
post(admin, "!marek check");
|
||||
var message = chat.getMessages().getFirst();
|
||||
var expected = FMT."""
|
||||
Rechnung für %s\{order.name()}
|
||||
alle Angaben ohne Gewähr
|
||||
|
||||
%s\{user.username()}: %.2f\{1.5 * margherita.price() + salami.price()} €
|
||||
%s\{user2.username()}: %.2f\{1.5 * margherita.price()} €
|
||||
|
||||
Gesamt: %.2f\{3 * margherita.price() + salami.price()} €
|
||||
""".trim();
|
||||
assertEquals(expected, strip(message.message()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkTip() throws SQLException {
|
||||
database.update(order.withPayment(40d));
|
||||
|
||||
post(admin, "!marek check");
|
||||
var message = chat.getMessages().getFirst();
|
||||
var expected = FMT."""
|
||||
Rechnung für %s\{order.name()} (mit Trinkgeld)
|
||||
alle Angaben ohne Gewähr
|
||||
|
||||
%s\{user.username()}: %.2f\{1.5 * margherita.price() + salami.price()} € (25.19 €)
|
||||
%s\{user2.username()}: %.2f\{1.5 * margherita.price()} € (14.81 €)
|
||||
|
||||
Gesamt: %.2f\{3 * margherita.price() + salami.price()} € (40.00 €)
|
||||
""".trim();
|
||||
assertEquals(expected, strip(message.message()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkPayed() throws SQLException {
|
||||
database.executeUpdate("UPDATE order_item_to_user SET payed = TRUE WHERE user_id = " + user.id());
|
||||
|
||||
post(admin, "!marek check");
|
||||
var message = chat.getMessages().getFirst();
|
||||
var expected = FMT."""
|
||||
Rechnung für %s\{order.name()}
|
||||
alle Angaben ohne Gewähr
|
||||
|
||||
%s\{user.username()}: %.2f\{1.5 * margherita.price() + salami.price()} €
|
||||
%s\{user2.username()}: %.2f\{1.5 * margherita.price()} €
|
||||
|
||||
Gesamt: %.2f\{3 * margherita.price() + salami.price()} € %.2f\{1.5 * margherita.price()} €
|
||||
""".trim();
|
||||
assertEquals(expected, strip(message.message()));
|
||||
assertEquals(strikethrough("User1: " + tabular(1.5 * margherita.price() + salami.price(), 0) + " €"), message.message().split("\n")[3]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkPartialPayed() throws SQLException {
|
||||
database.executeUpdate("UPDATE order_item_to_user SET payed = TRUE WHERE order_item_id = " + item5.id());
|
||||
|
||||
var user1Total = 1.5 * margherita.price() + salami.price();
|
||||
var user1Remaining = margherita.price() + salami.price();
|
||||
var user2Total = 1.5 * margherita.price();
|
||||
var user2Remaining = margherita.price();
|
||||
|
||||
post(admin, "!marek check");
|
||||
var message = chat.getMessages().getFirst();
|
||||
var expected = FMT."""
|
||||
Rechnung für %s\{order.name()}
|
||||
alle Angaben ohne Gewähr
|
||||
|
||||
%s\{user.username()}: %.2f\{user1Total} € %.2f\{user1Remaining} €
|
||||
%s\{user2.username()}: %.2f\{user2Total} € %.2f\{user2Remaining} €
|
||||
|
||||
Gesamt: %.2f\{user1Total + user2Total} € %.2f\{user1Remaining + user2Remaining} €
|
||||
""".trim();
|
||||
assertEquals(expected, strip(message.message()));
|
||||
assertEquals(
|
||||
STR."\{user.username()}: \{strikethrough(tabular(user1Total, 0)+ " €")} \{tabular(user1Remaining, 0)} €",
|
||||
message.message().split("\n")[3]
|
||||
);
|
||||
assertEquals(
|
||||
STR."\{user2.username()}: \{strikethrough(tabular(user2Total, 0)+ " €")} \{tabular(user2Remaining, 0)} €",
|
||||
message.message().split("\n")[4]
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkAllTip() throws SQLException {
|
||||
database.update(order.withPayment(40d));
|
||||
|
||||
post(admin, "!marek check --all");
|
||||
var message = chat.getMessages().getFirst();
|
||||
var expected = FMT."""
|
||||
Rechnung für %s\{event.name()} (mit Trinkgeld)
|
||||
alle Angaben ohne Gewähr
|
||||
|
||||
%s\{user.username()}: %.2f\{2.5 * margherita.price() + salami.price()} € (115.19 €)
|
||||
%s\{user2.username()}: %.2f\{1.5 * margherita.price()} € (14.81 €)
|
||||
|
||||
Gesamt: %.2f\{4 * margherita.price() + salami.price()} € (130.00 €)
|
||||
""".trim();
|
||||
assertEquals(expected, strip(message.message()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void summary() {
|
||||
post(admin, "!marek summary");
|
||||
var message = chat.getMessages().getFirst();
|
||||
var expected = FMT."""
|
||||
Übersicht %s\{order.name()}
|
||||
|
||||
3x \{margherita.name().toUpperCase(Locale.ROOT)} (\{user.username()}, \{user.username()}+\{user2.username()}, \{user2.username()})
|
||||
1x \{salami.name().toUpperCase(Locale.ROOT)} (\{user.username()})
|
||||
""".trim();
|
||||
assertEquals(expected, strip(message.message()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void summaryAll() {
|
||||
post(admin, "!marek summary --all");
|
||||
var message = chat.getMessages().getFirst();
|
||||
var expected = FMT."""
|
||||
Übersicht %s\{event.name()}
|
||||
|
||||
4x \{margherita.name().toUpperCase(Locale.ROOT)} (2x \{user.username()}, \{user.username()}+\{user2.username()}, \{user2.username()})
|
||||
1x \{salami.name().toUpperCase(Locale.ROOT)} (\{user.username()})
|
||||
""".trim();
|
||||
assertEquals(expected, strip(message.message()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,248 @@
|
||||
package eu.jonahbauer.chat.bot.pizza;
|
||||
/*
|
||||
* Slightly modified version of the com.ibatis.common.jdbc.ScriptRunner class
|
||||
* from the iBATIS Apache project. Only removed dependency on Resource class
|
||||
* and a constructor
|
||||
*/
|
||||
/*
|
||||
* Copyright 2004 Clinton Begin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.LineNumberReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Reader;
|
||||
import java.sql.*;
|
||||
|
||||
/**
|
||||
* Tool to run database scripts
|
||||
*/
|
||||
@SuppressWarnings("all")
|
||||
public class ScriptRunner {
|
||||
|
||||
private static final String DEFAULT_DELIMITER = ";";
|
||||
|
||||
private Connection connection;
|
||||
|
||||
private boolean stopOnError;
|
||||
private boolean autoCommit;
|
||||
|
||||
private PrintWriter logWriter = new PrintWriter(System.out);
|
||||
private PrintWriter errorLogWriter = new PrintWriter(System.err);
|
||||
|
||||
private String delimiter = DEFAULT_DELIMITER;
|
||||
private boolean fullLineDelimiter = false;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
public ScriptRunner(Connection connection, boolean autoCommit,
|
||||
boolean stopOnError) {
|
||||
this.connection = connection;
|
||||
this.autoCommit = autoCommit;
|
||||
this.stopOnError = stopOnError;
|
||||
}
|
||||
|
||||
public void setDelimiter(String delimiter, boolean fullLineDelimiter) {
|
||||
this.delimiter = delimiter;
|
||||
this.fullLineDelimiter = fullLineDelimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for logWriter property
|
||||
*
|
||||
* @param logWriter
|
||||
* - the new value of the logWriter property
|
||||
*/
|
||||
public void setLogWriter(PrintWriter logWriter) {
|
||||
this.logWriter = logWriter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for errorLogWriter property
|
||||
*
|
||||
* @param errorLogWriter
|
||||
* - the new value of the errorLogWriter property
|
||||
*/
|
||||
public void setErrorLogWriter(PrintWriter errorLogWriter) {
|
||||
this.errorLogWriter = errorLogWriter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an SQL script (read in using the Reader parameter)
|
||||
*
|
||||
* @param reader
|
||||
* - the source of the script
|
||||
*/
|
||||
public void runScript(Reader reader) throws IOException, SQLException {
|
||||
try {
|
||||
boolean originalAutoCommit = connection.getAutoCommit();
|
||||
try {
|
||||
if (originalAutoCommit != this.autoCommit) {
|
||||
connection.setAutoCommit(this.autoCommit);
|
||||
}
|
||||
runScript(connection, reader);
|
||||
} finally {
|
||||
connection.setAutoCommit(originalAutoCommit);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
} catch (SQLException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error running script. Cause: " + e, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an SQL script (read in using the Reader parameter) using the
|
||||
* connection passed in
|
||||
*
|
||||
* @param conn
|
||||
* - the connection to use for the script
|
||||
* @param reader
|
||||
* - the source of the script
|
||||
* @throws SQLException
|
||||
* if any SQL errors occur
|
||||
* @throws IOException
|
||||
* if there is an error reading from the Reader
|
||||
*/
|
||||
private void runScript(Connection conn, Reader reader) throws IOException,
|
||||
SQLException {
|
||||
StringBuffer command = null;
|
||||
try {
|
||||
LineNumberReader lineReader = new LineNumberReader(reader);
|
||||
String line = null;
|
||||
while ((line = lineReader.readLine()) != null) {
|
||||
if (command == null) {
|
||||
command = new StringBuffer();
|
||||
}
|
||||
String trimmedLine = line.trim();
|
||||
if (trimmedLine.startsWith("--")) {
|
||||
println(trimmedLine);
|
||||
} else if (trimmedLine.length() < 1
|
||||
|| trimmedLine.startsWith("//")) {
|
||||
// Do nothing
|
||||
} else if (trimmedLine.length() < 1
|
||||
|| trimmedLine.startsWith("--")) {
|
||||
// Do nothing
|
||||
} else if (!fullLineDelimiter
|
||||
&& trimmedLine.endsWith(getDelimiter())
|
||||
|| fullLineDelimiter
|
||||
&& trimmedLine.equals(getDelimiter())) {
|
||||
command.append(line.substring(0, line
|
||||
.lastIndexOf(getDelimiter())));
|
||||
command.append(" ");
|
||||
Statement statement = conn.createStatement();
|
||||
|
||||
println(command);
|
||||
|
||||
boolean hasResults = false;
|
||||
if (stopOnError) {
|
||||
hasResults = statement.execute(command.toString());
|
||||
} else {
|
||||
try {
|
||||
statement.execute(command.toString());
|
||||
} catch (SQLException e) {
|
||||
e.fillInStackTrace();
|
||||
printlnError("Error executing: " + command);
|
||||
printlnError(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (autoCommit && !conn.getAutoCommit()) {
|
||||
conn.commit();
|
||||
}
|
||||
|
||||
ResultSet rs = statement.getResultSet();
|
||||
if (hasResults && rs != null) {
|
||||
ResultSetMetaData md = rs.getMetaData();
|
||||
int cols = md.getColumnCount();
|
||||
for (int i = 0; i < cols; i++) {
|
||||
String name = md.getColumnLabel(i);
|
||||
print(name + "\t");
|
||||
}
|
||||
println("");
|
||||
while (rs.next()) {
|
||||
for (int i = 0; i < cols; i++) {
|
||||
String value = rs.getString(i);
|
||||
print(value + "\t");
|
||||
}
|
||||
println("");
|
||||
}
|
||||
}
|
||||
|
||||
command = null;
|
||||
try {
|
||||
statement.close();
|
||||
} catch (Exception e) {
|
||||
// Ignore to workaround a bug in Jakarta DBCP
|
||||
}
|
||||
Thread.yield();
|
||||
} else {
|
||||
command.append(line);
|
||||
command.append(" ");
|
||||
}
|
||||
}
|
||||
if (!autoCommit) {
|
||||
conn.commit();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.fillInStackTrace();
|
||||
printlnError("Error executing: " + command);
|
||||
printlnError(e);
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
e.fillInStackTrace();
|
||||
printlnError("Error executing: " + command);
|
||||
printlnError(e);
|
||||
throw e;
|
||||
} finally {
|
||||
conn.rollback();
|
||||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
private String getDelimiter() {
|
||||
return delimiter;
|
||||
}
|
||||
|
||||
private void print(Object o) {
|
||||
if (logWriter != null) {
|
||||
System.out.print(o);
|
||||
}
|
||||
}
|
||||
|
||||
private void println(Object o) {
|
||||
if (logWriter != null) {
|
||||
logWriter.println(o);
|
||||
}
|
||||
}
|
||||
|
||||
private void printlnError(Object o) {
|
||||
if (errorLogWriter != null) {
|
||||
errorLogWriter.println(o);
|
||||
}
|
||||
}
|
||||
|
||||
private void flush() {
|
||||
if (logWriter != null) {
|
||||
logWriter.flush();
|
||||
}
|
||||
if (errorLogWriter != null) {
|
||||
errorLogWriter.flush();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue