diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..98898ff --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.metadata/ +copy-go-contrib.bat +/nkjmlab-go-webapp/pom.xml.versionsBackup diff --git a/go-build.bat b/go-build.bat new file mode 100644 index 0000000..0483be8 --- /dev/null +++ b/go-build.bat @@ -0,0 +1,4 @@ +@echo off +cd /d %~dp0 +jps -lm|grep org.nkjmlab.go. | gawk "{print $1}" | xargs -r -n1 taskkill /F /T /PID +call mvn clean install dependency:copy-dependencies -DoutputDirectory=target/lib -f nkjmlab-go-webapp diff --git a/go-fullbuild.bat b/go-fullbuild.bat new file mode 100644 index 0000000..1fddeb5 --- /dev/null +++ b/go-fullbuild.bat @@ -0,0 +1,6 @@ +@echo off +cd /d %~dp0 +jps -lm|grep org.nkjmlab.go. | gawk "{print $1}" | xargs -r -n1 taskkill /F /T /PID +call mvn clean install -DskipTests=true -f sorm4j +call mvn clean install -DskipTests=true -f nkjmlab-utils +call mvn clean install dependency:copy-dependencies -DoutputDirectory=target/lib -f nkjmlab-go-webapp diff --git a/nkjmlab-go-webapp/pom.xml b/nkjmlab-go-webapp/pom.xml index e12bcfa..fc6de6c 100644 --- a/nkjmlab-go-webapp/pom.xml +++ b/nkjmlab-go-webapp/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.nkjmlab nkjmlab-go-webapp - 0.9.5 + 0.10.0 Go Web Application https://github.com/nkjmlab/nkjmlab-go-webapp @@ -24,49 +24,56 @@ https://github.com/yuu-nkjm/nkjmlab-go-webapp HEAD - - 0.8.2 - 3.0.15.RELEASE - 1.4.8 + 0.9.4 + 1.4.16 UTF-8 true true - org.nkjmlab - nkjmlab-utils-core + nkjmlab-utils-helper ${nkjmlab-utils-version} - org.nkjmlab sorm4j ${sorm4j-version} - + - javax.servlet - javax.servlet-api - 4.0.1 + jakarta.servlet + jakarta.servlet-api + 6.0.0 + provided - io.javalin javalin - 4.6.3 + 5.5.0 + + + + io.javalin + javalin-rendering + 5.5.0 + + + + org.eclipse.jetty.websocket + websocket-jetty-client + 11.0.15 - com.google.firebase firebase-admin - 9.0.0 + 9.1.1 @@ -74,32 +81,23 @@ h2 2.1.214 - com.zaxxer HikariCP 5.0.1 - org.thymeleaf thymeleaf - ${thymeleaf-version} + 3.1.1.RELEASE - - - org.thymeleaf.extras - thymeleaf-extras-java8time - 3.0.4.RELEASE - - commons-io commons-io - 2.11.0 + 2.12.0 @@ -107,50 +105,186 @@ commons-lang3 3.12.0 - - - com.orangesignal - orangesignal-csv - 2.2.1 - - com.fasterxml.jackson.core jackson-databind - 2.13.3 + 2.15.2 com.fasterxml.jackson.datatype jackson-datatype-jsr310 - 2.13.3 + 2.15.2 - - + org.apache.logging.log4j - log4j-slf4j-impl - 2.17.2 + log4j-slf4j2-impl + 2.20.0 org.apache.logging.log4j log4j-core - 2.17.2 + 2.20.0 + + + org.webjars.npm + jquery + 3.7.0 + + + * + * + + + + + org.webjars.npm + bootstrap + 5.3.0 + + + * + * + + + + + org.webjars.bower + bootstrap-treeview + 1.2.0 + + + * + * + + + + + org.webjars.npm + sweetalert2 + 11.7.5 + + + * + * + + + + + org.webjars.npm + fortawesome__fontawesome-free + 6.4.0 + + + * + * + + + + + org.webjars.npm + stacktrace-js + 2.0.2 + + + * + * + + + + + org.webjars.npm + clipboard + 2.0.11 + + + * + * + + + + + org.webjars + datatables + 1.13.2 + + + * + * + + + + + + org.webjars.npm + firebase + 9.19.1 + + + * + * + + + + + org.webjars.npm + firebaseui + 6.0.2 + + + * + * + + + + + org.webjars.npm + emojionearea + 3.4.2 + + + * + * + + + + + org.webjars.npm + blueimp-load-image + 5.16.0 + + + * + * + + + + + org.webjars.npm + ua-parser-js + 1.0.35 + + + * + * + + org.junit.jupiter junit-jupiter-engine - 5.9.0-M1 + 5.9.3 test org.assertj assertj-core - 3.23.1 + 3.24.2 test @@ -172,6 +306,21 @@ compile + + org.codehaus.mojo + versions-maven-plugin + 2.15.0 + + + + + regex + (?i).*(alpha|beta|snapshot|pre|rc|M\d).* + + + + + \ No newline at end of file diff --git a/nkjmlab-go-webapp/src/assembly/jar-with-dependencies.xml b/nkjmlab-go-webapp/src/assembly/jar-with-dependencies.xml deleted file mode 100644 index 684315c..0000000 --- a/nkjmlab-go-webapp/src/assembly/jar-with-dependencies.xml +++ /dev/null @@ -1,17 +0,0 @@ - - jar-with-dependencies - - dir - - false - - - / - true - true - runtime - - - diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/DataSourceManager.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/DataSourceManager.java new file mode 100644 index 0000000..b0e9dd8 --- /dev/null +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/DataSourceManager.java @@ -0,0 +1,100 @@ +package org.nkjmlab.go.javalin; + +import javax.sql.DataSource; +import org.h2.jdbcx.JdbcConnectionPool; +import org.nkjmlab.sorm4j.util.h2.datasource.H2LocalDataSourceFactory; +import org.nkjmlab.util.jackson.JacksonMapper; +import org.nkjmlab.util.java.concurrent.ForkJoinPoolUtils; +import org.nkjmlab.util.java.json.FileDatabaseConfigJson; +import org.nkjmlab.util.java.lang.ResourceUtils; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +public class DataSourceManager { + + private static final org.apache.logging.log4j.Logger log = + org.apache.logging.log4j.LogManager.getLogger(); + + private static final int DEFAULT_MAX_CONNECTIONS = + Math.min(ForkJoinPoolUtils.availableProcessors() * 2 * 2, 10); + + private static final int DEFAULT_TIMEOUT_SECONDS = 30; + + private H2LocalDataSourceFactory factory; + + public DataSourceManager() { + FileDatabaseConfigJson fileDbConf = getFileDbConfig(); + H2LocalDataSourceFactory factory = + H2LocalDataSourceFactory.builder(fileDbConf.databaseDirectory, fileDbConf.databaseName, + fileDbConf.username, fileDbConf.password).build(); + this.factory = factory; + factory.makeFileDatabaseIfNotExists(); + log.info("server jdbcUrl={}", factory.getServerModeJdbcUrl()); + } + + public DataSource createHikariInMemoryDataSource() { + return createHikariDataSource(factory.getInMemoryModeJdbcUrl(), factory.getUsername(), + factory.getPassword()); + } + + public DataSource createHikariServerModeDataSource() { + return createHikariDataSource(factory.getServerModeJdbcUrl(), factory.getUsername(), + factory.getPassword()); + } + + public JdbcConnectionPool createH2InMemoryDataSource() { + return createH2DataSource(factory.getInMemoryModeJdbcUrl(), factory.getUsername(), + factory.getPassword()); + } + + public JdbcConnectionPool createH2ServerModeDataSource() { + return createH2DataSource(factory.getServerModeJdbcUrl(), factory.getUsername(), + factory.getPassword()); + } + + + private static HikariDataSource createHikariDataSource(String url, String user, String password) { + HikariConfig config = new HikariConfig(); + config.setJdbcUrl(url); + config.setUsername(user); + config.setPassword(password); + config.setMaximumPoolSize(DEFAULT_MAX_CONNECTIONS); + config.setConnectionTimeout(DEFAULT_TIMEOUT_SECONDS * 1000); + config.addDataSourceProperty("useServerPrepStmts", "true"); + config.addDataSourceProperty("cachePrepStmts", "true"); + config.addDataSourceProperty("prepStmtCacheSize", "250"); + config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); + config.addDataSourceProperty("minimumIdle", "2048"); + return new HikariDataSource(config); + } + + + private static JdbcConnectionPool createH2DataSource(String url, String user, String password) { + JdbcConnectionPool ds = JdbcConnectionPool.create(url, user, password); + ds.setMaxConnections(DEFAULT_MAX_CONNECTIONS); + ds.setLoginTimeout(DEFAULT_TIMEOUT_SECONDS); + return ds; + } + + private static FileDatabaseConfigJson getFileDbConfig() { + try { + return JacksonMapper.getDefaultMapper() + .toObject(ResourceUtils.getResourceAsFile("/conf/h2.json"), + FileDatabaseConfigJson.Builder.class) + .build(); + } catch (Exception e) { + log.warn("Try to load h2.json.default"); + return JacksonMapper.getDefaultMapper() + .toObject(ResourceUtils.getResourceAsFile("/conf/h2.json.default"), + FileDatabaseConfigJson.Builder.class) + .build(); + } + } + + public H2LocalDataSourceFactory getFactory() { + return factory; + } + + + +} diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/GoAccessManager.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/GoAccessManager.java new file mode 100644 index 0000000..98d9b07 --- /dev/null +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/GoAccessManager.java @@ -0,0 +1,68 @@ +package org.nkjmlab.go.javalin; + +import java.util.Set; +import org.nkjmlab.go.javalin.auth.GoAuthService; +import org.nkjmlab.go.javalin.model.relation.UsersTable; +import org.nkjmlab.go.javalin.model.relation.UsersTable.User; +import io.javalin.http.Context; +import io.javalin.http.Handler; +import io.javalin.security.AccessManager; +import io.javalin.security.RouteRole; + +public class GoAccessManager implements AccessManager { + + public enum UserRole implements RouteRole { + + BEFORE_LOGIN, GUEST, STUDENT, ADMIN; + + static final UserRole[] LOGIN_ROLES = new UserRole[] {GUEST, STUDENT, ADMIN}; + + } + + private final UsersTable usersTable; + private final GoAuthService authService; + + public GoAccessManager(UsersTable usersTable, GoAuthService authService) { + this.usersTable = usersTable; + this.authService = authService; + } + + UserRole toUserRole(UsersTable usersTable, String sessionId) { + if (!authService.isSignin(sessionId)) { + return UserRole.BEFORE_LOGIN; + } + + User u = authService.toSigninSession(sessionId) + .map(login -> usersTable.selectByPrimaryKey(login.userId())).orElse(null); + if (u == null) { + return UserRole.BEFORE_LOGIN; + } + if (u.isAdmin()) { + return UserRole.ADMIN; + } + if (u.isStudent()) { + return UserRole.STUDENT; + } + if (u.isGuest()) { + return UserRole.GUEST; + } + return UserRole.BEFORE_LOGIN; + } + + + @Override + public void manage(Handler handler, Context ctx, Set routeRoles) + throws Exception { + if (routeRoles.size() == 0) { + handler.handle(ctx); + } else if (routeRoles.contains(toUserRole(usersTable, ctx.req().getSession().getId()))) { + handler.handle(ctx); + } else { + ctx.redirect("/app/index.html"); + } + + + } + + +} diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/GoAppHandlers.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/GoAppHandlers.java new file mode 100644 index 0000000..0abff3e --- /dev/null +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/GoAppHandlers.java @@ -0,0 +1,250 @@ +package org.nkjmlab.go.javalin; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.nkjmlab.go.javalin.GoAccessManager.UserRole; +import org.nkjmlab.go.javalin.auth.GoAuthService; +import org.nkjmlab.go.javalin.auth.GoAuthService.SigninSession; +import org.nkjmlab.go.javalin.model.relation.GameRecordsTable.GameRecord; +import org.nkjmlab.go.javalin.model.relation.GameStatesTable.GameState; +import org.nkjmlab.go.javalin.model.relation.HandUpsTable.HandUp; +import org.nkjmlab.go.javalin.model.relation.LoginsTable.Login; +import org.nkjmlab.go.javalin.model.relation.MatchingRequestsTable.MatchingRequest; +import org.nkjmlab.go.javalin.model.relation.UsersTable; +import org.nkjmlab.go.javalin.model.relation.UsersTable.User; +import org.nkjmlab.go.javalin.websocket.WebsocketSessionsManager; +import org.nkjmlab.sorm4j.common.Tuple.Tuple2; +import org.nkjmlab.util.java.net.UrlUtils; +import org.nkjmlab.util.java.web.ViewModel; +import io.javalin.Javalin; +import io.javalin.http.Context; +import io.javalin.http.Handler; +import jakarta.servlet.http.HttpServletRequest; + +public class GoAppHandlers { + + public static class GoHandler implements Handler { + + private final Function>>>>> handler; + private final GoTables goTables; + private final GoAuthService authService; + + public GoHandler(GoTables goTables, GoAuthService authService, + Function>>>>> handler) { + this.handler = handler; + this.goTables = goTables; + this.authService = authService; + } + + @Override + public void handle(Context ctx) throws Exception { + String filePath = UrlUtils.of(ctx.url()).getPath().replaceFirst("^/app/", ""); + ViewModel.Builder model = createDefaultViewModelBuilder(goTables.usersTable, ctx.req()); + Optional session = authService.toSigninSession(ctx.req().getSession().getId()); + + handler.apply(goTables).apply(ctx).apply(filePath).apply(model).accept(session); + } + + private ViewModel.Builder createDefaultViewModelBuilder(UsersTable usersTable, + HttpServletRequest request) { + Map map = ViewModel.builder() + .setFileModifiedDate(GoWebAppConfig.WEB_APP_CONFIG.getWebRootDirectory(), 10, "js", "css") + .put("webjars", GoWebAppConfig.WEB_APP_CONFIG.getWebJars()) + .put("currentUser", getCurrentUserAccount(usersTable, request)).build(); + return ViewModel.builder(map); + } + + private User getCurrentUserAccount(UsersTable usersTable, HttpServletRequest request) { + Optional u = authService.toSigninSession(request.getSession().getId()) + .map(uid -> usersTable.selectByPrimaryKey(uid.userId())); + return u.orElse(new User()); + } + } + + private static class PlayGoHandler extends GoHandler { + + public PlayGoHandler(GoTables goTables, GoAuthService authService) { + super(goTables, authService, gtbl -> ctx -> filePath -> model -> session -> { + session.ifPresent(opts -> { + boolean attend = gtbl.loginsTable.isAttendance(opts.userId()); + model.put("isAttendance", attend); + model.put("problemGroupsJson", gtbl.problemsTable.getProblemGroupsNode()); + }); + ctx.render(filePath, model.build()); + }); + + } + } + + private static class AppIndexGoHandler extends GoHandler { + public AppIndexGoHandler(GoTables goTables, GoAuthService authService) { + super(goTables, authService, gtbl -> ctx -> filePath -> model -> session -> { + session.ifPresent(opts -> { + boolean attend = gtbl.loginsTable.isAttendance(opts.userId()); + model.put("isAttendance", attend); + model.put("problemGroupsJson", gtbl.problemsTable.getProblemGroupsNode()); + }); + ctx.render(filePath, model.build()); + }); + } + } + + private static class PlayersAllGoHandler extends GoHandler { + public PlayersAllGoHandler(GoTables goTables, GoAuthService authService) { + super(goTables, authService, gtbl -> ctx -> filePath -> model -> session -> { + List> users = gtbl.usersTable.readAllWithLastLogin(); + List loginJsons = + users.stream().map(t -> new GoAppHandlers.LoginJson(t.getT2(), t.getT1())) + .collect(Collectors.toList()); + model.put("userAccounts", loginJsons); + ctx.render("players.html", model.build()); + }); + } + } + private static class PlayersGoHandler extends GoHandler { + public PlayersGoHandler(GoTables goTables, GoAuthService authService) { + super(goTables, authService, gtbl -> ctx -> filePath -> model -> session -> { + List> users = gtbl.usersTable.readAllWithLastLogin(); + List loginJsons = users.stream().filter(t -> t.getT1().isStudent()) + .map(t -> new GoAppHandlers.LoginJson(t.getT2(), t.getT1())) + .collect(Collectors.toList()); + model.put("userAccounts", loginJsons); + ctx.render(filePath, model.build()); + }); + } + } + private static class GamesAllGoHandler extends GoHandler { + public GamesAllGoHandler(GoTables goTables, GoAuthService authService, + WebsocketSessionsManager websocketManager) { + super(goTables, authService, gtbl -> ctx -> filePath -> model -> session -> { + List tmp = + gtbl.gameStatesTables.readTodayGameJsons().stream().map(gsj -> { + String gameId = gsj.gameId(); + GoAppHandlers.GameStateViewJson json = new GoAppHandlers.GameStateViewJson(gsj, + gtbl.handsUpTable.selectByPrimaryKey(gameId), + websocketManager.getWatchingUniqueStudentsNum(gameId)); + return json; + }).collect(Collectors.toList()); + model.put("games", tmp); + ctx.render("games.html", model.build()); + }); + } + } + private static class GamesGoHandler extends GoHandler { + public GamesGoHandler(GoTables goTables, GoAuthService authService, + WebsocketSessionsManager websocketManager) { + super(goTables, authService, gtbl -> ctx -> filePath -> model -> session -> { + List gids = websocketManager.readActiveGameIdsOrderByGameId(); + List tmp = + gids.stream().map(gid -> gtbl.gameStatesTables.readLatestGameState(gid)) + .map(gsj -> new GoAppHandlers.GameStateViewJson(gsj, + gtbl.handsUpTable.selectByPrimaryKey(gsj.gameId()), + websocketManager.getWatchingUniqueStudentsNum(gsj.gameId()))) + .collect(Collectors.toList()); + model.put("games", + tmp.stream().filter(j -> j.watchingStudentsNum() > 0).collect(Collectors.toList())); + ctx.render(filePath, model.build()); + }); + } + } + private static class GameRecordTableGoHandler extends GoHandler { + public GameRecordTableGoHandler(GoTables goTables, GoAuthService authService) { + super(goTables, authService, gtbl -> ctx -> filePath -> model -> session -> { + String userId = ctx.queryParam("userId"); + List records = gtbl.gameRecordsTable.readByUserId(userId); + model.put("records", records); + ctx.render("fragment/game-record-table.html", model.build()); + }); + } + } + + private static class QuestionTableGoHandler extends GoHandler { + public QuestionTableGoHandler(GoTables goTables, GoAuthService authService) { + super(goTables, authService, gtbl -> ctx -> filePath -> model -> session -> { + List gids = gtbl.handsUpTable.readAllGameIds(); + List tmp = gtbl.gameStatesTables.readLatestBoardsJson(gids) + .stream().map(gsj -> new GoAppHandlers.GameStateViewJson(gsj, + gtbl.handsUpTable.selectByPrimaryKey(gsj.gameId()), 0)) + .toList(); + model.put("games", tmp); + ctx.render(filePath, model.build()); + }); + } + } + + private static class WaitingRequestGoHandler extends GoHandler { + public WaitingRequestGoHandler(GoTables goTables, GoAuthService authService) { + super(goTables, authService, gtbl -> ctx -> filePath -> model -> session -> { + String userId = ctx.queryParam("userId"); + if (userId != null) { + List tmp = gtbl.matchingRequestsTable.readRequests(); + model.put("requests", + tmp.stream().filter(r -> r.userId().equals(userId)).collect(Collectors.toList())); + } else { + model.put("requests", gtbl.matchingRequestsTable.readRequests()); + } + ctx.render(filePath, model.build()); + }); + } + } + private static class WaitingRequestSmallGoHandler extends GoHandler { + public WaitingRequestSmallGoHandler(GoTables goTables, GoAuthService authService) { + super(goTables, authService, gtbl -> ctx -> filePath -> model -> session -> { + String userId = ctx.queryParam("userId"); + List tmp = gtbl.matchingRequestsTable.readRequests(); + MatchingRequest req = tmp.stream().filter(r -> r.userId().equals(userId)).findAny() + .orElse(new MatchingRequest()); + model.put("req", req); + model.put("reqNum", tmp.size()); + ctx.render(filePath, model.build()); + }); + } + } + + + public static void prepareGetHandler(Javalin app, WebsocketSessionsManager websocketManager, + GoTables goTables, GoAuthService authService) { + + app.get("/app", ctx -> ctx.redirect("/app/index.html")); + app.get("/app/index.html", new AppIndexGoHandler(goTables, authService)); + app.get("/app/play.html", new PlayGoHandler(goTables, authService), UserRole.LOGIN_ROLES); + app.get("/app/players-all.html", new PlayersAllGoHandler(goTables, authService), + UserRole.ADMIN); + app.get("/app/players.html", new PlayersGoHandler(goTables, authService), UserRole.ADMIN); + app.get("/app/games-all.html", new GamesAllGoHandler(goTables, authService, websocketManager), + UserRole.ADMIN); + app.get("/app/games.html", new GamesGoHandler(goTables, authService, websocketManager), + UserRole.ADMIN); + app.get("/app/fragment/game-record-table.html", + new GameRecordTableGoHandler(goTables, authService), UserRole.LOGIN_ROLES); + app.get("/app/fragment/question-table*", new QuestionTableGoHandler(goTables, authService), + UserRole.ADMIN); + + app.get("/app/fragment/waiting-request-table.html", + new WaitingRequestGoHandler(goTables, authService), UserRole.ADMIN); + + app.get("/app/fragment/waiting-request-table-small.html", + new WaitingRequestSmallGoHandler(goTables, authService), UserRole.LOGIN_ROLES); + + app.get("/app/*", + new GoHandler(goTables, authService, gtbl -> ctx -> filePath -> model -> session -> { + ctx.render(filePath, model.build()); + })); + + } + + public static record LoginJson(Login login, User user) { + + } + + public static record GameStateViewJson(GameState gameState, HandUp handUp, + int watchingStudentsNum) { + + } + + +} diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/GoApplication.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/GoApplication.java index 4d67d91..2e963ad 100644 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/GoApplication.java +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/GoApplication.java @@ -1,120 +1,43 @@ package org.nkjmlab.go.javalin; -import java.io.File; -import java.nio.file.Files; -import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import javax.servlet.http.HttpServletRequest; -import javax.sql.DataSource; -import org.h2.jdbcx.JdbcConnectionPool; -import org.nkjmlab.go.javalin.fbauth.AuthService; -import org.nkjmlab.go.javalin.fbauth.AuthServiceInterface; -import org.nkjmlab.go.javalin.fbauth.FirebaseUserSession; +import org.nkjmlab.go.javalin.auth.AuthService; +import org.nkjmlab.go.javalin.auth.GoAuthService; import org.nkjmlab.go.javalin.jsonrpc.GoJsonRpcService; -import org.nkjmlab.go.javalin.model.relation.GameRecordsTable; -import org.nkjmlab.go.javalin.model.relation.GameRecordsTable.GameRecord; -import org.nkjmlab.go.javalin.model.relation.GameStatesTable; -import org.nkjmlab.go.javalin.model.relation.GameStatesTable.GameState; -import org.nkjmlab.go.javalin.model.relation.GameStatesTable.GameStateJson; -import org.nkjmlab.go.javalin.model.relation.GameStatesTables; -import org.nkjmlab.go.javalin.model.relation.HandUpsTable; -import org.nkjmlab.go.javalin.model.relation.HandUpsTable.HandUp; -import org.nkjmlab.go.javalin.model.relation.LoginsTable; -import org.nkjmlab.go.javalin.model.relation.LoginsTable.Login; -import org.nkjmlab.go.javalin.model.relation.MatchingRequestsTable; -import org.nkjmlab.go.javalin.model.relation.MatchingRequestsTable.MatchingRequest; -import org.nkjmlab.go.javalin.model.relation.PasswordsTable; -import org.nkjmlab.go.javalin.model.relation.ProblemsTable; -import org.nkjmlab.go.javalin.model.relation.UsersTable; -import org.nkjmlab.go.javalin.model.relation.UsersTable.User; -import org.nkjmlab.go.javalin.model.relation.VotesTable; import org.nkjmlab.go.javalin.websocket.WebsocketSessionsManager; -import org.nkjmlab.go.javalin.websocket.WebsoketSessionsTable; -import org.nkjmlab.sorm4j.common.Tuple.Tuple2; -import org.nkjmlab.sorm4j.internal.util.ParameterizedStringUtils; -import org.nkjmlab.sorm4j.internal.util.Try; -import org.nkjmlab.util.h2.H2LocalDataSourceFactory; -import org.nkjmlab.util.h2.H2ServerUtils; +import org.nkjmlab.sorm4j.util.h2.server.H2TcpServerProcess; +import org.nkjmlab.sorm4j.util.h2.server.H2TcpServerProperties; +import org.nkjmlab.util.firebase.auth.BasicFirebaseAuthHandler; +import org.nkjmlab.util.firebase.auth.FirebaseAuthHandler; import org.nkjmlab.util.jackson.JacksonMapper; -import org.nkjmlab.util.java.concurrent.ForkJoinPoolUtils; -import org.nkjmlab.util.java.io.SystemFileUtils; -import org.nkjmlab.util.java.json.FileDatabaseConfigJson; +import org.nkjmlab.util.java.function.Try; import org.nkjmlab.util.java.lang.ProcessUtils; import org.nkjmlab.util.java.lang.ResourceUtils; import org.nkjmlab.util.java.lang.SystemPropertyUtils; -import org.nkjmlab.util.javax.servlet.JsonRpcService; -import org.nkjmlab.util.javax.servlet.UserSession; -import org.nkjmlab.util.javax.servlet.ViewModel; -import org.nkjmlab.util.javax.servlet.ViewModel.Builder; -import org.nkjmlab.util.jsonrpc.JsonRpcRequest; -import org.nkjmlab.util.jsonrpc.JsonRpcResponse; -import org.nkjmlab.util.thymeleaf.TemplateEngineBuilder; +import org.nkjmlab.util.javalin.JavalinJsonRpcService; +import org.nkjmlab.util.thymeleaf.ThymeleafTemplateEnginBuilder; import org.thymeleaf.TemplateEngine; -import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect; -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; import io.javalin.Javalin; import io.javalin.http.staticfiles.Location; -import io.javalin.plugin.rendering.template.JavalinThymeleaf; +import io.javalin.rendering.template.JavalinThymeleaf; public class GoApplication { private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(); - private static final File APP_ROOT_DIR = ResourceUtils.getResourceAsFile("/"); - private static final String WEBROOT_DIR_NAME = "/webroot"; - private static final File WEBROOT_DIR = new File(APP_ROOT_DIR, WEBROOT_DIR_NAME); - private static final File USER_HOME_DIR = SystemFileUtils.getUserHomeDirectory(); - public static final File BACKUP_DIR = new File(USER_HOME_DIR, "go-bkup/"); - public static final File PROBLEM_DIR = new File(APP_ROOT_DIR, "problem"); - public static final File PROBLEM_BACKUP_DIR = new File(APP_ROOT_DIR, "problem-auto-bkup"); - - public static final File CURRENT_ICON_DIR = new File(WEBROOT_DIR, "img/icon"); - public static final File UPLOADED_ICON_DIR = new File(WEBROOT_DIR, "img/icon-uploaded"); - public static final File RANDOM_ICON_DIR = new File(WEBROOT_DIR, "img/icon-random"); - public static final File INITIAL_ICON_DIR = new File(WEBROOT_DIR, "img/icon-initial"); - - private static long THYMELEAF_EXPIRE_TIME_MILLI_SECOND = 1 * 1000; - - private static int TRIM_THRESHOLD_OF_GAME_STATE_TABLE = 30000; - - private final DataSource memDbDataSource; - private final DataSource fileDbDataSource; - private final Javalin app; - private final ProblemsTable problemsTable; - private final HandUpsTable handsUpTable; - private final UsersTable usersTable; - private final PasswordsTable passwordsTable; - private final MatchingRequestsTable matchingRequestsTable; - private final GameStatesTables gameStatesTables; - private final VotesTable votesTable; - private final WebsoketSessionsTable websoketSessionsTable; - private final GameRecordsTable gameRecordsTable; - private final LoginsTable loginsTable; - private final WebsocketSessionsManager wsManager; - - - - static { - } public static void main(String[] args) { - if (args.length != 0) { - THYMELEAF_EXPIRE_TIME_MILLI_SECOND = Long.valueOf(args[0]); - } + int port = 4567; log.info("start (port:{}) => {}", port, SystemPropertyUtils.getJavaProperties()); ProcessUtils.stopProcessBindingPortIfExists(port); - H2ServerUtils.startDefaultTcpServerProcessAndWaitFor(); - H2ServerUtils.startDefaultWebConsoleServerProcessAndWaitFor(); + new H2TcpServerProcess(H2TcpServerProperties.builder().build()).awaitStart(); new GoApplication().start(port); } @@ -124,148 +47,54 @@ private void start(int port) { } public GoApplication() { - FileDatabaseConfigJson fileDbConf = getFileDbConfig(); - H2LocalDataSourceFactory factory = - H2LocalDataSourceFactory.builder(fileDbConf.databaseDirectory, fileDbConf.databaseName, - fileDbConf.username, fileDbConf.password).build(); + final long THYMELEAF_EXPIRE_TIME_MILLI_SECOND = 1 * 1000; - this.memDbDataSource = createH2DataSource(factory.getInMemoryModeJdbcUrl(), - factory.getUsername(), factory.getPassword()); - log.info("server jdbcUrl={}", factory.getServerModeJdbcUrl()); - this.fileDbDataSource = createHikariDataSource(factory.getServerModeJdbcUrl(), - factory.getUsername(), factory.getPassword()); - // H2Server.openBrowser(memDbDataSource, true); + log.info("log4j2.configurationFile={}, Logger level={}", + System.getProperty("log4j2.configurationFile"), log.getLevel()); - TemplateEngine engine = new TemplateEngineBuilder().setPrefix("/templates/") + TemplateEngine engine = ThymeleafTemplateEnginBuilder.builder() .setTtlMs(THYMELEAF_EXPIRE_TIME_MILLI_SECOND).build(); - engine.addDialect(new Java8TimeDialect()); - JavalinThymeleaf.configure(engine); + JavalinThymeleaf.init(engine); - this.app = Javalin.create(config -> { - config.addStaticFiles(WEBROOT_DIR_NAME, Location.CLASSPATH); - config.autogenerateEtags = true; - // config.precompressStaticFiles = true; - config.enableCorsForAllOrigins(); - }); + DataSourceManager basicDataSource = new DataSourceManager(); - { - this.problemsTable = new ProblemsTable(memDbDataSource); - problemsTable.dropAndInsertInitialProblemsToTable(PROBLEM_DIR); - } - { - this.loginsTable = new LoginsTable(fileDbDataSource); - loginsTable.createTableIfNotExists(); - loginsTable.createIndexesIfNotExists(); - loginsTable.writeCsv(new File(BACKUP_DIR, "logins-" + System.currentTimeMillis() + ".csv")); - } - - this.handsUpTable = new HandUpsTable(memDbDataSource); - { - this.usersTable = new UsersTable(fileDbDataSource); - usersTable.dropTableIfExists(); - usersTable.createTableAndIndexesIfNotExists(); - try { - File f = ResourceUtils.getResourceAsFile("/conf/users.csv"); - usersTable.readFromFileAndMerge(f); - } catch (Exception e) { - log.error(e, e); - log.warn("load users.csv.default ..."); - File f = ResourceUtils.getResourceAsFile("/conf/users.csv.default"); - usersTable.readFromFileAndMerge(f); - } - } - { - this.passwordsTable = new PasswordsTable(fileDbDataSource); - passwordsTable.createTableIfNotExists().createIndexesIfNotExists(); - try { - File f = ResourceUtils.getResourceAsFile("/conf/passwords.csv"); - passwordsTable.readFromFileAndMerge(f); - } catch (Exception e) { - log.warn("load password.csv.default ..."); - File f = ResourceUtils.getResourceAsFile("/conf/passwords.csv.default"); - passwordsTable.readFromFileAndMerge(f); - } - } - { - this.gameRecordsTable = new GameRecordsTable(fileDbDataSource); - gameRecordsTable.createTableIfNotExists().createIndexesIfNotExists(); - gameRecordsTable - .writeCsv(new File(BACKUP_DIR, "game-record" + System.currentTimeMillis() + ".csv")); - - gameRecordsTable.recalculateAndUpdateRank(usersTable); - } - { - this.matchingRequestsTable = new MatchingRequestsTable(memDbDataSource); - matchingRequestsTable.createTableIfNotExists().createIndexesIfNotExists(); - } - { - - GameStatesTable gameStatesTable = new GameStatesTable(fileDbDataSource); - gameStatesTable.createTableIfNotExists().createIndexesIfNotExists(); - - gameStatesTable.trimAndBackupToFile(factory.getDatabaseDirectory(), - TRIM_THRESHOLD_OF_GAME_STATE_TABLE); - - GameStatesTable gameStatesTableInMem = new GameStatesTable(memDbDataSource); - gameStatesTableInMem.createTableIfNotExists().createIndexesIfNotExists(); - gameStatesTableInMem.insert(gameStatesTable.selectAll().toArray(GameState[]::new)); - - this.gameStatesTables = new GameStatesTables(gameStatesTable, gameStatesTableInMem); - } - { - this.votesTable = new VotesTable(memDbDataSource); - votesTable.createTableIfNotExists().createIndexesIfNotExists(); - } - { - this.websoketSessionsTable = new WebsoketSessionsTable(memDbDataSource); - this.websoketSessionsTable.createTableIfNotExists().createIndexesIfNotExists(); - } - this.wsManager = new WebsocketSessionsManager(gameStatesTables, problemsTable, - websoketSessionsTable, usersTable, handsUpTable, matchingRequestsTable); - - - - prepareWebSocket(); - prepareJsonRpc(); - prepareGetHandler(); - } + GoTables goTables = GoTables.prepareTables(basicDataSource); + WebsocketSessionsManager webSocketManager = + new WebsocketSessionsManager(goTables, basicDataSource.createHikariInMemoryDataSource()); - private FileDatabaseConfigJson getFileDbConfig() { - try { - return getDefaultJacksonMapper().toObject(ResourceUtils.getResourceAsFile("/conf/h2.json"), - FileDatabaseConfigJson.Builder.class).build(); - } catch (Exception e) { - log.warn("Try to load h2.json.default"); - return getDefaultJacksonMapper() - .toObject(ResourceUtils.getResourceAsFile("/conf/h2.json.default"), - FileDatabaseConfigJson.Builder.class) - .build(); - } - } + scheduleCheckMatchingRequest(webSocketManager, goTables); - public static String getJdbcUrlOfInMemoryDb(String dbName) { - return "jdbc:h2:mem:" + dbName + ";DB_CLOSE_DELAY=-1"; - } + FirebaseAuthHandler firebaseService = BasicFirebaseAuthHandler.create( + goTables.usersTable.readAll().stream().map(u -> u.email()).toList(), + ResourceUtils.getResourceAsFile("/conf/firebase.json")); + GoAuthService authService = new GoAuthService(goTables.usersTable, firebaseService); - private void prepareWebSocket() { - app.ws("/websocket/play/checkcon", ws -> { - ws.onConnect(ctx -> { - log.debug("{}", ctx.session.getUpgradeRequest().getRequestURI()); - }); - }); - app.ws("/websocket/play", ws -> { - ws.onConnect(ctx -> wsManager.onConnect(ctx.session, ctx.queryParam("userId"), - ctx.queryParam("gameId"))); - ws.onClose(ctx -> wsManager.onClose(ctx.session, ctx.status(), ctx.reason())); - ws.onError(ctx -> wsManager.onError(ctx.session, ctx.error())); - ws.onMessage(ctx -> wsManager.onMessage(ctx.queryParam("gameId"), ctx)); + this.app = Javalin.create(config -> { + config.staticFiles.add(GoWebAppConfig.WEB_APP_CONFIG.getWebRootDirectory().getName(), + Location.CLASSPATH); + config.staticFiles.enableWebjars(); + config.http.generateEtags = true; + config.plugins.enableCors(cors -> cors.add(corsConfig -> corsConfig.anyHost())); + config.accessManager(new GoAccessManager(goTables.usersTable, authService)); }); + + prepareWebSocket(app, webSocketManager); + prepareJsonRpc(app, webSocketManager, new GoJsonRpcService(webSocketManager, goTables), + new AuthService.Factory(goTables.usersTable, goTables.loginsTable, goTables.passwordsTable, + authService)); + + + GoAppHandlers.prepareGetHandler(app, webSocketManager, goTables, authService); + } + + private static void scheduleCheckMatchingRequest(WebsocketSessionsManager webSocketManager, + GoTables goTables) { final int INTERVAL_IN_WAITING_ROOM = 10; ScheduledExecutorService srv = Executors.newSingleThreadScheduledExecutor(runnable -> { @@ -273,232 +102,42 @@ private void prepareWebSocket() { t.setDaemon(true); return t; }); + srv.scheduleWithFixedDelay(Try.createRunnable(() -> { - Set uids = matchingRequestsTable.createPairOfUsers(gameStatesTables); - wsManager.sendUpdateWaitingRequestStatus(uids); + Set uids = + goTables.matchingRequestsTable.createPairOfUsers(goTables.gameStatesTables); + webSocketManager.sendUpdateWaitingRequestStatus(uids); }, e -> log.error(e)), INTERVAL_IN_WAITING_ROOM, INTERVAL_IN_WAITING_ROOM, TimeUnit.SECONDS); - } - - private void prepareJsonRpc() { - - prepareFirebase(); - - final GoJsonRpcService goJsonRpcService = new GoJsonRpcService(wsManager, gameStatesTables, - problemsTable, usersTable, loginsTable, matchingRequestsTable, votesTable, handsUpTable, - websoketSessionsTable, gameRecordsTable); - - JacksonMapper mapper = GoApplication.getDefaultJacksonMapper(); - JsonRpcService jsonRpcService = new JsonRpcService(mapper); - - app.post("/app/json/GoJsonRpcService", ctx -> { - JsonRpcRequest jreq = jsonRpcService.toJsonRpcRequest(ctx.req); - Object srv = AuthServiceInterface.getDeclaredMethodNames().contains(jreq.getMethod()) - ? new AuthService(usersTable, loginsTable, passwordsTable, ctx.req) - : goJsonRpcService; - JsonRpcResponse jres = jsonRpcService.callHttpJsonRpc(srv, jreq, ctx.res); - String ret = mapper.toJson(jres); - ctx.result(ret).contentType("application/json"); - }); } - private boolean prepareFirebase() { - try { - String url = Files - .readAllLines(ResourceUtils.getResourceAsFile("/conf/firebase-url.conf").toPath()).get(0); - AuthService.initialize(url, ResourceUtils.getResourceAsFile("/conf/firebase.json")); - return true; - } catch (Exception e) { - log.warn("Skip firebase settings"); - return false; - } - } - - private void prepareGetHandler() { - app.get("/app", ctx -> ctx.redirect("/app/index.html")); - - app.get("/app/", ctx -> { - String pageName = - ctx.pathParam("pageName") == null ? "index.html" : ctx.pathParam("pageName"); - Builder model = createDefaultModel(usersTable, ctx.req); - switch (pageName) { - case "play.html" -> { - UserSession session = UserSession.wrap(ctx.req.getSession()); - if (!session.isLogined()) { - model.put("requireToLogin", true); - break; - } - session.getUserId().ifPresent(uid -> { - boolean attend = loginsTable.isAttendance(uid); - model.put("isAttendance", attend); - model.put("problemGroupsJson", problemsTable.getProblemGroupsNode()); - }); - } - case "players-all.html" -> { - try { - isAdminOrThrow(usersTable, ctx.req); - } catch (Exception e) { - ctx.redirect("/app/index.html"); - log.error(e.getMessage()); - return; - } - List> users = usersTable.readAllWithLastLogin(); - List loginJsons = users.stream().map(t -> new LoginJson(t.getT2(), t.getT1())) - .collect(Collectors.toList()); - model.put("userAccounts", loginJsons); - pageName = "players.html"; - } - case "players.html" -> { - try { - isAdminOrThrow(usersTable, ctx.req); - } catch (Exception e) { - ctx.redirect("/app/index.html"); - log.error(e.getMessage()); - return; - } - List> users = usersTable.readAllWithLastLogin(); - List loginJsons = users.stream().filter(t -> t.getT1().isStudent()) - .map(t -> new LoginJson(t.getT2(), t.getT1())).collect(Collectors.toList()); - model.put("userAccounts", loginJsons); - } - case "games-all.html" -> { - List tmp = gameStatesTables.readTodayGameJsons().stream().map(gsj -> { - String gameId = gsj.gameId(); - GameStateViewJson json = - new GameStateViewJson(gsj, handsUpTable.selectByPrimaryKey(gameId), - websoketSessionsTable.getWatchingUniqueStudentsNum(usersTable, gameId)); - return json; - }).collect(Collectors.toList()); - model.put("games", tmp); - pageName = "games.html"; - } - case "games.html" -> { - List gids = websoketSessionsTable.readActiveGameIdsOrderByGameId(usersTable); - List tmp = - gids.stream().map(gid -> gameStatesTables.readLatestGameStateJson(gid)).map(gsj -> { - String gameId = gsj.gameId(); - GameStateViewJson json = - new GameStateViewJson(gsj, handsUpTable.selectByPrimaryKey(gameId), - websoketSessionsTable.getWatchingUniqueStudentsNum(usersTable, gameId)); - return json; - }).collect(Collectors.toList()); - model.put("games", - tmp.stream().filter(j -> j.watchingStudentsNum() > 0).collect(Collectors.toList())); - } - case "fragment/game-record-table.html" -> { - String userId = ctx.queryParam("userId"); - List records = gameRecordsTable.readByUserId(userId); - model.put("records", records); - } - case "fragment/question-table.html", "fragment/question-table-small.html" -> { - List gids = handsUpTable.readAllGameIds(); - List tmp = - gameStatesTables.readLatestBoardsJson(gids).stream().map(gsj -> { - String gameId = gsj.gameId(); - GameStateViewJson json = - new GameStateViewJson(gsj, handsUpTable.selectByPrimaryKey(gameId), 0); - return json; - }).collect(Collectors.toList()); - model.put("games", tmp); - } - case "fragment/waiting-request-table.html" -> { - String userId = ctx.queryParam("userId"); - if (userId != null) { - List tmp = matchingRequestsTable.readRequests(); - model.put("requests", - tmp.stream().filter(r -> r.userId().equals(userId)).collect(Collectors.toList())); - } else { - model.put("requests", matchingRequestsTable.readRequests()); - } - } - case "fragment/waiting-request-table-small.html" -> { - String userId = ctx.queryParam("userId"); - List tmp = matchingRequestsTable.readRequests(); - MatchingRequest req = tmp.stream().filter(r -> r.userId().equals(userId)).findAny() - .orElse(new MatchingRequest()); - model.put("req", req); - model.put("reqNum", tmp.size()); - } - } - ctx.render(pageName, model.build().getMap()); + private static void prepareWebSocket(Javalin app, WebsocketSessionsManager webSocketManager) { + final int WS_PING_INTERVAL_SEC = 27; + app.ws("/websocket/play/checkcon", ws -> ws + .onConnect(ctx -> log.trace("{}", ctx.session.getUpgradeRequest().getRequestURI()))); + app.ws("/websocket/play", ws -> { + ws.onConnect(ctx -> { + webSocketManager.onConnect(ctx.session, ctx.queryParam("userId"), ctx.queryParam("gameId")); + ctx.enableAutomaticPings(WS_PING_INTERVAL_SEC, TimeUnit.SECONDS); + }); + ws.onClose(ctx -> webSocketManager.onClose(ctx.session, ctx.status(), ctx.reason())); + ws.onError(ctx -> webSocketManager.onError(ctx.session, ctx.error())); + ws.onMessage(ctx -> webSocketManager.onMessage(ctx.queryParam("gameId"), ctx)); }); } - private static final int DEFAULT_MAX_CONNECTIONS = - Math.min(ForkJoinPoolUtils.availableProcessors() * 2 * 2, 10); - private static final int DEFAULT_TIMEOUT_SECONDS = 30; - - private JdbcConnectionPool createH2DataSource(String url, String user, String password) { - JdbcConnectionPool ds = JdbcConnectionPool.create(url, user, password); - ds.setMaxConnections(DEFAULT_MAX_CONNECTIONS); - ds.setLoginTimeout(DEFAULT_TIMEOUT_SECONDS); - return ds; - } - - public static HikariDataSource createHikariDataSource(String url, String user, String password) { - HikariConfig config = new HikariConfig(); - config.setJdbcUrl(url); - config.setUsername(user); - config.setPassword(password); - config.setMaximumPoolSize(DEFAULT_MAX_CONNECTIONS); - config.setConnectionTimeout(DEFAULT_TIMEOUT_SECONDS * 1000); - config.addDataSourceProperty("useServerPrepStmts", "true"); - config.addDataSourceProperty("cachePrepStmts", "true"); - config.addDataSourceProperty("prepStmtCacheSize", "250"); - config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); - config.addDataSourceProperty("minimumIdle", "2048"); - return new HikariDataSource(config); - } - - - - private Builder createDefaultModel(UsersTable usersTable, HttpServletRequest request) { - ViewModel.Builder modelBuilder = - ViewModel.builder().setFileModifiedDate(WEBROOT_DIR, 10, "js", "css"); - modelBuilder.put("currentUser", getCurrentUserAccount(usersTable, request)); - return modelBuilder; + private static void prepareJsonRpc(Javalin app, WebsocketSessionsManager webSocketManager, + GoJsonRpcService jsonRpcSrv, AuthService.Factory authServiceFactory) { + JavalinJsonRpcService srv = new JavalinJsonRpcService(GoApplication.getDefaultJacksonMapper()); + app.post("/app/json/GoJsonRpcService", ctx -> srv.handle(ctx, jsonRpcSrv)); + app.post("/app/json/AuthRpcService", + ctx -> srv.handle(ctx, authServiceFactory.create(ctx.req()))); } - - private User getCurrentUserAccount(UsersTable usersTable, HttpServletRequest request) { - Optional u = UserSession.wrap(request.getSession()).getUserId() - .map(uid -> usersTable.selectByPrimaryKey(uid)); - return u.orElse(new User()); - } - - private void isAdminOrThrow(UsersTable usersTable, HttpServletRequest req) { - FirebaseUserSession session = FirebaseUserSession.wrap(req.getSession()); - User u = null; - if (session.isSigninFirebase()) { - String email = session.getEmail(); - u = usersTable.readByEmail(email); - } else if (session.isLogined()) { - u = session.getUserId().map(userId -> usersTable.selectByPrimaryKey(userId)).orElse(null); - } - if (u == null) { - throw new RuntimeException(ParameterizedStringUtils.newString("User not found")); - } - if (!u.isAdmin()) { - throw new RuntimeException(ParameterizedStringUtils.newString("User is not admin")); - } - - - } - - public static record LoginJson(Login login, User user) { - - } - public static JacksonMapper getDefaultJacksonMapper() { return JacksonMapper.getIgnoreUnknownPropertiesMapper(); } - public static record GameStateViewJson(GameStateJson gameState, HandUp handUp, - int watchingStudentsNum) { - - } - - } diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/GoTables.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/GoTables.java new file mode 100644 index 0000000..12a34ae --- /dev/null +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/GoTables.java @@ -0,0 +1,159 @@ +package org.nkjmlab.go.javalin; + +import java.io.File; +import javax.sql.DataSource; +import org.nkjmlab.go.javalin.model.relation.GameRecordsTable; +import org.nkjmlab.go.javalin.model.relation.GameStatesTable; +import org.nkjmlab.go.javalin.model.relation.GameStatesTable.GameState; +import org.nkjmlab.go.javalin.model.relation.GameStatesTables; +import org.nkjmlab.go.javalin.model.relation.HandUpsTable; +import org.nkjmlab.go.javalin.model.relation.LoginsTable; +import org.nkjmlab.go.javalin.model.relation.MatchingRequestsTable; +import org.nkjmlab.go.javalin.model.relation.PasswordsTable; +import org.nkjmlab.go.javalin.model.relation.ProblemsTable; +import org.nkjmlab.go.javalin.model.relation.UsersTable; +import org.nkjmlab.go.javalin.model.relation.VotesTable; +import org.nkjmlab.util.java.io.SystemFileUtils; +import org.nkjmlab.util.java.lang.ResourceUtils; + +public class GoTables { + + private static final org.apache.logging.log4j.Logger log = + org.apache.logging.log4j.LogManager.getLogger(); + + public final GameStatesTables gameStatesTables; + public final ProblemsTable problemsTable; + public final UsersTable usersTable; + public final LoginsTable loginsTable; + public final MatchingRequestsTable matchingRequestsTable; + public final VotesTable votesTable; + public final HandUpsTable handsUpTable; + public final GameRecordsTable gameRecordsTable; + public final PasswordsTable passwordsTable; + + private GoTables(GameStatesTables gameStatesTables, ProblemsTable problemsTable, + UsersTable usersTable, PasswordsTable passwordsTable, LoginsTable loginsTable, + MatchingRequestsTable matchingRequestsTable, VotesTable votesTable, HandUpsTable handsUpTable, + GameRecordsTable gameRecordsTable) { + this.gameStatesTables = gameStatesTables; + this.problemsTable = problemsTable; + this.usersTable = usersTable; + this.passwordsTable = passwordsTable; + this.loginsTable = loginsTable; + this.matchingRequestsTable = matchingRequestsTable; + this.votesTable = votesTable; + this.handsUpTable = handsUpTable; + this.gameRecordsTable = gameRecordsTable; + } + + public static GoTables prepareTables(DataSourceManager basicDataSource) { + + DataSource memDbDataSource = basicDataSource.createHikariInMemoryDataSource(); + DataSource fileDbDataSource = basicDataSource.createHikariServerModeDataSource(); + + + final ProblemsTable problemsTable = prepareProblemTables(memDbDataSource); + final HandUpsTable handsUpTable = new HandUpsTable(memDbDataSource); + final MatchingRequestsTable matchingRequestsTable = + prepareMatchingRequestsTable(memDbDataSource); + final PasswordsTable passwordsTable = preparePasswordsTable(memDbDataSource); + final VotesTable votesTable = prepareVotesTable(memDbDataSource); + + final GameStatesTables gameStatesTables = + prepareGameStateTables(basicDataSource, fileDbDataSource, memDbDataSource); + + final UsersTable usersTable = prepareUsersTable(fileDbDataSource); + final GameRecordsTable gameRecordsTable = prepareGameRecordsTable(fileDbDataSource, usersTable); + final LoginsTable loginsTable = prepareLoginsTable(fileDbDataSource); + + + GoTables goTables = new GoTables(gameStatesTables, problemsTable, usersTable, passwordsTable, + loginsTable, matchingRequestsTable, votesTable, handsUpTable, gameRecordsTable); + + return goTables; + } + + private static GameRecordsTable prepareGameRecordsTable(DataSource fileDbDataSource, + UsersTable usersTable) { + GameRecordsTable gameRecordsTable = new GameRecordsTable(fileDbDataSource); + gameRecordsTable.createTableIfNotExists().createIndexesIfNotExists(); + gameRecordsTable.writeCsv(new File(new File(SystemFileUtils.getUserHomeDirectory(), "go-bkup/"), + "game-record" + System.currentTimeMillis() + ".csv")); + gameRecordsTable.recalculateAndUpdateRank(usersTable); + return gameRecordsTable; + } + + private static VotesTable prepareVotesTable(DataSource memDbDataSource) { + VotesTable votesTable = new VotesTable(memDbDataSource); + votesTable.createTableIfNotExists().createIndexesIfNotExists(); + return votesTable; + } + + private static MatchingRequestsTable prepareMatchingRequestsTable(DataSource memDbDataSource) { + MatchingRequestsTable matchingRequestsTable = new MatchingRequestsTable(memDbDataSource); + matchingRequestsTable.createTableIfNotExists().createIndexesIfNotExists(); + return matchingRequestsTable; + } + + private static GameStatesTables prepareGameStateTables(DataSourceManager basicDataSource, + DataSource fileDbDataSource, DataSource memDbDataSource) { + final int TRIM_THRESHOLD_OF_GAME_STATE_TABLE = 30000; + + GameStatesTable gameStatesTable = new GameStatesTable(fileDbDataSource); + gameStatesTable.createTableIfNotExists().createIndexesIfNotExists(); + gameStatesTable.trimAndBackupToFile(basicDataSource.getFactory().getDatabaseDirectory(), + TRIM_THRESHOLD_OF_GAME_STATE_TABLE); + + GameStatesTable gameStatesTableInMem = new GameStatesTable(memDbDataSource); + gameStatesTableInMem.createTableIfNotExists().createIndexesIfNotExists(); + gameStatesTableInMem.insert(gameStatesTable.selectAll().toArray(GameState[]::new)); + + GameStatesTables gameStatesTables = new GameStatesTables(gameStatesTable, gameStatesTableInMem); + return gameStatesTables; + } + + private static PasswordsTable preparePasswordsTable(DataSource dataSource) { + PasswordsTable passwordsTable = new PasswordsTable(dataSource); + passwordsTable.createTableIfNotExists().createIndexesIfNotExists(); + try { + File f = ResourceUtils.getResourceAsFile("/conf/passwords.csv"); + passwordsTable.readFromFileAndMerge(f); + } catch (Exception e) { + log.warn("load password.csv.default ..."); + File f = ResourceUtils.getResourceAsFile("/conf/passwords.csv.default"); + passwordsTable.readFromFileAndMerge(f); + } + return passwordsTable; + } + + private static LoginsTable prepareLoginsTable(DataSource fileDbDataSource) { + LoginsTable loginsTable = new LoginsTable(fileDbDataSource); + loginsTable.createTableIfNotExists().createIndexesIfNotExists(); + loginsTable.writeCsv(new File(new File(SystemFileUtils.getUserHomeDirectory(), "go-bkup/"), + "logins-" + System.currentTimeMillis() + ".csv")); + return loginsTable; + } + + private static UsersTable prepareUsersTable(DataSource dataSource) { + UsersTable usersTable = new UsersTable(dataSource); + usersTable.dropTableIfExists(); + usersTable.createTableIfNotExists().createIndexesIfNotExists(); + try { + File f = ResourceUtils.getResourceAsFile("/conf/users.csv"); + usersTable.readFromFileAndMerge(f); + } catch (Exception e) { + log.error(e, e); + log.warn("load users.csv.default ..."); + File f = ResourceUtils.getResourceAsFile("/conf/users.csv.default"); + usersTable.readFromFileAndMerge(f); + } + return usersTable; + } + + private static ProblemsTable prepareProblemTables(DataSource memDbDataSource) { + ProblemsTable problemsTable = new ProblemsTable(memDbDataSource); + problemsTable.dropAndInsertInitialProblemsToTable(GoWebAppConfig.PROBLEM_DIR); + return problemsTable; + } + +} diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/GoWebAppConfig.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/GoWebAppConfig.java new file mode 100644 index 0000000..e5dc517 --- /dev/null +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/GoWebAppConfig.java @@ -0,0 +1,18 @@ +package org.nkjmlab.go.javalin; + +import java.io.File; +import org.nkjmlab.util.java.web.WebApplicationConfig; + +public class GoWebAppConfig { + public static final WebApplicationConfig WEB_APP_CONFIG = WebApplicationConfig.builder() + .addWebJar("jquery", "sweetalert2", "bootstrap", "bootstrap-treeview", "clipboard", + "fortawesome__fontawesome-free", "stacktrace-js", "datatables", "firebase", + "firebaseui", "ua-parser-js", "blueimp-load-image", "emojionearea") + .build(); + public static final File PROBLEM_DIR = + new File(GoWebAppConfig.WEB_APP_CONFIG.getAppRootDirectory(), "problem"); + public static final File CURRENT_ICON_DIR = + new File(GoWebAppConfig.WEB_APP_CONFIG.getWebRootDirectory(), "img/icon"); + public static final File UPLOADED_ICON_DIR = + new File(GoWebAppConfig.WEB_APP_CONFIG.getWebRootDirectory(), "img/icon-uploaded"); +} \ No newline at end of file diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/fbauth/AuthService.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/auth/AuthService.java similarity index 50% rename from nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/fbauth/AuthService.java rename to nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/auth/AuthService.java index a3bd3f7..62660fb 100644 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/fbauth/AuthService.java +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/auth/AuthService.java @@ -1,10 +1,8 @@ -package org.nkjmlab.go.javalin.fbauth; +package org.nkjmlab.go.javalin.auth; -import java.io.File; -import java.io.FileInputStream; import java.time.LocalDateTime; import java.util.Optional; -import javax.servlet.http.HttpServletRequest; +import org.nkjmlab.go.javalin.auth.GoAuthService.SigninSession; import org.nkjmlab.go.javalin.model.relation.LoginsTable; import org.nkjmlab.go.javalin.model.relation.LoginsTable.Login; import org.nkjmlab.go.javalin.model.relation.PasswordsTable; @@ -12,43 +10,56 @@ import org.nkjmlab.go.javalin.model.relation.UsersTable.User; import org.nkjmlab.go.javalin.model.relation.UsersTable.UserJson; import org.nkjmlab.sorm4j.result.RowMap; -import org.nkjmlab.util.javax.servlet.HttpRequestUtils; -import org.nkjmlab.util.javax.servlet.UserSession; -import com.google.auth.oauth2.GoogleCredentials; -import com.google.firebase.FirebaseApp; -import com.google.firebase.FirebaseOptions; -import com.google.firebase.auth.FirebaseAuth; -import com.google.firebase.auth.FirebaseAuthException; -import com.google.firebase.auth.FirebaseToken; +import org.nkjmlab.util.jakarta.servlet.HttpRequestUtils; +import jakarta.servlet.http.HttpServletRequest; public class AuthService implements AuthServiceInterface { + + public static class Factory { + private final UsersTable usersTable; + private final LoginsTable loginsTable; + private final PasswordsTable passwordsTable; + private final GoAuthService firebaseService; + + public Factory(UsersTable usersTable, LoginsTable loginsTable, PasswordsTable passwordsTable, + GoAuthService firebaseService) { + this.usersTable = usersTable; + this.loginsTable = loginsTable; + this.passwordsTable = passwordsTable; + this.firebaseService = firebaseService; + } + + public AuthService create(HttpServletRequest request) { + return new AuthService(usersTable, loginsTable, passwordsTable, firebaseService, request); + } + + } + private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(); private final UsersTable usersTable; private final LoginsTable loginsTable; private final PasswordsTable passwordsTable; + private final GoAuthService authService; private final HttpServletRequest request; - - public AuthService(UsersTable usersTable, LoginsTable loginsTable, PasswordsTable passwordsTable, - HttpServletRequest request) { + private AuthService(UsersTable usersTable, LoginsTable loginsTable, PasswordsTable passwordsTable, + GoAuthService firebaseService, HttpServletRequest request) { this.usersTable = usersTable; this.loginsTable = loginsTable; this.passwordsTable = passwordsTable; + this.authService = firebaseService; this.request = request; } @Override public boolean isSigninToFirebase() { - FirebaseUserSession session = FirebaseUserSession.wrap(request.getSession()); - return session.isSigninFirebase(); + return authService.isSignin(request.getSession().getId()); } @Override public boolean registerAttendance(String userId, String seatId) { - UserSession session = UserSession.wrap(request.getSession()); - session.setUserId(userId); User u = usersTable.selectByPrimaryKey(userId); usersTable.updateByPrimaryKey(RowMap.of("seat_id", seatId), u.userId()); loginsTable.insert(new Login(-1, userId, seatId, u.userName(), LocalDateTime.now(), @@ -59,11 +70,12 @@ public boolean registerAttendance(String userId, String seatId) { @Override public UserJson signinWithFirebase(String idToken, String seatId) { - return verifyIdToken(idToken).map(token -> usersTable.readByEmail(token.getEmail())).map(u -> { - FirebaseUserSession session = FirebaseUserSession.wrap(request.getSession()); - session.signinFirebase(idToken, u.email()); - session.setUserId(u.userId()); - registerAttendance(u.userId(), seatId); + Optional opt = + authService.signinWithFirebase(idToken, request.getSession().getId()); + return opt.map(ls -> { + User u = usersTable.selectByPrimaryKey(ls.userId()); + registerAttendance(ls.userId(), seatId); + authService.signin(request.getSession().getId(), ls.userId()); return new UserJson(u, true); }).orElseThrow(); } @@ -71,22 +83,21 @@ public UserJson signinWithFirebase(String idToken, String seatId) { @Override public boolean signoutFromFirebase() { - request.getSession().invalidate(); + authService.signout(request.getSession().getId()); return true; } @Override public boolean signupAsGuest(String userId, String username, String seatId) { - FirebaseUserSession session = FirebaseUserSession.wrap(request.getSession()); - if (session.isSigninFirebase()) { + if (authService.isSignin(request.getSession().getId())) { log.error("Already logined Firebase. userId=[{}]", userId); return false; } User u = usersTable.selectByPrimaryKey(userId); if (u != null && !u.isGuest()) { - log.error("Try guest siginup but userId [{}] conflict with a regular user", userId); + log.error("Try guest signinup but userId [{}] conflict with a regular user", userId); return false; } usersTable.merge(new User(userId, userId + "-guest@example.com", username, User.GUEST, seatId, @@ -94,13 +105,13 @@ public boolean signupAsGuest(String userId, String username, String seatId) { registerAttendance(userId, seatId); UsersTable.createIcon(userId); + authService.signin(request.getSession().getId(), userId); return true; } @Override public UserJson signinWithoutFirebase(String userId, String password, String seatId) { - FirebaseUserSession session = FirebaseUserSession.wrap(request.getSession()); - if (session.isSigninFirebase()) { + if (authService.isSignin(request.getSession().getId())) { log.error("Already logined Firebase. userId=[{}]", userId); return null; } @@ -112,32 +123,9 @@ public UserJson signinWithoutFirebase(String userId, String password, String sea User u = usersTable.selectByPrimaryKey(userId); registerAttendance(userId, seatId); UsersTable.createIcon(userId); + authService.signin(request.getSession().getId(), userId); return new UserJson(u, true); } - public static void initialize(String url, File firebaseJson) { - try (FileInputStream serviceAccount = new FileInputStream(firebaseJson)) { - FirebaseOptions options = FirebaseOptions.builder() - .setCredentials(GoogleCredentials.fromStream(serviceAccount)).setDatabaseUrl(url).build(); - FirebaseApp.initializeApp(options); - } catch (Exception e) { - log.error(e, e); - } - - } - - public static Optional verifyIdToken(String idToken) { - try { - if (idToken == null || idToken.length() == 0) { - return Optional.empty(); - } - FirebaseToken decodedToken = FirebaseAuth.getInstance().verifyIdToken(idToken); - return decodedToken.isEmailVerified() ? Optional.of(decodedToken) : Optional.empty(); - } catch (FirebaseAuthException e) { - log.error(e, e); - return Optional.empty(); - } - } - } diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/fbauth/AuthServiceInterface.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/auth/AuthServiceInterface.java similarity index 57% rename from nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/fbauth/AuthServiceInterface.java rename to nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/auth/AuthServiceInterface.java index fb49816..081298a 100644 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/fbauth/AuthServiceInterface.java +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/auth/AuthServiceInterface.java @@ -1,8 +1,5 @@ -package org.nkjmlab.go.javalin.fbauth; +package org.nkjmlab.go.javalin.auth; -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; import org.nkjmlab.go.javalin.model.relation.UsersTable.UserJson; public interface AuthServiceInterface { @@ -19,10 +16,5 @@ public interface AuthServiceInterface { boolean registerAttendance(String userId, String seatId); - static Set getDeclaredMethodNames() { - return Arrays.stream(AuthServiceInterface.class.getDeclaredMethods()).map(m -> m.getName()) - .collect(Collectors.toSet()); - } - } diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/auth/GoAuthService.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/auth/GoAuthService.java new file mode 100644 index 0000000..278cc20 --- /dev/null +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/auth/GoAuthService.java @@ -0,0 +1,84 @@ +package org.nkjmlab.go.javalin.auth; + +import java.util.Optional; +import org.nkjmlab.go.javalin.model.relation.UsersTable; +import org.nkjmlab.go.javalin.model.relation.UsersTable.User; +import org.nkjmlab.sorm4j.Sorm; +import org.nkjmlab.sorm4j.annotation.OrmRecord; +import org.nkjmlab.sorm4j.util.h2.BasicH2Table; +import org.nkjmlab.sorm4j.util.h2.datasource.H2LocalDataSourceFactory; +import org.nkjmlab.sorm4j.util.table_def.annotation.PrimaryKey; +import org.nkjmlab.util.firebase.auth.FirebaseAuthHandler; +import com.google.firebase.auth.FirebaseToken; + +public class GoAuthService { + private static final org.apache.logging.log4j.Logger log = + org.apache.logging.log4j.LogManager.getLogger(); + + private final FirebaseAuthHandler firebaseService; + private final SigninSessionsTable signinSessionsTable = new SigninSessionsTable(); + private final UsersTable usersTable; + + public GoAuthService(UsersTable usersTable, FirebaseAuthHandler firebaseService) { + this.usersTable = usersTable; + this.firebaseService = firebaseService; + signinSessionsTable.createTableIfNotExists().createIndexesIfNotExists(); + } + + public Optional signinWithFirebase(String idToken, String sessionId) { + Optional opt = firebaseService.isAcceptableIdToken(idToken); + User u = opt.map(fs -> usersTable.readByEmail(fs.getEmail())).get(); + if (u == null) { + log.error("invalid mail {}", opt.get().getEmail()); + return Optional.empty(); + } + + SigninSession ret = opt.map(fs -> new SigninSession(sessionId, u.userId())).get(); + signin(ret); + return Optional.of(ret); + } + + public SigninSession signin(String sessionId, String userId) { + SigninSession s = new SigninSession(sessionId, userId); + signin(s); + return s; + } + + + private SigninSession signin(SigninSession signinSession) { + signinSessionsTable.merge(signinSession); + return signinSession; + } + + public void signout(String sessionId) { + signinSessionsTable.deleteByPrimaryKey(sessionId); + + } + + public boolean isSignin(String sessionId) { + return signinSessionsTable.exists(sessionId); + } + + + public Optional toSigninSession(String sessionId) { + return isSignin(sessionId) ? Optional.of(signinSessionsTable.selectByPrimaryKey(sessionId)) + : Optional.empty(); + } + + @OrmRecord + public record SigninSession(@PrimaryKey String sessionId, String userId) { + + } + + private static class SigninSessionsTable extends BasicH2Table { + + public SigninSessionsTable() { + super(Sorm.create(H2LocalDataSourceFactory.createTemporalInMemoryDataSource()), + SigninSession.class); + } + + } + + + +} diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/client/websocket/GoWebSocketClient.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/client/websocket/GoWebSocketClient.java deleted file mode 100644 index 1f26ce2..0000000 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/client/websocket/GoWebSocketClient.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.nkjmlab.go.javalin.client.websocket; - -import java.net.URI; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; -import org.eclipse.jetty.websocket.api.annotations.WebSocket; -import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; -import org.eclipse.jetty.websocket.client.WebSocketClient; - -public class GoWebSocketClient { - private static final org.apache.logging.log4j.Logger log = - org.apache.logging.log4j.LogManager.getLogger(); - private static final int num = 64; - - private final String uri; - private final WebSocketClient client = new WebSocketClient(); - private final GoWebSocket webSocket = new GoWebSocket(); - - public GoWebSocketClient(String uri) { - this.uri = uri; - } - - public void start() { - try { - client.start(); - - for (int i = 0; i < num; i++) { - int stdId = 5519000 + i; - URI toUri = new URI(uri + "?userId=" + stdId + "&gameId=" + stdId); - client.connect(webSocket, toUri, new ClientUpgradeRequest()); - } - - } catch (Throwable t) { - log.error(t, t); - } finally { - try { - TimeUnit.SECONDS.sleep(20); - client.stop(); - } catch (Exception e) { - log.error(e, e); - } - } - } - - @WebSocket - public class GoWebSocket { - - private static final org.apache.logging.log4j.Logger log = - org.apache.logging.log4j.LogManager.getLogger(); - - private static AtomicInteger onConnectCounter = new AtomicInteger(0); - private static AtomicInteger onMessageCounter = new AtomicInteger(0); - - @OnWebSocketConnect - public void onConnect(Session session) { - log.info("onConnectCounter={}", onConnectCounter.getAndIncrement()); - } - - @OnWebSocketMessage - public void onMessage(Session session, String message) { - log.info("onMessageCounter={}", onMessageCounter.getAndIncrement()); - log.debug("onMessage={}", message); - } - - @OnWebSocketClose - public void onClose(Session session, int statusCode, String reason) { - try { - } catch (Throwable e) { - log.error(e, e); - throw e; - } - } - - @OnWebSocketError - public void onError(Session session, Throwable cause) { - try { - log.info("onError"); - log.error(cause, cause); - } catch (Throwable e) { - log.error(e, e); - throw e; - } - } - } - -} diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/fbauth/FirebaseUserSession.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/fbauth/FirebaseUserSession.java deleted file mode 100644 index e22bf81..0000000 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/fbauth/FirebaseUserSession.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.nkjmlab.go.javalin.fbauth; - -import javax.servlet.http.HttpSession; -import org.nkjmlab.util.javax.servlet.UserSession; - -public class FirebaseUserSession extends UserSession { - - private static final String EMAIL = "email"; - private static final String ID_TOKEN = "idToken"; - - public static FirebaseUserSession wrap(HttpSession session) { - return new FirebaseUserSession(session); - } - - protected FirebaseUserSession(HttpSession session) { - super(session); - } - - - public String getEmail() { - return getAttribute(EMAIL) == null ? null : getAttribute(EMAIL).toString(); - } - - public String getIdToken() { - return getAttribute(ID_TOKEN) == null ? null : getAttribute(ID_TOKEN).toString(); - } - - - public boolean isSetUserId() { - return getUserId() != null; - } - - public boolean isSigninFirebase() { - return getIdToken() != null; - } - - private void setEmail(String email) { - setAttribute(EMAIL, email); - } - - private void setIdToken(String idToken) { - setAttribute(ID_TOKEN, idToken); - } - - public void signinFirebase(String idToken, String email) { - setIdToken(idToken); - setEmail(email); - setMaxInactiveInterval(10 * 60 * 60); - } - - - -} diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/jsonrpc/GoJsonRpcService.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/jsonrpc/GoJsonRpcService.java index c1687ca..06d1955 100644 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/jsonrpc/GoJsonRpcService.java +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/jsonrpc/GoJsonRpcService.java @@ -1,135 +1,108 @@ package org.nkjmlab.go.javalin.jsonrpc; -import static org.nkjmlab.go.javalin.GoApplication.*; import java.io.File; import java.time.LocalDateTime; import java.util.Collections; -import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import org.nkjmlab.go.javalin.GoApplication; +import org.nkjmlab.go.javalin.GoTables; +import org.nkjmlab.go.javalin.GoWebAppConfig; import org.nkjmlab.go.javalin.model.common.ProblemJson; -import org.nkjmlab.go.javalin.model.problem.ProblemTextToJsonConverter; -import org.nkjmlab.go.javalin.model.relation.GameRecordsTable; -import org.nkjmlab.go.javalin.model.relation.GameStatesTable.GameStateJson; -import org.nkjmlab.go.javalin.model.relation.GameStatesTables; -import org.nkjmlab.go.javalin.model.relation.HandUpsTable; +import org.nkjmlab.go.javalin.model.relation.GameStatesTable.GameState; import org.nkjmlab.go.javalin.model.relation.HandUpsTable.HandUp; -import org.nkjmlab.go.javalin.model.relation.LoginsTable; -import org.nkjmlab.go.javalin.model.relation.MatchingRequestsTable; import org.nkjmlab.go.javalin.model.relation.MatchingRequestsTable.MatchingRequest; -import org.nkjmlab.go.javalin.model.relation.ProblemsTable; import org.nkjmlab.go.javalin.model.relation.ProblemsTable.Problem; -import org.nkjmlab.go.javalin.model.relation.UsersTable; import org.nkjmlab.go.javalin.model.relation.UsersTable.User; import org.nkjmlab.go.javalin.model.relation.UsersTable.UserJson; -import org.nkjmlab.go.javalin.model.relation.VotesTable; import org.nkjmlab.go.javalin.model.relation.VotesTable.Vote; import org.nkjmlab.go.javalin.model.relation.VotesTable.VoteResult; +import org.nkjmlab.go.javalin.util.CurrentTimeMillisIdGenerator; import org.nkjmlab.go.javalin.websocket.WebsocketSessionsManager; -import org.nkjmlab.go.javalin.websocket.WebsoketSessionsTable; import org.nkjmlab.sorm4j.result.RowMap; import org.nkjmlab.util.java.Base64Utils; import org.nkjmlab.util.java.json.JsonMapper; -import org.nkjmlab.util.java.lang.ParameterizedStringUtils; +import org.nkjmlab.util.java.lang.ParameterizedStringFormatter; import org.nkjmlab.util.javax.imageio.ImageIoUtils; +import org.threeten.bp.Instant; public class GoJsonRpcService implements GoJsonRpcServiceInterface { private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(); + private final WebsocketSessionsManager webSocketManager; - private final GameStatesTables gameStatesTables; - private final ProblemsTable problemsTable; - private final UsersTable usersTable; - private final LoginsTable loginsTable; - private final MatchingRequestsTable matchingRequestsTable; - private final VotesTable votesTable; - private final WebsocketSessionsManager wsManager; - private final WebsoketSessionsTable websoketSessionsTable; - private final HandUpsTable handsUpTable; - private final GameRecordsTable gameRecordsTable; + private final GoTables goTables; private static final JsonMapper mapper = GoApplication.getDefaultJacksonMapper(); - public GoJsonRpcService(WebsocketSessionsManager wsManager, GameStatesTables gameStatesTables, - ProblemsTable problemsTable, UsersTable usersTable, LoginsTable loginsTable, - MatchingRequestsTable matchingRequestsTable, VotesTable votesTable, HandUpsTable handsUpTable, - WebsoketSessionsTable websoketSessionsTable, GameRecordsTable gameRecordsTable) { - this.gameStatesTables = gameStatesTables; - this.problemsTable = problemsTable; - this.usersTable = usersTable; - this.loginsTable = loginsTable; - this.matchingRequestsTable = matchingRequestsTable; - this.wsManager = wsManager; - this.votesTable = votesTable; - this.handsUpTable = handsUpTable; - this.websoketSessionsTable = websoketSessionsTable; - this.gameRecordsTable = gameRecordsTable; - + public GoJsonRpcService(WebsocketSessionsManager webSocketManager, GoTables goTables) { + this.webSocketManager = webSocketManager; + this.goTables = goTables; } @Override - public void sendGameState(String gameId, GameStateJson json) { - wsManager.sendGameState(gameId, json); + public void sendGameState(String gameId, GameState json) { + webSocketManager.sendGameState(gameId, json); } @Override public void sendGlobalMessage(String userId, String message) { - wsManager.sendGlobalMessage(message); + webSocketManager.sendGlobalMessage(message); } @Override public void syncGameState(int sessionId, String gameId, String userId) { - wsManager.updateSession(sessionId, gameId, userId); - wsManager.sendLatestGameStateToSessions(gameId); + webSocketManager.updateSession(sessionId, gameId, userId); + webSocketManager.sendLatestGameStateToSessions(gameId); } @Override - public void newGame(String gameId, GameStateJson json) { - wsManager.newGame(gameId, json); + public void newGame(String gameId, GameState json) { + webSocketManager.newGame(gameId, json); } @Override public ProblemJson loadProblem(String gameId, long problemId) { - return wsManager.loadProblem(gameId, problemId); + return webSocketManager.loadProblem(gameId, problemId); } @Override public ProblemJson getProblem(long problemId) { - Problem p = problemsTable.selectByPrimaryKey(problemId); + Problem p = goTables.problemsTable.selectByPrimaryKey(problemId); return p == null ? new ProblemJson(-1) : ProblemJson.createFrom(p); } @Override - public void goBack(String gameId, GameStateJson json) { - wsManager.goBack(gameId); + public void goBack(String gameId, GameState json) { + webSocketManager.goBack(gameId); } @Override public ProblemJson saveProblem(String gameId, long problemId, String groupId, String name, String message) { Problem newP = createNewProblem(gameId, problemId, groupId, name, message); - problemsTable.merge(newP); - problemsTable.clearProblemsJson(); + goTables.problemsTable.merge(newP); + goTables.problemsTable.clearProblemsJson(); ProblemJson problemJson = ProblemJson.createFrom(newP); saveProblemJsonToFile(problemJson); return problemJson; } + private final CurrentTimeMillisIdGenerator problemIdGenerator = new CurrentTimeMillisIdGenerator(); + private Problem createNewProblem(String gameId, long problemId, String groupId, String name, String message) { - Problem prevP = problemsTable.selectByPrimaryKey(problemId); - GameStateJson currentState = gameStatesTables.readLatestGameStateJson(gameId); + Problem prevP = goTables.problemsTable.selectByPrimaryKey(problemId); + GameState currentState = goTables.gameStatesTables.readLatestGameState(gameId); if (prevP != null) { autoBackupProblemJsonToFile(ProblemJson.createFrom(prevP)); } return new Problem( - prevP != null ? prevP.id() - : (problemId == -1 ? ProblemTextToJsonConverter.getNewId() : problemId), + prevP != null ? prevP.id() : (problemId == -1 ? problemIdGenerator.getNewId() : problemId), LocalDateTime.now(), groupId, name, mapper.toJson(currentState.cells()), mapper.toJson(currentState.symbols()), mapper.toJson(currentState.agehama()), mapper.toJson(currentState.handHistory()), message == null ? "" : message); @@ -137,7 +110,7 @@ private Problem createNewProblem(String gameId, long problemId, String groupId, private void autoBackupProblemJsonToFile(ProblemJson p) { File bkupDir = getProblemAutoBackupDir(p.groupId()); - File o = new File(bkupDir, new Date().getTime() + "-copy-" + p.name() + ".json"); + File o = new File(bkupDir, Instant.now().toEpochMilli() + "-copy-" + p.name() + ".json"); mapper.toJsonAndWrite(p, o, true); } @@ -145,18 +118,20 @@ private void saveProblemJsonToFile(ProblemJson p) { File problemGroupDir = getProblemDir(p.groupId()); File o = new File(problemGroupDir, p.name() + ".json"); mapper.toJsonAndWrite(p, o, true); - log.info("Problep {} - {} is saved to {}", p.groupId(), p.name(), o); + log.info("Problem {} - {} is saved to {}", p.groupId(), p.name(), o); } private File getProblemDir(String groupId) { - File dir = new File(PROBLEM_DIR, groupId); + File dir = new File(GoWebAppConfig.PROBLEM_DIR, groupId); dir.mkdirs(); return dir; } private File getProblemAutoBackupDir(String groupId) { - File dir = new File(PROBLEM_BACKUP_DIR, groupId); + File dir = + new File(new File(GoWebAppConfig.WEB_APP_CONFIG.getAppRootDirectory(), "problem-auto-bkup"), + groupId); dir.mkdirs(); return dir; @@ -164,19 +139,19 @@ private File getProblemAutoBackupDir(String groupId) { @Override public void deleteProblem(long problemId) { - Problem p = problemsTable.selectByPrimaryKey(problemId); + Problem p = goTables.problemsTable.selectByPrimaryKey(problemId); if (p == null) { return; } autoBackupProblemJsonToFile(ProblemJson.createFrom(p)); - problemsTable.delete(p); - problemsTable.clearProblemsJson(); + goTables.problemsTable.delete(p); + goTables.problemsTable.clearProblemsJson(); } @Override public ProblemJson readProblem(long problemId) { - Problem p = problemsTable.selectByPrimaryKey(problemId); + Problem p = goTables.problemsTable.selectByPrimaryKey(problemId); if (p != null) { return ProblemJson.createFrom(p); } @@ -186,39 +161,39 @@ public ProblemJson readProblem(long problemId) { @Override public String getNextUser(String currentGameId) { - String nextUserId = loginsTable.getNextLoginUserId(usersTable, currentGameId); + String nextUserId = goTables.loginsTable.getNextLoginUserId(goTables.usersTable, currentGameId); return nextUserId; } @Override public String getPrevUser(String currentGameId) { - String nextUserId = loginsTable.getPrevLoginUserId(usersTable, currentGameId); + String nextUserId = goTables.loginsTable.getPrevLoginUserId(goTables.usersTable, currentGameId); return nextUserId; } @Override public String getNextQuestion(String currentGameId) { - String nextQuestionGameId = handsUpTable.getNextQuestion(currentGameId); + String nextQuestionGameId = goTables.handsUpTable.getNextQuestion(currentGameId); return nextQuestionGameId; } @Override public UserJson getUser(String userId) { - User u = usersTable.selectByPrimaryKey(userId); + User u = goTables.usersTable.selectByPrimaryKey(userId); if (u == null) { UserJson uj = new UserJson(userId); return uj; } - UserJson uj = new UserJson(u, loginsTable.isAttendance(userId)); + UserJson uj = new UserJson(u, goTables.loginsTable.isAttendance(userId)); return uj; } @Override public String getNextGame(String currentGameId) { - List ids = websoketSessionsTable.readActiveGameIdsOrderByGameId(usersTable); + List ids = webSocketManager.readActiveGameIdsOrderByGameId(); return getNextId(ids, currentGameId); } @@ -235,7 +210,7 @@ private String getNextId(List ids, String currentGameId) { @Override public String getPrevGame(String currentGameId) { - List ids = websoketSessionsTable.readActiveGameIdsOrderByGameId(usersTable); + List ids = webSocketManager.readActiveGameIdsOrderByGameId(); Collections.reverse(ids); return getNextId(ids, currentGameId); } @@ -244,21 +219,21 @@ public String getPrevGame(String currentGameId) { @Override public void enterWaitingRoom(String userId) { try { - User u = usersTable.selectByPrimaryKey(userId); + User u = goTables.usersTable.selectByPrimaryKey(userId); if (u == null) { log.error("userId {} is not found.", userId); return; } - usersTable.update(u); + goTables.usersTable.update(u); MatchingRequest matchingReq = MatchingRequest.createUnpaired(u); - if (!matchingRequestsTable.exists(matchingReq)) { - matchingRequestsTable.insert(matchingReq); + if (!goTables.matchingRequestsTable.exists(matchingReq)) { + goTables.matchingRequestsTable.insert(matchingReq); } else { - MatchingRequest m = matchingRequestsTable.selectByPrimaryKey(userId); - matchingRequestsTable.update(m); + MatchingRequest m = goTables.matchingRequestsTable.selectByPrimaryKey(userId); + goTables.matchingRequestsTable.update(m); } - wsManager.sendUpdateWaitingRequestStatus(Set.of(userId)); + webSocketManager.sendUpdateWaitingRequestStatus(Set.of(userId)); } catch (Exception e) { log.error("maching request for {} failed", userId); log.error(e, e); @@ -268,23 +243,22 @@ public void enterWaitingRoom(String userId) { @Override public void exitWaitingRoom(String userId) { - // logger.debug("{} exited from waiting room.", userId); - matchingRequestsTable.deleteByPrimaryKey(userId); - wsManager.sendUpdateWaitingRequestStatus(Set.of(userId)); + goTables.matchingRequestsTable.deleteByPrimaryKey(userId); + webSocketManager.sendUpdateWaitingRequestStatus(Set.of(userId)); } @Override public File uploadImage(String userId, String base64EncodedImage) { try { { - File outputFile = new File(UPLOADED_ICON_DIR, userId + ".png"); + File outputFile = new File(GoWebAppConfig.UPLOADED_ICON_DIR, userId + ".png"); outputFile.mkdirs(); ImageIoUtils.write(Base64Utils.decodeToImage(base64EncodedImage, "png"), "png", outputFile); } - File outputFile = new File(CURRENT_ICON_DIR, userId + ".png"); + File outputFile = new File(GoWebAppConfig.CURRENT_ICON_DIR, userId + ".png"); outputFile.mkdirs(); ImageIoUtils.write(Base64Utils.decodeToImage(base64EncodedImage, "png"), "png", outputFile); - log.info("Icon is uploaded={}", outputFile); + log.debug("Icon is uploaded={}", outputFile); return outputFile; } catch (Exception e) { log.error(e, e); @@ -300,12 +274,13 @@ public boolean sendLog(String logLevel, String location, String msg, String opti @Override public void vote(String gameId, String userId, long problemId, String vote, String voteId) { - votesTable.merge(new Vote(userId, problemId, vote, voteId, gameId, LocalDateTime.now())); + goTables.votesTable + .merge(new Vote(userId, problemId, vote, voteId, gameId, LocalDateTime.now())); } @Override public List getVoteResult(long problemId, String gameId) { - List ret = votesTable.readVoteResults(problemId, gameId); + List ret = goTables.votesTable.readVoteResults(problemId, gameId); return ret; } @@ -313,34 +288,33 @@ public List getVoteResult(long problemId, String gameId) { public void handUp(String gameId, boolean handUp, String message) { if (handUp) { { - HandUp h = handsUpTable.selectByPrimaryKey(gameId); + HandUp h = goTables.handsUpTable.selectByPrimaryKey(gameId); if (h == null) { - handsUpTable.insert(new HandUp(gameId, LocalDateTime.now(), message)); + goTables.handsUpTable.insert(new HandUp(gameId, LocalDateTime.now(), message)); } else { - handsUpTable + goTables.handsUpTable .update(new HandUp(h.gameId(), h.createdAt(), h.message() + "
" + message)); } } - wsManager.sendHandUp(gameId, handUp, handsUpTable.readOrder(gameId)); + webSocketManager.sendHandUp(gameId, handUp, goTables.handsUpTable.readOrder(gameId)); } else { - handsUpTable.deleteByPrimaryKey(gameId); - wsManager.sendHandDown(gameId); + goTables.handsUpTable.deleteByPrimaryKey(gameId); + webSocketManager.sendHandDown(gameId); - handsUpTable.readAllGameIds().stream().forEach(handupGameId -> wsManager - .sendHandUpOrder(handupGameId, handsUpTable.readOrder(handupGameId))); + goTables.handsUpTable.readAllGameIds().stream().forEach(handupGameId -> webSocketManager + .sendHandUpOrder(handupGameId, goTables.handsUpTable.readOrder(handupGameId))); } } @Override public int registerRecord(String userId, String opponentUserId, String jadge, String memo) { - // logger.debug("{},{},{},{}", userId, opponentUserId, jadge, memo); - int rank = - gameRecordsTable.registerRecordAndGetRank(usersTable, userId, opponentUserId, jadge, memo); + int rank = goTables.gameRecordsTable.registerRecordAndGetRank(goTables.usersTable, userId, + opponentUserId, jadge, memo); - User u = usersTable.selectByPrimaryKey(userId); + User u = goTables.usersTable.selectByPrimaryKey(userId); if (u.rank() != rank) { - usersTable.updateByPrimaryKey(RowMap.of("rank", rank), u.userId()); + goTables.usersTable.updateByPrimaryKey(RowMap.of("rank", rank), u.userId()); return rank; } return -1; @@ -358,27 +332,27 @@ public int registerRecord(String userId, String opponentUserId, String jadge, St @Override public String getKomi(String gameId) { - String msg = ""; try { - GameStateJson gs = gameStatesTables.readLatestGameStateJson(gameId); - User bp = usersTable.selectByPrimaryKey(gs.blackPlayerId()); - User wp = usersTable.selectByPrimaryKey(gs.whitePlayerId()); + GameState gs = goTables.gameStatesTables.readLatestGameState(gameId); + User bp = goTables.usersTable.selectByPrimaryKey(gs.blackPlayerId()); + User wp = goTables.usersTable.selectByPrimaryKey(gs.whitePlayerId()); int diff = Math.abs(wp.rank() - bp.rank()); int ro = gs.cells()[0].length; String roCol = ro == 19 ? "lg" : "sm"; String s1 = start.get(roCol).get(Math.min(diff, 5)); String s2 = midFlow.get(roCol).get(Math.min(diff, 5)); + Object[] params = {bp.userId(), bp.userName(), bp.rank(), wp.userId(), wp.userName(), + wp.rank(), diff, ro, s1, s2}; - msg = ParameterizedStringUtils.newString( - "{} ({},{}級) vs {} ({},{}級): {}級差,{}路
はじめから {}, 棋譜並べから {}
", - bp.userId(), bp.userName(), bp.rank(), wp.userId(), wp.userName(), wp.rank(), diff, ro, - s1, s2); + String msg = ParameterizedStringFormatter.DEFAULT.format( + (String) "{} ({},{}級) vs {} ({},{}級): {}級差,{}路
はじめから {}, 棋譜並べから {}
", + params); + return msg; } catch (Exception e) { - msg = ""; log.error(e); + return ""; } - return msg; } } diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/jsonrpc/GoJsonRpcServiceInterface.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/jsonrpc/GoJsonRpcServiceInterface.java index d494d05..00c4f11 100644 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/jsonrpc/GoJsonRpcServiceInterface.java +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/jsonrpc/GoJsonRpcServiceInterface.java @@ -3,7 +3,7 @@ import java.io.File; import java.util.List; import org.nkjmlab.go.javalin.model.common.ProblemJson; -import org.nkjmlab.go.javalin.model.relation.GameStatesTable.GameStateJson; +import org.nkjmlab.go.javalin.model.relation.GameStatesTable.GameState; import org.nkjmlab.go.javalin.model.relation.UsersTable.UserJson; import org.nkjmlab.go.javalin.model.relation.VotesTable.VoteResult; @@ -30,15 +30,15 @@ public interface GoJsonRpcServiceInterface { boolean sendLog(String logLevel, String location, String msg, String options); - void newGame(String gameId, GameStateJson json); + void newGame(String gameId, GameState json); ProblemJson loadProblem(String gameId, long problemId); ProblemJson getProblem(long problemId); - void goBack(String gameId, GameStateJson json); + void goBack(String gameId, GameState json); - void sendGameState(String gameId, GameStateJson json); + void sendGameState(String gameId, GameState json); ProblemJson saveProblem(String gameId, long problemId, String groupId, String name, String message); diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/common/Agehama.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/common/Agehama.java index eb09b68..bb9c8d6 100644 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/common/Agehama.java +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/common/Agehama.java @@ -1,5 +1,8 @@ package org.nkjmlab.go.javalin.model.common; +import org.nkjmlab.sorm4j.util.datatype.OrmJsonColumnContainer; + +@OrmJsonColumnContainer public record Agehama(int black, int white) { public Agehama increment(Stone stone) { diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/common/Hand.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/common/Hand.java index 68bb491..80c46c0 100644 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/common/Hand.java +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/common/Hand.java @@ -1,6 +1,7 @@ package org.nkjmlab.go.javalin.model.common; import java.util.stream.Stream; +import org.nkjmlab.sorm4j.util.datatype.OrmJsonColumnContainer; /** * stone
@@ -8,6 +9,7 @@ * 2桁目 0:ブランク, 1:□, 2:△, 3:x * */ +@OrmJsonColumnContainer public record Hand(String type, int number, int x, int y, int stone, String options) { public static Hand createDummyHand() { diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/problem/ProblemJsonReader.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/problem/ProblemJsonReader.java deleted file mode 100644 index bf8d685..0000000 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/problem/ProblemJsonReader.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.nkjmlab.go.javalin.model.problem; - -import java.io.File; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; -import org.nkjmlab.go.javalin.GoApplication; -import org.nkjmlab.go.javalin.model.common.ProblemJson; - -public class ProblemJsonReader { - private static final org.apache.logging.log4j.Logger log = - org.apache.logging.log4j.LogManager.getLogger(); - - - private static List readProblemJsonFiles(Path pathToProblemJsonDir) { - List result = new ArrayList<>(); - getGroupDirectories(pathToProblemJsonDir).forEach(groupDir -> { - Arrays.asList(groupDir.listFiles()).forEach(file -> { - if (!file.getName().endsWith(".json")) { - return; - } - result.add(file); - }); - }); - return result; - } - - public static List readProblemJsons(Path pathToProblemJsonDir) { - List files = readProblemJsonFiles(pathToProblemJsonDir); - log.info("detect [{}] problem files in [{}]", files.size(), pathToProblemJsonDir); - return files.stream().map(file -> { - try { - ProblemJson problem = - GoApplication.getDefaultJacksonMapper().toObject(file, ProblemJson.class); - return problem; - } catch (Exception e) { - log.error("file {}", file); - throw new RuntimeException(e); - } - }).collect(Collectors.toList()); - } - - private static List getGroupDirectories(Path path) { - File[] files = path.toFile().listFiles(); - if (files != null) { - return Arrays.asList(files).stream().filter(f -> f.isDirectory()) - .collect(Collectors.toList()); - } - return new ArrayList<>(); - } - -} diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/GameRecordsTable.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/GameRecordsTable.java index 7b417db..1b209ec 100644 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/GameRecordsTable.java +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/GameRecordsTable.java @@ -1,6 +1,5 @@ package org.nkjmlab.go.javalin.model.relation; -import static org.nkjmlab.go.javalin.model.relation.GameRecordsTable.GameRecord.*; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -15,6 +14,9 @@ import org.nkjmlab.sorm4j.util.table_def.annotation.PrimaryKey; public class GameRecordsTable extends BasicH2Table { + private static final String CREATED_AT = "created_at"; + private static final String USER_ID = "user_id"; + private static final String RANK = "rank"; public GameRecordsTable(DataSource dataSource) { super(Sorm.create(dataSource), GameRecord.class); @@ -92,10 +94,6 @@ public List readByUserId(String userId) { public static record GameRecord(@PrimaryKey @AutoIncrement int id, LocalDateTime createdAt, String userId, String opponentUserId, String jadge, String memo, int rank, int point, String message) { - private static final String CREATED_AT = "created_at"; - private static final String USER_ID = "user_id"; - private static final String RANK = "rank"; - } diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/GameStatesTable.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/GameStatesTable.java index 56e7d55..d1a9b5c 100644 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/GameStatesTable.java +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/GameStatesTable.java @@ -18,13 +18,12 @@ import org.nkjmlab.sorm4j.Sorm; import org.nkjmlab.sorm4j.annotation.OrmRecord; import org.nkjmlab.sorm4j.util.h2.BasicH2Table; +import org.nkjmlab.sorm4j.util.jackson.JacksonSormContext; import org.nkjmlab.sorm4j.util.table_def.annotation.AutoIncrement; import org.nkjmlab.sorm4j.util.table_def.annotation.Index; import org.nkjmlab.sorm4j.util.table_def.annotation.IndexColumns; import org.nkjmlab.sorm4j.util.table_def.annotation.NotNull; import org.nkjmlab.sorm4j.util.table_def.annotation.PrimaryKey; -import org.nkjmlab.util.jackson.JacksonMapper; -import com.fasterxml.jackson.core.type.TypeReference; public class GameStatesTable extends BasicH2Table { private static final org.apache.logging.log4j.Logger log = @@ -45,7 +44,10 @@ public class GameStatesTable extends BasicH2Table { public GameStatesTable(DataSource dataSource) { - super(Sorm.create(dataSource), GameState.class); + super( + Sorm.create(dataSource, JacksonSormContext + .builder(GoApplication.getDefaultJacksonMapper().getObjectMapper()).build()), + GameState.class); } @@ -82,7 +84,7 @@ public void trimAndBackupToFile(File backUpDir, int limit) { String selectSql = selectStarFrom(getTableName()) + where(cond(ROWNUM, "<=", deleteRowNum)) + orderBy(ID); - String st = getCallCsvWriteSql(outputFile, selectSql, StandardCharsets.UTF_8, ','); + String st = getCallCsvWriteSql(outputFile, selectSql, StandardCharsets.UTF_8, ',', null); log.info("{}", st); getOrm().executeUpdate(st); @@ -107,46 +109,19 @@ public List readTodayGameIds() { @IndexColumns({BLACK_PLAYER_ID, WHITE_PLAYER_ID}) public record GameState(@PrimaryKey @AutoIncrement long id, LocalDateTime createdAt, @Index @NotNull String gameId, @NotNull String blackPlayerId, @NotNull String whitePlayerId, - @NotNull String lastHand, @NotNull String agehama, @NotNull String cells, - @NotNull String symbols, @NotNull String handHistory, @NotNull long problemId, - @NotNull String options) { - } - - public record GameStateJson(long id, String gameId, String blackPlayerId, String whitePlayerId, - int[][] cells, Map symbols, Agehama agehama, Hand lastHand, - Hand[] handHistory, long problemId, Map options, LocalDateTime createdAt) { + @NotNull Hand lastHand, @NotNull Agehama agehama, @NotNull Integer[][] cells, + @NotNull Map symbols, @NotNull Hand[] handHistory, @NotNull long problemId, + @NotNull Map options) { public static final String DEFAULT_PLAYER_ID = "-1"; public static final int DEFAULT_RO = 9; - private static final JacksonMapper mapper = GoApplication.getDefaultJacksonMapper(); - - public GameStateJson updateHandHistory(List modifiedHistory) { - return new GameStateJson(id, gameId, blackPlayerId, whitePlayerId, cells, symbols, agehama, - lastHand, modifiedHistory.toArray(Hand[]::new), problemId, options, createdAt); - } - - - public GameStateJson(GameState gameState) { - this(gameState.id(), gameState.gameId(), gameState.blackPlayerId(), gameState.whitePlayerId(), - mapper.toObject(gameState.cells(), int[][].class), - mapper.toObject(gameState.symbols(), new TypeReference>() {}), - mapper.toObject(gameState.agehama(), Agehama.class), - mapper.toObject(gameState.lastHand(), Hand.class), - mapper.toObject(gameState.handHistory(), Hand[].class), gameState.problemId(), - mapper.toObject(gameState.options(), new TypeReference>() {}), - gameState.createdAt()); + public GameState updateHandHistory(List modifiedHistory) { + return new GameState(id, createdAt, gameId, blackPlayerId, whitePlayerId, lastHand, agehama, + cells, symbols, modifiedHistory.toArray(Hand[]::new), problemId, options); } - - public GameState toGameState() { - return new GameState(id(), LocalDateTime.now(), gameId, blackPlayerId, whitePlayerId, - mapper.toJson(lastHand), mapper.toJson(agehama), mapper.toJson(cells), - mapper.toJson(symbols), mapper.toJson(handHistory), problemId, mapper.toJson(options)); - } - - - } + } diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/GameStatesTables.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/GameStatesTables.java index ba42851..7806955 100644 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/GameStatesTables.java +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/GameStatesTables.java @@ -13,7 +13,6 @@ import org.nkjmlab.go.javalin.model.common.Agehama; import org.nkjmlab.go.javalin.model.common.Hand; import org.nkjmlab.go.javalin.model.relation.GameStatesTable.GameState; -import org.nkjmlab.go.javalin.model.relation.GameStatesTable.GameStateJson; import org.nkjmlab.sorm4j.internal.util.Try; public class GameStatesTables { @@ -31,29 +30,28 @@ public GameStatesTables(GameStatesTable fileDb, GameStatesTable memDb) { this.gameStatesTableInFile = fileDb; } - private static final Map statesCache = new ConcurrentHashMap<>(); + private static final Map statesCache = new ConcurrentHashMap<>(); - public void saveGameState(GameStateJson json) { + public void saveGameState(GameState json) { statesCache.put(json.gameId(), json); - GameState gs = json.toGameState(); - gameStatesTableInMem.insert(gs); - fileDbService.execute( - Try.createRunnable(() -> gameStatesTableInFile.insert(gs), e -> log.error(e.getMessage()))); + gameStatesTableInMem.insert(json); + fileDbService.execute(Try.createRunnable(() -> gameStatesTableInFile.insert(json), + e -> log.error(e.getMessage()))); } - public GameStateJson readLatestGameStateJson(String gameId) { + public GameState readLatestGameState(String gameId) { return statesCache.computeIfAbsent(gameId, - key -> gameStatesTableInMem.getLatestGameStateFromDb(gameId).map(s -> new GameStateJson(s)) - .orElseGet(() -> createNewGameState(gameId, GameStateJson.DEFAULT_PLAYER_ID, - GameStateJson.DEFAULT_PLAYER_ID, GameStateJson.DEFAULT_RO))); + key -> gameStatesTableInMem.getLatestGameStateFromDb(gameId) + .orElseGet(() -> createNewGameState(gameId, GameState.DEFAULT_PLAYER_ID, + GameState.DEFAULT_PLAYER_ID, GameState.DEFAULT_RO))); } - public List readLatestBoardsJson(List gids) { - return gids.stream().map(gid -> readLatestGameStateJson(gid)).collect(Collectors.toList()); + public List readLatestBoardsJson(List gids) { + return gids.stream().map(gid -> readLatestGameState(gid)).collect(Collectors.toList()); } - public List readTodayGameJsons() { + public List readTodayGameJsons() { return readLatestBoardsJson(gameStatesTableInMem.readTodayGameIds()); } @@ -61,26 +59,27 @@ public List readTodayGameJsons() { public void deleteLatestGameState(String gameId) { gameStatesTableInMem.delete(gameId); statesCache.put(gameId, - gameStatesTableInMem.getLatestGameStateFromDb(gameId).map(s -> new GameStateJson(s)) - .orElseGet(() -> createNewGameState(gameId, GameStateJson.DEFAULT_PLAYER_ID, - GameStateJson.DEFAULT_PLAYER_ID, GameStateJson.DEFAULT_RO))); + gameStatesTableInMem.getLatestGameStateFromDb(gameId) + .orElseGet(() -> createNewGameState(gameId, GameState.DEFAULT_PLAYER_ID, + GameState.DEFAULT_PLAYER_ID, GameState.DEFAULT_RO))); } - public GameStateJson createNewGameState(String gameId, String blackPlayerId, String whitePlayerId, + public GameState createNewGameState(String gameId, String blackPlayerId, String whitePlayerId, int ro) { String[] players = {blackPlayerId, whitePlayerId}; if (gameId.split(VS_SEPARATOR).length == 2) { players = gameId.split(VS_SEPARATOR); } - int[][] cells = new int[ro][ro]; + Integer[][] cells = new Integer[ro][ro]; for (int i = 0; i < cells.length; i++) { Arrays.fill(cells[i], 0); } - return new GameStateJson(-1, gameId, players[0], players[1], cells, new HashMap<>(), - new Agehama(0, 0), Hand.createDummyHand(), new Hand[0], -1, new HashMap<>(), - LocalDateTime.now()); + + return new GameState(-1, LocalDateTime.now(), gameId, players[0], players[1], + Hand.createDummyHand(), new Agehama(0, 0), cells, new HashMap<>(), new Hand[0], -1, + new HashMap<>()); } diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/MatchingRequestsTable.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/MatchingRequestsTable.java index 3b36304..389872c 100644 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/MatchingRequestsTable.java +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/MatchingRequestsTable.java @@ -1,6 +1,12 @@ package org.nkjmlab.go.javalin.model.relation; -import static org.nkjmlab.sorm4j.util.sql.SelectSql.*; +import static org.nkjmlab.sorm4j.util.sql.SelectSql.cond; +import static org.nkjmlab.sorm4j.util.sql.SelectSql.from; +import static org.nkjmlab.sorm4j.util.sql.SelectSql.literal; +import static org.nkjmlab.sorm4j.util.sql.SelectSql.orderByAsc; +import static org.nkjmlab.sorm4j.util.sql.SelectSql.select; +import static org.nkjmlab.sorm4j.util.sql.SelectSql.selectStarFrom; +import static org.nkjmlab.sorm4j.util.sql.SelectSql.where; import java.time.LocalDateTime; import java.util.HashSet; import java.util.List; @@ -68,7 +74,7 @@ public Set createPairOfUsers(GameStatesTables gameStatesTables) { } Set pastOpponents = gameStatesTables.readPastOpponents(target.userId()); - log.debug("[{}] has been matched with {} on today", target.userId(), pastOpponents); + log.trace("[{}] has been matched with {} on today", target.userId(), pastOpponents); MatchingRequest nextOpponent = selectNextOponent(target, pastOpponents); if (nextOpponent == null) { @@ -79,7 +85,7 @@ public Set createPairOfUsers(GameStatesTables gameStatesTables) { String white = target.rank() >= nextOpponent.rank() ? nextOpponent.userId() : target.userId(); String gameId = black + GameStatesTables.VS_SEPARATOR + white; - log.debug("[{}] is created", gameId); + log.trace("[{}] is created", gameId); updateByPrimaryKey(RowMap.of("game_id", gameId), target.userId); updateByPrimaryKey(RowMap.of("game_id", gameId), nextOpponent.userId); @@ -120,7 +126,7 @@ private MatchingRequest selectNextOponent(MatchingRequest target, Set pa public static record MatchingRequest(@PrimaryKey String userId, String seatId, String userName, int rank, @Index String gameId, LocalDateTime createdAt) { - public static final String UNPAIRED = "UNPAIRED"; + static final String UNPAIRED = "UNPAIRED"; public MatchingRequest() { this("", "", "", 30, UNPAIRED, LocalDateTime.now()); @@ -131,10 +137,13 @@ public static MatchingRequest createUnpaired(User u) { LocalDateTime.now()); } + // Thymeleaf templateからよばれるのでpublicのままにする必要がある. public boolean isUnpaired() { return UNPAIRED.equals(gameId); } } + + } diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/PasswordsTable.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/PasswordsTable.java index 0070b81..768ef5a 100644 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/PasswordsTable.java +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/PasswordsTable.java @@ -3,16 +3,12 @@ import java.io.File; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import javax.sql.DataSource; import org.nkjmlab.go.javalin.model.relation.PasswordsTable.Password; import org.nkjmlab.sorm4j.Sorm; import org.nkjmlab.sorm4j.annotation.OrmRecord; import org.nkjmlab.sorm4j.util.h2.BasicH2Table; import org.nkjmlab.sorm4j.util.table_def.annotation.PrimaryKey; -import org.nkjmlab.util.orangesignal_csv.OrangeSignalCsvUtils; -import org.nkjmlab.util.orangesignal_csv.OrangeSignalCsvUtils.Row; -import com.orangesignal.csv.CsvConfig; /*** * @@ -31,15 +27,8 @@ public boolean isValid(String userId, String password) { } public void readFromFileAndMerge(File usersCsvFile) { - CsvConfig conf = OrangeSignalCsvUtils.createDefaultCsvConfig(); - conf.setSkipLines(1); - List users = OrangeSignalCsvUtils.readAllRows(usersCsvFile, conf); - transformToPassword(users).forEach(user -> merge(user)); - } - - private static List transformToPassword(List rows) { - return rows.stream().map(row -> new Password(row.get(0), row.get(1))) - .collect(Collectors.toList()); + List password = readCsvWithHeader(usersCsvFile); + merge(password); } @OrmRecord diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/ProblemsTable.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/ProblemsTable.java index 4f51050..01dbe3a 100644 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/ProblemsTable.java +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/ProblemsTable.java @@ -1,14 +1,20 @@ package org.nkjmlab.go.javalin.model.relation; -import static org.nkjmlab.sorm4j.util.sql.SelectSql.*; +import static org.nkjmlab.sorm4j.util.sql.SelectSql.from; +import static org.nkjmlab.sorm4j.util.sql.SelectSql.orderByAsc; +import static org.nkjmlab.sorm4j.util.sql.SelectSql.selectDistinct; +import static org.nkjmlab.sorm4j.util.sql.SelectSql.selectStarFrom; +import static org.nkjmlab.sorm4j.util.sql.SelectSql.where; import java.io.File; +import java.nio.file.Path; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; import javax.sql.DataSource; import org.nkjmlab.go.javalin.GoApplication; import org.nkjmlab.go.javalin.model.common.ProblemJson; -import org.nkjmlab.go.javalin.model.problem.ProblemJsonReader; import org.nkjmlab.go.javalin.model.relation.ProblemsTable.Problem; import org.nkjmlab.sorm4j.Sorm; import org.nkjmlab.sorm4j.annotation.OrmRecord; @@ -56,7 +62,7 @@ private List readProblemsByGroupId(String groupId) { public void dropAndInsertInitialProblemsToTable(File problemDir) { log.info("{} is problem dir", problemDir); deleteAll(); - List probs = ProblemJsonReader.readProblemJsons(problemDir.toPath()); + List probs = readProblemJsons(problemDir.toPath()); log.info("[{}] problems are loaded", probs.size()); probs.forEach(j -> { try { @@ -69,6 +75,43 @@ public void dropAndInsertInitialProblemsToTable(File problemDir) { }); } + private static List readProblemJsonFiles(Path pathToProblemJsonDir) { + List result = new ArrayList<>(); + getGroupDirectories(pathToProblemJsonDir).forEach(groupDir -> { + Arrays.asList(groupDir.listFiles()).forEach(file -> { + if (!file.getName().endsWith(".json")) { + return; + } + result.add(file); + }); + }); + return result; + } + + static List readProblemJsons(Path pathToProblemJsonDir) { + List files = readProblemJsonFiles(pathToProblemJsonDir); + log.debug("detect [{}] problem files in [{}]", files.size(), pathToProblemJsonDir); + return files.stream().map(file -> { + try { + ProblemJson problem = + GoApplication.getDefaultJacksonMapper().toObject(file, ProblemJson.class); + return problem; + } catch (Exception e) { + log.error("file {}", file); + throw new RuntimeException(e); + } + }).collect(Collectors.toList()); + } + + private static List getGroupDirectories(Path path) { + File[] files = path.toFile().listFiles(); + if (files != null) { + return Arrays.asList(files).stream().filter(f -> f.isDirectory()) + .collect(Collectors.toList()); + } + return new ArrayList<>(); + } + @OrmRecord public static record Problem(@PrimaryKey long id, LocalDateTime createdAt, @Index @NotNull String groupId, @NotNull String name, @NotNull String cells, diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/UsersTable.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/UsersTable.java index 7d1ad3b..237c51e 100644 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/UsersTable.java +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/relation/UsersTable.java @@ -1,7 +1,6 @@ package org.nkjmlab.go.javalin.model.relation; -import static org.nkjmlab.go.javalin.GoApplication.*; -import static org.nkjmlab.sorm4j.util.sql.SelectSql.*; +import static org.nkjmlab.sorm4j.util.sql.SelectSql.selectStarFrom; import java.io.File; import java.io.IOException; import java.time.LocalDateTime; @@ -13,6 +12,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import javax.sql.DataSource; +import org.nkjmlab.go.javalin.GoWebAppConfig; import org.nkjmlab.go.javalin.model.relation.LoginsTable.Login; import org.nkjmlab.go.javalin.model.relation.UsersTable.User; import org.nkjmlab.sorm4j.Sorm; @@ -26,9 +26,6 @@ import org.nkjmlab.sorm4j.util.table_def.annotation.Index; import org.nkjmlab.sorm4j.util.table_def.annotation.PrimaryKey; import org.nkjmlab.sorm4j.util.table_def.annotation.Unique; -import org.nkjmlab.util.orangesignal_csv.OrangeSignalCsvUtils; -import org.nkjmlab.util.orangesignal_csv.OrangeSignalCsvUtils.Row; -import com.orangesignal.csv.CsvConfig; /*** * Userという名前で統一したかったけど,H2のデフォルトのテーブルと衝突してしまうので,Playerに改名した. @@ -48,13 +45,6 @@ public UsersTable(DataSource dataSource) { super(Sorm.create(dataSource), User.class); } - - - public void createTableAndIndexesIfNotExists() { - createTableIfNotExists().createIndexesIfNotExists(); - } - - public User getUser(String uid) { User entry = selectByPrimaryKey(uid); return entry; @@ -82,23 +72,24 @@ public User readByEmail(String email) { public void readFromFileAndMerge(File usersCsvFile) { - CsvConfig conf = OrangeSignalCsvUtils.createDefaultCsvConfig(); - conf.setSkipLines(1); - List users = OrangeSignalCsvUtils.readAllRows(usersCsvFile, conf); - transformToUser(users).forEach(user -> { + BasicH2Table table = new BasicH2Table<>(getOrm(), UserCsv.class); + + transformToUser(table.readCsvWithHeader(usersCsvFile)).forEach(user -> { createIcon(user.userId()); insert(user); }); } + @OrmRecord + public static record UserCsv(String userId, String email, String username, String role) { + + } + - private static List transformToUser(List rows) { - return rows.stream().map(row -> { - User user = - new User(row.get(0), row.get(1), row.get(2), row.get(3), "-1", 30, LocalDateTime.now()); - return user; - }).collect(Collectors.toList()); + private List transformToUser(List users) { + return users.stream().map(row -> new User(row.userId(), row.email(), row.username(), row.role(), + "-1", 30, LocalDateTime.now())).collect(Collectors.toList()); } public List readListByUids(Collection uids) { @@ -129,23 +120,22 @@ public boolean isAdmin(String userId) { public static void createIcon(String userId) { - File uploadedIcon = new File(UPLOADED_ICON_DIR, userId + ".png"); - File initialIcon = new File(INITIAL_ICON_DIR, userId + ".png"); - - File srcFile = - uploadedIcon - .exists() - ? uploadedIcon - : (initialIcon - .exists() - ? initialIcon - : getRandom(Stream.of(RANDOM_ICON_DIR.listFiles()) - .filter(f -> f.getName().toLowerCase().endsWith(".png") - || f.getName().toLowerCase().endsWith(".jpg")) - .toList()).orElseThrow()); + File uploadedIcon = new File(GoWebAppConfig.UPLOADED_ICON_DIR, userId + ".png"); + File initialIcon = + new File(new File(GoWebAppConfig.WEB_APP_CONFIG.getWebRootDirectory(), "img/icon-initial"), + userId + ".png"); + + File srcFile = uploadedIcon.exists() ? uploadedIcon + : (initialIcon.exists() ? initialIcon + : getRandom(Stream + .of(new File(GoWebAppConfig.WEB_APP_CONFIG.getWebRootDirectory(), + "img/icon-random").listFiles()) + .filter(f -> f.getName().toLowerCase().endsWith(".png") + || f.getName().toLowerCase().endsWith(".jpg")) + .toList()).orElseThrow()); try { org.apache.commons.io.FileUtils.copyFile(srcFile, - new File(CURRENT_ICON_DIR, userId + ".png")); + new File(GoWebAppConfig.CURRENT_ICON_DIR, userId + ".png")); } catch (IOException e) { log.warn(e, e); } diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/problem/ProblemTextToJsonConverter.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/obsolete/ProblemTextToJsonConverter.java similarity index 88% rename from nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/problem/ProblemTextToJsonConverter.java rename to nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/obsolete/ProblemTextToJsonConverter.java index 14b9a67..154a0d5 100644 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/model/problem/ProblemTextToJsonConverter.java +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/obsolete/ProblemTextToJsonConverter.java @@ -1,4 +1,4 @@ -package org.nkjmlab.go.javalin.model.problem; +package org.nkjmlab.go.javalin.obsolete; import java.io.File; import java.io.IOException; @@ -10,8 +10,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import org.apache.commons.lang3.builder.ToStringBuilder; @@ -24,6 +22,7 @@ import org.nkjmlab.go.javalin.model.common.Stone; import org.nkjmlab.go.javalin.model.common.Stone.Color; import org.nkjmlab.go.javalin.model.common.Stone.Symbol; +import org.nkjmlab.go.javalin.util.CurrentTimeMillisIdGenerator; public class ProblemTextToJsonConverter { private static final org.apache.logging.log4j.Logger log = @@ -46,26 +45,8 @@ private static List getGroupDirectories(Path path) { return new ArrayList<>(); } - private static final Queue ids = new ConcurrentLinkedQueue<>(); - - - public static synchronized long getNewId() { - while (true) { - long id = System.currentTimeMillis(); - if (!ids.contains(id)) { - ids.offer(id); - if (ids.size() > 1000) { - ids.poll(); - } - return id; - } - try { - Thread.sleep(1); - } catch (InterruptedException e) { - } - } - } + private static final CurrentTimeMillisIdGenerator problemIdGenerator = new CurrentTimeMillisIdGenerator(); private static Map convertTxtToJson(Path pathToProblemTxtDir) { Map result = new LinkedHashMap<>(); @@ -78,7 +59,7 @@ private static Map convertTxtToJson(Path pathToProblemTxtDir) try { List lines = Files.readAllLines(file.toPath()); Builder json = new Builder(); - json.setProblemId(getNewId()); + json.setProblemId(problemIdGenerator.getNewId()); json.setGroupId(groupDir.getName()); String name = file.getName().replace(".txt", ""); json.setName(name); diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/util/CurrentTimeMillisIdGenerator.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/util/CurrentTimeMillisIdGenerator.java new file mode 100644 index 0000000..d8647aa --- /dev/null +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/util/CurrentTimeMillisIdGenerator.java @@ -0,0 +1,23 @@ +package org.nkjmlab.go.javalin.util; + +import java.util.concurrent.TimeUnit; + +public class CurrentTimeMillisIdGenerator { + + private volatile long prev = -1; + + public synchronized long getNewId() { + while (true) { + long id = System.currentTimeMillis(); + if (prev == id) { + try { + TimeUnit.MILLISECONDS.sleep(1); + } catch (InterruptedException e) { + } + continue; + } + prev = id; + return id; + } + } +} diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/websocket/WebsocketSessionsManager.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/websocket/WebsocketSessionsManager.java index d6b2209..387aefa 100644 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/websocket/WebsocketSessionsManager.java +++ b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/websocket/WebsocketSessionsManager.java @@ -5,33 +5,43 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Queue; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.Collectors; +import javax.sql.DataSource; import org.eclipse.jetty.websocket.api.RemoteEndpoint; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WriteCallback; import org.nkjmlab.go.javalin.GoApplication; +import org.nkjmlab.go.javalin.GoTables; import org.nkjmlab.go.javalin.model.common.Agehama; import org.nkjmlab.go.javalin.model.common.Hand; import org.nkjmlab.go.javalin.model.common.Hand.HandType; import org.nkjmlab.go.javalin.model.common.ProblemJson; -import org.nkjmlab.go.javalin.model.relation.GameStatesTable.GameStateJson; -import org.nkjmlab.go.javalin.model.relation.GameStatesTables; -import org.nkjmlab.go.javalin.model.relation.HandUpsTable; -import org.nkjmlab.go.javalin.model.relation.MatchingRequestsTable; -import org.nkjmlab.go.javalin.model.relation.ProblemsTable; +import org.nkjmlab.go.javalin.model.relation.GameStatesTable.GameState; import org.nkjmlab.go.javalin.model.relation.ProblemsTable.Problem; import org.nkjmlab.go.javalin.model.relation.UsersTable; import org.nkjmlab.go.javalin.model.relation.UsersTable.User; import org.nkjmlab.go.javalin.model.relation.UsersTable.UserJson; +import org.nkjmlab.go.javalin.websocket.WebsocketSessionsManager.WebsoketSessionsTable.WebSocketSession; +import org.nkjmlab.sorm4j.Sorm; +import org.nkjmlab.sorm4j.annotation.OrmRecord; +import org.nkjmlab.sorm4j.sql.OrderedParameterSqlParser; +import org.nkjmlab.sorm4j.sql.ParameterizedSql; +import org.nkjmlab.sorm4j.sql.ParameterizedSqlParser; +import org.nkjmlab.sorm4j.util.h2.BasicH2Table; +import org.nkjmlab.sorm4j.util.table_def.annotation.Index; +import org.nkjmlab.sorm4j.util.table_def.annotation.PrimaryKey; import org.nkjmlab.util.jackson.JacksonMapper; import org.nkjmlab.util.java.concurrent.ForkJoinPoolUtils; import org.nkjmlab.util.java.json.JsonMapper; @@ -45,32 +55,22 @@ public class WebsocketSessionsManager { private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(); - private final ProblemsTable problemsTable; - private final GameStatesTables gameStatesTables; private final WebsoketSessionsTable websoketSessionsTable; - private final UsersTable usersTable; - private final HandUpsTable handsUpTable; - private final MatchingRequestsTable matchingRequestsTable; - private final Queue globalMessages = new ConcurrentLinkedQueue<>(); private final WebSocketJsonSenderService jsonSenderService = new WebSocketJsonSenderService(); + private GoTables goTables; - public WebsocketSessionsManager(GameStatesTables gameStatesTables, ProblemsTable problemsTable, - WebsoketSessionsTable websoketSessionsTable, UsersTable usersTable, HandUpsTable handsUpTable, - MatchingRequestsTable matchingRequestsTable) { - this.gameStatesTables = gameStatesTables; - this.problemsTable = problemsTable; - this.websoketSessionsTable = websoketSessionsTable; - this.usersTable = usersTable; - this.handsUpTable = handsUpTable; - this.matchingRequestsTable = matchingRequestsTable; - } + public WebsocketSessionsManager(GoTables goTables, DataSource memDbDataSource) { + this.goTables = goTables; + this.websoketSessionsTable = new WebsoketSessionsTable(memDbDataSource); + this.websoketSessionsTable.createTableIfNotExists().createIndexesIfNotExists(); + } public void onMessage(String gameId, WsMessageContext ctx) { - GameStateJson gs = ctx.messageAsClass(GameStateJson.class); + GameState gs = ctx.messageAsClass(GameState.class); sendGameState(gameId, gs); } @@ -92,7 +92,7 @@ public void updateSession(int sessionId, String gameId, String userId) { public void onConnect(Session session, String userId, String gameId) { - if (!usersTable.exists(userId)) { + if (!goTables.usersTable.exists(userId)) { log.info("userId=[{}] dose not exists", userId); jsonSenderService.submitRequestToLogin(session, userId); return; @@ -100,12 +100,13 @@ public void onConnect(Session session, String userId, String gameId) { websoketSessionsTable.registerSession(gameId, userId, session); jsonSenderService.submitInitSession(session, session.hashCode(), - usersTable.selectByPrimaryKey(userId)); + goTables.usersTable.selectByPrimaryKey(userId)); - Optional.ofNullable(handsUpTable.selectByPrimaryKey(gameId)).ifPresent(h -> jsonSenderService - .submitHandUpOrDown(List.of(session), true, handsUpTable.readOrder(gameId))); + Optional.ofNullable(goTables.handsUpTable.selectByPrimaryKey(gameId)) + .ifPresent(h -> jsonSenderService.submitHandUpOrDown(List.of(session), true, + goTables.handsUpTable.readOrder(gameId))); - Optional.ofNullable(matchingRequestsTable.selectByPrimaryKey(userId)) + Optional.ofNullable(goTables.matchingRequestsTable.selectByPrimaryKey(userId)) .ifPresent(h -> jsonSenderService.submitUpdateWaitingRequestStatus(List.of(session))); jsonSenderService.submitGlobalMessages(session, globalMessages); @@ -115,49 +116,49 @@ public void onConnect(Session session, String userId, String gameId) { public void goBack(String gameId) { - gameStatesTables.deleteLatestGameState(gameId); + goTables.gameStatesTables.deleteLatestGameState(gameId); sendLatestGameStateToSessions(gameId); } public ProblemJson loadProblem(String gameId, long problemId) { - Problem p = problemsTable.selectByPrimaryKey(problemId); + Problem p = goTables.problemsTable.selectByPrimaryKey(problemId); Hand[] handHistory = mapper.toObject(p.handHistory(), Hand[].class); Hand lastHand = handHistory.length != 0 ? handHistory[handHistory.length - 1] : Hand.createDummyHand(); - GameStateJson json = - new GameStateJson(-1, gameId, "", "", mapper.toObject(p.cells(), int[][].class), - mapper.toObject(p.symbols(), new TypeReference>() {}), - mapper.toObject(p.agehama(), Agehama.class), lastHand, handHistory, p.id(), - new HashMap<>(), LocalDateTime.now()); + + GameState json = new GameState(-1, LocalDateTime.now(), gameId, "", "", lastHand, + mapper.toObject(p.agehama(), Agehama.class), mapper.toObject(p.cells(), Integer[][].class), + mapper.toObject(p.symbols(), new TypeReference>() {}), handHistory, + p.id(), new HashMap<>()); sendGameState(gameId, json); - return ProblemJson.createFrom(problemsTable.selectByPrimaryKey(problemId)); + return ProblemJson.createFrom(goTables.problemsTable.selectByPrimaryKey(problemId)); } - public void newGame(String gameId, GameStateJson json) { - GameStateJson newGameJson = gameStatesTables.createNewGameState(gameId, json.blackPlayerId(), - json.whitePlayerId(), json.cells().length); + public void newGame(String gameId, GameState json) { + GameState newGameJson = goTables.gameStatesTables.createNewGameState(gameId, + json.blackPlayerId(), json.whitePlayerId(), json.cells().length); sendGameState(gameId, newGameJson); } private void sendEntriesToSessions(String gameId) { - List users = websoketSessionsTable.readUsers(usersTable, gameId).stream() + List users = websoketSessionsTable.readUsers(goTables.usersTable, gameId).stream() .map(u -> new UserJson(u, true)).collect(Collectors.toList()); jsonSenderService.submitEntries(websoketSessionsTable.getSessionsByGameId(gameId), users); } - public void sendGameState(String gameId, GameStateJson json) { - GameStateJson newJson = removeHagashi(json); + public void sendGameState(String gameId, GameState json) { + GameState newJson = removeHagashi(json); - gameStatesTables.saveGameState(newJson); + goTables.gameStatesTables.saveGameState(newJson); sendGameStateToSessions(gameId, newJson); } - private GameStateJson removeHagashi(GameStateJson json) { + private GameState removeHagashi(GameState json) { List history = Arrays.asList(json.handHistory()); if (history.size() <= 2) { return json; @@ -177,7 +178,7 @@ private GameStateJson removeHagashi(GameStateJson json) { } - private void sendGameStateToSessions(String gameId, GameStateJson json) { + private void sendGameStateToSessions(String gameId, GameState json) { jsonSenderService.submitGameState(websoketSessionsTable.getSessionsByGameId(gameId), json); } @@ -202,11 +203,12 @@ public void sendHandUpOrder(String gameId, int order) { private void sendHandUpToSessions(String gameId, boolean handUp, int order) { jsonSenderService.submitHandUpOrDown(websoketSessionsTable.getSessionsByGameId(gameId), handUp, order); - jsonSenderService.submitUpdateHandUpTable(websoketSessionsTable.getAdminSessions(usersTable)); + jsonSenderService + .submitUpdateHandUpTable(websoketSessionsTable.getAdminSessions(goTables.usersTable)); } public void sendLatestGameStateToSessions(String gameId) { - sendGameStateToSessions(gameId, gameStatesTables.readLatestGameStateJson(gameId)); + sendGameStateToSessions(gameId, goTables.gameStatesTables.readLatestGameState(gameId)); } @@ -238,7 +240,7 @@ private enum MethodName { public WebSocketJsonSenderService() {} - public void submitGameState(List sessions, GameStateJson json) { + public void submitGameState(List sessions, GameState json) { submit(sessions, MethodName.GAME_STATE, json); } @@ -327,4 +329,143 @@ public String toString() { } } + public static class WebsoketSessionsTable extends BasicH2Table { + + private static final String USER_ID = "user_id"; + private static final String GAME_ID = "game_id"; + + private static Map sessions = new ConcurrentHashMap<>(); + + public WebsoketSessionsTable(DataSource dataSource) { + super(Sorm.create(dataSource), WebSocketSession.class); + } + + + + List readSessionsByGameId(String gameId) { + return readList(SELECT_STAR + FROM + getTableName() + WHERE + GAME_ID + "=?", gameId); + } + + List readSessionsByUserId(String userId) { + return readList("select * " + FROM + getTableName() + WHERE + USER_ID + "=?", userId); + } + + List getSessionsByGameId(String gameId) { + List result = + readSessionsByGameId(gameId).stream().map(session -> sessions.get(session.sessionId())) + .filter(Objects::nonNull).collect(Collectors.toList()); + return result; + } + + List getSessionsByUserId(String userId) { + List result = + readSessionsByUserId(userId).stream().map(session -> sessions.get(session.sessionId())) + .filter(s -> Objects.nonNull(s)).collect(Collectors.toList()); + return result; + } + + public List getSessionsByUserIds(List userIds) { + if (userIds.size() == 0) { + return Collections.emptyList(); + } + ParameterizedSql psql = ParameterizedSqlParser + .parse("select * from " + getTableName() + " where " + USER_ID + " IN() ", userIds); + return readList(psql.getSql(), psql.getParameters()).stream() + .map(session -> sessions.get(session.sessionId())).filter(s -> Objects.nonNull(s)) + .collect(Collectors.toList()); + } + + + + List getAllSessions() { + List result = selectAll().stream().map(session -> sessions.get(session.sessionId())) + .filter(s -> Objects.nonNull(s)).collect(Collectors.toList()); + return result; + } + + + void registerSession(String gameId, String userId, Session session) { + int sessionId = session.hashCode(); + WebSocketSession ws = new WebSocketSession(sessionId, userId, gameId, LocalDateTime.now()); + if (exists(ws)) { + log.warn("{} already exists.", ws); + return; + } + insert(ws); + sessions.put(sessionId, session); + log.info("WebSocket is registered={}", ws); + } + + void updateSession(int sessionId, String gameId, String userId) { + update(new WebSocketSession(sessionId, userId, gameId, LocalDateTime.now())); + + } + + Optional removeSession(Session session) { + if (sessions.entrySet().removeIf(e -> e.getKey() == session.hashCode())) { + WebSocketSession gs = selectByPrimaryKey(session.hashCode()); + delete(gs); + return Optional.of(gs.gameId()); + } + return Optional.empty(); + + } + + public List readUsers(UsersTable usersTable, String gameId) { + Set uids = + readSessionsByGameId(gameId).stream().map(u -> u == null ? null : u.userId()) + .filter(Objects::nonNull).collect(Collectors.toSet()); + return usersTable.readListByUids(uids); + + } + + public List readStudents(UsersTable usersTable, String gameId) { + return readUsers(usersTable, gameId).stream().filter(u -> u.isStudent()) + .collect(Collectors.toList()); + } + + public List readActiveGameIdsOrderByGameId(UsersTable usersTable) { + ParameterizedSql psql = ParameterizedSqlParser.parse( + SELECT + DISTINCT + GAME_ID + FROM + getTableName() + WHERE + GAME_ID + LIKE + "?", + "%-vs-%"); + return getOrm().readList(String.class, psql).stream() + .filter(gid -> readUsers(usersTable, gid).size() > 0).sorted() + .collect(Collectors.toList()); + } + + public int getWatchingUniqueStudentsNum(UsersTable usersTable, String gameId) { + return (int) readStudents(usersTable, gameId).stream().map(uj -> uj.userId()) + .collect(Collectors.toSet()).stream().count(); + } + + public List getAdminSessions(UsersTable usersTable) { + List ids = usersTable.getAdminUserIds(); + if (ids.size() == 0) { + return Collections.emptyList(); + } + ParameterizedSql st = OrderedParameterSqlParser + .parse("select * from " + getTableName() + " where " + USER_ID + " IN ()", ids); + return readList(st.getSql(), st.getParameters()).stream() + .map(ws -> sessions.get(ws.sessionId())).filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + + @OrmRecord + public static record WebSocketSession(@PrimaryKey int sessionId, String userId, + @Index String gameId, LocalDateTime createdAt) { + + } + + } + + public int getWatchingUniqueStudentsNum(String gameId) { + return websoketSessionsTable.getWatchingUniqueStudentsNum(goTables.usersTable, gameId); + } + + + public List readActiveGameIdsOrderByGameId() { + return websoketSessionsTable.readActiveGameIdsOrderByGameId(goTables.usersTable); + } + } diff --git a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/websocket/WebsoketSessionsTable.java b/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/websocket/WebsoketSessionsTable.java deleted file mode 100644 index 32f1dc5..0000000 --- a/nkjmlab-go-webapp/src/main/java/org/nkjmlab/go/javalin/websocket/WebsoketSessionsTable.java +++ /dev/null @@ -1,158 +0,0 @@ -package org.nkjmlab.go.javalin.websocket; - -import java.time.LocalDateTime; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; -import javax.sql.DataSource; -import org.eclipse.jetty.websocket.api.Session; -import org.nkjmlab.go.javalin.model.relation.UsersTable; -import org.nkjmlab.go.javalin.model.relation.UsersTable.User; -import org.nkjmlab.go.javalin.websocket.WebsoketSessionsTable.WebSocketSession; -import org.nkjmlab.sorm4j.Sorm; -import org.nkjmlab.sorm4j.annotation.OrmRecord; -import org.nkjmlab.sorm4j.sql.OrderedParameterSqlParser; -import org.nkjmlab.sorm4j.sql.ParameterizedSql; -import org.nkjmlab.sorm4j.sql.ParameterizedSqlParser; -import org.nkjmlab.sorm4j.util.h2.BasicH2Table; -import org.nkjmlab.sorm4j.util.table_def.annotation.Index; -import org.nkjmlab.sorm4j.util.table_def.annotation.PrimaryKey; - -public class WebsoketSessionsTable extends BasicH2Table { - private static final org.apache.logging.log4j.Logger log = - org.apache.logging.log4j.LogManager.getLogger(); - - - private static final String USER_ID = "user_id"; - private static final String GAME_ID = "game_id"; - - private static Map sessions = new ConcurrentHashMap<>(); - - public WebsoketSessionsTable(DataSource dataSource) { - super(Sorm.create(dataSource), WebSocketSession.class); - } - - - - List readSessionsByGameId(String gameId) { - return readList(SELECT_STAR + FROM + getTableName() + WHERE + GAME_ID + "=?", gameId); - } - - List readSessionsByUserId(String userId) { - return readList("select * " + FROM + getTableName() + WHERE + USER_ID + "=?", userId); - } - - List getSessionsByGameId(String gameId) { - List result = - readSessionsByGameId(gameId).stream().map(session -> sessions.get(session.sessionId())) - .filter(Objects::nonNull).collect(Collectors.toList()); - return result; - } - - List getSessionsByUserId(String userId) { - List result = - readSessionsByUserId(userId).stream().map(session -> sessions.get(session.sessionId())) - .filter(s -> Objects.nonNull(s)).collect(Collectors.toList()); - return result; - } - - public List getSessionsByUserIds(List userIds) { - if (userIds.size() == 0) { - return Collections.emptyList(); - } - ParameterizedSql psql = ParameterizedSqlParser - .parse("select * from " + getTableName() + " where " + USER_ID + " IN() ", userIds); - return readList(psql.getSql(), psql.getParameters()).stream() - .map(session -> sessions.get(session.sessionId())).filter(s -> Objects.nonNull(s)) - .collect(Collectors.toList()); - } - - - - List getAllSessions() { - List result = selectAll().stream().map(session -> sessions.get(session.sessionId())) - .filter(s -> Objects.nonNull(s)).collect(Collectors.toList()); - return result; - } - - - void registerSession(String gameId, String userId, Session session) { - int sessionId = session.hashCode(); - WebSocketSession ws = new WebSocketSession(sessionId, userId, gameId, LocalDateTime.now()); - if (exists(ws)) { - log.warn("{} already exists.", ws); - return; - } - insert(ws); - sessions.put(sessionId, session); - log.info("WebSocket is registered={}", ws); - } - - void updateSession(int sessionId, String gameId, String userId) { - update(new WebSocketSession(sessionId, userId, gameId, LocalDateTime.now())); - - } - - Optional removeSession(Session session) { - for (Entry e : sessions.entrySet()) { - if (e.getValue().equals(session)) { - sessions.remove(e.getKey()); - WebSocketSession gs = selectByPrimaryKey(e.getKey()); - delete(gs); - return Optional.of(gs.gameId()); - } - } - return Optional.empty(); - } - - public List readUsers(UsersTable usersTable, String gameId) { - Set uids = readSessionsByGameId(gameId).stream().map(u -> u == null ? null : u.userId()) - .filter(Objects::nonNull).collect(Collectors.toSet()); - return usersTable.readListByUids(uids); - - } - - public List readStudents(UsersTable usersTable, String gameId) { - return readUsers(usersTable, gameId).stream().filter(u -> u.isStudent()) - .collect(Collectors.toList()); - } - - public List readActiveGameIdsOrderByGameId(UsersTable usersTable) { - ParameterizedSql psql = ParameterizedSqlParser.parse( - SELECT + DISTINCT + GAME_ID + FROM + getTableName() + WHERE + GAME_ID + LIKE + "?", - "%-vs-%"); - return getOrm().readList(String.class, psql).stream() - .filter(gid -> readUsers(usersTable, gid).size() > 0).sorted().collect(Collectors.toList()); - } - - public int getWatchingUniqueStudentsNum(UsersTable usersTable, String gameId) { - return (int) readStudents(usersTable, gameId).stream().map(uj -> uj.userId()) - .collect(Collectors.toSet()).stream().count(); - } - - public List getAdminSessions(UsersTable usersTable) { - List ids = usersTable.getAdminUserIds(); - if (ids.size() == 0) { - return Collections.emptyList(); - } - ParameterizedSql st = OrderedParameterSqlParser - .parse("select * from " + getTableName() + " where " + USER_ID + " IN ()", ids); - return readList(st.getSql(), st.getParameters()).stream() - .map(ws -> sessions.get(ws.sessionId())).filter(Objects::nonNull) - .collect(Collectors.toList()); - } - - - @OrmRecord - public static record WebSocketSession(@PrimaryKey int sessionId, String userId, - @Index String gameId, LocalDateTime createdAt) { - - } - -} diff --git a/nkjmlab-go-webapp/src/main/resources/conf/firebase-url.conf.sample b/nkjmlab-go-webapp/src/main/resources/conf/firebase-url.conf.sample deleted file mode 100644 index 93aaa4d..0000000 --- a/nkjmlab-go-webapp/src/main/resources/conf/firebase-url.conf.sample +++ /dev/null @@ -1 +0,0 @@ -https://*******.firebaseio.com diff --git a/nkjmlab-go-webapp/src/main/resources/conf/passwords.csv.default b/nkjmlab-go-webapp/src/main/resources/conf/passwords.csv.default index a82e2bd..be6a54d 100644 --- a/nkjmlab-go-webapp/src/main/resources/conf/passwords.csv.default +++ b/nkjmlab-go-webapp/src/main/resources/conf/passwords.csv.default @@ -1,4 +1,4 @@ -userId,password +user_id,password 5500900,example0@example.com 5500911,example1@example.com 5500920,example2@example.com diff --git a/nkjmlab-go-webapp/src/main/resources/conf/users.csv.default b/nkjmlab-go-webapp/src/main/resources/conf/users.csv.default index 3aa0e6d..ebfe2bf 100644 --- a/nkjmlab-go-webapp/src/main/resources/conf/users.csv.default +++ b/nkjmlab-go-webapp/src/main/resources/conf/users.csv.default @@ -1,4 +1,4 @@ -userId,email,username,role +user_id,email,username,role 5500900,example0@example.com,管理者1,ADMIN 5500911,example1@example.com,TA1,TA 5500920,example2@example.com,学生0,STUDENT diff --git a/nkjmlab-go-webapp/src/main/resources/go-webui.bat b/nkjmlab-go-webapp/src/main/resources/go-webui.bat index f821ff9..19b8986 100644 --- a/nkjmlab-go-webapp/src/main/resources/go-webui.bat +++ b/nkjmlab-go-webapp/src/main/resources/go-webui.bat @@ -1,4 +1,6 @@ +@echo off setlocal cd /d %~dp0 cd ../ -java -cp classes;lib/* -Dfile.encoding=UTF-8 org.nkjmlab.go.javalin.GoApplication +start /B javaw -cp classes;lib/* -Dfile.encoding=UTF-8 org.nkjmlab.go.javalin.GoApplication +endlocal diff --git a/nkjmlab-go-webapp/src/main/resources/go-webui.sh b/nkjmlab-go-webapp/src/main/resources/go-webui.sh index c97b5b6..52e7640 100644 --- a/nkjmlab-go-webapp/src/main/resources/go-webui.sh +++ b/nkjmlab-go-webapp/src/main/resources/go-webui.sh @@ -1,4 +1,4 @@ cd `dirname $0` cd ../ -java -cp classes:lib/* -Dfile.encoding=UTF-8 org.nkjmlab.go.javalin.GoApplication $@ +java -cp classes:lib/* -Dfile.encoding=UTF-8 -Dlog4j2.configurationFile=./classes/log4j2-production.xml org.nkjmlab.go.javalin.GoApplication $@ diff --git a/nkjmlab-go-webapp/src/main/resources/log4j2-production.xml b/nkjmlab-go-webapp/src/main/resources/log4j2-production.xml new file mode 100644 index 0000000..d652597 --- /dev/null +++ b/nkjmlab-go-webapp/src/main/resources/log4j2-production.xml @@ -0,0 +1,56 @@ + + + + + nkjmlab-go + logs + INFO + INFO + ${log_pattern_loc} + + %d{yyyy-MM-dd HH:mm:ss.SSS} %5p [%t] %c.%M(%F:%L) %m%n + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %5p [%t] %c{3.} %m%n + + + %m%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nkjmlab-go-webapp/src/main/resources/templates/fragment/creator.html b/nkjmlab-go-webapp/src/main/resources/templates/fragment/creator.html index a5f02ec..0e44722 100644 --- a/nkjmlab-go-webapp/src/main/resources/templates/fragment/creator.html +++ b/nkjmlab-go-webapp/src/main/resources/templates/fragment/creator.html @@ -4,26 +4,26 @@
-
+ -
+
ユーザ (学生) ユーザ (全ユーザ) 対局 (進行中) 対局 (本日)
- +
-
+
-
-
- -
-
-
-
+
+ class="badge bg-info" th:if='${isAttendance and !currentUser.isAdmin()}'>出
-
+
学生 @@ -43,9 +41,7 @@
- +
@@ -29,7 +27,7 @@
@@ -50,9 +48,7 @@
diff --git a/nkjmlab-go-webapp/src/main/resources/templates/fragment/modal-records.html b/nkjmlab-go-webapp/src/main/resources/templates/fragment/modal-records.html index 86e33c9..5ce1820 100644 --- a/nkjmlab-go-webapp/src/main/resources/templates/fragment/modal-records.html +++ b/nkjmlab-go-webapp/src/main/resources/templates/fragment/modal-records.html @@ -8,9 +8,7 @@ - +
diff --git a/nkjmlab-go-webapp/src/main/resources/templates/fragment/waiting-request-table.html b/nkjmlab-go-webapp/src/main/resources/templates/fragment/waiting-request-table.html index a99c635..a714d72 100644 --- a/nkjmlab-go-webapp/src/main/resources/templates/fragment/waiting-request-table.html +++ b/nkjmlab-go-webapp/src/main/resources/templates/fragment/waiting-request-table.html @@ -15,13 +15,13 @@ 募集中 募集中 + th:src="|/img/icon/${req.userId}.png|" + onerror="this.onerror=null;this.src='/img/icon/no-player-icon.png'">
diff --git a/nkjmlab-go-webapp/src/main/resources/templates/games.html b/nkjmlab-go-webapp/src/main/resources/templates/games.html index 6f6b20f..d3fabbe 100644 --- a/nkjmlab-go-webapp/src/main/resources/templates/games.html +++ b/nkjmlab-go-webapp/src/main/resources/templates/games.html @@ -2,7 +2,7 @@ - + - - - - -ようこそ | 囲碁の演習と対局 + + + + + + + + + ようこそ | 囲碁の演習と対局
- +

教育用囲碁Webアプリケーション

@@ -26,19 +26,18 @@

教育用囲碁Webアプリケーション

- Let's GO + Let's GO

このシステムはオンライン上での囲碁教育を支援するシステムです.Webブラウザが使える端末があれば,簡単に授業に参加することが出来ます.