first commit

This commit is contained in:
Lucas Saburido
2026-05-13 16:26:45 +01:00
commit cabf2025cd
252 changed files with 13524 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
DB_URL=jdbc:postgresql://localhost:5432/riotz
DB_USERNAME=postgres
DB_PASSWORD=postgres
JWT_ACCESS_SECRET=change-me-access
JWT_REFRESH_SECRET=change-me-refresh
JWT_ACCESS_EXPIRATION_MS=900000
JWT_REFRESH_EXPIRATION_MS=604800000
MEDIA_PROVIDER=s3
MEDIA_BUCKET_NAME=riotz-media

69
backend/riotz-api/pom.xml Normal file
View File

@@ -0,0 +1,69 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
<relativePath/>
</parent>
<groupId>com.riotz</groupId>
<artifactId>riotz-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>riotz-api</name>
<description>RIOTZ backend API</description>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</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>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,12 @@
package com.riotz.api;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RiotzApiApplication {
public static void main(String[] args) {
SpringApplication.run(RiotzApiApplication.class, args);
}
}

View File

@@ -0,0 +1,31 @@
package com.riotz.api.admin.controller;
import com.riotz.api.admin.service.AdminService;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/admin")
public class AdminController {
private final AdminService adminService;
@PatchMapping("/users/{userId}/ban")
public ResponseEntity<Void> banUser(@PathVariable UUID userId) {
adminService.banUser(userId);
return ResponseEntity.noContent().build();
}
@DeleteMapping("/posts/{postId}")
public ResponseEntity<Void> deletePost(@PathVariable UUID postId) {
adminService.deletePost(postId);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,8 @@
package com.riotz.api.admin.service;
import java.util.UUID;
public interface AdminService {
void banUser(UUID userId);
void deletePost(UUID postId);
}

View File

@@ -0,0 +1,33 @@
package com.riotz.api.admin.service;
import com.riotz.api.common.exception.ResourceNotFoundException;
import com.riotz.api.post.repository.PostRepository;
import com.riotz.api.user.repository.UserRepository;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class AdminServiceImpl implements AdminService {
private final UserRepository userRepository;
private final PostRepository postRepository;
@Override
@Transactional
public void banUser(UUID userId) {
final var user = userRepository.findById(userId)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
user.setBanned(true);
}
@Override
public void deletePost(UUID postId) {
if (!postRepository.existsById(postId)) {
throw new ResourceNotFoundException("Post not found");
}
postRepository.deleteById(postId);
}
}

View File

@@ -0,0 +1,21 @@
package com.riotz.api.auth.controller;
import com.riotz.api.auth.service.AuthService;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/auth")
public class AuthController {
private final AuthService authService;
@GetMapping("/health")
public Map<String, String> health() {
return Map.of("status", authService.health());
}
}

View File

@@ -0,0 +1,5 @@
package com.riotz.api.auth.service;
public interface AuthService {
String health();
}

View File

@@ -0,0 +1,12 @@
package com.riotz.api.auth.service;
import org.springframework.stereotype.Service;
@Service
public class AuthServiceImpl implements AuthService {
@Override
public String health() {
return "auth-module-ready";
}
}

View File

@@ -0,0 +1,22 @@
package com.riotz.api.chat.controller;
import com.riotz.api.chat.model.Chat;
import com.riotz.api.chat.service.ChatService;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/chats")
public class ChatController {
private final ChatService chatService;
@GetMapping
public List<Chat> listChats() {
return chatService.listChats();
}
}

View File

@@ -0,0 +1,26 @@
package com.riotz.api.chat.model;
import com.riotz.api.common.model.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(name = "chats")
public class Chat extends BaseEntity {
@Id
private UUID id;
@Column(nullable = false, length = 120)
private String name;
@Column(nullable = false)
private boolean groupChat = true;
}

View File

@@ -0,0 +1,8 @@
package com.riotz.api.chat.repository;
import com.riotz.api.chat.model.Chat;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ChatRepository extends JpaRepository<Chat, UUID> {
}

View File

@@ -0,0 +1,8 @@
package com.riotz.api.chat.service;
import com.riotz.api.chat.model.Chat;
import java.util.List;
public interface ChatService {
List<Chat> listChats();
}

View File

@@ -0,0 +1,19 @@
package com.riotz.api.chat.service;
import com.riotz.api.chat.model.Chat;
import com.riotz.api.chat.repository.ChatRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class ChatServiceImpl implements ChatService {
private final ChatRepository chatRepository;
@Override
public List<Chat> listChats() {
return chatRepository.findAll();
}
}

View File

@@ -0,0 +1,24 @@
package com.riotz.api.comment.controller;
import com.riotz.api.comment.model.Comment;
import com.riotz.api.comment.service.CommentService;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/comments")
public class CommentController {
private final CommentService commentService;
@GetMapping("/post/{postId}")
public List<Comment> listPostComments(@PathVariable UUID postId) {
return commentService.listByPost(postId);
}
}

View File

@@ -0,0 +1,29 @@
package com.riotz.api.comment.model;
import com.riotz.api.common.model.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(name = "comments")
public class Comment extends BaseEntity {
@Id
private UUID id;
@Column(nullable = false)
private UUID postId;
@Column(nullable = false)
private UUID userId;
@Column(nullable = false, length = 1200)
private String content;
}

View File

@@ -0,0 +1,10 @@
package com.riotz.api.comment.repository;
import com.riotz.api.comment.model.Comment;
import java.util.List;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CommentRepository extends JpaRepository<Comment, UUID> {
List<Comment> findByPostIdOrderByCreatedAtAsc(UUID postId);
}

View File

@@ -0,0 +1,9 @@
package com.riotz.api.comment.service;
import com.riotz.api.comment.model.Comment;
import java.util.List;
import java.util.UUID;
public interface CommentService {
List<Comment> listByPost(UUID postId);
}

View File

@@ -0,0 +1,20 @@
package com.riotz.api.comment.service;
import com.riotz.api.comment.model.Comment;
import com.riotz.api.comment.repository.CommentRepository;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class CommentServiceImpl implements CommentService {
private final CommentRepository commentRepository;
@Override
public List<Comment> listByPost(UUID postId) {
return commentRepository.findByPostIdOrderByCreatedAtAsc(postId);
}
}

View File

@@ -0,0 +1,9 @@
package com.riotz.api.common.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@Configuration
@EnableJpaAuditing
public class JpaConfig {
}

View File

@@ -0,0 +1,31 @@
package com.riotz.api.common.exception;
import java.time.OffsetDateTime;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<Map<String, Object>> handleNotFound(ResourceNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of(
"timestamp", OffsetDateTime.now(),
"error", "NOT_FOUND",
"message", ex.getMessage()
));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleValidation(MethodArgumentNotValidException ex) {
return ResponseEntity.badRequest().body(Map.of(
"timestamp", OffsetDateTime.now(),
"error", "VALIDATION_ERROR",
"message", "Invalid request payload"
));
}
}

View File

@@ -0,0 +1,7 @@
package com.riotz.api.common.exception;
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,26 @@
package com.riotz.api.common.model;
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import java.time.OffsetDateTime;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@Getter
@Setter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
@CreatedDate
@Column(nullable = false, updatable = false)
private OffsetDateTime createdAt;
@LastModifiedDate
@Column(nullable = false)
private OffsetDateTime updatedAt;
}

View File

@@ -0,0 +1,24 @@
package com.riotz.api.message.controller;
import com.riotz.api.message.model.Message;
import com.riotz.api.message.service.MessageService;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/messages")
public class MessageController {
private final MessageService messageService;
@GetMapping("/chat/{chatId}")
public List<Message> listByChat(@PathVariable UUID chatId) {
return messageService.listByChat(chatId);
}
}

View File

@@ -0,0 +1,29 @@
package com.riotz.api.message.model;
import com.riotz.api.common.model.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(name = "messages")
public class Message extends BaseEntity {
@Id
private UUID id;
@Column(nullable = false)
private UUID chatId;
@Column(nullable = false)
private UUID senderId;
@Column(nullable = false, length = 1200)
private String content;
}

View File

@@ -0,0 +1,10 @@
package com.riotz.api.message.repository;
import com.riotz.api.message.model.Message;
import java.util.List;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MessageRepository extends JpaRepository<Message, UUID> {
List<Message> findByChatIdOrderByCreatedAtAsc(UUID chatId);
}

View File

@@ -0,0 +1,9 @@
package com.riotz.api.message.service;
import com.riotz.api.message.model.Message;
import java.util.List;
import java.util.UUID;
public interface MessageService {
List<Message> listByChat(UUID chatId);
}

View File

@@ -0,0 +1,20 @@
package com.riotz.api.message.service;
import com.riotz.api.message.model.Message;
import com.riotz.api.message.repository.MessageRepository;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class MessageServiceImpl implements MessageService {
private final MessageRepository messageRepository;
@Override
public List<Message> listByChat(UUID chatId) {
return messageRepository.findByChatIdOrderByCreatedAtAsc(chatId);
}
}

View File

@@ -0,0 +1,22 @@
package com.riotz.api.post.controller;
import com.riotz.api.post.dto.PostResponse;
import com.riotz.api.post.service.PostService;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/posts")
public class PostController {
private final PostService postService;
@GetMapping("/feed")
public List<PostResponse> getFeed() {
return postService.getFeed();
}
}

View File

@@ -0,0 +1,14 @@
package com.riotz.api.post.dto;
import java.time.OffsetDateTime;
import java.util.UUID;
public record PostResponse(
UUID id,
UUID userId,
String caption,
String mediaUrl,
int likesCount,
OffsetDateTime createdAt
) {
}

View File

@@ -0,0 +1,32 @@
package com.riotz.api.post.model;
import com.riotz.api.common.model.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(name = "posts")
public class Post extends BaseEntity {
@Id
private UUID id;
@Column(nullable = false)
private UUID userId;
@Column(length = 2800)
private String caption;
@Column(nullable = false, length = 1024)
private String mediaUrl;
@Column(nullable = false)
private int likesCount = 0;
}

View File

@@ -0,0 +1,11 @@
package com.riotz.api.post.repository;
import com.riotz.api.post.model.Post;
import java.util.List;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostRepository extends JpaRepository<Post, UUID> {
List<Post> findAllByOrderByCreatedAtDesc();
List<Post> findByUserIdOrderByCreatedAtDesc(UUID userId);
}

View File

@@ -0,0 +1,8 @@
package com.riotz.api.post.service;
import com.riotz.api.post.dto.PostResponse;
import java.util.List;
public interface PostService {
List<PostResponse> getFeed();
}

View File

@@ -0,0 +1,31 @@
package com.riotz.api.post.service;
import com.riotz.api.post.dto.PostResponse;
import com.riotz.api.post.model.Post;
import com.riotz.api.post.repository.PostRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class PostServiceImpl implements PostService {
private final PostRepository postRepository;
@Override
public List<PostResponse> getFeed() {
return postRepository.findAllByOrderByCreatedAtDesc().stream().map(this::toResponse).toList();
}
private PostResponse toResponse(Post post) {
return new PostResponse(
post.getId(),
post.getUserId(),
post.getCaption(),
post.getMediaUrl(),
post.getLikesCount(),
post.getCreatedAt()
);
}
}

View File

@@ -0,0 +1,23 @@
package com.riotz.api.profile.controller;
import com.riotz.api.profile.dto.ProfileResponse;
import com.riotz.api.profile.service.ProfileService;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/profiles")
public class ProfileController {
private final ProfileService profileService;
@GetMapping("/{userId}")
public ProfileResponse getProfile(@PathVariable UUID userId) {
return profileService.findByUserId(userId);
}
}

View File

@@ -0,0 +1,10 @@
package com.riotz.api.profile.dto;
import java.util.UUID;
public record ProfileResponse(
UUID userId,
String avatarUrl,
String bio
) {
}

View File

@@ -0,0 +1,26 @@
package com.riotz.api.profile.model;
import com.riotz.api.common.model.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(name = "profiles")
public class Profile extends BaseEntity {
@Id
private UUID userId;
@Column(length = 1024)
private String avatarUrl;
@Column(length = 280)
private String bio;
}

View File

@@ -0,0 +1,8 @@
package com.riotz.api.profile.repository;
import com.riotz.api.profile.model.Profile;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProfileRepository extends JpaRepository<Profile, UUID> {
}

View File

@@ -0,0 +1,8 @@
package com.riotz.api.profile.service;
import com.riotz.api.profile.dto.ProfileResponse;
import java.util.UUID;
public interface ProfileService {
ProfileResponse findByUserId(UUID userId);
}

View File

@@ -0,0 +1,27 @@
package com.riotz.api.profile.service;
import com.riotz.api.common.exception.ResourceNotFoundException;
import com.riotz.api.profile.dto.ProfileResponse;
import com.riotz.api.profile.model.Profile;
import com.riotz.api.profile.repository.ProfileRepository;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class ProfileServiceImpl implements ProfileService {
private final ProfileRepository profileRepository;
@Override
public ProfileResponse findByUserId(UUID userId) {
return profileRepository.findById(userId)
.map(this::toResponse)
.orElseThrow(() -> new ResourceNotFoundException("Profile not found"));
}
private ProfileResponse toResponse(Profile profile) {
return new ProfileResponse(profile.getUserId(), profile.getAvatarUrl(), profile.getBio());
}
}

View File

@@ -0,0 +1,22 @@
package com.riotz.api.security.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.cors(Customizer.withDefaults())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
.build();
}
}

View File

@@ -0,0 +1,24 @@
package com.riotz.api.track.controller;
import com.riotz.api.track.model.Track;
import com.riotz.api.track.service.TrackService;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/tracks")
public class TrackController {
private final TrackService trackService;
@GetMapping("/user/{userId}")
public List<Track> listByUser(@PathVariable UUID userId) {
return trackService.listByUser(userId);
}
}

View File

@@ -0,0 +1,32 @@
package com.riotz.api.track.model;
import com.riotz.api.common.model.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(name = "tracks")
public class Track extends BaseEntity {
@Id
private UUID id;
@Column(nullable = false)
private UUID userId;
@Column(nullable = false, length = 120)
private String title;
@Column(nullable = false, length = 1024)
private String audioUrl;
@Column(nullable = false, length = 256)
private String tags;
}

View File

@@ -0,0 +1,10 @@
package com.riotz.api.track.repository;
import com.riotz.api.track.model.Track;
import java.util.List;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
public interface TrackRepository extends JpaRepository<Track, UUID> {
List<Track> findByUserIdOrderByCreatedAtDesc(UUID userId);
}

View File

@@ -0,0 +1,9 @@
package com.riotz.api.track.service;
import com.riotz.api.track.model.Track;
import java.util.List;
import java.util.UUID;
public interface TrackService {
List<Track> listByUser(UUID userId);
}

View File

@@ -0,0 +1,20 @@
package com.riotz.api.track.service;
import com.riotz.api.track.model.Track;
import com.riotz.api.track.repository.TrackRepository;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class TrackServiceImpl implements TrackService {
private final TrackRepository trackRepository;
@Override
public List<Track> listByUser(UUID userId) {
return trackRepository.findByUserIdOrderByCreatedAtDesc(userId);
}
}

View File

@@ -0,0 +1,29 @@
package com.riotz.api.user.controller;
import com.riotz.api.user.dto.UserResponse;
import com.riotz.api.user.service.UserService;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/users")
public class UserController {
private final UserService userService;
@GetMapping
public List<UserResponse> listUsers() {
return userService.findAll();
}
@GetMapping("/{userId}")
public UserResponse getUser(@PathVariable UUID userId) {
return userService.findById(userId);
}
}

View File

@@ -0,0 +1,13 @@
package com.riotz.api.user.dto;
import com.riotz.api.user.model.Role;
import java.util.UUID;
public record UserResponse(
UUID id,
String email,
String username,
Role role,
boolean banned
) {
}

View File

@@ -0,0 +1,6 @@
package com.riotz.api.user.model;
public enum Role {
USER,
ADMIN
}

View File

@@ -0,0 +1,38 @@
package com.riotz.api.user.model;
import com.riotz.api.common.model.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(name = "users")
public class User extends BaseEntity {
@Id
private UUID id;
@Column(nullable = false, unique = true, length = 255)
private String email;
@Column(nullable = false, length = 60)
private String passwordHash;
@Column(nullable = false, unique = true, length = 32)
private String username;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 16)
private Role role = Role.USER;
@Column(nullable = false)
private boolean banned = false;
}

View File

@@ -0,0 +1,11 @@
package com.riotz.api.user.repository;
import com.riotz.api.user.model.User;
import java.util.Optional;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, UUID> {
Optional<User> findByEmail(String email);
Optional<User> findByUsername(String username);
}

View File

@@ -0,0 +1,10 @@
package com.riotz.api.user.service;
import com.riotz.api.user.dto.UserResponse;
import java.util.List;
import java.util.UUID;
public interface UserService {
List<UserResponse> findAll();
UserResponse findById(UUID userId);
}

View File

@@ -0,0 +1,32 @@
package com.riotz.api.user.service;
import com.riotz.api.common.exception.ResourceNotFoundException;
import com.riotz.api.user.dto.UserResponse;
import com.riotz.api.user.model.User;
import com.riotz.api.user.repository.UserRepository;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
@Override
public List<UserResponse> findAll() {
return userRepository.findAll().stream().map(this::toResponse).toList();
}
@Override
public UserResponse findById(UUID userId) {
return userRepository.findById(userId).map(this::toResponse)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
}
private UserResponse toResponse(User user) {
return new UserResponse(user.getId(), user.getEmail(), user.getUsername(), user.getRole(), user.isBanned());
}
}

View File

@@ -0,0 +1,27 @@
spring:
application:
name: riotz-api
datasource:
url: ${DB_URL:jdbc:postgresql://localhost:5432/riotz}
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:postgres}
jpa:
hibernate:
ddl-auto: validate
properties:
hibernate:
format_sql: true
open-in-view: false
server:
port: ${SERVER_PORT:8080}
jwt:
access-token-secret: ${JWT_ACCESS_SECRET:change-me-access}
refresh-token-secret: ${JWT_REFRESH_SECRET:change-me-refresh}
access-token-expiration-ms: ${JWT_ACCESS_EXPIRATION_MS:900000}
refresh-token-expiration-ms: ${JWT_REFRESH_EXPIRATION_MS:604800000}
media:
provider: ${MEDIA_PROVIDER:s3}
bucket-name: ${MEDIA_BUCKET_NAME:riotz-media}