Merge pull request #2 from SlimusMinus/added-db

added liquibase db
This commit is contained in:
SlimusMinus
2026-03-30 00:13:34 +03:00
committed by GitHub
27 changed files with 150 additions and 440 deletions

View File

@@ -27,17 +27,12 @@ repositories {
dependencies { dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.liquibase:liquibase-core' implementation 'org.liquibase:liquibase-core'
implementation 'org.springframework.boot:spring-boot-starter-websocket' implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.mapstruct:mapstruct:1.5.5.Final' implementation 'org.mapstruct:mapstruct:1.5.5.Final'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
compileOnly 'org.projectlombok:lombok' compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'

View File

@@ -3,7 +3,7 @@ package com.krylov.refound;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication(scanBasePackages = "com.krylov.refound")
public class ReFoundApplication { public class ReFoundApplication {
public static void main(String[] args) { public static void main(String[] args) {

View File

@@ -1,39 +0,0 @@
package com.krylov.refound.config;
import com.krylov.refound.service.security.JwtFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtFilter jwtFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@@ -2,6 +2,7 @@ package com.krylov.refound.config;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -16,4 +17,13 @@ public class WebConfig implements WebMvcConfigurer {
.addResourceHandler("/files/**") .addResourceHandler("/files/**")
.addResourceLocations("file:" + uploadDir); .addResourceLocations("file:" + uploadDir);
} }
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*") // 🔥 важно
.allowedMethods("*")
.allowedHeaders("*")
.allowCredentials(true);
}
} }

View File

@@ -1,6 +1,5 @@
package com.krylov.refound.config; package com.krylov.refound.config;
import com.krylov.refound.util.JwtHandshakeInterceptor;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
@@ -21,7 +20,6 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
public void registerStompEndpoints(StompEndpointRegistry registry) { public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws") registry.addEndpoint("/ws")
.setAllowedOriginPatterns("*") .setAllowedOriginPatterns("*")
.addInterceptors(new JwtHandshakeInterceptor()) // 🔥 JWT
.withSockJS(); .withSockJS();
} }
} }

View File

@@ -1,27 +0,0 @@
package com.krylov.refound.controller;
import com.krylov.refound.dto.AuthRequest;
import com.krylov.refound.dto.AuthResponse;
import com.krylov.refound.service.security.AuthService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthService service;
@PostMapping("/register")
public AuthResponse register(@RequestBody AuthRequest request) {
return service.register(request);
}
@PostMapping("/login")
public AuthResponse login(@RequestBody AuthRequest request) {
return service.login(request);
}
}

View File

@@ -53,29 +53,4 @@ public interface MessageRepository extends JpaRepository<Message, Long> {
""") """)
void markAsRead(Long userId, Long otherUserId); void markAsRead(Long userId, Long otherUserId);
@Query("""
SELECT new com.krylov.refound.dto.DialogResponse(
CAST(
CASE
WHEN m.sender.id = :userId THEN m.receiver.id
ELSE m.sender.id
END AS long
),
CASE
WHEN m.sender.id = :userId THEN m.receiver.email
ELSE m.sender.email
END,
m.content,
m.createdAt,
SUM(CASE WHEN m.receiver.id = :userId AND m.isRead = false THEN 1 ELSE 0 END)
)
FROM Message m
WHERE m.sender.id = :userId OR m.receiver.id = :userId
GROUP BY
CASE WHEN m.sender.id = :userId THEN m.receiver.id ELSE m.sender.id END,
CASE WHEN m.sender.id = :userId THEN m.receiver.email ELSE m.sender.email END,
m.content,
m.createdAt
""")
List<DialogResponse> findDialogsWithUnread(Long userId);
} }

View File

@@ -8,6 +8,8 @@ import com.krylov.refound.exception.ApiException;
import com.krylov.refound.repository.MessageRepository; import com.krylov.refound.repository.MessageRepository;
import com.krylov.refound.repository.UserRepository; import com.krylov.refound.repository.UserRepository;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@@ -17,7 +19,7 @@ import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class MessageService { public class MessageService {
private final MessageRepository repository; private final MessageRepository messageRepository;
private final UserRepository userRepository; private final UserRepository userRepository;
private final UserService userService; private final UserService userService;
@@ -35,17 +37,40 @@ public class MessageService {
message.setCreatedAt(LocalDateTime.now()); message.setCreatedAt(LocalDateTime.now());
message.setRead(false); message.setRead(false);
return repository.save(message); return messageRepository.save(message);
} }
public List<Message> getChat(Long userId) { public List<Message> getChat(Long userId) {
User current = userService.getCurrentUser(); User current = userService.getCurrentUser();
return repository.findChat(current.getId(), userId); return messageRepository.findChat(current.getId(), userId);
} }
public List<DialogResponse> getDialogs() { public List<DialogResponse> getDialogs() {
User current = userService.getCurrentUser(); Long userId = 1L;
return repository.findDialogsWithUnread(current.getId()); List<Message> dialogs = messageRepository.findDialogs(userId);
return dialogs.stream().map(m -> {
boolean isMeSender = m.getSender().getId().equals(userId);
Long otherUserId = isMeSender
? m.getReceiver().getId()
: m.getSender().getId();
String email = isMeSender
? m.getReceiver().getEmail()
: m.getSender().getEmail();
int unread = messageRepository.countUnread(userId, otherUserId);
return new DialogResponse(
otherUserId,
email,
m.getContent(),
m.getCreatedAt(),
(long )unread
);
}).toList();
} }
@Transactional @Transactional
@@ -53,6 +78,6 @@ public class MessageService {
User current = userService.getCurrentUser(); User current = userService.getCurrentUser();
repository.markAsRead(current.getId(), otherUserId); messageRepository.markAsRead(current.getId(), otherUserId);
} }
} }

View File

@@ -4,7 +4,6 @@ import com.krylov.refound.entity.User;
import com.krylov.refound.enums.ErrorCode; import com.krylov.refound.enums.ErrorCode;
import com.krylov.refound.exception.ApiException; import com.krylov.refound.exception.ApiException;
import com.krylov.refound.repository.UserRepository; import com.krylov.refound.repository.UserRepository;
import com.krylov.refound.util.SecurityUtils;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -15,9 +14,8 @@ public class UserService {
private final UserRepository repository; private final UserRepository repository;
public User getCurrentUser() { public User getCurrentUser() {
String email = SecurityUtils.getCurrentUserEmail();
return repository.findByEmail(email) return repository.findByEmail("moscow_city231@rambler.ru")
.orElseThrow(() -> new ApiException( .orElseThrow(() -> new ApiException(
ErrorCode.NOT_FOUND, ErrorCode.NOT_FOUND,
"User not found", "User not found",

View File

@@ -1,51 +0,0 @@
package com.krylov.refound.service.security;
import com.krylov.refound.dto.AuthRequest;
import com.krylov.refound.dto.AuthResponse;
import com.krylov.refound.entity.User;
import com.krylov.refound.enums.ErrorCode;
import com.krylov.refound.exception.ApiException;
import com.krylov.refound.repository.UserRepository;
import java.time.LocalDateTime;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class AuthService {
private final UserRepository repository;
private final PasswordEncoder passwordEncoder;
private final JwtService jwtService;
public AuthResponse register(AuthRequest request) {
if (repository.findByEmail(request.getEmail()).isPresent()) {
throw new ApiException(ErrorCode.BAD_REQUEST, "Email already exists", HttpStatus.BAD_REQUEST);
}
User user = new User();
user.setEmail(request.getEmail());
user.setPassword(passwordEncoder.encode(request.getPassword()));
user.setCreatedAt(LocalDateTime.now());
repository.save(user);
String token = jwtService.generateToken(user.getEmail());
return new AuthResponse(token);
}
public AuthResponse login(AuthRequest request) {
User user = repository.findByEmail(request.getEmail())
.orElseThrow(() -> new ApiException(ErrorCode.NOT_FOUND, "User not found", HttpStatus.NOT_FOUND));
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
throw new ApiException(ErrorCode.BAD_REQUEST, "Invalid password", HttpStatus.BAD_REQUEST);
}
String token = jwtService.generateToken(user.getEmail());
return new AuthResponse(token);
}
}

View File

@@ -1,27 +0,0 @@
package com.krylov.refound.service.security;
import com.krylov.refound.entity.User;
import com.krylov.refound.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository repository;
@Override
public UserDetails loadUserByUsername(String email) {
User user = repository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return org.springframework.security.core.userdetails.User
.withUsername(user.getEmail())
.password(user.getPassword())
.authorities("USER")
.build();
}
}

View File

@@ -1,55 +0,0 @@
package com.krylov.refound.service.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
@Component
@RequiredArgsConstructor
public class JwtFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final CustomUserDetailsService userDetailsService;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
String token = header.substring(7);
String email = jwtService.extractEmail(token);
if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(email);
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(request, response);
}
}

View File

@@ -1,29 +0,0 @@
package com.krylov.refound.service.security;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import java.util.Date;
import org.springframework.stereotype.Service;
@Service
public class JwtService {
private final String SECRET = "very_secret_key_123";
public String generateToken(String email) {
return Jwts.builder()
.setSubject(email)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 1 день
.signWith(Keys.hmacShaKeyFor(SECRET.getBytes()))
.compact();
}
public String extractEmail(String token) {
return Jwts.parserBuilder()
.setSigningKey(SECRET.getBytes())
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
}
}

View File

@@ -1,46 +0,0 @@
package com.krylov.refound.util;
import java.util.List;
import java.util.Map;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
public class JwtHandshakeInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(
ServerHttpRequest request,
ServerHttpResponse response,
WebSocketHandler wsHandler,
Map<String, Object> attributes
) throws Exception {
List<String> auth = request.getHeaders().get("Authorization");
if (auth == null || auth.isEmpty()) {
return false; // запретить подключение
}
String token = auth.get(0).replace("Bearer ", "");
// 🔥 проверяем JWT
String userEmail = JwtUtils.validateTokenAndGetEmail(token);
if (userEmail == null) {
return false;
}
attributes.put("userEmail", userEmail);
return true;
}
@Override
public void afterHandshake(
ServerHttpRequest request,
ServerHttpResponse response,
WebSocketHandler wsHandler,
Exception exception
) { }
}

View File

@@ -1,23 +0,0 @@
package com.krylov.refound.util;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;
@Component
public class JwtUtils {
private static final String SECRET = "SECRET_KEY"; // заменить на env var
public static String validateTokenAndGetEmail(String token) {
try {
return Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(SECRET.getBytes()))
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
} catch (Exception e) {
return null;
}
}
}

View File

@@ -1,11 +0,0 @@
package com.krylov.refound.util;
import org.springframework.security.core.context.SecurityContextHolder;
public class SecurityUtils {
public static String getCurrentUserEmail() {
return SecurityContextHolder.getContext()
.getAuthentication()
.getName();
}
}

View File

@@ -1,57 +0,0 @@
databaseChangeLog:
- changeSet:
id: 001-create-users
author: you
changes:
- createTable:
tableName: users
columns:
- column: { name: id, type: BIGSERIAL, constraints: { primaryKey: true } }
- column: { name: email, type: VARCHAR(255), constraints: { nullable: false, unique: true } }
- column: { name: password, type: VARCHAR(255), constraints: { nullable: false } }
- column: { name: created_at, type: TIMESTAMP }
- changeSet:
id: 002-create-posts
author: you
changes:
- createTable:
tableName: posts
columns:
- column: { name: id, type: BIGSERIAL, constraints: { primaryKey: true } }
- column: { name: type, type: VARCHAR(20) }
- column: { name: title, type: VARCHAR(255) }
- column: { name: description, type: TEXT }
- column: { name: category, type: VARCHAR(100) }
- column: { name: city, type: VARCHAR(100) }
- column: { name: district, type: VARCHAR(100) }
- column: { name: latitude, type: DOUBLE }
- column: { name: longitude, type: DOUBLE }
- column: { name: status, type: VARCHAR(20) }
- column: { name: created_at, type: TIMESTAMP }
- column: { name: user_id, type: BIGINT }
- addForeignKeyConstraint:
baseTableName: posts
baseColumnNames: user_id
referencedTableName: users
referencedColumnNames: id
constraintName: fk_posts_user # ✅ ДОБАВИТЬ
- changeSet:
id: 003-create-images
author: you
changes:
- createTable:
tableName: images
columns:
- column: { name: id, type: BIGSERIAL, constraints: { primaryKey: true } }
- column: { name: url, type: VARCHAR(500) }
- column: { name: post_id, type: BIGINT }
- addForeignKeyConstraint:
baseTableName: images
baseColumnNames: post_id
referencedTableName: posts
referencedColumnNames: id
constraintName: fk_images_post # ✅

View File

@@ -0,0 +1,11 @@
databaseChangeLog:
- changeSet:
id: 007-constraint-images
author: alexandr krylov
changes:
- addForeignKeyConstraint:
baseTableName: images
baseColumnNames: post_id
referencedTableName: posts
referencedColumnNames: id
constraintName: fk_images_post

View File

@@ -0,0 +1,11 @@
databaseChangeLog:
- changeSet:
id: 006-constraint-post
author: alexandr krylov
changes:
- addForeignKeyConstraint:
baseTableName: posts
baseColumnNames: user_id
referencedTableName: users
referencedColumnNames: id
constraintName: fk_posts_user

View File

@@ -1,31 +1,21 @@
databaseChangeLog: databaseChangeLog:
- changeSet: - changeSet:
id: 004-create-messages id: 008-constraint-message
author: you author: alexandr krylov
changes: changes:
- createTable:
tableName: messages
columns:
- column: { name: id, type: BIGSERIAL, constraints: { primaryKey: true } }
- column: { name: sender_id, type: BIGINT }
- column: { name: receiver_id, type: BIGINT }
- column: { name: content, type: TEXT }
- column: { name: created_at, type: TIMESTAMP }
- column: { name: is_read, type: BOOLEAN }
- addForeignKeyConstraint: - addForeignKeyConstraint:
baseTableName: messages baseTableName: messages
baseColumnNames: sender_id baseColumnNames: sender_id
referencedTableName: users referencedTableName: users
referencedColumnNames: id referencedColumnNames: id
constraintName: fk_messages_sender # ✅ constraintName: fk_messages_sender
- addForeignKeyConstraint: - addForeignKeyConstraint:
baseTableName: messages baseTableName: messages
baseColumnNames: receiver_id baseColumnNames: receiver_id
referencedTableName: users referencedTableName: users
referencedColumnNames: id referencedColumnNames: id
constraintName: fk_messages_receiver # ✅ constraintName: fk_messages_receiver
# 🔥 индекс для чата # 🔥 индекс для чата
- createIndex: - createIndex:

View File

@@ -1,28 +1,21 @@
databaseChangeLog: databaseChangeLog:
- changeSet: - changeSet:
id: 005-create-favorites id: 009-fk-favorite-user
author: you author: alexandr krylov
changes: changes:
- createTable:
tableName: favorites
columns:
- column: { name: id, type: BIGSERIAL, constraints: { primaryKey: true } }
- column: { name: user_id, type: BIGINT }
- column: { name: post_id, type: BIGINT }
- addForeignKeyConstraint: - addForeignKeyConstraint:
baseTableName: favorites baseTableName: favorites
baseColumnNames: user_id baseColumnNames: user_id
referencedTableName: users referencedTableName: users
referencedColumnNames: id referencedColumnNames: id
constraintName: fk_favorites_user # ✅ constraintName: fk_favorites_user
- addForeignKeyConstraint: - addForeignKeyConstraint:
baseTableName: favorites baseTableName: favorites
baseColumnNames: post_id baseColumnNames: post_id
referencedTableName: posts referencedTableName: posts
referencedColumnNames: id referencedColumnNames: id
constraintName: fk_favorites_post # ✅ constraintName: fk_favorites_post
# 🔥 защита от дублей # 🔥 защита от дублей
- addUniqueConstraint: - addUniqueConstraint:

View File

@@ -0,0 +1,12 @@
databaseChangeLog:
- changeSet:
id: 001-create-users
author: you
changes:
- createTable:
tableName: users
columns:
- column: { name: id, type: BIGSERIAL, constraints: { primaryKey: true } }
- column: { name: email, type: VARCHAR(255), constraints: { nullable: false, unique: true } }
- column: { name: password, type: VARCHAR(255), constraints: { nullable: false } }
- column: { name: created_at, type: TIMESTAMP }

View File

@@ -0,0 +1,20 @@
databaseChangeLog:
- changeSet:
id: 005-create-posts
author: you
changes:
- createTable:
tableName: posts
columns:
- column: { name: id, type: BIGSERIAL, constraints: { primaryKey: true } }
- column: { name: type, type: VARCHAR(20) }
- column: { name: title, type: VARCHAR(255) }
- column: { name: description, type: TEXT }
- column: { name: category, type: VARCHAR(100) }
- column: { name: city, type: VARCHAR(100) }
- column: { name: district, type: VARCHAR(100) }
- column: { name: latitude, type: DOUBLE }
- column: { name: longitude, type: DOUBLE }
- column: { name: status, type: VARCHAR(20) }
- column: { name: created_at, type: TIMESTAMP }
- column: { name: user_id, type: BIGINT }

View File

@@ -0,0 +1,11 @@
databaseChangeLog:
- changeSet:
id: 003-create-images
author: you
changes:
- createTable:
tableName: images
columns:
- column: { name: id, type: BIGSERIAL, constraints: { primaryKey: true } }
- column: { name: url, type: VARCHAR(500) }
- column: { name: post_id, type: BIGINT }

View File

@@ -0,0 +1,14 @@
databaseChangeLog:
- changeSet:
id: 004-create-messages
author: you
changes:
- createTable:
tableName: messages
columns:
- column: { name: id, type: BIGSERIAL, constraints: { primaryKey: true } }
- column: { name: sender_id, type: BIGINT }
- column: { name: receiver_id, type: BIGINT }
- column: { name: content, type: TEXT }
- column: { name: created_at, type: TIMESTAMP }
- column: { name: is_read, type: BOOLEAN }

View File

@@ -0,0 +1,11 @@
databaseChangeLog:
- changeSet:
id: 005-create-favorites
author: you
changes:
- createTable:
tableName: favorites
columns:
- column: { name: id, type: BIGSERIAL, constraints: { primaryKey: true } }
- column: { name: user_id, type: BIGINT }
- column: { name: post_id, type: BIGINT }

View File

@@ -1,9 +1,10 @@
databaseChangeLog: databaseChangeLog:
- include: - include: { file: db/changelog/create/001-create-users.yaml }
file: db/changelog/001-init.yaml - include: { file: db/changelog/create/002-create-posts.yaml }
- include: { file: db/changelog/create/003-create-images.yaml }
- include: - include: { file: db/changelog/create/004-create-message.yaml }
file: db/changelog/002-message.yaml - include: { file: db/changelog/create/005-create-favorite.yaml }
- include: { file: db/changelog/constraint/006-fk-images-post-constraint.yaml }
- include: - include: { file: db/changelog/constraint/007-fk-post-user-constraint.yaml }
file: db/changelog/003-favorite.yaml - include: { file: db/changelog/constraint/008-fk-message-user.yaml }
- include: { file: db/changelog/constraint/009-fk-favorite-user.yaml }