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
|
||||
ADD . .
|
||||
RUN mvn package
|
||||
ARG KC_HOSTNAME
|
||||
RUN mvn package -DKC_HOSTNAME=${KC_HOSTNAME}
|
||||
|
||||
|
||||
FROM eclipse-temurin:21-jdk-alpine
|
||||
|
||||
19
pom.xml
19
pom.xml
@@ -37,7 +37,24 @@
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>42.7.3</version>
|
||||
</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>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
package de.anxietyprime.swajodel;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
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 {
|
||||
// id of the post
|
||||
public Long id;
|
||||
// Parent id
|
||||
public Optional<Long> parent = Optional.empty();
|
||||
// id of the author in db
|
||||
private Long authorID;
|
||||
private final Long authorID;
|
||||
// anonymized authorID
|
||||
public Long anonymousID;
|
||||
// title of the post
|
||||
@@ -16,32 +24,27 @@ public class JodelPost {
|
||||
// content of the post
|
||||
public String content;
|
||||
// date of the post
|
||||
public Date date;
|
||||
public Timestamp date;
|
||||
// location if the post
|
||||
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)
|
||||
public Optional<Boolean> reaction;
|
||||
// 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
|
||||
public void anonymize(Optional<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());
|
||||
}
|
||||
public void anonymize(Vector<Long> idCache) {
|
||||
|
||||
// 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 (i == -1) {
|
||||
// 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
|
||||
idCache.get().add(this.authorID);
|
||||
idCache.add(this.authorID);
|
||||
}
|
||||
// the authorID has been anonymized once before, so we can get it from cache
|
||||
else this.anonymousID = (long) i;
|
||||
@@ -53,9 +56,10 @@ public class JodelPost {
|
||||
}
|
||||
|
||||
// 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
|
||||
if (parent == this.id) {
|
||||
if (Objects.equals(post.parent.get(), this.id)) {
|
||||
// add the comment
|
||||
this.comments.add(post);
|
||||
// return success
|
||||
@@ -64,14 +68,52 @@ public class JodelPost {
|
||||
// recursively repeat this for all comments
|
||||
for (JodelPost comment : this.comments) {
|
||||
// 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 false;
|
||||
}
|
||||
|
||||
// constructor with private authorID
|
||||
public JodelPost(long authorID) {
|
||||
this.authorID = authorID;
|
||||
// constructor from
|
||||
public JodelPost(ResultSet rs) throws SQLException {
|
||||
// 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;
|
||||
|
||||
public class Location {
|
||||
public float longitude;
|
||||
public float latitude;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
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.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;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
@@ -10,16 +10,20 @@ import java.util.Optional;
|
||||
import java.util.Vector;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin(origins = "*", allowedHeaders = "*", maxAge = 3600)
|
||||
public class Routes {
|
||||
|
||||
@GetMapping("/posts/{longitude}/{latitude}")
|
||||
public Vector<JodelPost> getPosts(@PathVariable("longitude") float longitude, @PathVariable("latitude") float latitude) {
|
||||
@GetMapping("/posts/{latitude}/{longitude}")
|
||||
public Vector<JodelPost> getPostsByLocation(@PathVariable("longitude") float longitude, @PathVariable("latitude") float latitude) {
|
||||
// list of all posts (not comments) in range
|
||||
Vector<JodelPost> posts = new Vector<>();
|
||||
|
||||
// get UserID
|
||||
Long userID = User.getID();
|
||||
|
||||
// DB connection and statement
|
||||
Connection c = null;
|
||||
PreparedStatement stmt = null;
|
||||
Connection c;
|
||||
PreparedStatement stmt;
|
||||
|
||||
// try to get data from db
|
||||
try {
|
||||
@@ -34,40 +38,49 @@ public class Routes {
|
||||
c.setAutoCommit(false);
|
||||
|
||||
// create a new statement
|
||||
stmt = c.prepareStatement("WITH RECURSIVE targets AS (\n" +
|
||||
" SELECT\n" +
|
||||
" id,\n" +
|
||||
" id AS parent,\n" +
|
||||
" author,\n" +
|
||||
" title,\n" +
|
||||
" content,\n" +
|
||||
" postdate,\n" +
|
||||
" postlocation[0] AS longitude,\n" +
|
||||
" postlocation[1] AS latitude\n" +
|
||||
" FROM\n" +
|
||||
" posts\n" +
|
||||
" WHERE\n" +
|
||||
" deleted IS NULL\n" +
|
||||
" AND sqrt(power(postlocation[0] - (?), 2) + power(postlocation[1] - (?), 2)) <= 10\n" +
|
||||
" AND id NOT IN (SELECT child FROM comments)\n" +
|
||||
" UNION\n" +
|
||||
" SELECT\n" +
|
||||
" com.child,\n" +
|
||||
" com.parent,\n" +
|
||||
" com.author,\n" +
|
||||
" com.title,\n" +
|
||||
" com.content,\n" +
|
||||
" com.postdate,\n" +
|
||||
" com.postlocation[0],\n" +
|
||||
" com.postlocation[1]\n" +
|
||||
" FROM\n" +
|
||||
" (SELECT * FROM comments inner join posts ON comments.child = posts.id) com\n" +
|
||||
" inner join targets ON targets.id = com.parent\n" +
|
||||
")\n" +
|
||||
"SELECT * FROM targets;");
|
||||
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 earth_distance(
|
||||
ll_to_earth(posts.postlocation[0], posts.postlocation[1]),
|
||||
ll_to_earth((?), (?))
|
||||
) <= 10000
|
||||
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, longitude);
|
||||
stmt.setObject(2, latitude);
|
||||
stmt.setObject(1, latitude);
|
||||
stmt.setObject(2, longitude);
|
||||
stmt.setObject(3, userID);
|
||||
|
||||
// query recursively for posts inside a 10km radius
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
@@ -75,20 +88,11 @@ public class Routes {
|
||||
// for all the posts found
|
||||
while ( rs.next() ) {
|
||||
// create a post from the author id
|
||||
JodelPost post = new JodelPost(rs.getLong("author"));
|
||||
// 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");
|
||||
JodelPost post = new JodelPost(rs);
|
||||
|
||||
// check if the parent is the own id
|
||||
// if it is, it is a post
|
||||
if (parent == post.id) {
|
||||
if (!post.isComment()) {
|
||||
// add the post to the posts
|
||||
posts.add(post);
|
||||
}
|
||||
@@ -97,7 +101,7 @@ public class Routes {
|
||||
// iterate over all posts
|
||||
for (JodelPost p : posts) {
|
||||
// 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
|
||||
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 posts
|
||||
Vector<Long> anonymousIDs = new Vector<Long>();
|
||||
anonymousIDs.add(userID);
|
||||
posts.forEach(post -> {
|
||||
post.anonymize(Optional.empty());
|
||||
post.anonymize(anonymousIDs);
|
||||
});
|
||||
|
||||
// return the 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.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
|
||||
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
|
||||
|
||||
# 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.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Vector;
|
||||
|
||||
@SpringBootTest
|
||||
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