diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/config/SecurityConfig.java b/backend/src/main/java/es/adeptusminiaturium/backend/config/SecurityConfig.java new file mode 100644 index 0000000..5513e38 --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/config/SecurityConfig.java @@ -0,0 +1,53 @@ +package es.adeptusminiaturium.backend.config; + +import es.adeptusminiaturium.backend.service.CustomUserDetailsService; +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.web.builders.HttpSecurity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +public class SecurityConfig { + + private final CustomUserDetailsService userDetailsService; + + public SecurityConfig(CustomUserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf(Customizer.withDefaults()) + .authorizeHttpRequests(auth -> auth + //.requestMatchers("/admin/**").hasRole("ADMIN") + //.requestMatchers("/posts/**").authenticated() + .anyRequest().permitAll() + ) + .formLogin(Customizer.withDefaults()) + .httpBasic(Customizer.withDefaults()); + + return http.build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(12); + } + + @Bean + public AuthenticationManager authManager(HttpSecurity http) { + AuthenticationManagerBuilder authBuilder = + http.getSharedObject(AuthenticationManagerBuilder.class); + + authBuilder.userDetailsService(userDetailsService) + .passwordEncoder(passwordEncoder()); + + return authBuilder.build(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/controller/MediaController.java b/backend/src/main/java/es/adeptusminiaturium/backend/controller/MediaController.java new file mode 100644 index 0000000..4e721dc --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/controller/MediaController.java @@ -0,0 +1,43 @@ +package es.adeptusminiaturium.backend.controller; + +import es.adeptusminiaturium.backend.dto.MediaDto; +import es.adeptusminiaturium.backend.service.MediaService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/media") +public class MediaController { + + private final MediaService service; + + public MediaController(MediaService service) { this.service = service; } + + @GetMapping + public ResponseEntity> getAll() { + return ResponseEntity.ok(service.getAll()); + } + + @GetMapping("/{id}") + public ResponseEntity getOne(@PathVariable Long id) { + return ResponseEntity.ofNullable(service.getById(id)); + } + + @PostMapping + public ResponseEntity create(@RequestBody MediaDto.Request dto) { + return ResponseEntity.ok(service.create(dto)); + } + + @PutMapping("/{id}") + public ResponseEntity update(@PathVariable Long id, @RequestBody MediaDto.Request dto) { + return ResponseEntity.ofNullable(service.update(id, dto)); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable Long id) { + service.delete(id); + return ResponseEntity.noContent().build(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/controller/PostController.java b/backend/src/main/java/es/adeptusminiaturium/backend/controller/PostController.java new file mode 100644 index 0000000..49904df --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/controller/PostController.java @@ -0,0 +1,38 @@ +package es.adeptusminiaturium.backend.controller; + +import es.adeptusminiaturium.backend.service.PostService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import es.adeptusminiaturium.backend.dto.PostDto; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/posts") +public class PostController { + + private final PostService postService; + + public PostController(PostService postService) { + this.postService = postService; + } + + @GetMapping + public ResponseEntity> getAll() { + return ResponseEntity.ok(postService.getAllPosts()); + } + + @GetMapping("/{id}") + public ResponseEntity getById(@PathVariable Long id) { + return ResponseEntity.ok(postService.getPostById(id)); + } + + @PostMapping + public ResponseEntity create(@RequestBody PostDto.Request dto) { + return ResponseEntity.ok(postService.createPost(dto)); + } +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/controller/PublicationController.java b/backend/src/main/java/es/adeptusminiaturium/backend/controller/PublicationController.java new file mode 100644 index 0000000..ff7dc08 --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/controller/PublicationController.java @@ -0,0 +1,43 @@ +package es.adeptusminiaturium.backend.controller; + +import es.adeptusminiaturium.backend.dto.PublicationDto; +import es.adeptusminiaturium.backend.service.PublicationService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/publications") +public class PublicationController { + + private final PublicationService service; + + public PublicationController(PublicationService service) { this.service = service; } + + @GetMapping + public ResponseEntity> getAll() { + return ResponseEntity.ok(service.getAll()); + } + + @GetMapping("/{id}") + public ResponseEntity getOne(@PathVariable Long id) { + return ResponseEntity.ofNullable(service.getById(id)); + } + + @PostMapping + public ResponseEntity create(@RequestBody PublicationDto.Request dto) { + return ResponseEntity.ok(service.create(dto)); + } + + @PutMapping("/{id}") + public ResponseEntity update(@PathVariable Long id, @RequestBody PublicationDto.Request dto) { + return ResponseEntity.ofNullable(service.update(id, dto)); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable Long id) { + service.delete(id); + return ResponseEntity.noContent().build(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/controller/UserController.java b/backend/src/main/java/es/adeptusminiaturium/backend/controller/UserController.java new file mode 100644 index 0000000..1ec4f15 --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/controller/UserController.java @@ -0,0 +1,47 @@ +package es.adeptusminiaturium.backend.controller; + +import es.adeptusminiaturium.backend.service.UserService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +import es.adeptusminiaturium.backend.dto.UserDto; +import org.springframework.web.bind.annotation.*; + + +@RestController +@RequestMapping("/api/users") +public class UserController { + + private final UserService service; + + public UserController(UserService service) { this.service = service; } + + @GetMapping + public ResponseEntity> getAll() { + return ResponseEntity.ok(service.getAllUsers()); + } + + @GetMapping("/{id}") + public ResponseEntity getOne(@PathVariable UUID id) { + return ResponseEntity.ofNullable(service.getUser(id)); + } + + @PostMapping + public ResponseEntity create(@RequestBody UserDto.Request dto) { + return ResponseEntity.ok(service.createUser(dto)); + } + + @PutMapping("/{id}") + public ResponseEntity update(@PathVariable UUID id, @RequestBody UserDto.Request dto) { + return ResponseEntity.ofNullable(service.updateUser(id, dto)); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable UUID id) { + service.deleteUser(id); + return ResponseEntity.noContent().build(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/dto/MediaDto.java b/backend/src/main/java/es/adeptusminiaturium/backend/dto/MediaDto.java new file mode 100644 index 0000000..dc3081c --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/dto/MediaDto.java @@ -0,0 +1,46 @@ +package es.adeptusminiaturium.backend.dto; + +import es.adeptusminiaturium.backend.enums.MediaType; + +import java.time.LocalDateTime; + +public class MediaDto { + + public static class Request { + private Long postId; + private MediaType mediaType; + private String url; + private Integer position; + + public Long getPostId() { return postId; } + public void setPostId(Long postId) { this.postId = postId; } + public MediaType getMediaType() { return mediaType; } + public void setMediaType(MediaType mediaType) { this.mediaType = mediaType; } + public String getUrl() { return url; } + public void setUrl(String url) { this.url = url; } + public Integer getPosition() { return position; } + public void setPosition(Integer position) { this.position = position; } + } + + public static class Response { + private Long mediaId; + private PostDto.Response post; + private MediaType mediaType; + private String url; + private Integer position; + private LocalDateTime createdAt; + + public Long getMediaId() { return mediaId; } + public void setMediaId(Long mediaId) { this.mediaId = mediaId; } + public PostDto.Response getPost() { return post; } + public void setPost(PostDto.Response post) { this.post = post; } + public MediaType getMediaType() { return mediaType; } + public void setMediaType(MediaType mediaType) { this.mediaType = mediaType; } + public String getUrl() { return url; } + public void setUrl(String url) { this.url = url; } + public Integer getPosition() { return position; } + public void setPosition(Integer position) { this.position = position; } + public LocalDateTime getCreatedAt() { return createdAt; } + public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } + } +} diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/dto/PostDto.java b/backend/src/main/java/es/adeptusminiaturium/backend/dto/PostDto.java new file mode 100644 index 0000000..07ef964 --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/dto/PostDto.java @@ -0,0 +1,61 @@ +package es.adeptusminiaturium.backend.dto; + +import es.adeptusminiaturium.backend.enums.PostStatus; + +import java.time.LocalDateTime; + +import java.time.LocalDateTime; +import java.util.UUID; + +public class PostDto { + + public static class Request { + private UUID authorId; + private String title; + private String body; + private String hashtags; + private PostStatus status; + + public UUID getAuthorId() { return authorId; } + public void setAuthorId(UUID authorId) { this.authorId = authorId; } + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + public String getBody() { return body; } + public void setBody(String body) { this.body = body; } + public String getHashtags() { return hashtags; } + public void setHashtags(String hashtags) { this.hashtags = hashtags; } + public PostStatus getStatus() { return status; } + public void setStatus(PostStatus status) { this.status = status; } + } + + public static class Response { + private Long postId; + private UserDto.Response author; + private String title; + private String body; + private String hashtags; + private PostStatus status; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + private LocalDateTime publishedAt; + + public Long getPostId() { return postId; } + public void setPostId(Long postId) { this.postId = postId; } + public UserDto.Response getAuthor() { return author; } + public void setAuthor(UserDto.Response author) { this.author = author; } + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + public String getBody() { return body; } + public void setBody(String body) { this.body = body; } + public String getHashtags() { return hashtags; } + public void setHashtags(String hashtags) { this.hashtags = hashtags; } + public PostStatus getStatus() { return status; } + public void setStatus(PostStatus status) { this.status = status; } + public LocalDateTime getCreatedAt() { return createdAt; } + public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } + public LocalDateTime getUpdatedAt() { return updatedAt; } + public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } + public LocalDateTime getPublishedAt() { return publishedAt; } + public void setPublishedAt(LocalDateTime publishedAt) { this.publishedAt = publishedAt; } + } +} diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/dto/PublicationDto.java b/backend/src/main/java/es/adeptusminiaturium/backend/dto/PublicationDto.java new file mode 100644 index 0000000..0216d72 --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/dto/PublicationDto.java @@ -0,0 +1,55 @@ +package es.adeptusminiaturium.backend.dto; + +import es.adeptusminiaturium.backend.enums.Platform; +import es.adeptusminiaturium.backend.enums.PublicationStatus; + +import java.time.LocalDateTime; + +public class PublicationDto { + + public static class Request { + private Long postId; + private Platform platform; + private String externalId; + private PublicationStatus status; + + // getters y setters + public Long getPostId() { return postId; } + public void setPostId(Long postId) { this.postId = postId; } + public Platform getPlatform() { return platform; } + public void setPlatform(Platform platform) { this.platform = platform; } + public String getExternalId() { return externalId; } + public void setExternalId(String externalId) { this.externalId = externalId; } + public PublicationStatus getStatus() { return status; } + public void setStatus(PublicationStatus status) { this.status = status; } + } + + public static class Response { + private Long publicationId; + private PostDto.Response post; + private Platform platform; + private String externalId; + private PublicationStatus status; + private LocalDateTime publishedAt; + private String errorMessage; + private LocalDateTime createdAt; + + // getters y setters + public Long getPublicationId() { return publicationId; } + public void setPublicationId(Long publicationId) { this.publicationId = publicationId; } + public PostDto.Response getPost() { return post; } + public void setPost(PostDto.Response post) { this.post = post; } + public Platform getPlatform() { return platform; } + public void setPlatform(Platform platform) { this.platform = platform; } + public String getExternalId() { return externalId; } + public void setExternalId(String externalId) { this.externalId = externalId; } + public PublicationStatus getStatus() { return status; } + public void setStatus(PublicationStatus status) { this.status = status; } + public LocalDateTime getPublishedAt() { return publishedAt; } + public void setPublishedAt(LocalDateTime publishedAt) { this.publishedAt = publishedAt; } + public String getErrorMessage() { return errorMessage; } + public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; } + public LocalDateTime getCreatedAt() { return createdAt; } + public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } + } +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/dto/UserDto.java b/backend/src/main/java/es/adeptusminiaturium/backend/dto/UserDto.java new file mode 100644 index 0000000..5a0cefe --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/dto/UserDto.java @@ -0,0 +1,63 @@ +package es.adeptusminiaturium.backend.dto; + +import es.adeptusminiaturium.backend.enums.UserRole; +import es.adeptusminiaturium.backend.enums.UserStatus; + +import java.time.LocalDateTime; +import java.util.UUID; + +import java.time.Instant; +import java.util.UUID; + +public class UserDto { + + public static class Request { + private String displayName; + private String userName; + private String password; + private String avatar; + private UserRole role; + private UserStatus status; + + public String getDisplayName() { return displayName; } + public void setDisplayName(String displayName) { this.displayName = displayName; } + public String getUserName() { return userName; } + public void setUserName(String userName) { this.userName = userName; } + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } + public String getAvatar() { return avatar; } + public void setAvatar(String avatar) { this.avatar = avatar; } + public UserRole getRole() { return role; } + public void setRole(UserRole role) { this.role = role; } + public UserStatus getStatus() { return status; } + public void setStatus(UserStatus status) { this.status = status; } + } + + public static class Response { + private UUID userId; + private String displayName; + private String userName; + private String avatar; + private UserRole role; + private UserStatus status; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + public UUID getUserId() { return userId; } + public void setUserId(UUID userId) { this.userId = userId; } + public String getDisplayName() { return displayName; } + public void setDisplayName(String displayName) { this.displayName = displayName; } + public String getUserName() { return userName; } + public void setUserName(String userName) { this.userName = userName; } + public String getAvatar() { return avatar; } + public void setAvatar(String avatar) { this.avatar = avatar; } + public UserRole getRole() { return role; } + public void setRole(UserRole role) { this.role = role; } + public UserStatus getStatus() { return status; } + public void setStatus(UserStatus status) { this.status = status; } + public LocalDateTime getCreatedAt() { return createdAt; } + public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } + public LocalDateTime getUpdatedAt() { return updatedAt; } + public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } + } +} diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/enums/MediaType.java b/backend/src/main/java/es/adeptusminiaturium/backend/enums/MediaType.java new file mode 100644 index 0000000..ae5c53e --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/enums/MediaType.java @@ -0,0 +1,6 @@ +package es.adeptusminiaturium.backend.enums; + +public enum MediaType { + IMAGE, // 0 + VIDEO // 1 +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/enums/Platform.java b/backend/src/main/java/es/adeptusminiaturium/backend/enums/Platform.java new file mode 100644 index 0000000..3c8d016 --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/enums/Platform.java @@ -0,0 +1,14 @@ +package es.adeptusminiaturium.backend.enums; + +import java.util.List; + +public enum Platform { + WEB, // 0 + INSTAGRAM, // 1 + TIKTOK, // 2 + TWITTER; // 3 + + public static List valuesList() { + return List.of(values()); + } +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/enums/PostStatus.java b/backend/src/main/java/es/adeptusminiaturium/backend/enums/PostStatus.java new file mode 100644 index 0000000..55dda48 --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/enums/PostStatus.java @@ -0,0 +1,7 @@ +package es.adeptusminiaturium.backend.enums; + +public enum PostStatus { + DRAFT, // 0 + PUBLISHED, // 1 + ARCHIVED // 2 +} diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/enums/PublicationStatus.java b/backend/src/main/java/es/adeptusminiaturium/backend/enums/PublicationStatus.java new file mode 100644 index 0000000..dede07a --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/enums/PublicationStatus.java @@ -0,0 +1,7 @@ +package es.adeptusminiaturium.backend.enums; + +public enum PublicationStatus { + PENDING, // 0 + PUBLISHED, // 1 + FAILED // 2 +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/enums/UserRole.java b/backend/src/main/java/es/adeptusminiaturium/backend/enums/UserRole.java new file mode 100644 index 0000000..dab8299 --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/enums/UserRole.java @@ -0,0 +1,6 @@ +package es.adeptusminiaturium.backend.enums; + +public enum UserRole { + USER, // 0 + ADMIN // 1 +} diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/enums/UserStatus.java b/backend/src/main/java/es/adeptusminiaturium/backend/enums/UserStatus.java new file mode 100644 index 0000000..fb58ebb --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/enums/UserStatus.java @@ -0,0 +1,6 @@ +package es.adeptusminiaturium.backend.enums; + +public enum UserStatus { + INACTIVE, // 0 + ACTIVE // 1 +} diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/mapper/MediaMapper.java b/backend/src/main/java/es/adeptusminiaturium/backend/mapper/MediaMapper.java new file mode 100644 index 0000000..99caefe --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/mapper/MediaMapper.java @@ -0,0 +1,31 @@ +package es.adeptusminiaturium.backend.mapper; + +import es.adeptusminiaturium.backend.dto.MediaDto; +import es.adeptusminiaturium.backend.model.Media; + +import java.time.LocalDateTime; + +public class MediaMapper { + + public static MediaDto.Response toResponse(Media entity) { + MediaDto.Response dto = new MediaDto.Response(); + dto.setMediaId(entity.getMediaId()); + dto.setPost(PostMapper.toResponse(entity.getPost())); + dto.setMediaType(entity.getMediaType()); + dto.setUrl(entity.getUrl()); + dto.setPosition(entity.getPosition()); + dto.setCreatedAt(entity.getCreatedAt()); + return dto; + } + + public static Media toEntity(MediaDto.Request dto) { + Media entity = new Media(); + entity.setMediaId(null); // autoincrement + entity.setPost(null); // se setea después con repo.findById(dto.getPostId()) + entity.setMediaType(dto.getMediaType()); + entity.setUrl(dto.getUrl()); + entity.setPosition(dto.getPosition()); + entity.setCreatedAt(LocalDateTime.now()); + return entity; + } +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/mapper/PostMapper.java b/backend/src/main/java/es/adeptusminiaturium/backend/mapper/PostMapper.java new file mode 100644 index 0000000..baf7d42 --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/mapper/PostMapper.java @@ -0,0 +1,39 @@ +package es.adeptusminiaturium.backend.mapper; + +import es.adeptusminiaturium.backend.dto.PostDto; +import es.adeptusminiaturium.backend.dto.UserDto; +import es.adeptusminiaturium.backend.model.Post; + +import java.time.Instant; +import java.time.LocalDateTime; + +public class PostMapper { + + public static PostDto.Response toResponse(Post entity) { + PostDto.Response dto = new PostDto.Response(); + dto.setPostId(entity.getPostId()); + dto.setAuthor(UserMapper.toResponse(entity.getAuthor())); + dto.setTitle(entity.getTitle()); + dto.setBody(entity.getBody()); + dto.setHashtags(entity.getHashtags()); + dto.setStatus(entity.getStatus()); + dto.setCreatedAt(entity.getCreatedAt()); + dto.setUpdatedAt(entity.getUpdatedAt()); + dto.setPublishedAt(entity.getPublishedAt()); + return dto; + } + + public static Post toEntity(PostDto.Request dto) { + Post entity = new Post(); + entity.setPostId(null); + entity.setAuthor(null); + entity.setTitle(dto.getTitle()); + entity.setBody(dto.getBody()); + entity.setHashtags(dto.getHashtags()); + entity.setStatus(dto.getStatus()); + entity.setCreatedAt(LocalDateTime.now()); + entity.setUpdatedAt(LocalDateTime.now()); + entity.setPublishedAt(null); + return entity; + } +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/mapper/PublicationMapper.java b/backend/src/main/java/es/adeptusminiaturium/backend/mapper/PublicationMapper.java new file mode 100644 index 0000000..b2b7e45 --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/mapper/PublicationMapper.java @@ -0,0 +1,35 @@ +package es.adeptusminiaturium.backend.mapper; + +import es.adeptusminiaturium.backend.dto.PublicationDto; +import es.adeptusminiaturium.backend.model.Publication; + +import java.time.LocalDateTime; + +public class PublicationMapper { + + public static PublicationDto.Response toResponse(Publication entity) { + PublicationDto.Response dto = new PublicationDto.Response(); + dto.setPublicationId(entity.getPublicationId()); + dto.setPost(PostMapper.toResponse(entity.getPost())); + dto.setPlatform(entity.getPlatform()); + dto.setExternalId(entity.getExternalId()); + dto.setStatus(entity.getStatus()); + dto.setPublishedAt(entity.getPublishedAt()); + dto.setErrorMessage(entity.getErrorMessage()); + dto.setCreatedAt(entity.getCreatedAt()); + return dto; + } + + public static Publication toEntity(PublicationDto.Request dto) { + Publication entity = new Publication(); + entity.setPublicationId(null); + entity.setPost(null); + entity.setPlatform(dto.getPlatform()); + entity.setExternalId(dto.getExternalId()); + entity.setStatus(dto.getStatus()); + entity.setPublishedAt(null); + entity.setErrorMessage(null); + entity.setCreatedAt(LocalDateTime.now()); + return entity; + } +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/mapper/UserMapper.java b/backend/src/main/java/es/adeptusminiaturium/backend/mapper/UserMapper.java new file mode 100644 index 0000000..3fb4b7e --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/mapper/UserMapper.java @@ -0,0 +1,38 @@ +package es.adeptusminiaturium.backend.mapper; + +import es.adeptusminiaturium.backend.dto.UserDto; +import es.adeptusminiaturium.backend.model.User; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.UUID; + +public class UserMapper { + + public static UserDto.Response toResponse(User entity) { + UserDto.Response dto = new UserDto.Response(); + dto.setUserId(entity.getUserId()); + dto.setDisplayName(entity.getDisplayName()); + dto.setUserName(entity.getUserName()); + dto.setAvatar(entity.getAvatar()); + dto.setRole(entity.getRole()); + dto.setStatus(entity.getStatus()); + dto.setCreatedAt(entity.getCreatedAt()); + dto.setUpdatedAt(entity.getUpdatedAt()); + return dto; + } + + public static User toEntity(UserDto.Request dto) { + User entity = new User(); + entity.setUserId(UUID.randomUUID()); + entity.setDisplayName(dto.getDisplayName()); + entity.setUserName(dto.getUserName()); + entity.setPassword(dto.getPassword()); + entity.setAvatar(dto.getAvatar()); + entity.setRole(dto.getRole()); + entity.setStatus(dto.getStatus()); + entity.setCreatedAt(LocalDateTime.now()); + entity.setUpdatedAt(LocalDateTime.now()); + return entity; + } +} diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/model/Media.java b/backend/src/main/java/es/adeptusminiaturium/backend/model/Media.java index 5a651d7..f6a9e04 100644 --- a/backend/src/main/java/es/adeptusminiaturium/backend/model/Media.java +++ b/backend/src/main/java/es/adeptusminiaturium/backend/model/Media.java @@ -1,4 +1,86 @@ package es.adeptusminiaturium.backend.model; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import es.adeptusminiaturium.backend.enums.MediaType; +import jakarta.persistence.*; +import org.hibernate.annotations.CreationTimestamp; + +import java.time.LocalDateTime; +import java.time.LocalDateTime; + +@Entity +@Table(name = "media") public class Media { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "media_id") + private Long mediaId; + + @JsonManagedReference + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id", nullable = false) + private Post post; + + @Enumerated(EnumType.ORDINAL) + @Column(name = "media_type", nullable = false) + private MediaType mediaType = MediaType.IMAGE; + + @Column(nullable = false, length = 512) + private String url; + + @Column(nullable = false) + private Integer position = 0; + + @CreationTimestamp + @Column(name = "created_at", insertable = false, updatable = false) + private LocalDateTime createdAt; + + public Long getMediaId() { + return mediaId; + } + + public void setMediaId(Long mediaId) { + this.mediaId = mediaId; + } + + public Post getPost() { + return post; + } + + public void setPost(Post post) { + this.post = post; + } + + public MediaType getMediaType() { + return mediaType; + } + + public void setMediaType(MediaType mediaType) { + this.mediaType = mediaType; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public Integer getPosition() { + return position; + } + + public void setPosition(Integer position) { + this.position = position; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } } diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/model/Post.java b/backend/src/main/java/es/adeptusminiaturium/backend/model/Post.java index abe1325..7f354ee 100644 --- a/backend/src/main/java/es/adeptusminiaturium/backend/model/Post.java +++ b/backend/src/main/java/es/adeptusminiaturium/backend/model/Post.java @@ -1,4 +1,143 @@ package es.adeptusminiaturium.backend.model; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import es.adeptusminiaturium.backend.enums.PostStatus; +import jakarta.persistence.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; +import java.util.List; + +@Entity +@Table(name = "posts") public class Post { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "post_id") + private Long postId; + + @JsonManagedReference + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "author_id", nullable = false) + private User author; + + @Column(nullable = false) + private String title; + + @Column(columnDefinition = "TEXT") + private String body; + + @Column(columnDefinition = "TEXT") + private String hashtags; + + @Enumerated(EnumType.ORDINAL) + @Column(nullable = false) + private PostStatus status = PostStatus.DRAFT; + + @Column(name = "published_at") + private LocalDateTime publishedAt; + + @CreationTimestamp + @Column(name = "created_at", insertable = false, updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at", insertable = false, updatable = false) + private LocalDateTime updatedAt; + + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) + private List media; + + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) + private List publications; + + public Long getPostId() { + return postId; + } + + public void setPostId(Long postId) { + this.postId = postId; + } + + public User getAuthor() { + return author; + } + + public void setAuthor(User author) { + this.author = author; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public String getHashtags() { + return hashtags; + } + + public void setHashtags(String hashtags) { + this.hashtags = hashtags; + } + + public PostStatus getStatus() { + return status; + } + + public void setStatus(PostStatus status) { + this.status = status; + } + + public LocalDateTime getPublishedAt() { + return publishedAt; + } + + public void setPublishedAt(LocalDateTime publishedAt) { + this.publishedAt = publishedAt; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + public List getMedia() { + return media; + } + + public void setMedia(List media) { + this.media = media; + } + + public List getPublications() { + return publications; + } + + public void setPublications(List publications) { + this.publications = publications; + } } diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/model/Publication.java b/backend/src/main/java/es/adeptusminiaturium/backend/model/Publication.java index 487e5c5..85fa64f 100644 --- a/backend/src/main/java/es/adeptusminiaturium/backend/model/Publication.java +++ b/backend/src/main/java/es/adeptusminiaturium/backend/model/Publication.java @@ -1,4 +1,111 @@ package es.adeptusminiaturium.backend.model; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import es.adeptusminiaturium.backend.enums.Platform; +import es.adeptusminiaturium.backend.enums.PublicationStatus; +import jakarta.persistence.*; +import org.hibernate.annotations.CreationTimestamp; + +import java.time.LocalDateTime; + +@Entity +@Table( + name = "publications", + uniqueConstraints = @UniqueConstraint(columnNames = {"post_id", "platform"}) +) public class Publication { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "publication_id") + private Long publicationId; + + @JsonManagedReference + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id", nullable = false) + private Post post; + + @Enumerated(EnumType.ORDINAL) + @Column(nullable = false) + private Platform platform; + + private String externalId; + + @Enumerated(EnumType.ORDINAL) + @Column(nullable = false) + private PublicationStatus status = PublicationStatus.PENDING; + + @Column(name = "published_at") + private LocalDateTime publishedAt; + + @Column(columnDefinition = "TEXT") + private String errorMessage; + + @CreationTimestamp + @Column(name = "created_at", insertable = false, updatable = false) + private LocalDateTime createdAt; + + public Long getPublicationId() { + return publicationId; + } + + public void setPublicationId(Long publicationId) { + this.publicationId = publicationId; + } + + public Post getPost() { + return post; + } + + public void setPost(Post post) { + this.post = post; + } + + public Platform getPlatform() { + return platform; + } + + public void setPlatform(Platform platform) { + this.platform = platform; + } + + public String getExternalId() { + return externalId; + } + + public void setExternalId(String externalId) { + this.externalId = externalId; + } + + public PublicationStatus getStatus() { + return status; + } + + public void setStatus(PublicationStatus status) { + this.status = status; + } + + public LocalDateTime getPublishedAt() { + return publishedAt; + } + + public void setPublishedAt(LocalDateTime publishedAt) { + this.publishedAt = publishedAt; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } } diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/model/User.java b/backend/src/main/java/es/adeptusminiaturium/backend/model/User.java index a189afb..3054c68 100644 --- a/backend/src/main/java/es/adeptusminiaturium/backend/model/User.java +++ b/backend/src/main/java/es/adeptusminiaturium/backend/model/User.java @@ -1,4 +1,118 @@ package es.adeptusminiaturium.backend.model; +import es.adeptusminiaturium.backend.enums.UserRole; +import es.adeptusminiaturium.backend.enums.UserStatus; +import jakarta.persistence.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; +import java.util.UUID; + +@Entity +@Table(name = "users") public class User { + + @Id + @Column(name = "user_id", nullable = false, updatable = false) + private UUID userId; + + @Column(name = "display_name") + private String displayName; + + @Column(name = "user_name", nullable = false, unique = true) + private String userName; + + @Column(nullable = false) + private String password; + + private String avatar; + + @Enumerated(EnumType.ORDINAL) + @Column(nullable = false) + private UserRole role = UserRole.USER; + + @Enumerated(EnumType.ORDINAL) + @Column(nullable = false) + private UserStatus status = UserStatus.ACTIVE; + + @CreationTimestamp + @Column(name = "created_at", insertable = false, updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at", insertable = false, updatable = false) + private LocalDateTime updatedAt; + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getAvatar() { + return avatar; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public UserRole getRole() { + return role; + } + + public void setRole(UserRole role) { + this.role = role; + } + + public UserStatus getStatus() { + return status; + } + + public void setStatus(UserStatus status) { + this.status = status; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } } diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/repository/MediaRepository.java b/backend/src/main/java/es/adeptusminiaturium/backend/repository/MediaRepository.java new file mode 100644 index 0000000..ab2613a --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/repository/MediaRepository.java @@ -0,0 +1,10 @@ +package es.adeptusminiaturium.backend.repository; + +import es.adeptusminiaturium.backend.model.Media; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface MediaRepository extends JpaRepository { + List findByPostPostId(Long postId); +} diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/repository/PostRepository.java b/backend/src/main/java/es/adeptusminiaturium/backend/repository/PostRepository.java new file mode 100644 index 0000000..dc9b64f --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/repository/PostRepository.java @@ -0,0 +1,8 @@ +package es.adeptusminiaturium.backend.repository; + +import es.adeptusminiaturium.backend.model.Post; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PostRepository extends JpaRepository { + +} diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/repository/PublicationRepository.java b/backend/src/main/java/es/adeptusminiaturium/backend/repository/PublicationRepository.java new file mode 100644 index 0000000..37f2224 --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/repository/PublicationRepository.java @@ -0,0 +1,10 @@ +package es.adeptusminiaturium.backend.repository; + +import es.adeptusminiaturium.backend.model.Publication; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface PublicationRepository extends JpaRepository { + List findByPostPostId(Long postId); +} diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/repository/UserRepository.java b/backend/src/main/java/es/adeptusminiaturium/backend/repository/UserRepository.java new file mode 100644 index 0000000..434b677 --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/repository/UserRepository.java @@ -0,0 +1,11 @@ +package es.adeptusminiaturium.backend.repository; + +import es.adeptusminiaturium.backend.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; +import java.util.UUID; + +public interface UserRepository extends JpaRepository { + Optional findByUserName(String userName); +} diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/security/CustomUserDetails.java b/backend/src/main/java/es/adeptusminiaturium/backend/security/CustomUserDetails.java new file mode 100644 index 0000000..ef6a0ad --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/security/CustomUserDetails.java @@ -0,0 +1,40 @@ +package es.adeptusminiaturium.backend.security; + +import es.adeptusminiaturium.backend.enums.UserStatus; +import es.adeptusminiaturium.backend.model.User; +import es.adeptusminiaturium.backend.enums.UserRole; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.List; + +public class CustomUserDetails implements UserDetails { + + private final User user; + + public CustomUserDetails(User user) { + this.user = user; + } + + @Override + public Collection getAuthorities() { + return List.of(new SimpleGrantedAuthority("ROLE_" + user.getRole().name())); + } + + @Override + public String getPassword() { + return user.getPassword(); + } + + @Override + public String getUsername() { + return user.getUserName(); + } + + @Override + public boolean isEnabled() { + return user.getStatus().equals(UserStatus.ACTIVE); + } +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/service/CustomUserDetailsService.java b/backend/src/main/java/es/adeptusminiaturium/backend/service/CustomUserDetailsService.java new file mode 100644 index 0000000..ece8ceb --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/service/CustomUserDetailsService.java @@ -0,0 +1,25 @@ +package es.adeptusminiaturium.backend.service; + +import es.adeptusminiaturium.backend.repository.UserRepository; +import es.adeptusminiaturium.backend.security.CustomUserDetails; +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 +public class CustomUserDetailsService implements UserDetailsService { + + private final UserRepository userRepo; + + public CustomUserDetailsService(UserRepository userRepo) { + this.userRepo = userRepo; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + return userRepo.findByUserName(username) + .map(CustomUserDetails::new) + .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username)); + } +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/service/MediaService.java b/backend/src/main/java/es/adeptusminiaturium/backend/service/MediaService.java new file mode 100644 index 0000000..382bda1 --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/service/MediaService.java @@ -0,0 +1,44 @@ +package es.adeptusminiaturium.backend.service; + +import es.adeptusminiaturium.backend.dto.MediaDto; +import es.adeptusminiaturium.backend.mapper.MediaMapper; +import es.adeptusminiaturium.backend.model.Media; +import es.adeptusminiaturium.backend.repository.MediaRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class MediaService { + + private final MediaRepository repo; + + public MediaService(MediaRepository repo) { this.repo = repo; } + + @Transactional + public List getAll() { + return repo.findAll().stream().map(MediaMapper::toResponse).collect(Collectors.toList()); + } + + public MediaDto.Response getById(Long id) { + return repo.findById(id).map(MediaMapper::toResponse).orElse(null); + } + + public MediaDto.Response create(MediaDto.Request dto) { + Media media = MediaMapper.toEntity(dto); + return MediaMapper.toResponse(repo.save(media)); + } + + public MediaDto.Response update(Long id, MediaDto.Request dto) { + return repo.findById(id).map(media -> { + media.setMediaType(dto.getMediaType()); + media.setUrl(dto.getUrl()); + media.setPosition(dto.getPosition()); + return MediaMapper.toResponse(repo.save(media)); + }).orElse(null); + } + + public void delete(Long id) { repo.deleteById(id); } +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/service/PostService.java b/backend/src/main/java/es/adeptusminiaturium/backend/service/PostService.java new file mode 100644 index 0000000..299dfab --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/service/PostService.java @@ -0,0 +1,45 @@ +package es.adeptusminiaturium.backend.service; + +import es.adeptusminiaturium.backend.dto.PostDto; +import es.adeptusminiaturium.backend.mapper.PostMapper; +import es.adeptusminiaturium.backend.model.Post; +import es.adeptusminiaturium.backend.repository.PostRepository; +import es.adeptusminiaturium.backend.repository.UserRepository; +import es.adeptusminiaturium.backend.validator.PostValidator; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +public class PostService { + + private final PostRepository postRepository; + private final UserRepository userRepository; + + public PostService(PostRepository postRepository, UserRepository userRepository) { + this.postRepository = postRepository; + this.userRepository = userRepository; + } + + @Transactional + public List getAllPosts() { + return postRepository.findAll().stream() + .map(PostMapper::toResponse) + .collect(Collectors.toList()); + } + + public PostDto.Response createPost(PostDto.Request dto) { + Post post = PostMapper.toEntity(dto); + post.setAuthor(userRepository.findById(dto.getAuthorId()).orElseThrow()); + Post saved = postRepository.save(post); + return PostMapper.toResponse(saved); + } + + public PostDto.Response getPostById(Long postId) { + Post post = postRepository.findById(postId).orElseThrow(); + return PostMapper.toResponse(post); + } +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/service/PublicationService.java b/backend/src/main/java/es/adeptusminiaturium/backend/service/PublicationService.java new file mode 100644 index 0000000..7cd68ba --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/service/PublicationService.java @@ -0,0 +1,44 @@ +package es.adeptusminiaturium.backend.service; + +import es.adeptusminiaturium.backend.dto.PublicationDto; +import es.adeptusminiaturium.backend.mapper.PublicationMapper; +import es.adeptusminiaturium.backend.model.Publication; +import es.adeptusminiaturium.backend.repository.PublicationRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class PublicationService { + + private final PublicationRepository repo; + + public PublicationService(PublicationRepository repo) { this.repo = repo; } + + @Transactional + public List getAll() { + return repo.findAll().stream().map(PublicationMapper::toResponse).collect(Collectors.toList()); + } + + public PublicationDto.Response getById(Long id) { + return repo.findById(id).map(PublicationMapper::toResponse).orElse(null); + } + + public PublicationDto.Response create(PublicationDto.Request dto) { + Publication pub = PublicationMapper.toEntity(dto); + return PublicationMapper.toResponse(repo.save(pub)); + } + + public PublicationDto.Response update(Long id, PublicationDto.Request dto) { + return repo.findById(id).map(pub -> { + pub.setPlatform(dto.getPlatform()); + pub.setExternalId(dto.getExternalId()); + pub.setStatus(dto.getStatus()); + return PublicationMapper.toResponse(repo.save(pub)); + }).orElse(null); + } + + public void delete(Long id) { repo.deleteById(id); } +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/service/UserService.java b/backend/src/main/java/es/adeptusminiaturium/backend/service/UserService.java new file mode 100644 index 0000000..54fdfe8 --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/service/UserService.java @@ -0,0 +1,48 @@ +package es.adeptusminiaturium.backend.service; + + +import es.adeptusminiaturium.backend.dto.UserDto; +import es.adeptusminiaturium.backend.mapper.UserMapper; +import es.adeptusminiaturium.backend.model.User; +import es.adeptusminiaturium.backend.repository.UserRepository; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +public class UserService { + + private final UserRepository repo; + + public UserService(UserRepository repo) { this.repo = repo; } + + public List getAllUsers() { + return repo.findAll().stream().map(UserMapper::toResponse).collect(Collectors.toList()); + } + + public UserDto.Response getUser(UUID userId) { + return repo.findById(userId).map(UserMapper::toResponse).orElse(null); + } + + public UserDto.Response createUser(UserDto.Request dto) { + User user = UserMapper.toEntity(dto); + return UserMapper.toResponse(repo.save(user)); + } + + public UserDto.Response updateUser(UUID userId, UserDto.Request dto) { + return repo.findById(userId).map(user -> { + user.setDisplayName(dto.getDisplayName()); + user.setUserName(dto.getUserName()); + user.setPassword(dto.getPassword()); + user.setRole(dto.getRole()); + user.setStatus(dto.getStatus()); + user.setUpdatedAt(LocalDateTime.now()); + return UserMapper.toResponse(repo.save(user)); + }).orElse(null); + } + + public void deleteUser(UUID userId) { repo.deleteById(userId); } +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/validator/MediaValidator.java b/backend/src/main/java/es/adeptusminiaturium/backend/validator/MediaValidator.java new file mode 100644 index 0000000..42d21df --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/validator/MediaValidator.java @@ -0,0 +1,36 @@ +package es.adeptusminiaturium.backend.validator; + +import es.adeptusminiaturium.backend.model.Media; + +import java.util.ArrayList; +import java.util.List; + +public class MediaValidator { + + public static List validate(Media media) { + List errors = new ArrayList<>(); + + if (media.getPost() == null) { + errors.add("Media must belong to a post"); + } + + if (media.getUrl() == null || media.getUrl().isBlank()) { + errors.add("URL cannot be empty"); + } else if (media.getUrl().length() > 512) { + errors.add("URL too long"); + } + + if (media.getPosition() == null || media.getPosition() < 0) { + errors.add("Position cannot be negative"); + } + + return errors; + } + + public static void validateOrThrow(Media media) { + List errors = validate(media); + if (!errors.isEmpty()) { + throw new IllegalArgumentException(String.join(", ", errors)); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/validator/PostValidator.java b/backend/src/main/java/es/adeptusminiaturium/backend/validator/PostValidator.java new file mode 100644 index 0000000..f858c1e --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/validator/PostValidator.java @@ -0,0 +1,40 @@ +package es.adeptusminiaturium.backend.validator; + +import es.adeptusminiaturium.backend.model.Post; + +import java.util.ArrayList; +import java.util.List; + +public class PostValidator { + + public static List validate(Post post) { + List errors = new ArrayList<>(); + + if (post.getAuthor() == null) { + errors.add("Post must have an author"); + } + + if (post.getTitle() == null || post.getTitle().isBlank()) { + errors.add("Title cannot be empty"); + } else if (post.getTitle().length() > 255) { + errors.add("Title too long"); + } + + if (post.getBody() != null && post.getBody().length() > 10000) { + errors.add("Body too long"); + } + + if (post.getHashtags() != null && post.getHashtags().length() > 1000) { + errors.add("Hashtags too long"); + } + + return errors; + } + + public static void validateOrThrow(Post post) { + List errors = validate(post); + if (!errors.isEmpty()) { + throw new IllegalArgumentException(String.join(", ", errors)); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/validator/PublicationValidator.java b/backend/src/main/java/es/adeptusminiaturium/backend/validator/PublicationValidator.java new file mode 100644 index 0000000..5275699 --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/validator/PublicationValidator.java @@ -0,0 +1,45 @@ +package es.adeptusminiaturium.backend.validator; + +import es.adeptusminiaturium.backend.enums.Platform; +import es.adeptusminiaturium.backend.model.Publication; + +import java.util.ArrayList; +import java.util.List; + +public class PublicationValidator { + + public static List validate(Publication pub) { + List errors = new ArrayList<>(); + + if (pub.getPost() == null) { + errors.add("Publication must belong to a post"); + } + + if (pub.getPlatform() == null) { + errors.add("Platform cannot be null"); + } else if (!Platform.valuesList().contains(pub.getPlatform())) { + errors.add("Invalid platform"); + } + + if (pub.getStatus() == null) { + errors.add("Status cannot be null"); + } + + if (pub.getExternalId() != null && pub.getExternalId().length() > 255) { + errors.add("ExternalId too long"); + } + + if (pub.getErrorMessage() != null && pub.getErrorMessage().length() > 1000) { + errors.add("ErrorMessage too long"); + } + + return errors; + } + + public static void validateOrThrow(Publication pub) { + List errors = validate(pub); + if (!errors.isEmpty()) { + throw new IllegalArgumentException(String.join(", ", errors)); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/es/adeptusminiaturium/backend/validator/UserValidator.java b/backend/src/main/java/es/adeptusminiaturium/backend/validator/UserValidator.java new file mode 100644 index 0000000..f0b30f0 --- /dev/null +++ b/backend/src/main/java/es/adeptusminiaturium/backend/validator/UserValidator.java @@ -0,0 +1,38 @@ +package es.adeptusminiaturium.backend.validator; + +import es.adeptusminiaturium.backend.model.User; + +import java.util.ArrayList; +import java.util.List; + +public class UserValidator { + + public static List validate(User user) { + List errors = new ArrayList<>(); + + if (user.getUserName() == null || user.getUserName().isBlank()) { + errors.add("Username cannot be empty"); + } + + if (user.getPassword() == null || user.getPassword().length() < 8) { + errors.add("Password must have at least 8 characters"); + } + + if (user.getDisplayName() != null && user.getDisplayName().length() > 255) { + errors.add("DisplayName too long"); + } + + if (user.getAvatar() != null && user.getAvatar().length() > 255) { + errors.add("Avatar URL too long"); + } + + return errors; + } + + public static void validateOrThrow(User user) { + List errors = validate(user); + if (!errors.isEmpty()) { + throw new IllegalArgumentException(String.join(", ", errors)); + } + } +} \ No newline at end of file diff --git a/backend/src/main/resources/application-dev.yaml b/backend/src/main/resources/application-dev.yaml index 9d618c1..b4c96ee 100644 --- a/backend/src/main/resources/application-dev.yaml +++ b/backend/src/main/resources/application-dev.yaml @@ -1,9 +1,9 @@ server: port: 8080 - servlet: - context-path: /v1/ spring: + jpa: + show-sql: true datasource: url: jdbc:mariadb://localhost:3306/miniaturium username: admin diff --git a/backend/src/main/resources/application-prod.yaml b/backend/src/main/resources/application-prod.yaml index 87c6604..40b1330 100644 --- a/backend/src/main/resources/application-prod.yaml +++ b/backend/src/main/resources/application-prod.yaml @@ -1,7 +1,5 @@ server: port: 8080 - servlet: - context-path: /v1/ spring: datasource: diff --git a/backend/src/main/resources/bd.sql b/backend/src/main/resources/bd.sql new file mode 100644 index 0000000..39c7ab9 --- /dev/null +++ b/backend/src/main/resources/bd.sql @@ -0,0 +1,52 @@ +CREATE TABLE users( + user_id UUID PRIMARY KEY, + display_name VARCHAR(255), + user_name VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + avatar VARCHAR(255) DEFAULT NULL, + role TINYINT NOT NULL DEFAULT 0, -- 0 = user, 1 = admin + status TINYINT NOT NULL DEFAULT 1, -- 0 = inactivo, 1 = activo + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP(), + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP() +); + +CREATE TABLE posts( + post_id BIGINT AUTO_INCREMENT PRIMARY KEY, + author_id UUID NOT NULL, + title VARCHAR(255) NOT NULL, + body TEXT, + hashtags TEXT, + status TINYINT NOT NULL DEFAULT 0, -- 0 = draft, 1 = published, 2 = archived + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP(), + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP(), + published_at TIMESTAMP NULL, + FOREIGN KEY (author_id) REFERENCES users(user_id) +); + +CREATE TABLE media( + media_id BIGINT AUTO_INCREMENT PRIMARY KEY, + post_id BIGINT NOT NULL, + media_type TINYINT NOT NULL DEFAULT 0, -- 0 = image, 1 = video + url VARCHAR(512) NOT NULL, + position BIGINT NOT NULL DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP(), + FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE +); + +CREATE TABLE publications( + publication_id BIGINT AUTO_INCREMENT PRIMARY KEY, + post_id BIGINT NOT NULL, + platform TINYINT NOT NULL, -- 0 = web, 1 = instagram, 2 = tiktok, 3 = twitter + external_id VARCHAR(255) DEFAULT NULL, + status TINYINT NOT NULL DEFAULT 0, -- 0 = pending, 1 = published, 2 = failed + published_at TIMESTAMP NULL, + error_message TEXT DEFAULT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP(), + UNIQUE (post_id, platform), + FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE +); + +CREATE INDEX idx_posts_author ON posts(author_id); +CREATE INDEX idx_posts_status_published ON posts(status, published_at); +CREATE INDEX idx_media_post_position ON media(post_id, position); +CREATE INDEX idx_publications_status ON publications(status); \ No newline at end of file