Compare commits
41 Commits
5d494d0ad4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 66fa985a33 | |||
| df0db226a9 | |||
| 57418213f5 | |||
| 9fd843da78 | |||
| 5e74228959 | |||
| 370912a091 | |||
| 5a74e1cb5b | |||
| c293f20559 | |||
| 8943641baf | |||
| 2ca997fca9 | |||
| aa669a98a9 | |||
| 5032ce5d63 | |||
| 07b90d1256 | |||
| 22bde7ef79 | |||
| cafde860a7 | |||
| ded34c5f43 | |||
| 018285c109 | |||
| 350e1a8a43 | |||
| 24fd96c8b2 | |||
| 696adec54d | |||
| 3bca9ced56 | |||
| 02e6b4ac23 | |||
| e2ba872293 | |||
| a548cd1cf4 | |||
| def5442793 | |||
| 60ad5c660d | |||
| 7ca41840cf | |||
| c04b12769a | |||
| c151fb8320 | |||
| 02d7facd4c | |||
| acb779de7e | |||
| 614f393463 | |||
| 5e6e5492d9 | |||
| ee65a2cf60 | |||
| 49254f3d87 | |||
| d843bbd787 | |||
| 74d421514d | |||
| 9070d1452b | |||
| c6deee3da7 | |||
| c7493a34b6 | |||
| 4c1699c314 |
@@ -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
|
||||||
|
|||||||
19
pom.xml
19
pom.xml
@@ -37,7 +37,24 @@
|
|||||||
<artifactId>postgresql</artifactId>
|
<artifactId>postgresql</artifactId>
|
||||||
<version>42.7.3</version>
|
<version>42.7.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
package de.anxietyprime.swajodel;
|
package de.anxietyprime.swajodel;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import java.sql.*;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
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 Long authorID;
|
private final Long authorID;
|
||||||
// anonymized authorID
|
// anonymized authorID
|
||||||
public Long anonymousID;
|
public Long anonymousID;
|
||||||
// title of the post
|
// title of the post
|
||||||
@@ -16,32 +24,27 @@ public class JodelPost {
|
|||||||
// content of the post
|
// content of the post
|
||||||
public String content;
|
public String content;
|
||||||
// date of the post
|
// date of the post
|
||||||
public Date 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 Reaction reactions;
|
public Reactions reactions;
|
||||||
|
// list of all comments for the post
|
||||||
|
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;
|
||||||
@@ -53,9 +56,10 @@ public class JodelPost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add a comment if it is really a comment
|
// add a comment if it is really a comment
|
||||||
public boolean addComment(JodelPost post, long parent) {
|
public boolean addComment(JodelPost post) {
|
||||||
|
if (post.parent.isEmpty()) return true;
|
||||||
// check if the post is a direct comment
|
// check if the post is a direct comment
|
||||||
if (parent == this.id) {
|
if (Objects.equals(post.parent.get(), this.id)) {
|
||||||
// add the comment
|
// add the comment
|
||||||
this.comments.add(post);
|
this.comments.add(post);
|
||||||
// return success
|
// return success
|
||||||
@@ -64,14 +68,52 @@ public class JodelPost {
|
|||||||
// recursively repeat this for all comments
|
// recursively repeat this for all comments
|
||||||
for (JodelPost comment : this.comments) {
|
for (JodelPost comment : this.comments) {
|
||||||
// return success if the post is a comment on a child
|
// return success if the post is a comment on a child
|
||||||
if (comment.addComment(post, parent)) return true;
|
if (comment.addComment(post)) return true;
|
||||||
}
|
}
|
||||||
// return no success
|
// return no success
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// constructor with private authorID
|
// constructor from
|
||||||
public JodelPost(long authorID) {
|
public JodelPost(ResultSet rs) throws SQLException {
|
||||||
this.authorID = authorID;
|
// add all other information to the post
|
||||||
|
this.authorID = rs.getLong("author");
|
||||||
|
this.id = rs.getLong("id");
|
||||||
|
this.title = rs.getString("title");
|
||||||
|
this.content = rs.getString("content");
|
||||||
|
this.date = rs.getTimestamp("postdate");
|
||||||
|
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.parent = Optional.of(rs.getLong("parent"));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// constructor from authorID for tests
|
||||||
|
public JodelPost(int authorID) {
|
||||||
|
this.authorID = (long) authorID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public JodelPost(@JsonProperty("title") String title,
|
||||||
|
@JsonProperty("content") String content,
|
||||||
|
@JsonProperty("date") Timestamp date,
|
||||||
|
@JsonProperty("location") Location location,
|
||||||
|
@JsonProperty("parent") Optional<Long> parent) {
|
||||||
|
this.authorID = User.getID(); // TODO: getter from Keycloak
|
||||||
|
this.title = title;
|
||||||
|
this.content = content;
|
||||||
|
this.date = date;
|
||||||
|
this.location = location;
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public long getAuthorID() {
|
||||||
|
return authorID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public boolean isComment() {
|
||||||
|
return parent.filter(aLong -> !Objects.equals(id, aLong)).isPresent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
package de.anxietyprime.swajodel;
|
package de.anxietyprime.swajodel;
|
||||||
|
|
||||||
public class Location {
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
public float longitude;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
public float latitude;
|
|
||||||
|
|
||||||
public Location(float longitude, float latitude) {
|
public class Location {
|
||||||
|
public float latitude;
|
||||||
|
public float longitude;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public Location(@JsonProperty("longitude") float longitude, @JsonProperty("latitude") float latitude ) {
|
||||||
this.longitude = longitude;
|
this.longitude = longitude;
|
||||||
this.latitude = latitude;
|
this.latitude = latitude;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
package de.anxietyprime.swajodel;
|
|
||||||
|
|
||||||
public class Reaction {
|
|
||||||
private long positive;
|
|
||||||
private long negative;
|
|
||||||
}
|
|
||||||
11
src/main/java/de/anxietyprime/swajodel/Reactions.java
Normal file
11
src/main/java/de/anxietyprime/swajodel/Reactions.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package de.anxietyprime.swajodel;
|
||||||
|
|
||||||
|
public class Reactions {
|
||||||
|
public long positive;
|
||||||
|
public long negative;
|
||||||
|
|
||||||
|
Reactions(long positive, long negative) {
|
||||||
|
this.positive = positive;
|
||||||
|
this.negative = negative;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package de.anxietyprime.swajodel;
|
package de.anxietyprime.swajodel;
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
import java.sql.*;
|
import java.sql.*;
|
||||||
|
|
||||||
@@ -10,16 +10,20 @@ import java.util.Optional;
|
|||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@CrossOrigin(origins = "*", allowedHeaders = "*", maxAge = 3600)
|
||||||
public class Routes {
|
public class Routes {
|
||||||
|
|
||||||
@GetMapping("/posts/{longitude}/{latitude}")
|
@GetMapping("/posts/{latitude}/{longitude}")
|
||||||
public Vector<JodelPost> getPosts(@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 = null;
|
Connection c;
|
||||||
PreparedStatement stmt = null;
|
PreparedStatement stmt;
|
||||||
|
|
||||||
// try to get data from db
|
// try to get data from db
|
||||||
try {
|
try {
|
||||||
@@ -34,40 +38,49 @@ public class Routes {
|
|||||||
c.setAutoCommit(false);
|
c.setAutoCommit(false);
|
||||||
|
|
||||||
// create a new statement
|
// create a new statement
|
||||||
stmt = c.prepareStatement("WITH RECURSIVE targets AS (\n" +
|
stmt = c.prepareStatement("""
|
||||||
" SELECT\n" +
|
WITH RECURSIVE targets AS (
|
||||||
" id,\n" +
|
SELECT
|
||||||
" id AS parent,\n" +
|
posts.id,
|
||||||
" author,\n" +
|
posts.id AS parent,
|
||||||
" title,\n" +
|
posts.author,
|
||||||
" content,\n" +
|
posts.title,
|
||||||
" postdate,\n" +
|
posts.content,
|
||||||
" postlocation[0] AS longitude,\n" +
|
posts.postdate,
|
||||||
" postlocation[1] AS latitude\n" +
|
posts.postlocation[0] AS latitude,
|
||||||
" FROM\n" +
|
posts.postlocation[1] AS longitude,
|
||||||
" posts\n" +
|
(SELECT count(*) FROM reactions WHERE reactions.post = posts.id AND reactions.positive = TRUE) AS positive,
|
||||||
" WHERE\n" +
|
(SELECT count(*) FROM reactions WHERE reactions.post = posts.id AND reactions.positive = FALSE) AS negative
|
||||||
" deleted IS NULL\n" +
|
FROM
|
||||||
" AND sqrt(power(postlocation[0] - (?), 2) + power(postlocation[1] - (?), 2)) <= 10\n" +
|
posts
|
||||||
" AND id NOT IN (SELECT child FROM comments)\n" +
|
WHERE
|
||||||
" UNION\n" +
|
deleted IS NULL
|
||||||
" SELECT\n" +
|
AND earth_distance(
|
||||||
" com.child,\n" +
|
ll_to_earth(posts.postlocation[0], posts.postlocation[1]),
|
||||||
" com.parent,\n" +
|
ll_to_earth((?), (?))
|
||||||
" com.author,\n" +
|
) <= 10000
|
||||||
" com.title,\n" +
|
AND posts.id NOT IN (SELECT child FROM comments)
|
||||||
" com.content,\n" +
|
UNION
|
||||||
" com.postdate,\n" +
|
SELECT
|
||||||
" com.postlocation[0],\n" +
|
com.child,
|
||||||
" com.postlocation[1]\n" +
|
com.parent,
|
||||||
" FROM\n" +
|
com.author,
|
||||||
" (SELECT * FROM comments inner join posts ON comments.child = posts.id) com\n" +
|
com.title,
|
||||||
" inner join targets ON targets.id = com.parent\n" +
|
com.content,
|
||||||
")\n" +
|
com.postdate,
|
||||||
"SELECT * FROM targets;");
|
com.postlocation[0],
|
||||||
|
com.postlocation[1],
|
||||||
|
(SELECT count(*) FROM reactions WHERE reactions.post = com.child AND reactions.positive = TRUE) AS positive,
|
||||||
|
(SELECT count(*) FROM reactions WHERE reactions.post = com.child AND reactions.positive = FALSE) AS negative
|
||||||
|
FROM
|
||||||
|
(SELECT * FROM comments inner join posts ON comments.child = posts.id) com
|
||||||
|
inner join targets ON targets.id = com.parent
|
||||||
|
)
|
||||||
|
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();
|
||||||
@@ -75,20 +88,11 @@ public class Routes {
|
|||||||
// for all the posts found
|
// for all the posts found
|
||||||
while ( rs.next() ) {
|
while ( rs.next() ) {
|
||||||
// create a post from the author id
|
// create a post from the author id
|
||||||
JodelPost post = new JodelPost(rs.getLong("author"));
|
JodelPost post = new JodelPost(rs);
|
||||||
// add all other information to the post
|
|
||||||
post.id = rs.getLong("id");
|
|
||||||
post.title = rs.getString("title");
|
|
||||||
post.content = rs.getString("content");
|
|
||||||
post.date = rs.getDate("postdate");
|
|
||||||
post.location = new Location(rs.getLong("longitude"), rs.getLong("latitude"));
|
|
||||||
|
|
||||||
// get the posts parent
|
|
||||||
long parent = rs.getLong("parent");
|
|
||||||
|
|
||||||
// check if the parent is the own id
|
// check if the parent is the own id
|
||||||
// if it is, it is a post
|
// if it is, it is a post
|
||||||
if (parent == post.id) {
|
if (!post.isComment()) {
|
||||||
// add the post to the posts
|
// add the post to the posts
|
||||||
posts.add(post);
|
posts.add(post);
|
||||||
}
|
}
|
||||||
@@ -97,7 +101,7 @@ public class Routes {
|
|||||||
// iterate over all posts
|
// iterate over all posts
|
||||||
for (JodelPost p : posts) {
|
for (JodelPost p : posts) {
|
||||||
// try to add the post to a parent
|
// try to add the post to a parent
|
||||||
p.addComment(post, parent);
|
p.addComment(post);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,13 +115,304 @@ public class Routes {
|
|||||||
// else log the error
|
// else log the error
|
||||||
catch ( Exception e ) {
|
catch ( Exception e ) {
|
||||||
System.err.println( e.getClass().getName()+": "+ e.getMessage() );
|
System.err.println( e.getClass().getName()+": "+ e.getMessage() );
|
||||||
|
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "Database is offline");
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate anonymous IDs for the posts
|
// calculate anonymous IDs for the posts
|
||||||
|
Vector<Long> anonymousIDs = new Vector<Long>();
|
||||||
|
anonymousIDs.add(userID);
|
||||||
posts.forEach(post -> {
|
posts.forEach(post -> {
|
||||||
post.anonymize(Optional.empty());
|
post.anonymize(anonymousIDs);
|
||||||
});
|
});
|
||||||
|
|
||||||
// return the posts
|
// return the posts
|
||||||
return posts;
|
return posts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/post/{id}")
|
||||||
|
public JodelPost getPostByID(@PathVariable("id") long id) {
|
||||||
|
// list of all posts (not comments) in range
|
||||||
|
Optional<JodelPost> root_post = Optional.empty();
|
||||||
|
|
||||||
|
// get UserID
|
||||||
|
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("""
|
||||||
|
WITH RECURSIVE targets AS (
|
||||||
|
SELECT
|
||||||
|
posts.id,
|
||||||
|
posts.id AS parent,
|
||||||
|
posts.author,
|
||||||
|
posts.title,
|
||||||
|
posts.content,
|
||||||
|
posts.postdate,
|
||||||
|
posts.postlocation[0] 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 = FALSE) AS negative
|
||||||
|
FROM
|
||||||
|
posts
|
||||||
|
WHERE
|
||||||
|
deleted IS NULL
|
||||||
|
AND posts.id = (?)
|
||||||
|
AND posts.id NOT IN (SELECT child FROM comments)
|
||||||
|
UNION
|
||||||
|
SELECT
|
||||||
|
com.child,
|
||||||
|
com.parent,
|
||||||
|
com.author,
|
||||||
|
com.title,
|
||||||
|
com.content,
|
||||||
|
com.postdate,
|
||||||
|
com.postlocation[0],
|
||||||
|
com.postlocation[1],
|
||||||
|
(SELECT count(*) FROM reactions WHERE reactions.post = com.child AND reactions.positive = TRUE) AS positive,
|
||||||
|
(SELECT count(*) FROM reactions WHERE reactions.post = com.child AND reactions.positive = FALSE) AS negative
|
||||||
|
FROM
|
||||||
|
(SELECT * FROM comments inner join posts ON comments.child = posts.id) com
|
||||||
|
inner join targets ON targets.id = com.parent
|
||||||
|
)
|
||||||
|
SELECT * FROM targets LEFT JOIN (SELECT post, positive as reaction FROM reactions WHERE userid = (?)) ON post = id;""");
|
||||||
|
|
||||||
|
stmt.setObject(1, id);
|
||||||
|
stmt.setObject(2, userID);
|
||||||
|
|
||||||
|
// query recursively for posts inside a 10km radius
|
||||||
|
ResultSet rs = stmt.executeQuery();
|
||||||
|
|
||||||
|
// for all the posts found
|
||||||
|
while ( rs.next() ) {
|
||||||
|
// create a post from the author id
|
||||||
|
JodelPost post = new JodelPost(rs);
|
||||||
|
|
||||||
|
// get the posts parent
|
||||||
|
long parent = rs.getLong("parent");
|
||||||
|
|
||||||
|
// check if the parent is the own id
|
||||||
|
// if it is, it is a post
|
||||||
|
if (parent == post.id) {
|
||||||
|
// add the post to the posts
|
||||||
|
root_post = Optional.of(post);
|
||||||
|
}
|
||||||
|
// else it is a comment
|
||||||
|
else {
|
||||||
|
// try to add the post to parent
|
||||||
|
root_post.ifPresent(root -> root.addComment(post));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// close all connections to db
|
||||||
|
rs.close();
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate anonymous IDs for the post
|
||||||
|
Vector<Long> anonymousIDs = new Vector<Long>();
|
||||||
|
anonymousIDs.add(userID);
|
||||||
|
root_post.ifPresent(root -> root.anonymize(anonymousIDs));
|
||||||
|
|
||||||
|
// return the posts
|
||||||
|
if (root_post.isEmpty()) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No post found");
|
||||||
|
return root_post.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/posts")
|
||||||
|
public JodelPost postPost(@RequestBody JodelPost post) {
|
||||||
|
// 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("INSERT INTO Posts(author, title, content, postdate, postlocation) " +
|
||||||
|
"VALUES (?, ?, ?, ?, Point(?, ?)) RETURNING id");
|
||||||
|
|
||||||
|
stmt.setObject(1, post.getAuthorID());
|
||||||
|
stmt.setObject(2, post.title);
|
||||||
|
stmt.setObject(3, post.content);
|
||||||
|
stmt.setObject(4, post.date);
|
||||||
|
stmt.setObject(5, post.location.latitude);
|
||||||
|
stmt.setObject(6, post.location.longitude);
|
||||||
|
|
||||||
|
// insert post and get its id
|
||||||
|
ResultSet rs = stmt.executeQuery();
|
||||||
|
|
||||||
|
// get the id of the new post
|
||||||
|
rs.next();
|
||||||
|
post.id = rs.getLong("id");
|
||||||
|
|
||||||
|
// check if there is a parent
|
||||||
|
if (post.parent.isPresent()) {
|
||||||
|
// create a new statement
|
||||||
|
stmt = c.prepareStatement("INSERT INTO comments(parent, child) VALUES (?, ?)");
|
||||||
|
|
||||||
|
// fill statement
|
||||||
|
stmt.setObject(1, post.parent.get());
|
||||||
|
stmt.setObject(2, post.id);
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill missing post values
|
||||||
|
if (post.parent.isEmpty()) post.parent = Optional.of(post.id);
|
||||||
|
post.reactions = new Reactions(0, 0);
|
||||||
|
post.reaction = Optional.empty();
|
||||||
|
|
||||||
|
// anonymize code
|
||||||
|
Vector<Long> anonymousIDs = new Vector<Long>();
|
||||||
|
anonymousIDs.add(0L);
|
||||||
|
post.anonymize(anonymousIDs);
|
||||||
|
|
||||||
|
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}")
|
||||||
|
public void deletePost(@PathVariable long id) {
|
||||||
|
// 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 Posts SET deleted = now() WHERE id = ? AND author = (?) AND deleted IS NULL");
|
||||||
|
|
||||||
|
stmt.setObject(1, id);
|
||||||
|
stmt.setObject(2, User.getID());
|
||||||
|
|
||||||
|
// insert delete time
|
||||||
|
stmt.execute();
|
||||||
|
|
||||||
|
// close all connections to db
|
||||||
|
stmt.close();
|
||||||
|
c.commit();
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package de.anxietyprime.swajodel;
|
|||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class SwaJodelApplication {
|
public class SwaJodelApplication {
|
||||||
|
|||||||
77
src/main/java/de/anxietyprime/swajodel/User.java
Normal file
77
src/main/java/de/anxietyprime/swajodel/User.java
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package de.anxietyprime.swajodel;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
|
||||||
|
public class User {
|
||||||
|
static public String getUUID () {
|
||||||
|
return SecurityContextHolder.getContext().getAuthentication().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
static public Long getID() {
|
||||||
|
String uuid = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||||
|
long id;
|
||||||
|
|
||||||
|
// 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("SELECT id FROM users WHERE username = (?)");
|
||||||
|
|
||||||
|
stmt.setObject(1, uuid);
|
||||||
|
|
||||||
|
// 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 users(username, password) VALUES (?, 'unused') RETURNING id");
|
||||||
|
|
||||||
|
// fill statement
|
||||||
|
stmt.setObject(1, uuid);
|
||||||
|
|
||||||
|
// execute statement
|
||||||
|
rs = stmt.executeQuery();
|
||||||
|
|
||||||
|
// commit the changes
|
||||||
|
c.commit();
|
||||||
|
|
||||||
|
rs.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
id = rs.getLong("id");
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package de.anxietyprime.swajodel.security;
|
||||||
|
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtClaimNames;
|
||||||
|
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class JwtConverter implements Converter<Jwt, AbstractAuthenticationToken> {
|
||||||
|
|
||||||
|
private final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
|
||||||
|
|
||||||
|
private final JwtConverterProperties properties;
|
||||||
|
|
||||||
|
public JwtConverter(JwtConverterProperties properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AbstractAuthenticationToken convert(Jwt jwt) {
|
||||||
|
Collection<GrantedAuthority> authorities = Stream.concat(
|
||||||
|
jwtGrantedAuthoritiesConverter.convert(jwt).stream(),
|
||||||
|
extractResourceRoles(jwt).stream()).collect(Collectors.toSet());
|
||||||
|
return new JwtAuthenticationToken(jwt, authorities, getPrincipalClaimName(jwt));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPrincipalClaimName(Jwt jwt) {
|
||||||
|
String claimName = JwtClaimNames.SUB;
|
||||||
|
if (properties.getPrincipalAttribute() != null) {
|
||||||
|
claimName = properties.getPrincipalAttribute();
|
||||||
|
}
|
||||||
|
return jwt.getClaim(claimName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<? extends GrantedAuthority> extractResourceRoles(Jwt jwt) {
|
||||||
|
Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
|
||||||
|
Map<String, Object> resource;
|
||||||
|
Collection<String> resourceRoles;
|
||||||
|
|
||||||
|
if (resourceAccess == null
|
||||||
|
|| (resource = (Map<String, Object>) resourceAccess.get(properties.getResourceId())) == null
|
||||||
|
|| (resourceRoles = (Collection<String>) resource.get("roles")) == null) {
|
||||||
|
return Set.of();
|
||||||
|
}
|
||||||
|
return resourceRoles.stream()
|
||||||
|
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package de.anxietyprime.swajodel.security;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Validated
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "jwt.auth.converter")
|
||||||
|
|
||||||
|
|
||||||
|
public class JwtConverterProperties {
|
||||||
|
|
||||||
|
private String resourceId;
|
||||||
|
private String principalAttribute;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package de.anxietyprime.swajodel.security;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
public static final String ADMIN = "admin";
|
||||||
|
public static final String USER = "user";
|
||||||
|
private final JwtConverter jwtConverter;
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http.authorizeHttpRequests((authz) ->
|
||||||
|
authz.requestMatchers(HttpMethod.GET, "/messages/").permitAll()
|
||||||
|
.anyRequest().authenticated());
|
||||||
|
|
||||||
|
http.sessionManagement(sess -> sess.sessionCreationPolicy(
|
||||||
|
SessionCreationPolicy.STATELESS));
|
||||||
|
http.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtConverter)));
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1 +1,5 @@
|
|||||||
spring.application.name=SWA-Jodel
|
spring.application.name=SWA-Jodel
|
||||||
|
|
||||||
|
# Security Configuration
|
||||||
|
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
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user