Skip to content

Commit bb02f99

Browse files
authored
Merge pull request #42 from TP-RENTPLACE/development
Merge MVP backend version
2 parents 463b758 + 70039f8 commit bb02f99

108 files changed

Lines changed: 4321 additions & 198 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/backend-ci-cd.yml

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ on:
1919
- release/**
2020
paths:
2121
- 'rentplace/**'
22+
2223
jobs:
2324
build-and-test:
2425
runs-on: ubuntu-latest
2526
steps:
2627
- uses: actions/checkout@v4
28+
2729
- uses: actions/setup-java@v4
2830
with:
2931
java-version: '17'
@@ -33,16 +35,72 @@ jobs:
3335
- name: Fix gradlew permissions
3436
working-directory: rentplace
3537
run: chmod +x gradlew
36-
38+
3739
- name: Setup Gradle
3840
uses: gradle/actions/setup-gradle@v4
3941
with:
4042
working-directory: rentplace
41-
43+
4244
- name: Build
4345
working-directory: rentplace
4446
run: ./gradlew build --no-daemon
45-
47+
4648
- name: Test
4749
working-directory: rentplace
4850
run: ./gradlew test
51+
52+
build-and-push-docker-image:
53+
runs-on: ubuntu-latest
54+
needs: build-and-test
55+
if: github.ref == 'refs/heads/development' || github.ref == 'refs/heads/main'
56+
steps:
57+
- uses: actions/checkout@v4
58+
59+
- name: Setup Java 17
60+
uses: actions/setup-java@v4
61+
with:
62+
java-version: '17'
63+
distribution: 'temurin'
64+
cache: 'gradle'
65+
66+
- name: Fix gradlew permissions
67+
working-directory: rentplace
68+
run: chmod +x gradlew
69+
70+
- name: Setup Gradle
71+
uses: gradle/actions/setup-gradle@v4
72+
with:
73+
working-directory: rentplace
74+
75+
- name: Build
76+
working-directory: rentplace
77+
run: ./gradlew build --no-daemon
78+
- name: Login to Docker Hub
79+
uses: docker/login-action@v3
80+
with:
81+
username: ${{ secrets.DOCKERHUB_USERNAME }}
82+
password: ${{ secrets.DOCKERHUB_PASSWORD }}
83+
84+
- name: Build Docker image
85+
working-directory: rentplace
86+
run: docker build -t ${{ secrets.DOCKERHUB_BACKEND_IMAGE_NAME }} -f Dockerfile .
87+
88+
- name: Push Docker Image
89+
working-directory: rentplace
90+
run: docker push ${{ secrets.DOCKERHUB_BACKEND_IMAGE_NAME }}
91+
deploy:
92+
runs-on: ubuntu-latest
93+
needs: build-and-push-docker-image
94+
steps:
95+
- name: SSH Execute Commands
96+
uses: appleboy/ssh-action@v1
97+
with:
98+
host: ${{ secrets.SSH_HOST }}
99+
username: ${{ secrets.SSH_USERNAME }}
100+
key: ${{ secrets.SSH_PRIVATE_KEY }}
101+
script: |
102+
cd Deploy
103+
docker compose down
104+
docker rm $(docker ps -a -q)
105+
docker pull ${{ secrets.DOCKERHUB_BACKEND_IMAGE_NAME }}
106+
docker compose up -d --build

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
1-
# Backend
1+
# Backend
2+
3+
## Запуск docker-compose
4+
5+
Находясь в корневой папке проекта:
6+
1. `./gradlew build` - Для сборки проекта
7+
2. `docker build -f Dockerfile .` - Для сборки Dockerfile
8+
3. `docker compose build` - Для сборки docker-compose
9+
4. `docker compose up` - Запуск docker-compose

rentplace/build.gradle

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,33 @@ repositories {
2525

2626
dependencies {
2727
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
28-
//implementation 'org.springframework.boot:spring-boot-starter-security'
2928
runtimeOnly 'org.flywaydb:flyway-database-postgresql:11.2.0'
3029
implementation 'org.flywaydb:flyway-core:11.2.0'
3130
implementation 'org.springframework.boot:spring-boot-starter-web'
3231
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
3332
implementation 'org.flywaydb:flyway-core'
3433

34+
implementation("org.springframework.boot:spring-boot-starter-mail:3.4.3")
35+
36+
implementation("org.springframework.security:spring-security-core:6.4.4")
37+
implementation 'org.springframework.boot:spring-boot-starter-security'
38+
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.6")
39+
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6")
40+
implementation("io.jsonwebtoken:jjwt-api:0.12.6")
41+
3542
compileOnly 'org.projectlombok:lombok'
3643
runtimeOnly 'org.postgresql:postgresql'
3744
runtimeOnly 'com.h2database:h2'
3845
annotationProcessor 'org.projectlombok:lombok'
3946
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
4047

41-
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0")
42-
implementation 'io.swagger.core.v3:swagger-annotations:2.2.22'
48+
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.6")
49+
implementation("io.swagger.core.v3:swagger-annotations:2.2.29")
50+
51+
implementation("org.hibernate.validator:hibernate-validator:8.0.0.Final")
4352

4453
testImplementation 'org.springframework.boot:spring-boot-starter-test'
45-
//testImplementation 'org.springframework.security:spring-security-test'
54+
testImplementation 'org.springframework.security:spring-security-test'
4655

4756
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
4857
testRuntimeOnly 'com.h2database:h2'

rentplace/docker-compose.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ services:
1717
depends_on:
1818
- postgres
1919
environment:
20+
UPLOAD_PATH: /uploads/
2021
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/rentplace_db
2122
SPRING_DATASOURCE_USERNAME: kattsyn
2223
SPRING_DATASOURCE_PASSWORD: katt
2324
SPRING_DATASOURCE_DRIVER_CLASS_NAME: org.postgresql.Driver
24-
SERVER_PORT: 8080
25+
SERVER_PORT: 8080
26+
APP_BASE-URL: http://localhost:8080

rentplace/src/main/java/kattsyn/dev/rentplace/RentPlaceApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import org.springframework.boot.SpringApplication;
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
import org.springframework.context.annotation.PropertySource;
56

67
@SpringBootApplication
8+
@PropertySource("classpath:application.yml")
79
public class RentPlaceApplication {
810

911
public static void main(String[] args) {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package kattsyn.dev.rentplace.auth;
2+
3+
import kattsyn.dev.rentplace.enums.Role;
4+
import lombok.Getter;
5+
import lombok.Setter;
6+
import org.springframework.security.core.Authentication;
7+
import org.springframework.security.core.GrantedAuthority;
8+
9+
import java.util.Collection;
10+
import java.util.List;
11+
12+
@Getter
13+
@Setter
14+
public class JwtAuthentication implements Authentication {
15+
16+
private boolean authenticated;
17+
private String email;
18+
private String name;
19+
private Role role;
20+
21+
@Override
22+
public Collection<? extends GrantedAuthority> getAuthorities() {
23+
return List.of(role);
24+
}
25+
26+
@Override
27+
public Object getCredentials() {
28+
return null;
29+
}
30+
31+
@Override
32+
public Object getDetails() {
33+
return null;
34+
}
35+
36+
@Override
37+
public Object getPrincipal() {
38+
return email;
39+
}
40+
41+
@Override
42+
public boolean isAuthenticated() {
43+
return authenticated;
44+
}
45+
46+
@Override
47+
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
48+
this.authenticated = isAuthenticated;
49+
}
50+
51+
@Override
52+
public String getName() {
53+
return email;
54+
}
55+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package kattsyn.dev.rentplace.auth;
2+
3+
import io.jsonwebtoken.Claims;
4+
import jakarta.servlet.FilterChain;
5+
import jakarta.servlet.ServletException;
6+
import jakarta.servlet.http.HttpServletRequest;
7+
import jakarta.servlet.http.HttpServletResponse;
8+
import kattsyn.dev.rentplace.utils.JwtUtils;
9+
import lombok.NonNull;
10+
import lombok.RequiredArgsConstructor;
11+
import lombok.extern.slf4j.Slf4j;
12+
import org.springframework.security.core.context.SecurityContextHolder;
13+
import org.springframework.stereotype.Component;
14+
import org.springframework.util.StringUtils;
15+
import org.springframework.web.filter.OncePerRequestFilter;
16+
17+
import java.io.IOException;
18+
19+
@RequiredArgsConstructor
20+
@Slf4j
21+
@Component
22+
public class JwtFilter extends OncePerRequestFilter {
23+
24+
private static final String AUTHORIZATION = "Authorization";
25+
26+
private final JwtProvider jwtProvider;
27+
28+
@Override
29+
public void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain)
30+
throws IOException, ServletException {
31+
final String token = getTokenFromRequest(request);
32+
log.info("JWT token: {}", token);
33+
if (token != null && jwtProvider.validateAccessToken(token)) {
34+
final Claims claims = jwtProvider.getAccessClaims(token);
35+
final JwtAuthentication jwtInfoToken = JwtUtils.generate(claims);
36+
log.info(jwtInfoToken.getRole().getAuthority());
37+
jwtInfoToken.setAuthenticated(true);
38+
SecurityContextHolder.getContext().setAuthentication(jwtInfoToken);
39+
}
40+
filterChain.doFilter(request, response);
41+
}
42+
43+
private String getTokenFromRequest(HttpServletRequest request) {
44+
final String bearer = request.getHeader(AUTHORIZATION);
45+
if (StringUtils.hasText(bearer) && bearer.startsWith("Bearer ")) {
46+
return bearer.substring(7);
47+
}
48+
return null;
49+
}
50+
51+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package kattsyn.dev.rentplace.auth;
2+
3+
import io.jsonwebtoken.*;
4+
import io.jsonwebtoken.io.Decoders;
5+
import io.jsonwebtoken.security.Keys;
6+
import io.micrometer.common.lang.NonNull;
7+
import kattsyn.dev.rentplace.entities.User;
8+
import lombok.extern.slf4j.Slf4j;
9+
import org.springframework.stereotype.Component;
10+
11+
import javax.crypto.SecretKey;
12+
import java.time.Instant;
13+
14+
import org.springframework.beans.factory.annotation.Value;
15+
16+
import java.time.LocalDateTime;
17+
import java.time.ZoneId;
18+
import java.util.Date;
19+
20+
@Component
21+
@Slf4j
22+
public class JwtProvider {
23+
24+
private final int accessTokenExpTimeInMinutes;
25+
private final int refreshTokenExpTimeInDays;
26+
27+
private final SecretKey jwtAccessSecret;
28+
private final SecretKey jwtRefreshSecret;
29+
30+
public JwtProvider(
31+
@Value("${jwt.secret.access}") String jwtAccessSecret,
32+
@Value("${jwt.secret.refresh}") String jwtRefreshSecret,
33+
@Value("${jwt.expiration_time_in_minutes.access}") int accessTokenExpTimeInMinutes,
34+
@Value("${jwt.expiration_time_in_days.refresh}") int refreshTokenExpTimeInDays
35+
) {
36+
this.jwtAccessSecret = Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtAccessSecret));
37+
this.jwtRefreshSecret = Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtRefreshSecret));
38+
this.accessTokenExpTimeInMinutes = accessTokenExpTimeInMinutes;
39+
this.refreshTokenExpTimeInDays = refreshTokenExpTimeInDays;
40+
}
41+
42+
public String generateAccessToken(User user) {
43+
final LocalDateTime now = LocalDateTime.now();
44+
final Instant accessExpirationInstant = now.plusMinutes(accessTokenExpTimeInMinutes).atZone(ZoneId.systemDefault()).toInstant();
45+
final Date accessExpiration = Date.from(accessExpirationInstant);
46+
return Jwts.builder()
47+
.subject(user.getEmail())
48+
.expiration(accessExpiration)
49+
.signWith(jwtAccessSecret)
50+
.claim("role", user.getRole().getAuthority())
51+
.claim("name", user.getName())
52+
.compact();
53+
}
54+
55+
public String generateRefreshToken(@NonNull User user) {
56+
final LocalDateTime now = LocalDateTime.now();
57+
final Instant refreshExpirationInstant = now.plusDays(refreshTokenExpTimeInDays).atZone(ZoneId.systemDefault()).toInstant();
58+
final Date refreshExpiration = Date.from(refreshExpirationInstant);
59+
return Jwts.builder()
60+
.subject(user.getEmail())
61+
.issuedAt(Date.from(Instant.now()))
62+
.expiration(refreshExpiration)
63+
.signWith(jwtRefreshSecret)
64+
.compact();
65+
}
66+
67+
public boolean validateAccessToken(@NonNull String accessToken) {
68+
return validateToken(accessToken, jwtAccessSecret);
69+
}
70+
71+
public boolean validateRefreshToken(@NonNull String refreshToken) {
72+
return validateToken(refreshToken, jwtRefreshSecret);
73+
}
74+
75+
private boolean validateToken(@NonNull String token, @NonNull SecretKey secret) {
76+
try {
77+
Jwts.parser()
78+
.verifyWith(secret)
79+
.build()
80+
.parseSignedClaims(token);
81+
return true;
82+
} catch (ExpiredJwtException expEx) {
83+
log.error("Token expired", expEx);
84+
} catch (UnsupportedJwtException unsEx) {
85+
log.error("Unsupported jwt", unsEx);
86+
} catch (MalformedJwtException mjEx) {
87+
log.error("Malformed jwt", mjEx);
88+
} catch (Exception e) {
89+
log.error("invalid token", e);
90+
}
91+
return false;
92+
}
93+
94+
public Claims getAccessClaims(@NonNull String token) {
95+
return getClaims(token, jwtAccessSecret);
96+
}
97+
98+
public Claims getRefreshClaims(@NonNull String token) {
99+
return getClaims(token, jwtRefreshSecret);
100+
}
101+
102+
private Claims getClaims(@NonNull String token, @NonNull SecretKey secret) {
103+
return Jwts.parser()
104+
.verifyWith(secret)
105+
.build()
106+
.parseSignedClaims(token)
107+
.getPayload();
108+
}
109+
110+
}

0 commit comments

Comments
 (0)