Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
a4488e8
Renamed GlobalExceptionHandler
ehottinen Apr 3, 2025
7d50e80
#84 User (#11)
malvamalmgren Apr 3, 2025
2b204e6
recommendation-controller-service-repository (#7)
ehottinen Apr 3, 2025
9f0c41b
reminder-controller-service-repository (#9)
ehottinen Apr 3, 2025
037d6ec
Renamed controller Added RecommendationViewController & HTML template…
ehottinen Apr 3, 2025
c86cf14
cat-controller-service-repository-html (#16)
Minhya Apr 3, 2025
cfbe39f
V2 flyaway and schema
Minhya Apr 3, 2025
a72c0bc
Eventparticipant endpoints (#14)
JenDevs Apr 3, 2025
d72d3e1
Add enum for user roles (#19)
JenDevs Apr 4, 2025
224b283
Catphoto-Controller-Service-Repository (#12)
LinusAltemark Apr 4, 2025
5c37d85
Deleted the duplicates (#21)
LinusAltemark Apr 10, 2025
0167def
Graphql user (#25)
JenDevs Apr 11, 2025
a3f9d63
Created SecurityConfig (#18)
ehottinen Apr 11, 2025
4d01310
Login logout button (#28)
malvamalmgren Apr 14, 2025
ed37b7b
fixing-i18n (#27)
Minhya Apr 14, 2025
6acb144
Translate README to English to trigger GitHub Actions workflow (#24)
JenDevs Apr 14, 2025
af01a56
Graphql cat (#26)
JenDevs Apr 15, 2025
dbffbdc
HTML forms (#23)
malvamalmgren Apr 16, 2025
cd6bde3
Graph event (#30)
JenDevs Apr 16, 2025
ed22828
Graph reminder (#35)
JenDevs Apr 16, 2025
2b43c05
Graph eventparticipant (#31)
JenDevs Apr 16, 2025
4790545
Graph post (#34)
JenDevs Apr 16, 2025
4806270
refactor(user): migrate to DTO-based architecture and enforce role-ba…
ehottinen Apr 17, 2025
47ae80c
Graph catphoto (#33)
JenDevs Apr 17, 2025
1edd982
CSRF (#40)
malvamalmgren Apr 20, 2025
e457745
Cat image api (#39)
JenDevs Apr 20, 2025
737e6f1
comment (#41)
ehottinen Apr 20, 2025
bb60a82
Graph recommendation (#36)
JenDevs Apr 20, 2025
7b64ea6
Find and plan events (#43)
Minhya Apr 20, 2025
40d87f6
Reminder (#42)
Minhya Apr 21, 2025
574f1da
Selection of catbreed gender and limit age (#45)
Minhya Apr 21, 2025
0ff541b
Test user (#47)
JenDevs Apr 22, 2025
aaaf744
Fix participant add and view (by user/event), removed update feature …
JenDevs Apr 22, 2025
38a3a0f
language-updates (#50)
Minhya Apr 22, 2025
6cd9b6e
Refactor: Separate Cat entity from API using DTOs (#37)
ehottinen Apr 22, 2025
9feebc4
Add reminder notification (#51)
malvamalmgren Apr 22, 2025
e2b8bb2
Renamed GlobalExceptionHandler
ehottinen Apr 3, 2025
cc7014b
Merge remote-tracking branch 'origin/rename-globalexceptionhandler' i…
ehottinen Apr 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/CI-Maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ on:
jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Set up JDK 24
Expand All @@ -28,3 +27,5 @@ jobs:

- name: Compile and run tests with Maven
run: mvn clean verify -B --no-transfer-progress
env:
CATAPI_KEY: ${{ secrets.CATAPI_KEY }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
.env

### STS ###
.apt_generated
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 🐱 Catinder

Catinder är ett grupprojekt utvecklat som en del av en laboration i kursen Komplex Javaprogrammering, med fokus på fullstackutveckling i Spring Boot. Applikationen är en social plattform för kattägare där användare kan skapa profiler för sina katter, dela bilder och historier, hitta kattträffar i närheten och få AI-genererade rekommendationer för leksaker, mat och vårdtips.
Catinder is a group project developed as part of a lab assignment in the course Advanced Java Programming, with a focus on full-stack development using Spring Boot.
The application is a social platform for cat owners, where users can create profiles for their cats, share photos and stories, find local cat meetups, and receive AI-generated recommendations for toys, food, and care tips.

The project also includes a REST API, GraphQL endpoints, and a responsive web interface built with Thymeleaf. It uses role-based authorization, GitHub Actions for CI, and is developed collaboratively by students using Git and GitHub.

13 changes: 12 additions & 1 deletion compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,15 @@ services:
- 'POSTGRES_PASSWORD=secret'
- 'POSTGRES_USER=myuser'
ports:
- '5432'
- '5432:5432'
app:
depends_on:
- postgres
image: 'catinder:latest'
env_file:
- .env
environment:
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
ports:
- "8080:8080"
96 changes: 73 additions & 23 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.projectlombok</groupId>-->
<!-- <artifactId>lombok</artifactId>-->
<!-- <optional>true</optional>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.projectlombok</groupId>-->
<!-- <artifactId>lombok</artifactId>-->
<!-- <optional>true</optional>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand All @@ -97,21 +97,71 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<dependency>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>flexmark-all</artifactId>
<version>0.64.8</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.graphql</groupId>
<artifactId>spring-graphql-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-extended-scalars</artifactId>
<version>20.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>java-dotenv</artifactId>
<version>5.2.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<!-- <configuration>-->
<!-- <annotationProcessorPaths>-->
<!-- <path>-->
<!-- <groupId>org.projectlombok</groupId>-->
<!-- <artifactId>lombok</artifactId>-->
<!-- </path>-->
<!-- </annotationProcessorPaths>-->
<!-- </configuration>-->
<!-- <configuration>-->
<!-- <annotationProcessorPaths>-->
<!-- <path>-->
<!-- <groupId>org.projectlombok</groupId>-->
<!-- <artifactId>lombok</artifactId>-->
<!-- </path>-->
<!-- </annotationProcessorPaths>-->
<!-- </configuration>-->
</plugin>
<plugin>
<groupId>org.hibernate.orm.tooling</groupId>
Expand All @@ -138,15 +188,15 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- <configuration>-->
<!-- <excludes>-->
<!-- <exclude>-->
<!-- <groupId>org.projectlombok</groupId>-->
<!-- <artifactId>lombok</artifactId>-->
<!-- -->
<!-- </exclude>-->
<!-- </excludes>-->
<!-- </configuration>-->
<!-- <configuration>-->
<!-- <excludes>-->
<!-- <exclude>-->
<!-- <groupId>org.projectlombok</groupId>-->
<!-- <artifactId>lombok</artifactId>-->
<!-- -->
<!-- </exclude>-->
<!-- </excludes>-->
<!-- </configuration>-->
</plugin>
</plugins>
</build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

@SpringBootApplication
@EnableWebSecurity
public class Springboot25Application {

public static void main(String[] args) {
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/org/example/springboot25/config/GraphQLConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.example.springboot25.config;

import graphql.scalars.ExtendedScalars;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;

@Configuration
public class GraphQLConfig {

@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer() {
return builder -> builder.scalar(ExtendedScalars.DateTime);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.example.springboot25.config;

import org.springframework.format.Formatter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class LocalDateTimeFormatter implements Formatter<LocalDateTime> {

private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm");

@Override
public LocalDateTime parse(String text, Locale locale) {
return LocalDateTime.parse(text, FORMATTER);
}

@Override
public String print(LocalDateTime object, Locale locale) {
return object.format(FORMATTER);
}
}
14 changes: 14 additions & 0 deletions src/main/java/org/example/springboot25/config/PasswordConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.example.springboot25.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class PasswordConfig {
@Bean
public static PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
72 changes: 72 additions & 0 deletions src/main/java/org/example/springboot25/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.example.springboot25.config;

import org.example.springboot25.security.CustomOAuth2UserService;
import org.example.springboot25.security.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Configuration
@EnableMethodSecurity
public class SecurityConfig {

private final CustomOAuth2UserService customOAuth2UserService;
private static final Logger log = LoggerFactory.getLogger(SecurityConfig.class);

public SecurityConfig(CustomOAuth2UserService customOAuth2UserService) {
this.customOAuth2UserService = customOAuth2UserService;
}

@Value("${remember.me.key:myLongTermRememberMeKey}")
private String rememberMeKey;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(Customizer.withDefaults())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/premium/**").hasRole("PREMIUM")
.requestMatchers("/user/**").hasAnyRole("BASIC", "PREMIUM")
.requestMatchers("/", "/about","/contact", "/register", "/css/**", "/js/**", "/images/**", "/error/**").permitAll()
.anyRequest().authenticated()
)
.rememberMe(remember -> remember
.key(rememberMeKey)
.tokenValiditySeconds(7 * 24 * 60 * 60) // 1 vecka
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/") // Redirect to homepage after logout
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll()
)
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService))
.successHandler((_req, res, auth) -> {
boolean isAdmin = auth.getAuthorities().stream()
.anyMatch(a -> a.getAuthority().equals("ADMIN"));
res.sendRedirect("/");
})
)
.build();
}

@Bean
public AuthenticationManager authenticationManager(HttpSecurity http, CustomUserDetailsService customUserDetailsService) throws Exception {
AuthenticationManagerBuilder builder = http.getSharedObject(AuthenticationManagerBuilder.class);
builder
.userDetailsService(customUserDetailsService)
.passwordEncoder(PasswordConfig.passwordEncoder());
return builder.build();
}
}
14 changes: 14 additions & 0 deletions src/main/java/org/example/springboot25/config/ThymeleafConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.example.springboot25.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.thymeleaf.extras.springsecurity6.dialect.SpringSecurityDialect;

@Configuration
public class ThymeleafConfig {

@Bean
public SpringSecurityDialect springSecurityDialect() {
return new SpringSecurityDialect();
}
}
48 changes: 48 additions & 0 deletions src/main/java/org/example/springboot25/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.example.springboot25.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

import java.util.Locale;

@Configuration
//@EnableWebMvc satans påfund
public class WebConfig implements WebMvcConfigurer {

@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new LocalDateTimeFormatter());
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry){
registry.addResourceHandler("/css/**")
.addResourceLocations("classpath:/static/css/");
}

@Bean(name = "localeResolver")
public LocaleResolver localeResolver() {
SessionLocaleResolver localeResolver = new SessionLocaleResolver();
localeResolver.setDefaultLocale(Locale.ENGLISH);
return localeResolver;
}

@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
interceptor.setParamName("lang");
return interceptor;
}

@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(localeChangeInterceptor());
}
}
Loading