Compare commits

..

16 Commits

7 changed files with 111 additions and 75 deletions

View File

@@ -7,7 +7,8 @@ FROM mvn as builder
WORKDIR /build WORKDIR /build
ADD . . ADD . .
RUN mvn package ARG KC_HOSTNAME
RUN mvn package -DKC_HOSTNAME=${KC_HOSTNAME}
FROM eclipse-temurin:21-jdk-alpine FROM eclipse-temurin:21-jdk-alpine

View File

@@ -13,6 +13,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
public class JodelPost { public class JodelPost {
// id of the post // id of the post
public Long id; public Long id;
// Parent id
public Optional<Long> parent = Optional.empty();
// id of the author in db // id of the author in db
private final Long authorID; private final Long authorID;
// anonymized authorID // anonymized authorID
@@ -25,31 +27,24 @@ public class JodelPost {
public Timestamp date; public Timestamp date;
// location if the post // location if the post
public Location location; public Location location;
// list of all comments for the post
public Vector<JodelPost> comments = new Vector<>();
// the own reaction (null = none, true = positive, false = negative) // the own reaction (null = none, true = positive, false = negative)
public Optional<Boolean> reaction; public Optional<Boolean> reaction;
// all other reactions // all other reactions
public Reactions reactions; public Reactions reactions;
// Parent id // list of all comments for the post
public Optional<Long> parent = Optional.empty(); public Vector<JodelPost> comments = new Vector<>();
// anonymize function to recursively anonymize the posts // anonymize function to recursively anonymize the posts
public void anonymize(Optional<Vector<Long>> idCache) { public void anonymize(Vector<Long> idCache) {
// check if this is the first post in this process
if (idCache.isEmpty()) {
// create a new Vector as cache
idCache = Optional.of(new Vector<>());
}
// get the anonymized id as index in cached authorIDs // get the anonymized id as index in cached authorIDs
int i = idCache.get().indexOf(this.authorID); int i = idCache.indexOf(this.authorID);
// if the index is -1 the authorID has not been cached jet // if the index is -1 the authorID has not been cached jet
if (i == -1) { if (i == -1) {
// set the current anonymousID as length of the cache (== next index) // set the current anonymousID as length of the cache (== next index)
this.anonymousID = (long) idCache.get().size(); this.anonymousID = (long) idCache.size();
// push the current authorID to to cache // push the current authorID to to cache
idCache.get().add(this.authorID); idCache.add(this.authorID);
} }
// the authorID has been anonymized once before, so we can get it from cache // the authorID has been anonymized once before, so we can get it from cache
else this.anonymousID = (long) i; else this.anonymousID = (long) i;
@@ -88,6 +83,7 @@ public class JodelPost {
this.content = rs.getString("content"); this.content = rs.getString("content");
this.date = rs.getTimestamp("postdate"); this.date = rs.getTimestamp("postdate");
this.location = new Location(rs.getFloat("longitude"), rs.getFloat("latitude")); this.location = new Location(rs.getFloat("longitude"), rs.getFloat("latitude"));
this.reaction = rs.getString("reaction") == null ? Optional.empty() : Optional.of(rs.getBoolean("reaction"));
this.reactions = new Reactions(rs.getLong("positive"), rs.getLong("negative")); this.reactions = new Reactions(rs.getLong("positive"), rs.getLong("negative"));
this.parent = Optional.of(rs.getLong("parent")); this.parent = Optional.of(rs.getLong("parent"));
} }
@@ -102,10 +98,8 @@ public class JodelPost {
@JsonProperty("content") String content, @JsonProperty("content") String content,
@JsonProperty("date") Timestamp date, @JsonProperty("date") Timestamp date,
@JsonProperty("location") Location location, @JsonProperty("location") Location location,
@JsonProperty("parent") Optional<Long> parent, @JsonProperty("parent") Optional<Long> parent) {
// TODO: getter from Keycloak this.authorID = User.getID(); // TODO: getter from Keycloak
@JsonProperty("authorID") long author) {
this.authorID = author; // TODO: getter from Keycloak
this.title = title; this.title = title;
this.content = content; this.content = content;
this.date = date; this.date = date;

View File

@@ -4,8 +4,8 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
public class Location { public class Location {
public float longitude;
public float latitude; public float latitude;
public float longitude;
@JsonCreator @JsonCreator
public Location(@JsonProperty("longitude") float longitude, @JsonProperty("latitude") float latitude ) { public Location(@JsonProperty("longitude") float longitude, @JsonProperty("latitude") float latitude ) {

View File

@@ -1,8 +1,6 @@
package de.anxietyprime.swajodel; package de.anxietyprime.swajodel;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;
@@ -15,11 +13,14 @@ import java.util.Vector;
@CrossOrigin(origins = "*", allowedHeaders = "*", maxAge = 3600) @CrossOrigin(origins = "*", allowedHeaders = "*", maxAge = 3600)
public class Routes { public class Routes {
@GetMapping("/posts/{longitude}/{latitude}") @GetMapping("/posts/{latitude}/{longitude}")
public Vector<JodelPost> getPostsByLocation(@PathVariable("longitude") float longitude, @PathVariable("latitude") float latitude) { public Vector<JodelPost> getPostsByLocation(@PathVariable("longitude") float longitude, @PathVariable("latitude") float latitude) {
// list of all posts (not comments) in range // list of all posts (not comments) in range
Vector<JodelPost> posts = new Vector<>(); Vector<JodelPost> posts = new Vector<>();
// get UserID
Long userID = User.getID();
// DB connection and statement // DB connection and statement
Connection c; Connection c;
PreparedStatement stmt; PreparedStatement stmt;
@@ -46,8 +47,8 @@ public class Routes {
posts.title, posts.title,
posts.content, posts.content,
posts.postdate, posts.postdate,
posts.postlocation[0] AS longitude, posts.postlocation[0] AS latitude,
posts.postlocation[1] AS latitude, posts.postlocation[1] AS longitude,
(SELECT count(*) FROM reactions WHERE reactions.post = posts.id AND reactions.positive = TRUE) AS positive, (SELECT count(*) FROM reactions WHERE reactions.post = posts.id AND reactions.positive = TRUE) AS positive,
(SELECT count(*) FROM reactions WHERE reactions.post = posts.id AND reactions.positive = FALSE) AS negative (SELECT count(*) FROM reactions WHERE reactions.post = posts.id AND reactions.positive = FALSE) AS negative
FROM FROM
@@ -75,10 +76,11 @@ public class Routes {
(SELECT * FROM comments inner join posts ON comments.child = posts.id) com (SELECT * FROM comments inner join posts ON comments.child = posts.id) com
inner join targets ON targets.id = com.parent inner join targets ON targets.id = com.parent
) )
SELECT * FROM targets;"""); SELECT * FROM targets LEFT JOIN (SELECT post, positive as reaction FROM reactions WHERE userid = (?)) ON post = id;""");
stmt.setObject(1, longitude); stmt.setObject(1, latitude);
stmt.setObject(2, latitude); stmt.setObject(2, longitude);
stmt.setObject(3, userID);
// query recursively for posts inside a 10km radius // query recursively for posts inside a 10km radius
ResultSet rs = stmt.executeQuery(); ResultSet rs = stmt.executeQuery();
@@ -117,7 +119,12 @@ public class Routes {
} }
// calculate anonymous IDs for the posts // calculate anonymous IDs for the posts
posts.forEach(post -> post.anonymize(Optional.empty())); Vector<Long> anonymousIDs = new Vector<Long>();
anonymousIDs.add(userID);
posts.forEach(post -> {
post.anonymize(anonymousIDs);
});
// return the posts // return the posts
return posts; return posts;
} }
@@ -127,6 +134,9 @@ public class Routes {
// list of all posts (not comments) in range // list of all posts (not comments) in range
Optional<JodelPost> root_post = Optional.empty(); Optional<JodelPost> root_post = Optional.empty();
// get UserID
Long userID = User.getID();
// DB connection and statement // DB connection and statement
Connection c; Connection c;
PreparedStatement stmt; PreparedStatement stmt;
@@ -153,8 +163,8 @@ public class Routes {
posts.title, posts.title,
posts.content, posts.content,
posts.postdate, posts.postdate,
posts.postlocation[0] AS longitude, posts.postlocation[0] AS latitude,
posts.postlocation[1] AS latitude, posts.postlocation[1] AS longitude,
(SELECT count(*) FROM reactions WHERE reactions.post = posts.id AND reactions.positive = TRUE) AS positive, (SELECT count(*) FROM reactions WHERE reactions.post = posts.id AND reactions.positive = TRUE) AS positive,
(SELECT count(*) FROM reactions WHERE reactions.post = posts.id AND reactions.positive = FALSE) AS negative (SELECT count(*) FROM reactions WHERE reactions.post = posts.id AND reactions.positive = FALSE) AS negative
FROM FROM
@@ -179,9 +189,10 @@ public class Routes {
(SELECT * FROM comments inner join posts ON comments.child = posts.id) com (SELECT * FROM comments inner join posts ON comments.child = posts.id) com
inner join targets ON targets.id = com.parent inner join targets ON targets.id = com.parent
) )
SELECT * FROM targets;"""); SELECT * FROM targets LEFT JOIN (SELECT post, positive as reaction FROM reactions WHERE userid = (?)) ON post = id;""");
stmt.setObject(1, id); stmt.setObject(1, id);
stmt.setObject(2, userID);
// query recursively for posts inside a 10km radius // query recursively for posts inside a 10km radius
ResultSet rs = stmt.executeQuery(); ResultSet rs = stmt.executeQuery();
@@ -220,7 +231,9 @@ public class Routes {
} }
// calculate anonymous IDs for the post // calculate anonymous IDs for the post
root_post.ifPresent(root -> root.anonymize(Optional.empty())); Vector<Long> anonymousIDs = new Vector<Long>();
anonymousIDs.add(userID);
root_post.ifPresent(root -> root.anonymize(anonymousIDs));
// return the posts // return the posts
if (root_post.isEmpty()) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No post found"); if (root_post.isEmpty()) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No post found");
@@ -253,8 +266,8 @@ public class Routes {
stmt.setObject(2, post.title); stmt.setObject(2, post.title);
stmt.setObject(3, post.content); stmt.setObject(3, post.content);
stmt.setObject(4, post.date); stmt.setObject(4, post.date);
stmt.setObject(5, post.location.longitude); stmt.setObject(5, post.location.latitude);
stmt.setObject(6, post.location.latitude); stmt.setObject(6, post.location.longitude);
// insert post and get its id // insert post and get its id
ResultSet rs = stmt.executeQuery(); ResultSet rs = stmt.executeQuery();
@@ -296,11 +309,73 @@ public class Routes {
post.reaction = Optional.empty(); post.reaction = Optional.empty();
// anonymize code // anonymize code
post.anonymize(Optional.empty()); Vector<Long> anonymousIDs = new Vector<Long>();
anonymousIDs.add(0L);
post.anonymize(anonymousIDs);
return post; return post;
} }
@PatchMapping("/post/{id}")
public void reactToPost(@PathVariable long id, @RequestBody JodelPost post) {
System.out.println(post.reaction);
long userID = User.getID();
// DB connection and statement
Connection c;
PreparedStatement stmt;
// try to get data from db
try {
// check for the driver
Class.forName("org.postgresql.Driver");
// get the connection with credentials from env variables
c = DriverManager
.getConnection("jdbc:postgresql://"+
System.getenv("POSTGRES_IP")+"/"+System.getenv("POSTGRES_DB"),
System.getenv("POSTGRES_USER"), System.getenv("POSTGRES_PASSWORD"));
// disable auto commits
c.setAutoCommit(false);
// create a new statement
stmt = c.prepareStatement("UPDATE Reactions SET positive = (?) WHERE post = (?) AND userid = (?) RETURNING id, positive");
stmt.setObject(1, post.reaction.orElse(null));
stmt.setObject(2, id);
stmt.setObject(3, userID);
// insert post and get its id
ResultSet rs = stmt.executeQuery();
// check if there is a parent
if (!rs.next()) {
// create a new statement
stmt = c.prepareStatement("INSERT INTO Reactions(userid, post, positive) VALUES ((?), (?), (?))");
// fill statement
stmt.setObject(1, userID);
stmt.setObject(2, id);
stmt.setObject(3, post.reaction.orElse(null));
// execute statement
stmt.execute();
}
// commit the changes
c.commit();
// close all connections to db
stmt.close();
c.close();
}
// else log the error
catch ( Exception e ) {
System.err.println( e.getClass().getName()+": "+ e.getMessage() );
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "Database is offline");
}
}
@DeleteMapping("/post/{id}") @DeleteMapping("/post/{id}")
public void deletePost(@PathVariable long id) { public void deletePost(@PathVariable long id) {
// DB connection and statement // DB connection and statement
@@ -320,9 +395,10 @@ public class Routes {
c.setAutoCommit(false); c.setAutoCommit(false);
// create a new statement // create a new statement
stmt = c.prepareStatement("UPDATE Posts SET deleted = now() WHERE id = ? AND deleted IS NULL"); stmt = c.prepareStatement("UPDATE Posts SET deleted = now() WHERE id = ? AND author = (?) AND deleted IS NULL");
stmt.setObject(1, id); stmt.setObject(1, id);
stmt.setObject(2, User.getID());
// insert delete time // insert delete time
stmt.execute(); stmt.execute();

View File

@@ -14,9 +14,9 @@ public class User {
return SecurityContextHolder.getContext().getAuthentication().getName(); return SecurityContextHolder.getContext().getAuthentication().getName();
} }
static public Long getUserID () { static public Long getID() {
String uuid = SecurityContextHolder.getContext().getAuthentication().getName(); String uuid = SecurityContextHolder.getContext().getAuthentication().getName();
Long id = -1L; long id;
// DB connection and statement // DB connection and statement
Connection c; Connection c;

View File

@@ -1,5 +1,5 @@
spring.application.name=SWA-Jodel spring.application.name=SWA-Jodel
# Security Configuration # Security Configuration
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://keycloak.anxietyprime.de/realms/Jodel spring.security.oauth2.resourceserver.jwt.issuer-uri=https://${KC_HOSTNAME}/realms/Jodel
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=${spring.security.oauth2.resourceserver.jwt.issuer-uri}/protocol/openid-connect/certs spring.security.oauth2.resourceserver.jwt.jwk-set-uri=${spring.security.oauth2.resourceserver.jwt.issuer-uri}/protocol/openid-connect/certs

View File

@@ -3,44 +3,9 @@ package de.anxietyprime.swajodel;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import java.util.Optional; import java.util.Vector;
@SpringBootTest @SpringBootTest
class SwaJodelApplicationTests { class SwaJodelApplicationTests {
@Test
void anonymousTest() {
JodelPost post = new JodelPost(1000);
post.comments.add(new JodelPost(1001));
post.comments.add(new JodelPost(1002));
post.comments.get(0).comments.add(new JodelPost(1000));
post.comments.get(0).comments.add(new JodelPost(1001));
post.comments.get(0).comments.add(new JodelPost(1000));
post.comments.add(new JodelPost(1003));
post.comments.get(2).comments.add(new JodelPost(1001));
post.comments.get(2).comments.add(new JodelPost(1002));
post.comments.get(2).comments.add(new JodelPost(1003));
post.comments.get(2).comments.add(new JodelPost(1000));
post.comments.add(new JodelPost(1000));
post.comments.add(new JodelPost(1001));
post.comments.add(new JodelPost(1001));
post.anonymize(Optional.empty());
assert (post.anonymousID == 0);
assert (post.comments.get(0).anonymousID == 1);
assert (post.comments.get(1).anonymousID == 2);
assert (post.comments.get(2).anonymousID == 3);
assert (post.comments.get(3).anonymousID == 0);
assert (post.comments.get(4).anonymousID == 1);
assert (post.comments.get(5).anonymousID == 1);
assert (post.comments.get(0).comments.get(0).anonymousID == 0);
assert (post.comments.get(0).comments.get(1).anonymousID == 1);
assert (post.comments.get(0).comments.get(2).anonymousID == 0);
assert (post.comments.get(2).comments.get(0).anonymousID == 1);
assert (post.comments.get(2).comments.get(1).anonymousID == 2);
assert (post.comments.get(2).comments.get(2).anonymousID == 3);
assert (post.comments.get(2).comments.get(3).anonymousID == 0);
}
} }