From c1b531442f7cf1bd444ef8277f90898ef7da1bf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilda=20S=C3=B6nnergaard?= Date: Mon, 3 Feb 2025 13:50:11 +0100 Subject: [PATCH 1/8] Complete core --- .gitignore | 3 + Dockerfile | 9 ++ build.gradle | 19 ++-- docker-compose.yml | 21 +++++ src/main/java/com/booleanuk/api/Main.java | 13 +++ .../api/controller/PostController.java | 63 ++++++++++++++ .../api/controller/UserController.java | 51 +++++++++++ .../java/com/booleanuk/api/model/Post.java | 86 +++++++++++++++++++ .../java/com/booleanuk/api/model/User.java | 67 +++++++++++++++ .../api/repository/PostRepository.java | 11 +++ .../api/repository/UserRepository.java | 8 ++ src/main/resources/application.properties | 1 + src/main/resources/application.yml | 7 -- 13 files changed, 346 insertions(+), 13 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 src/main/java/com/booleanuk/api/Main.java create mode 100644 src/main/java/com/booleanuk/api/controller/PostController.java create mode 100644 src/main/java/com/booleanuk/api/controller/UserController.java create mode 100644 src/main/java/com/booleanuk/api/model/Post.java create mode 100644 src/main/java/com/booleanuk/api/model/User.java create mode 100644 src/main/java/com/booleanuk/api/repository/PostRepository.java create mode 100644 src/main/java/com/booleanuk/api/repository/UserRepository.java create mode 100644 src/main/resources/application.properties delete mode 100644 src/main/resources/application.yml diff --git a/.gitignore b/.gitignore index c2065bc..e46b350 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ out/ ### VS Code ### .vscode/ + +# misc +application.yml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..921e94a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM mcr.microsoft.com/openjdk/jdk:21-ubuntu + +WORKDIR /app + +COPY /build/libs/java.docker.day.2-0.0.1.jar /app/java.docker.day.2-0.0.1.jar + +EXPOSE 4000 + +ENTRYPOINT [ "java", "-jar", "java.docker.day.2-0.0.1.jar" ] \ No newline at end of file diff --git a/build.gradle b/build.gradle index fb070c4..70849a8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,17 @@ plugins { id 'java' - id 'org.springframework.boot' version '3.1.4' - id 'io.spring.dependency-management' version '1.1.3' + id 'org.springframework.boot' version '3.4.1' + id 'io.spring.dependency-management' version '1.1.7' } group = 'com.booleanuk' version = '0.0.1' -sourceCompatibility = '17' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} configurations { compileOnly { @@ -21,11 +26,13 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' - compileOnly 'org.projectlombok:lombok' +// compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' - implementation 'org.postgresql:postgresql:42.6.0' - annotationProcessor 'org.projectlombok:lombok' +// runtimeOnly 'org.postgresql:postgresql' + implementation 'org.postgresql:postgresql:42.7.4' +// annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..362d18d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +services: + app: + image: 'posting-app:latest' + container_name: app + depends_on: + - db + ports: + - '4000:4000' + environment: + - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/mypostgresuser + - SPRING_DATASOURCE_USERNAME=mypostgresuser + - SPRING_DATASOURCE_PASSWORD=mypostgrespassword + - SPRING_JPA_HIBERNATE_DDL_AUTO=update + + db: + image: 'postgres:latest' + container_name: db + environment: + - POSTGRES_USER=mypostgresuser + - POSTGRES_DATABASE=mypostgresuser + - POSTGRES_PASSWORD=mypostgrespassword \ No newline at end of file diff --git a/src/main/java/com/booleanuk/api/Main.java b/src/main/java/com/booleanuk/api/Main.java new file mode 100644 index 0000000..2fc336f --- /dev/null +++ b/src/main/java/com/booleanuk/api/Main.java @@ -0,0 +1,13 @@ +package com.booleanuk.api; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Main { + + public static void main(String[] args) { + SpringApplication.run(Main.class, args); + } + +} diff --git a/src/main/java/com/booleanuk/api/controller/PostController.java b/src/main/java/com/booleanuk/api/controller/PostController.java new file mode 100644 index 0000000..0ea0668 --- /dev/null +++ b/src/main/java/com/booleanuk/api/controller/PostController.java @@ -0,0 +1,63 @@ +package com.booleanuk.api.controller; + +import com.booleanuk.api.model.*; +import com.booleanuk.api.model.Post; +import com.booleanuk.api.repository.PostRepository; +import com.booleanuk.api.repository.UserRepository; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.List; + +@RestController +@RequestMapping("/posts") +public class PostController { + private UserRepository userRepository; + private PostRepository postRepository; + + public PostController(UserRepository userRepository, PostRepository postRepository) { + this.userRepository = userRepository; + this.postRepository = postRepository; + } + + @GetMapping + public List getAllPosts() { + return postRepository.findAll(); + } + + @GetMapping("/{post_id}") + public Post getPost(@PathVariable int postId) { + return postRepository.findById(postId).orElse(null); + } + + public record PostUserRecord(String username, String body) {} + + @PostMapping + public Post createPost(@RequestBody PostUserRecord postRecord) { + User user = userRepository.findByUsername(postRecord.username); + if (user == null) return null; + Post post = new Post(); + post.setUser(user); + post.setBody(postRecord.body); + post.setTimestamp(LocalDateTime.now()); + return postRepository.save(post); + } + + @PostMapping("/{post_id}/{username}") + public Post repost(@PathVariable int postId, @RequestBody PostUserRecord postUser) { + User user = userRepository.findByUsername(postUser.username); + if (user == null) return null; + Post originalPost = postRepository.findById(postId).orElseThrow(); + Post repost = new Post(); + repost.setUser(user); + repost.setOriginalPost(originalPost); + repost.setTimestamp(LocalDateTime.now()); + repost.setBody(postUser.body); + return postRepository.save(repost); + } + + @GetMapping("/{username}") + public List getPostsByUser(@PathVariable String username) { + return userRepository.findByUsername(username).getPosts(); + } +} diff --git a/src/main/java/com/booleanuk/api/controller/UserController.java b/src/main/java/com/booleanuk/api/controller/UserController.java new file mode 100644 index 0000000..d14d49f --- /dev/null +++ b/src/main/java/com/booleanuk/api/controller/UserController.java @@ -0,0 +1,51 @@ +package com.booleanuk.api.controller; + +import com.booleanuk.api.model.Post; +import com.booleanuk.api.model.User; +import com.booleanuk.api.repository.PostRepository; +import com.booleanuk.api.repository.UserRepository; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/user") +public class UserController { + private UserRepository userRepository; + private PostRepository postRepository; + + public UserController(UserRepository userRepository, PostRepository postRepository) { + this.userRepository = userRepository; + this.postRepository = postRepository; + } + + @GetMapping + public List getAllUsers() { + return userRepository.findAll(); + } + + @GetMapping("/{username}") + public User getUser(@PathVariable String username) { + return userRepository.findByUsername(username); + } + + @PostMapping + public User createUser(@RequestBody User user) { + return userRepository.save(user); + } + + @PutMapping("/{username}") + public User updateUser(@PathVariable String username, @RequestBody User user) { + User userToUpdate = userRepository.findByUsername(username); + if (userToUpdate == null) return null; + user.setUsername(user.getUsername()); + return userRepository.save(userToUpdate); + } + + @GetMapping("/{username}/posts") + public List getAllPostsByUser(@PathVariable String username) { + User user = userRepository.findByUsername(username); + if (user == null) return null; + return postRepository.findAllByUser(user); + } +} diff --git a/src/main/java/com/booleanuk/api/model/Post.java b/src/main/java/com/booleanuk/api/model/Post.java new file mode 100644 index 0000000..ea12fb6 --- /dev/null +++ b/src/main/java/com/booleanuk/api/model/Post.java @@ -0,0 +1,86 @@ +package com.booleanuk.api.model; + +import jakarta.persistence.*; + +import java.time.LocalDateTime; +import java.util.List; + +@Entity +@Table(name = "posts") +public class Post { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @ManyToOne @JoinColumn + private User user; + + @Column + private String body; + + @ManyToOne + private Post originalPost; + + @Column + private LocalDateTime timestamp; + + @ManyToMany + private List upvotes; + + public Post(User user, String body, Post originalPost) { + this.user = user; + this.body = body; + this.originalPost = originalPost; + } + + public Post() { + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public LocalDateTime getTimestamp() { + return timestamp; + } + + public void setTimestamp(LocalDateTime timestamp) { + this.timestamp = timestamp; + } + + public List getUpvotes() { + return upvotes; + } + + public void setUpvotes(List upvotes) { + this.upvotes = upvotes; + } + + public Post getOriginalPost() { + return originalPost; + } + + public void setOriginalPost(Post originalPost) { + this.originalPost = originalPost; + } +} diff --git a/src/main/java/com/booleanuk/api/model/User.java b/src/main/java/com/booleanuk/api/model/User.java new file mode 100644 index 0000000..3faa4b6 --- /dev/null +++ b/src/main/java/com/booleanuk/api/model/User.java @@ -0,0 +1,67 @@ +package com.booleanuk.api.model; + +import jakarta.persistence.*; + +import java.util.List; + +@Entity +@Table( + name = "users", + uniqueConstraints = { + @UniqueConstraint(columnNames = "username") + }) +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @Column + private String username; + + @OneToMany + List posts; + + @ManyToMany + List friends; + + public User(String username, List posts, List friends) { + this.username = username; + this.posts = posts; + this.friends = friends; + } + + public User() { + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public List getPosts() { + return posts; + } + + public void setPosts(List posts) { + this.posts = posts; + } + + public List getFriends() { + return friends; + } + + public void setFriends(List friends) { + this.friends = friends; + } +} diff --git a/src/main/java/com/booleanuk/api/repository/PostRepository.java b/src/main/java/com/booleanuk/api/repository/PostRepository.java new file mode 100644 index 0000000..ca5e6ba --- /dev/null +++ b/src/main/java/com/booleanuk/api/repository/PostRepository.java @@ -0,0 +1,11 @@ +package com.booleanuk.api.repository; + +import com.booleanuk.api.model.Post; +import com.booleanuk.api.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface PostRepository extends JpaRepository { + List findAllByUser(User user); +} diff --git a/src/main/java/com/booleanuk/api/repository/UserRepository.java b/src/main/java/com/booleanuk/api/repository/UserRepository.java new file mode 100644 index 0000000..a248583 --- /dev/null +++ b/src/main/java/com/booleanuk/api/repository/UserRepository.java @@ -0,0 +1,8 @@ +package com.booleanuk.api.repository; + +import com.booleanuk.api.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { + public User findByUsername(String username); +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..8857c64 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.jpa.hibernate.ddl-auto=update \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml deleted file mode 100644 index 02c2591..0000000 --- a/src/main/resources/application.yml +++ /dev/null @@ -1,7 +0,0 @@ -server: - port: 4000 - error: - include-message: always - include-binding-errors: always - include-stacktrace: never - include-exception: false From 6d62c9ac4508788f2dfb60883d801d26c774fa0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilda=20S=C3=B6nnergaard?= Date: Mon, 3 Feb 2025 13:52:23 +0100 Subject: [PATCH 2/8] Rename 'friends' to 'following' --- src/main/java/com/booleanuk/api/model/User.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/booleanuk/api/model/User.java b/src/main/java/com/booleanuk/api/model/User.java index 3faa4b6..5513b02 100644 --- a/src/main/java/com/booleanuk/api/model/User.java +++ b/src/main/java/com/booleanuk/api/model/User.java @@ -22,12 +22,12 @@ public class User { List posts; @ManyToMany - List friends; + List following; - public User(String username, List posts, List friends) { + public User(String username, List posts, List following) { this.username = username; this.posts = posts; - this.friends = friends; + this.following = following; } public User() { @@ -57,11 +57,11 @@ public void setPosts(List posts) { this.posts = posts; } - public List getFriends() { - return friends; + public List getFollowing() { + return following; } - public void setFriends(List friends) { - this.friends = friends; + public void setFollowing(List following) { + this.following = following; } } From 17fbfe0d14921225eb52c0e26a3dcc651121bfcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilda=20S=C3=B6nnergaard?= Date: Mon, 3 Feb 2025 14:38:01 +0100 Subject: [PATCH 3/8] Make it possible to upvote posts --- .../api/controller/PostController.java | 45 ++++++++++++++----- .../java/com/booleanuk/api/model/Post.java | 7 +-- .../java/com/booleanuk/api/model/User.java | 9 ++-- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/booleanuk/api/controller/PostController.java b/src/main/java/com/booleanuk/api/controller/PostController.java index 0ea0668..2528e2f 100644 --- a/src/main/java/com/booleanuk/api/controller/PostController.java +++ b/src/main/java/com/booleanuk/api/controller/PostController.java @@ -8,6 +8,7 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Set; @RestController @RequestMapping("/posts") @@ -26,38 +27,60 @@ public List getAllPosts() { } @GetMapping("/{post_id}") - public Post getPost(@PathVariable int postId) { + public Post getPost(@PathVariable(name="post_id") int postId) { return postRepository.findById(postId).orElse(null); } public record PostUserRecord(String username, String body) {} @PostMapping - public Post createPost(@RequestBody PostUserRecord postRecord) { - User user = userRepository.findByUsername(postRecord.username); + public Post createPost(@RequestBody PostUserRecord postUser) { + User user = userRepository.findByUsername(postUser.username); if (user == null) return null; Post post = new Post(); post.setUser(user); - post.setBody(postRecord.body); + post.setBody(postUser.body); post.setTimestamp(LocalDateTime.now()); return postRepository.save(post); } - @PostMapping("/{post_id}/{username}") - public Post repost(@PathVariable int postId, @RequestBody PostUserRecord postUser) { + @PostMapping("/{post_id}") + public Post repost(@PathVariable(name="post_id") int postId, @RequestBody PostUserRecord postUser) { User user = userRepository.findByUsername(postUser.username); - if (user == null) return null; Post originalPost = postRepository.findById(postId).orElseThrow(); + if (user == null) return null; Post repost = new Post(); repost.setUser(user); + repost.setBody(postUser.body); repost.setOriginalPost(originalPost); repost.setTimestamp(LocalDateTime.now()); - repost.setBody(postUser.body); return postRepository.save(repost); } - @GetMapping("/{username}") - public List getPostsByUser(@PathVariable String username) { - return userRepository.findByUsername(username).getPosts(); + @PostMapping("/{post_id}/upvotes") + public Set upvote(@PathVariable(name="post_id") int postId, @RequestBody User requestUser) { + Post post = postRepository.findById(postId).orElseThrow(); + User user = userRepository.findByUsername(requestUser.getUsername()); + if (user == null) return null; + post.getUpvotes().add(user); + postRepository.save(post); + return post.getUpvotes(); + } + + @GetMapping("/{post_id}/upvotes") + public Set getUpvotes(@PathVariable(name="post_id") int postId) { + Post post = postRepository.findById(postId).orElseThrow(); + return post.getUpvotes(); + } + + // todo: maybe error handling when user has not upvoted or maybe that's ok. maybe different response + @DeleteMapping("/{post_id}/upvotes") + public Set removeUpvote(@PathVariable(name="post_id") int postId, @RequestBody User requestUser) { + Post post = postRepository.findById(postId).orElseThrow(); + User user = userRepository.findByUsername(requestUser.getUsername()); + if (user == null) return null; + post.getUpvotes().remove(user); + postRepository.save(post); + return post.getUpvotes(); } } diff --git a/src/main/java/com/booleanuk/api/model/Post.java b/src/main/java/com/booleanuk/api/model/Post.java index ea12fb6..7d71bde 100644 --- a/src/main/java/com/booleanuk/api/model/Post.java +++ b/src/main/java/com/booleanuk/api/model/Post.java @@ -4,6 +4,7 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Set; @Entity @Table(name = "posts") @@ -25,7 +26,7 @@ public class Post { private LocalDateTime timestamp; @ManyToMany - private List upvotes; + private Set upvotes; public Post(User user, String body, Post originalPost) { this.user = user; @@ -68,11 +69,11 @@ public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; } - public List getUpvotes() { + public Set getUpvotes() { return upvotes; } - public void setUpvotes(List upvotes) { + public void setUpvotes(Set upvotes) { this.upvotes = upvotes; } diff --git a/src/main/java/com/booleanuk/api/model/User.java b/src/main/java/com/booleanuk/api/model/User.java index 5513b02..591a723 100644 --- a/src/main/java/com/booleanuk/api/model/User.java +++ b/src/main/java/com/booleanuk/api/model/User.java @@ -3,6 +3,7 @@ import jakarta.persistence.*; import java.util.List; +import java.util.Set; @Entity @Table( @@ -22,9 +23,9 @@ public class User { List posts; @ManyToMany - List following; + Set following; - public User(String username, List posts, List following) { + public User(String username, List posts, Set following) { this.username = username; this.posts = posts; this.following = following; @@ -57,11 +58,11 @@ public void setPosts(List posts) { this.posts = posts; } - public List getFollowing() { + public Set getFollowing() { return following; } - public void setFollowing(List following) { + public void setFollowing(Set following) { this.following = following; } } From 5ff3caa05a421277042df326d637e29e01f99932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilda=20S=C3=B6nnergaard?= Date: Mon, 3 Feb 2025 15:10:13 +0100 Subject: [PATCH 4/8] Make it possible to follow users and see feed with posts made by users you follow --- .../api/controller/UserController.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/main/java/com/booleanuk/api/controller/UserController.java b/src/main/java/com/booleanuk/api/controller/UserController.java index d14d49f..c327eec 100644 --- a/src/main/java/com/booleanuk/api/controller/UserController.java +++ b/src/main/java/com/booleanuk/api/controller/UserController.java @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Set; @RestController @RequestMapping("/user") @@ -48,4 +49,34 @@ public List getAllPostsByUser(@PathVariable String username) { if (user == null) return null; return postRepository.findAllByUser(user); } + + @GetMapping("/{username}/following") + public Set getFollowing(@PathVariable String username) { + return userRepository.findByUsername(username).getFollowing(); + } + + // todo: tbd; join table, list of followers probably easiest way + @GetMapping("/{username}/followers") + public Set getFollowers(@PathVariable String username) { + return null; + } + + @PostMapping("/{username}/follow") + public Set startFollowing(@PathVariable(name="username") String usernameToFollow, @RequestBody User requestFollower) { + User follower = userRepository.findByUsername(requestFollower.getUsername()); + User userToFollow = userRepository.findByUsername(usernameToFollow); + if (follower == null || userToFollow == null) return null; + follower.getFollowing().add(userToFollow); + userRepository.save(follower); + return follower.getFollowing(); + } + + @GetMapping("/{username}/feed") + public List getPostsByFriends(@PathVariable String username) { + User user = userRepository.findByUsername(username); + if (user == null) return null; + return postRepository.findAll().stream().filter( + p -> user.getFollowing().contains(p.getUser()) + ).toList(); + } } From 0c81584f3c69214f54be5c75c14e4d97d2a5a714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilda=20S=C3=B6nnergaard?= Date: Mon, 3 Feb 2025 15:22:12 +0100 Subject: [PATCH 5/8] Add followers field to User --- .../booleanuk/api/controller/UserController.java | 7 +++++-- src/main/java/com/booleanuk/api/model/User.java | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/booleanuk/api/controller/UserController.java b/src/main/java/com/booleanuk/api/controller/UserController.java index c327eec..3396647 100644 --- a/src/main/java/com/booleanuk/api/controller/UserController.java +++ b/src/main/java/com/booleanuk/api/controller/UserController.java @@ -55,10 +55,11 @@ public Set getFollowing(@PathVariable String username) { return userRepository.findByUsername(username).getFollowing(); } - // todo: tbd; join table, list of followers probably easiest way @GetMapping("/{username}/followers") public Set getFollowers(@PathVariable String username) { - return null; + User user = userRepository.findByUsername(username); + if (user == null) return null; + return user.getFollowers(); } @PostMapping("/{username}/follow") @@ -67,7 +68,9 @@ public Set startFollowing(@PathVariable(name="username") String usernameTo User userToFollow = userRepository.findByUsername(usernameToFollow); if (follower == null || userToFollow == null) return null; follower.getFollowing().add(userToFollow); + userToFollow.getFollowers().add(follower); userRepository.save(follower); + userRepository.save(userToFollow); return follower.getFollowing(); } diff --git a/src/main/java/com/booleanuk/api/model/User.java b/src/main/java/com/booleanuk/api/model/User.java index 591a723..ed15b99 100644 --- a/src/main/java/com/booleanuk/api/model/User.java +++ b/src/main/java/com/booleanuk/api/model/User.java @@ -25,10 +25,16 @@ public class User { @ManyToMany Set following; - public User(String username, List posts, Set following) { + // the way this is implemented, it's possible that following-follower is a 1-way relationship, which should never happen. + // but i'll leave it like this for now; it works as long as nothing unexpected happens + @ManyToMany + Set followers; + + public User(String username, List posts, Set following, Set followers) { this.username = username; this.posts = posts; this.following = following; + this.followers = followers; } public User() { @@ -65,4 +71,12 @@ public Set getFollowing() { public void setFollowing(Set following) { this.following = following; } + + public Set getFollowers() { + return followers; + } + + public void setFollowers(Set followers) { + this.followers = followers; + } } From 3a62f63b120ec4a20c297d13f5abf6efe9b64eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilda=20S=C3=B6nnergaard?= Date: Mon, 3 Feb 2025 15:27:31 +0100 Subject: [PATCH 6/8] Clean up fields in json output --- src/main/java/com/booleanuk/api/model/Post.java | 2 ++ src/main/java/com/booleanuk/api/model/User.java | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/main/java/com/booleanuk/api/model/Post.java b/src/main/java/com/booleanuk/api/model/Post.java index 7d71bde..51b08cb 100644 --- a/src/main/java/com/booleanuk/api/model/Post.java +++ b/src/main/java/com/booleanuk/api/model/Post.java @@ -1,5 +1,6 @@ package com.booleanuk.api.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import java.time.LocalDateTime; @@ -25,6 +26,7 @@ public class Post { @Column private LocalDateTime timestamp; + @JsonIgnore @ManyToMany private Set upvotes; diff --git a/src/main/java/com/booleanuk/api/model/User.java b/src/main/java/com/booleanuk/api/model/User.java index ed15b99..d9eb7b7 100644 --- a/src/main/java/com/booleanuk/api/model/User.java +++ b/src/main/java/com/booleanuk/api/model/User.java @@ -1,5 +1,7 @@ package com.booleanuk.api.model; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIncludeProperties; import jakarta.persistence.*; import java.util.List; @@ -19,14 +21,18 @@ public class User { @Column private String username; + @JsonIgnore @OneToMany List posts; + @JsonIgnore @ManyToMany Set following; // the way this is implemented, it's possible that following-follower is a 1-way relationship, which should never happen. // but i'll leave it like this for now; it works as long as nothing unexpected happens +// @JsonIncludeProperties({"username"}) + @JsonIgnore @ManyToMany Set followers; From 1497a83ef8ff3706c57c3599a29e0bc5d68932ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilda=20S=C3=B6nnergaard?= Date: Mon, 3 Feb 2025 15:27:31 +0100 Subject: [PATCH 7/8] Clean up fields in json output --- src/main/java/com/booleanuk/api/model/Post.java | 5 ++++- src/main/java/com/booleanuk/api/model/User.java | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/booleanuk/api/model/Post.java b/src/main/java/com/booleanuk/api/model/Post.java index 7d71bde..5c89886 100644 --- a/src/main/java/com/booleanuk/api/model/Post.java +++ b/src/main/java/com/booleanuk/api/model/Post.java @@ -1,9 +1,10 @@ package com.booleanuk.api.model; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIncludeProperties; import jakarta.persistence.*; import java.time.LocalDateTime; -import java.util.List; import java.util.Set; @Entity @@ -14,6 +15,7 @@ public class Post { private int id; @ManyToOne @JoinColumn + @JsonIncludeProperties({"id", "username"}) private User user; @Column @@ -25,6 +27,7 @@ public class Post { @Column private LocalDateTime timestamp; + @JsonIgnore @ManyToMany private Set upvotes; diff --git a/src/main/java/com/booleanuk/api/model/User.java b/src/main/java/com/booleanuk/api/model/User.java index ed15b99..d9eb7b7 100644 --- a/src/main/java/com/booleanuk/api/model/User.java +++ b/src/main/java/com/booleanuk/api/model/User.java @@ -1,5 +1,7 @@ package com.booleanuk.api.model; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIncludeProperties; import jakarta.persistence.*; import java.util.List; @@ -19,14 +21,18 @@ public class User { @Column private String username; + @JsonIgnore @OneToMany List posts; + @JsonIgnore @ManyToMany Set following; // the way this is implemented, it's possible that following-follower is a 1-way relationship, which should never happen. // but i'll leave it like this for now; it works as long as nothing unexpected happens +// @JsonIncludeProperties({"username"}) + @JsonIgnore @ManyToMany Set followers; From 596335f91ad51aaf32ad1a0aab022aeab44eaadf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilda=20S=C3=B6nnergaard?= Date: Mon, 3 Feb 2025 15:43:18 +0100 Subject: [PATCH 8/8] Add API spec --- docs/openapi-exported.yml | 600 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 600 insertions(+) create mode 100644 docs/openapi-exported.yml diff --git a/docs/openapi-exported.yml b/docs/openapi-exported.yml new file mode 100644 index 0000000..cf5d48a --- /dev/null +++ b/docs/openapi-exported.yml @@ -0,0 +1,600 @@ +_type: export +__export_format: 4 +__export_date: 2025-02-03T14:42:51.192Z +__export_source: insomnia.desktop.app:v10.3.0 +resources: + - _id: req_69bf9502e3dd47bc9029127ec689a6cb + parentId: fld_3c8e86ca5f5b4a8e8a41b33b9d581db4 + modified: 1738589685799 + created: 1738589558527 + url: localhost:4000/posts/2/upvotes + name: Remove upvote + description: "" + method: DELETE + body: + mimeType: application/json + text: |- + { + "username": "superuser" + } + parameters: [] + headers: + - name: Content-Type + value: application/json + - name: User-Agent + value: insomnia/10.3.0 + authentication: {} + metaSortKey: -1738589558527 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: fld_3c8e86ca5f5b4a8e8a41b33b9d581db4 + parentId: wrk_c7ae656895504466aa58ef09f4dcdd60 + modified: 1738589546408 + created: 1738589546408 + name: Upvotes + description: "" + environment: {} + environmentPropertyOrder: null + metaSortKey: -1738589546408 + environmentType: kv + _type: request_group + - _id: wrk_c7ae656895504466aa58ef09f4dcdd60 + parentId: null + modified: 1738579611211 + created: 1738579611211 + name: java-docker-day-2 Twittery + description: "" + scope: collection + _type: workspace + - _id: req_59930f90093e4134bfa714993cf8fd50 + parentId: fld_3c8e86ca5f5b4a8e8a41b33b9d581db4 + modified: 1738593695077 + created: 1738588445244 + url: localhost:4000/posts/1/upvotes + name: Get upvotes + description: "" + method: GET + body: + mimeType: application/json + text: "" + parameters: [] + headers: + - name: Content-Type + value: application/json + - name: User-Agent + value: insomnia/10.3.0 + authentication: {} + metaSortKey: -1738589553167 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: req_7593081b72a14e81b426886da0ab1a88 + parentId: fld_3c8e86ca5f5b4a8e8a41b33b9d581db4 + modified: 1738593711016 + created: 1738588457417 + url: localhost:4000/posts/1/upvotes + name: Upvote post + description: "" + method: POST + body: + mimeType: application/json + text: '{"username": "superuser"}' + parameters: [] + headers: + - name: Content-Type + value: application/json + - name: User-Agent + value: insomnia/10.3.0 + authentication: {} + metaSortKey: -1738589553067 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: req_dd92b5528e1041ce8bd7f22c9413d848 + parentId: fld_19e50eb474024f3fb3ac0d906472824c + modified: 1738591712037 + created: 1738591690557 + url: localhost:4000/posts/3 + name: Update post by ID + description: "" + method: PUT + body: {} + parameters: [] + headers: + - name: User-Agent + value: insomnia/10.3.0 + authentication: {} + metaSortKey: -1738591690557 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: fld_19e50eb474024f3fb3ac0d906472824c + parentId: wrk_c7ae656895504466aa58ef09f4dcdd60 + modified: 1738588458856 + created: 1738579624272 + name: Posts + description: "" + environment: {} + environmentPropertyOrder: null + metaSortKey: -1738579624272 + environmentType: kv + _type: request_group + - _id: req_4f62ab5a4a344a7eb04f46d56ab69cda + parentId: fld_19e50eb474024f3fb3ac0d906472824c + modified: 1738591685499 + created: 1738591668605 + url: localhost:4000/posts/1 + name: Delete post by ID + description: "" + method: DELETE + body: {} + parameters: [] + headers: + - name: User-Agent + value: insomnia/10.3.0 + authentication: {} + metaSortKey: -1738591668605 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: req_28e51c4bf9834ec0aaf241f7664d04be + parentId: fld_19e50eb474024f3fb3ac0d906472824c + modified: 1738584270355 + created: 1738584256372 + url: localhost:4000/user/superuser/posts + name: Get all by user + description: "" + method: GET + body: {} + parameters: [] + headers: + - name: User-Agent + value: insomnia/10.3.0 + authentication: {} + metaSortKey: -1738580116722 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: req_2cabbd498dea47ba9fd3fe3e94a23b69 + parentId: fld_19e50eb474024f3fb3ac0d906472824c + modified: 1738584241912 + created: 1738584229242 + url: localhost:4000/posts + name: Get all posts + description: "" + method: GET + body: {} + parameters: [] + headers: + - name: User-Agent + value: insomnia/10.3.0 + authentication: {} + metaSortKey: -1738580116622 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: req_f40db463ea9046efaf8f362d9ec5cdc7 + parentId: fld_19e50eb474024f3fb3ac0d906472824c + modified: 1738593207600 + created: 1738588757852 + url: localhost:4000/posts/1 + name: Get post by ID + description: "" + method: GET + body: {} + parameters: [] + headers: + - name: User-Agent + value: insomnia/10.3.0 + authentication: {} + metaSortKey: -1738580116597 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: req_2ddffe65b64f45588e89cddb044d1c16 + parentId: fld_19e50eb474024f3fb3ac0d906472824c + modified: 1738593191327 + created: 1738587557566 + url: localhost:4000/posts/1 + name: Create repost + description: "" + method: POST + body: + mimeType: application/json + text: |- + { + "username": "superuser", + "body": "this is a repost!!" + } + parameters: [] + headers: + - name: Content-Type + value: application/json + - name: User-Agent + value: insomnia/10.3.0 + authentication: {} + metaSortKey: -1738580116572 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: req_36010f7349644f86bd7038d4f09bc435 + parentId: fld_19e50eb474024f3fb3ac0d906472824c + modified: 1738593106381 + created: 1738580116522 + url: localhost:4000/posts + name: Create post + description: "" + method: POST + body: + mimeType: application/json + text: |- + { + "username": "superuser", + "body": "This is a post!" + } + parameters: [] + headers: + - name: Content-Type + value: application/json + - name: User-Agent + value: insomnia/10.3.0 + authentication: {} + metaSortKey: -1738580116522 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: req_8074367133c74b50970a530b66069c9b + parentId: fld_b7e13d11ecd848e5bb9d80f8121bb112 + modified: 1738591423115 + created: 1738591350348 + url: localhost:4000/user/superduper/feed + name: Get feed + description: "" + method: GET + body: {} + parameters: [] + headers: + - name: User-Agent + value: insomnia/10.3.0 + authentication: {} + metaSortKey: -1738591423055 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: fld_b7e13d11ecd848e5bb9d80f8121bb112 + parentId: wrk_c7ae656895504466aa58ef09f4dcdd60 + modified: 1738591415157 + created: 1738591403946 + name: Follow + description: "" + environment: {} + environmentPropertyOrder: null + metaSortKey: -1738579622310 + environmentType: kv + _type: request_group + - _id: req_a9ee85f931dd4430997ad12a06c7620e + parentId: fld_b7e13d11ecd848e5bb9d80f8121bb112 + modified: 1738592588319 + created: 1738589927474 + url: localhost:4000/user/superuser/followers + name: Get followers + description: "" + method: GET + body: {} + parameters: [] + headers: + - name: User-Agent + value: insomnia/10.3.0 + authentication: {} + metaSortKey: -1738591422955 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: req_40cb90ab75c142eebc1926dfae5def21 + parentId: fld_b7e13d11ecd848e5bb9d80f8121bb112 + modified: 1738591428241 + created: 1738579650490 + url: localhost:4000/user/superduper/following + name: Get following + description: "" + method: GET + body: {} + parameters: [] + headers: + - name: User-Agent + value: insomnia/10.3.0 + authentication: {} + metaSortKey: -1738591422855 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: req_a61a950f52224ca5a7ab77a547e43074 + parentId: fld_b7e13d11ecd848e5bb9d80f8121bb112 + modified: 1738591432531 + created: 1738589946212 + url: localhost:4000/user/1 + name: Remove following + description: "" + method: DELETE + body: + mimeType: application/json + text: |- + { + "username": " " + } + parameters: [] + headers: + - name: Content-Type + value: application/json + - name: User-Agent + value: insomnia/10.3.0 + authentication: {} + metaSortKey: -1738591422805 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: req_9279a6c1e22e4211bebf0a0cab3c8ff2 + parentId: fld_b7e13d11ecd848e5bb9d80f8121bb112 + modified: 1738591430930 + created: 1738589933342 + url: localhost:4000/user/superuser/follow + name: Start following + description: "" + method: POST + body: + mimeType: application/json + text: |- + { + "username": "superduper" + } + parameters: [] + headers: + - name: Content-Type + value: application/json + - name: User-Agent + value: insomnia/10.3.0 + authentication: {} + metaSortKey: -1738591422755 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: req_a4a0790fc2a14fd8a47ef43f66fb3ec9 + parentId: fld_be2a2efd3e7a4111b4121d4aef77ca2f + modified: 1738591652381 + created: 1738591611010 + url: localhost:4000/user/user123 + name: Update user + description: "" + method: PUT + body: + mimeType: application/json + text: |- + { + "username": "newUser" + } + parameters: [] + headers: + - name: Content-Type + value: application/json + - name: User-Agent + value: insomnia/10.3.0 + authentication: {} + metaSortKey: -1738591611010 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: fld_be2a2efd3e7a4111b4121d4aef77ca2f + parentId: wrk_c7ae656895504466aa58ef09f4dcdd60 + modified: 1738579620348 + created: 1738579620348 + name: User + description: "" + environment: {} + environmentPropertyOrder: null + metaSortKey: -1738579620348 + environmentType: kv + _type: request_group + - _id: req_cbf064ce36aa469b96e513e7656231a7 + parentId: fld_be2a2efd3e7a4111b4121d4aef77ca2f + modified: 1738591640124 + created: 1738591437166 + url: localhost:4000/user/user123 + name: Delete user + description: "" + method: DELETE + body: {} + parameters: [] + headers: + - name: User-Agent + value: insomnia/10.3.0 + authentication: {} + metaSortKey: -1738591437166 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: req_47d7ecafc18849508b33d621641ef40f + parentId: fld_be2a2efd3e7a4111b4121d4aef77ca2f + modified: 1738593086914 + created: 1738579638071 + url: localhost:4000/user + name: Create user + description: "" + method: POST + body: + mimeType: application/json + text: |- + { + "username": "superuser" + } + parameters: [] + headers: + - name: Content-Type + value: application/json + - name: User-Agent + value: insomnia/10.3.0 + authentication: {} + metaSortKey: -1738579638071 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: req_85334da48fd84056913516f18d66739e + parentId: fld_be2a2efd3e7a4111b4121d4aef77ca2f + modified: 1738579678846 + created: 1738579627440 + url: localhost:4000/user + name: Get all + description: "" + method: GET + body: {} + parameters: [] + headers: + - name: User-Agent + value: insomnia/10.3.0 + authentication: {} + metaSortKey: -1738579627440 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: env_6d55e1e67ec616efee5fe201bb467b88c9064c92 + parentId: wrk_c7ae656895504466aa58ef09f4dcdd60 + modified: 1738579611213 + created: 1738579611213 + name: Base Environment + data: {} + dataPropertyOrder: null + color: null + isPrivate: false + metaSortKey: 1738579611213 + environmentType: kv + _type: environment + - _id: jar_6d55e1e67ec616efee5fe201bb467b88c9064c92 + parentId: wrk_c7ae656895504466aa58ef09f4dcdd60 + modified: 1738579722531 + created: 1738579611215 + name: Default Jar + cookies: + - key: JSESSIONID + value: 40F9A26D27C132281731ECB70992CF1C + domain: localhost + path: / + httpOnly: true + hostOnly: true + creation: 2025-02-03T10:48:42.530Z + lastAccessed: 2025-02-03T10:48:42.530Z + id: 6a2a9d0e-b139-4e7c-822f-b9757847d1df + _type: cookie_jar