diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml index 546db64..5b21d2d 100644 --- a/.idea/sqldialects.xml +++ b/.idea/sqldialects.xml @@ -2,6 +2,5 @@ - \ No newline at end of file diff --git a/src/at/nanopenguin/mtcg/application/UserCards.java b/src/at/nanopenguin/mtcg/application/UserCards.java new file mode 100644 index 0000000..a33d400 --- /dev/null +++ b/src/at/nanopenguin/mtcg/application/UserCards.java @@ -0,0 +1,57 @@ +package at.nanopenguin.mtcg.application; + +import at.nanopenguin.mtcg.application.service.schemas.Card; +import at.nanopenguin.mtcg.db.DbQuery; +import at.nanopenguin.mtcg.db.SqlCommand; +import at.nanopenguin.mtcg.db.Table; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.val; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.UUID; + +public class UserCards extends User { + public static Card[] get(UUID userUuid, boolean deckOnly) throws SQLException { + val dbQueryBuilder = DbQuery.builder() + .command(SqlCommand.SELECT) + .table(Table.CARDS) + .column("uuid AS id") + .column("name") + .column("damage") + .condition("owner", userUuid); + if (deckOnly) dbQueryBuilder.condition("deck", true); + ArrayList cards = new ArrayList<>(); + for (val row : dbQueryBuilder.executeQuery()) { + cards.add(new ObjectMapper().convertValue(row, Card.class)); + } + return cards.toArray(new Card[0]); + } + + public static synchronized boolean setDeck(UUID[] cards, UUID userUuid) throws SQLException { + if (DbQuery.builder() + .command(SqlCommand.SELECT) + .table(Table.CARDS) + .condition("owner", userUuid) + .condition("uuid", Arrays.asList(cards)) + .executeQuery().size() != 4) + return false; + + DbQuery.builder() + .command(SqlCommand.UPDATE) + .table(Table.CARDS) + .parameter("deck", false) + .condition("owner", userUuid) + .executeUpdate(); + + DbQuery.builder() + .command(SqlCommand.UPDATE) + .table(Table.CARDS) + .parameter("deck", true) + .condition("uuid", Arrays.asList(cards)) + .executeUpdate(); + + return true; + } +} diff --git a/src/at/nanopenguin/mtcg/application/service/CardsService.java b/src/at/nanopenguin/mtcg/application/service/CardsService.java index 22c0e92..ba358e1 100644 --- a/src/at/nanopenguin/mtcg/application/service/CardsService.java +++ b/src/at/nanopenguin/mtcg/application/service/CardsService.java @@ -1,34 +1,57 @@ package at.nanopenguin.mtcg.application.service; +import at.nanopenguin.mtcg.application.SessionHandler; +import at.nanopenguin.mtcg.application.TokenValidity; +import at.nanopenguin.mtcg.application.User; +import at.nanopenguin.mtcg.application.UserCards; import at.nanopenguin.mtcg.http.HttpMethod; import at.nanopenguin.mtcg.http.HttpRequest; import at.nanopenguin.mtcg.http.HttpStatus; import at.nanopenguin.mtcg.http.Response; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.val; +import java.sql.SQLException; import java.util.Arrays; +import java.util.List; +import java.util.UUID; public class CardsService implements Service { @Override - public Response handleRequest(HttpRequest request) throws JsonProcessingException { - try { - if (request.getPath().split("/")[1].equals("cards") && request.getMethod() == HttpMethod.GET) { - return new Response(HttpStatus.NOT_IMPLEMENTED); - } + public Response handleRequest(HttpRequest request) throws JsonProcessingException, SQLException, ArrayIndexOutOfBoundsException { - if (request.getPath().split("/")[1].equals("deck")) { - return switch (request.getMethod()) { - case GET -> new Response(HttpStatus.NOT_IMPLEMENTED); - case PUT -> new Response(HttpStatus.NOT_IMPLEMENTED); // String[] array = new ObjectMapper().readValue(request.getBody(), String[].class) - default -> new Response(HttpStatus.NOT_FOUND); - }; - } + UUID authToken = SessionHandler.tokenFromHttpHeader(request.getHttpHeader("Authorization")); + if (SessionHandler.getInstance().verifyUUID(authToken) != TokenValidity.VALID) + return new Response(HttpStatus.UNAUTHORIZED); + UUID userUuid = SessionHandler.getInstance().userUuidFromToken(authToken); - return new Response(HttpStatus.NOT_FOUND); - } catch (ArrayIndexOutOfBoundsException e) { - return new Response(HttpStatus.BAD_REQUEST); + if (request.getPath().split("/")[1].equals("cards") && request.getMethod() == HttpMethod.GET) { + val result = UserCards.get(userUuid, false); + if (result.length == 0) return new Response(HttpStatus.NO_CONTENT); + return new Response(HttpStatus.OK, new ObjectMapper().writeValueAsString(result)); } + + if (request.getPath().split("/")[1].equals("deck")) { + return switch (request.getMethod()) { + case GET -> { + val result = UserCards.get(userUuid, true); + if (result.length == 0) yield new Response(HttpStatus.NO_CONTENT); + yield new Response(HttpStatus.OK, new ObjectMapper().writeValueAsString(result)); + } + case PUT -> { + UUID[] cards = new ObjectMapper().readValue(request.getBody(), UUID[].class); + if (cards.length != 4) + yield new Response(HttpStatus.BAD_REQUEST); + yield new Response(UserCards.setDeck(cards, userUuid) ? HttpStatus.OK : HttpStatus.FORBIDDEN); + } + + default -> new Response(HttpStatus.NOT_FOUND); + }; + } + + return new Response(HttpStatus.NOT_FOUND); } } diff --git a/src/at/nanopenguin/mtcg/application/service/PackagesService.java b/src/at/nanopenguin/mtcg/application/service/PackagesService.java index ba56586..17b04fe 100644 --- a/src/at/nanopenguin/mtcg/application/service/PackagesService.java +++ b/src/at/nanopenguin/mtcg/application/service/PackagesService.java @@ -30,8 +30,7 @@ public class PackagesService implements Service { case MISSING, INVALID -> new Response(HttpStatus.UNAUTHORIZED); case FORBIDDEN -> new Response(HttpStatus.FORBIDDEN); case VALID -> new Response( - Package.create(new ObjectMapper().readValue(request.getBody(), new TypeReference>() { - })) ? + Package.create(new ObjectMapper().readValue(request.getBody(), new TypeReference>() {})) ? HttpStatus.CREATED : HttpStatus.CONFLICT); }; diff --git a/src/at/nanopenguin/mtcg/db/DbQuery.java b/src/at/nanopenguin/mtcg/db/DbQuery.java index c017f7d..f98bb9b 100644 --- a/src/at/nanopenguin/mtcg/db/DbQuery.java +++ b/src/at/nanopenguin/mtcg/db/DbQuery.java @@ -7,6 +7,7 @@ import lombok.val; import java.sql.*; import java.util.*; +import java.util.concurrent.locks.Condition; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -80,11 +81,29 @@ public final class DbQuery { return DriverManager.getConnection(connectionString); } - private String buildParameterizedString(@NonNull SortedMap parameters) { + private String buildParameterizedString(@NonNull SortedMap parameters, String separator) { if (parameters.isEmpty()) { return ""; } - return String.join(" = ?, ", parameters.keySet()) + " = ?"; + StringBuilder stringBuilder = new StringBuilder(); + + for (val parameter : parameters.entrySet()) { + if (!stringBuilder.isEmpty()) stringBuilder.append(separator); + if (parameter.getValue() instanceof List) { + stringBuilder + .append(parameter.getKey()) + .append(" IN (") + .append(String.join("", Collections.nCopies(((List) parameter.getValue()).size() - 1, "?, "))) + .append("?)"); + continue; + } + + stringBuilder + .append(parameter.getKey()) + .append(" = ?"); + } + + return stringBuilder.toString(); } private static int executeUpdate(String sql, List parameterValues) throws SQLException { @@ -98,6 +117,11 @@ public final class DbQuery { new PreparedStatementExecutor(connection.prepareStatement(sql)); int i = 1; for (val value : parameterValues) { + if (value instanceof List) { + for (Object o : (List) value) + statementExecutor.setObject(i++, o); + continue; + } statementExecutor.setObject(i++, value); } @@ -116,6 +140,11 @@ public final class DbQuery { new PreparedStatementExecutor(connection.prepareStatement(sql)); int i = 1; for (val value : parameterValues) { + if (value instanceof List) { + for (Object o : (List) value) + statementExecutor.setObject(i++, o); + continue; + } statementExecutor.setObject(i++, value); } @@ -134,6 +163,11 @@ public final class DbQuery { new PreparedStatementExecutor(connection.prepareStatement(sql)); int i = 1; for (val value : parameterValues) { + if (value instanceof List) { + for (Object o : (List) value) + statementExecutor.setObject(i++, o); + continue; + } statementExecutor.setObject(i++, value); } @@ -157,7 +191,7 @@ public final class DbQuery { if (this.parameters.isEmpty()) throw new SQLException("No parameters provided for INSERT statement."); String columns = this.parameters.keySet().stream() - .filter(columnName -> columnName.matches("[a-zA-Z0-9_]+")) + .filter(columnName -> columnName.matches("^[a-zA-Z0-9_]+( AS [a-zA-Z0-9_]+)?$")) .collect(Collectors.joining(", ")); return String.format("INSERT INTO %s (%s) VALUES (%s) ON CONFLICT DO NOTHING%s;", @@ -188,7 +222,7 @@ public final class DbQuery { String sql = String.format("SELECT %s FROM %s%s;", columnJoiner, table.table, - this.conditions.isEmpty() ? "" : " WHERE (" + this.buildParameterizedString(this.conditions) + ")"); + this.conditions.isEmpty() ? "" : " WHERE " + this.buildParameterizedString(this.conditions, " AND ")); return executeQuery(sql, new ArrayList<>(this.conditions.values())); } @@ -197,10 +231,10 @@ public final class DbQuery { if (this.parameters.isEmpty()) throw new SQLException(); if (this.conditions.isEmpty()) throw new SQLException(); - return String.format("UPDATE %s SET %s WHERE (%s)%s;", + return String.format("UPDATE %s SET %s WHERE %s%s;", table.table, - this.buildParameterizedString(this.parameters), - this.buildParameterizedString(this.conditions), + this.buildParameterizedString(this.parameters, ", "), + this.buildParameterizedString(this.conditions, " AND "), hasReturn ? " RETURNING " + this.returnColumn : ""); } @@ -226,9 +260,9 @@ public final class DbQuery { private int delete() throws SQLException { if (this.conditions.isEmpty()) throw new SQLException(); - String sql = String.format("DELETE FROM %s WHERE (%s);", + String sql = String.format("DELETE FROM %s WHERE %s;", table.table, - this.buildParameterizedString(this.conditions)); + this.buildParameterizedString(this.conditions, " AND ")); return executeUpdate(sql, new ArrayList<>(this.conditions.values())); } diff --git a/src/at/nanopenguin/mtcg/http/Response.java b/src/at/nanopenguin/mtcg/http/Response.java index 3cf8c62..4ea457b 100644 --- a/src/at/nanopenguin/mtcg/http/Response.java +++ b/src/at/nanopenguin/mtcg/http/Response.java @@ -9,6 +9,10 @@ public class Response { private final String contentType; private final String content; + public Response(HttpStatus httpStatus, String content) { + this(httpStatus, "application/json", content); + } + public Response(HttpStatus httpStatus, String contentType, String content) { this.httpStatus = httpStatus;