Commit before switching libraries

Signed-off-by: Louis Vallat <louis@louis-vallat.xyz>
This commit is contained in:
Louis Vallat 2020-11-25 21:42:18 +01:00
parent 3db8134153
commit e92fdd2813
12 changed files with 355 additions and 25 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
.idea
logs
build
*.db

View File

@ -2,6 +2,7 @@ package xyz.vallat.louis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.vallat.louis.managers.database.DBManager;
import xyz.vallat.louis.managers.discord.DiscordManager;
public class App {
@ -14,6 +15,8 @@ public class App {
public static void main(String[] args) {
logger.info("Starting {} ver. {}.", NAME, VERSION);
DBManager.testConnection();
DBManager.initializeDatabase();
DiscordManager.login();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {

View File

@ -0,0 +1,17 @@
package xyz.vallat.louis.codes;
public enum ExitCodes {
CANNOT_CONNECT_TO_DB(1),
SQL_FATAL_ERROR(2)
;
private final int code;
ExitCodes(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}

View File

@ -0,0 +1,46 @@
package xyz.vallat.louis.commands;
import biweekly.component.VEvent;
import discord4j.common.util.Snowflake;
import discord4j.core.event.domain.message.MessageCreateEvent;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
import xyz.vallat.louis.managers.calendar.CalendarManager;
import xyz.vallat.louis.managers.database.EventManager;
import xyz.vallat.louis.managers.database.dao.Student;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Inscription extends Command {
private static final Logger logger = LoggerFactory.getLogger(Inscription.class.getCanonicalName());
public Inscription(String name) {
super(name, "S'inscrire aux rappels.", name + " <identifiant ressource ade>");
}
@Override
public Mono<Void> execute(MessageCreateEvent event) {
String[] args = event.getMessage().getContent().split(" ");
if (args.length < 2 || !StringUtils.isNumeric(args[1]))
return event.getMessage().getChannel().flatMap(channel -> channel.createMessage("Error: " + getUsage())).then();
return event.getMessage().getChannel().flatMap(messageChannel -> {
Snowflake snowflake = event.getMessage().getAuthor().isEmpty() ? null :
event.getMessage().getAuthor().get().getId();
Student student = new Student(snowflake, Integer.parseInt(args[1]));
List<VEvent> events = CalendarManager.getEventsFromResource(Integer.parseInt(args[1]));
Map<Student, List<VEvent>> studentListMap = new HashMap<>();
studentListMap.put(student, events);
int importedEvents = EventManager.importEvents(studentListMap);
if (importedEvents == 0) return messageChannel.createMessage(
"On dirait qu'il y a eu une erreur lors de l'importation. Es-tu sûr que c'est bien ton identifiant ADE ?");
else return messageChannel.createMessage("Hey " +
(student.getSnowflake() == null ? "" : "<@!" + student.getSnowflake().asString() + "> ")
+ " tout est bon ! Je surveille maintenant tes " + importedEvents + " prochains évènements !");
}).then().onErrorResume(throwable -> fatalError(event, throwable));
}
}

View File

@ -1,16 +0,0 @@
package xyz.vallat.louis.commands;
import discord4j.core.event.domain.message.MessageCreateEvent;
import reactor.core.publisher.Mono;
public class Subscribe extends Command {
public Subscribe(String name) {
super(name, "S'inscrire aux rappels.", name + " <identifiant ressource>");
}
@Override
public Mono<Void> execute(MessageCreateEvent event) {
return event.getMessage().getChannel().flatMap(messageChannel -> messageChannel.createMessage("Ponnnng!")).then();
}
}

View File

@ -2,7 +2,13 @@ package xyz.vallat.louis.environment;
public enum EnvironmentVariables {
DISCORD_TOKEN("DISCORD_TOKEN"),
MOODLE_PRESENCE_LINK("MOODLE_PRESENCE_LINK")
MOODLE_PRESENCE_LINK("MOODLE_PRESENCE_LINK"),
DB_USERNAME("DB_USERNAME"),
DB_PASSWORD("DB_PASSWORD"),
DB_NAME("DB_NAME"),
DB_HOST("DB_HOST"),
DB_PORT("DB_PORT"),
ADE_URL("ADE_URL")
;

View File

@ -0,0 +1,52 @@
package xyz.vallat.louis.managers.calendar;
import biweekly.Biweekly;
import biweekly.component.VEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.vallat.louis.environment.EnvironmentVariables;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.TimeZone;
public final class CalendarManager {
private static final Logger logger = LoggerFactory.getLogger(CalendarManager.class.getCanonicalName());
public static final Calendar TZ_UTC = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
private CalendarManager() {
}
public static List<VEvent> getEventsFromResource(int resource) {
logger.debug("Getting agenda from student with id '{}'.", resource);
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create(System.getenv(
EnvironmentVariables.ADE_URL.getValue()).replace("{}", String.valueOf(resource)))).build();
try {
HttpResponse<String> response = getHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 200 && response.statusCode() < 400) {
return Biweekly.parse(response.body()).first().getEvents();
} else logger.error("Got status code {}.", response.statusCode());
} catch (IOException e) {
logger.error("IOException:", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return new ArrayList<>();
}
private static HttpClient getHttpClient() {
return HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.build();
}
}

View File

@ -0,0 +1,66 @@
package xyz.vallat.louis.managers.database;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.vallat.louis.codes.ExitCodes;
import xyz.vallat.louis.environment.EnvironmentVariables;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public final class DBManager {
private static final Logger logger = LoggerFactory.getLogger(DBManager.class.getCanonicalName());
private static final String CONNECTION_PREFIX = "postgresql";
private static final String USERNAME = System.getenv(EnvironmentVariables.DB_USERNAME.getValue());
private static final String PASSWORD = System.getenv(EnvironmentVariables.DB_PASSWORD.getValue());
private static final String DATABASE = System.getenv(EnvironmentVariables.DB_NAME.getValue());
private static final int PORT = System.getenv(EnvironmentVariables.DB_PORT.getValue()) == null ? 5432 :
Integer.parseInt(System.getenv(EnvironmentVariables.DB_PORT.getValue()));
private static final String HOST = System.getenv(EnvironmentVariables.DB_HOST.getValue());
private DBManager() {}
public static Connection getConnection() {
try {
return DriverManager.getConnection("jdbc:" + CONNECTION_PREFIX + "://" + HOST + ":" + PORT + "/"
+ DATABASE, USERNAME, PASSWORD);
} catch (SQLException e) {
logger.error("Could not connect to database. Reason: {}.", e.getMessage());
System.exit(ExitCodes.CANNOT_CONNECT_TO_DB.getCode());
}
return null;
}
public static void testConnection() {
logger.debug("Testing database connection.");
getConnection();
logger.info("Database connection OK.");
}
public static void initializeDatabase() {
logger.info("Initializing database if needed.");
try (Connection connection = getConnection()) {
connection.setAutoCommit(false);
StudentManager.initialize(connection);
EventManager.initialize(connection);
attemptCommit(connection);
} catch (SQLException e) {
fatalSQLError(e);
}
}
public static void attemptCommit(Connection connection) throws SQLException {
try {
connection.commit();
} catch (SQLException e) {
connection.rollback();
}
}
public static void fatalSQLError(SQLException e) {
logger.error("Fatal SQL Error:", e);
System.exit(ExitCodes.SQL_FATAL_ERROR.getCode());
}
}

View File

@ -0,0 +1,83 @@
package xyz.vallat.louis.managers.database;
import biweekly.component.VEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.vallat.louis.managers.calendar.CalendarManager;
import xyz.vallat.louis.managers.database.dao.Student;
import java.sql.*;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.List;
import java.util.Map;
public final class EventManager {
private static final Logger logger = LoggerFactory.getLogger(EventManager.class.getCanonicalName());
private EventManager() {
}
public static int importEvents(Map<Student, List<VEvent>> events) {
int imported_events = 0;
try (Connection connection = DBManager.getConnection()) {
connection.setAutoCommit(false);
for (Map.Entry<Student, List<VEvent>> e : events.entrySet()) {
if (e.getValue().isEmpty()) continue;
e.getKey().setId(StudentManager
.addStudent(e.getKey().getSnowflake().asString(), e.getKey().getAde(), connection));
for (VEvent event : e.getValue())
if (importEvent(e.getKey(), event, connection) != 0) imported_events++;
}
try {
connection.commit();
} catch (SQLException e) {
connection.rollback();
return 0;
}
} catch (SQLException e) {
logger.error("Couldn't import events.", e);
return 0;
}
return imported_events;
}
private static int importEvent(Student student, VEvent event, Connection connection) throws SQLException {
String sql = "INSERT INTO events(students_id, summary, start_event, end_event) VALUES (?, ?, ?, ?);";
try (PreparedStatement stmt = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
Instant start = event.getDateStart().getValue().toInstant();
Instant end = event.getDateEnd().getValue().toInstant();
stmt.setInt(1, student.getId());
stmt.setString(2, event.getSummary().getValue());
stmt.setTimestamp(3, start == null ? null : new Timestamp(start.toEpochMilli()), event.getDateStart().getValue().getRawComponents().);
stmt.setTimestamp(4, end == null ? null : new Timestamp(end.toEpochMilli()), CalendarManager.TZ_UTC);
stmt.executeUpdate();
stmt.getGeneratedKeys().next();
assert start != null;
logger.debug("Imported event with id '{}'.", stmt.getGeneratedKeys().getInt(1));
return stmt.getGeneratedKeys().getInt(1);
}
}
public static void initialize(Connection connection) throws SQLException {
String sql = """
CREATE TABLE IF NOT EXISTS events
(
id int GENERATED ALWAYS AS IDENTITY,
students_id int NOT NULL,
summary text NOT NULL,
start_event timestamptz NOT NULL,
end_event timestamptz NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (students_id)
REFERENCES students (id)
ON DELETE CASCADE
);
""";
try (Statement stmt = connection.createStatement()) {
stmt.executeUpdate(sql);
}
}
}

View File

@ -0,0 +1,45 @@
package xyz.vallat.louis.managers.database;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
public final class StudentManager {
private static final Logger logger = LoggerFactory.getLogger(StudentManager.class.getCanonicalName());
private StudentManager() {
}
public static void initialize(Connection connection) throws SQLException {
String sql = """
CREATE TABLE IF NOT EXISTS students
(
id int GENERATED ALWAYS AS IDENTITY,
snowflake text NOT NULL,
ade_resource int NOT NULL,
PRIMARY KEY (id),
UNIQUE (snowflake)
);
""";
try (Statement stmt = connection.createStatement()) {
stmt.executeUpdate(sql);
}
}
public static int addStudent(String snowflake, int ade, Connection connection) throws SQLException {
String sql = "INSERT INTO students(snowflake, ade_resource) VALUES (?, ?);";
try (PreparedStatement stmt = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
stmt.setString(1, snowflake);
stmt.setInt(2, ade);
stmt.executeUpdate();
stmt.getGeneratedKeys().next();
logger.debug("Inserted student with id '{}'.", stmt.getGeneratedKeys().getInt(1));
return stmt.getGeneratedKeys().getInt(1);
}
}
}

View File

@ -0,0 +1,32 @@
package xyz.vallat.louis.managers.database.dao;
import discord4j.common.util.Snowflake;
public class Student {
private int id;
private final Snowflake snowflake;
private final int ade;
public Student(Snowflake snowflake, int ade) {
this.snowflake = snowflake;
this.ade = ade;
this.id = 0;
}
public Snowflake getSnowflake() {
return snowflake;
}
public int getAde() {
return ade;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}

View File

@ -9,10 +9,9 @@ import discord4j.core.object.presence.Activity;
import discord4j.core.object.presence.Presence;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
import xyz.vallat.louis.commands.Command;
import xyz.vallat.louis.commands.Inscription;
import xyz.vallat.louis.commands.Lien;
import xyz.vallat.louis.commands.Subscribe;
import xyz.vallat.louis.environment.EnvironmentVariables;
import java.util.ArrayList;
@ -20,14 +19,14 @@ import java.util.List;
import static xyz.vallat.louis.App.PREFIX;
public class DiscordManager {
public final class DiscordManager {
private static final List<Command> commands = new ArrayList<>();
private static final Logger logger = LoggerFactory.getLogger(DiscordManager.class.getCanonicalName());
private static GatewayDiscordClient discordClient;
static {
commands.add(new Subscribe(PREFIX + "subscribe"));
commands.add(new Inscription(PREFIX + "inscription"));
commands.add(new Lien(PREFIX + "lien"));
}
@ -69,10 +68,6 @@ public class DiscordManager {
discordClient.onDisconnect().block();
}
public static Mono<Long> getGuilds() {
return discordClient.getGuilds().count();
}
public static List<Command> getCommands() {
return new ArrayList<>(commands);
}