Daniel Bohry пре 6 дана
родитељ
комит
d102e35dd4

+ 10 - 11
src/main/java/com/lhamacorp/knotes/api/NoteController.java

@@ -19,7 +19,6 @@ import static com.lhamacorp.knotes.domain.EncryptionMode.PRIVATE;
 import static com.lhamacorp.knotes.domain.EncryptionMode.PUBLIC;
 import static com.lhamacorp.knotes.domain.Note.ANONYMOUS;
 import static org.springframework.http.HttpStatus.FORBIDDEN;
-import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
 import static org.springframework.http.ResponseEntity.badRequest;
 import static org.springframework.http.ResponseEntity.ok;
 
@@ -28,22 +27,23 @@ import static org.springframework.http.ResponseEntity.ok;
 @CrossOrigin(origins = "*")
 public class NoteController {
 
-    private final NoteService service;
+    private final NoteService noteService;
 
-    public NoteController(NoteService service) {
-        this.service = service;
+
+    public NoteController(NoteService noteService) {
+        this.noteService = noteService;
     }
 
     @GetMapping
     public ResponseEntity<List<String>> findByUserId() {
-        return ok(service.findAll());
+        return ok(noteService.findAll());
     }
 
     @GetMapping("/{id}")
     public ResponseEntity<NoteResponse> findById(@PathVariable String id,
                                                  @RequestParam(required = false) String password) {
         UserContext user = UserContextHolder.get();
-        Note note = service.findById(id);
+        Note note = noteService.findById(id);
 
         if (!canAccess(note, user.id(), password)) {
             return ResponseEntity.status(FORBIDDEN).build();
@@ -53,13 +53,12 @@ public class NoteController {
             case PRIVATE -> ResponseEntity.ok(NoteResponse.fromPrivate(note, user.id()));
             case PASSWORD_SHARED -> ResponseEntity.ok(NoteResponse.fromPasswordShared(note, password));
             case PUBLIC -> ResponseEntity.ok(NoteResponse.from(note));
-            default -> ResponseEntity.status(INTERNAL_SERVER_ERROR).build();
         };
     }
 
     @GetMapping("{id}/metadata")
     public ResponseEntity<NoteMetadata> getMetadata(@PathVariable String id) {
-        NoteMetadata metadata = service.findMetadataById(id);
+        NoteMetadata metadata = noteService.findMetadataById(id);
         return ok().body(metadata);
     }
 
@@ -84,7 +83,7 @@ public class NoteController {
             }
         }
 
-        Note updatedNote = service.update(id, request.content(), mode, password);
+        Note updatedNote = noteService.update(id, request.content(), mode, password);
         EncryptionMode finalMode = updatedNote.encryptionMode() != null ? updatedNote.encryptionMode() : PUBLIC;
 
         return switch (finalMode) {
@@ -99,7 +98,7 @@ public class NoteController {
         String userId = isAuthenticated() ? UserContextHolder.get().id() : ANONYMOUS;
 
         EncryptionMode mode = userId.equals(ANONYMOUS) ? PUBLIC : PRIVATE;
-        Note savedNote = service.save(request.note(), mode);
+        Note savedNote = noteService.save(request.note(), mode);
 
         return switch (mode) {
             case PRIVATE -> ok().body(NoteResponse.fromPrivate(savedNote, userId));
@@ -111,7 +110,7 @@ public class NoteController {
     @DeleteMapping("{id}")
     public ResponseEntity<Void> delete(@PathVariable String id) {
         if (isAuthenticated()) {
-            service.delete(id);
+            noteService.delete(id);
         }
 
         return ok().build();

+ 55 - 0
src/main/java/com/lhamacorp/knotes/api/PinController.java

@@ -0,0 +1,55 @@
+package com.lhamacorp.knotes.api;
+
+import com.lhamacorp.knotes.api.dto.PinRequest;
+import com.lhamacorp.knotes.context.UserContextHolder;
+import com.lhamacorp.knotes.domain.Pin;
+import com.lhamacorp.knotes.service.PinService;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static com.lhamacorp.knotes.context.UserContextHolder.isAuthenticated;
+import static org.springframework.http.HttpStatus.UNAUTHORIZED;
+
+@RestController
+@RequestMapping("api/pins")
+@CrossOrigin(origins = "*")
+public class PinController {
+
+    private final PinService service;
+
+    public PinController(PinService service) {
+        this.service = service;
+    }
+
+    @GetMapping
+    public ResponseEntity<List<Pin>> get() {
+        if (isAuthenticated()) {
+            List<Pin> pins = service.get(UserContextHolder.get().id());
+            return ResponseEntity.ok(pins);
+        }
+
+        return ResponseEntity.status(UNAUTHORIZED).build();
+    }
+
+    @PostMapping
+    public ResponseEntity<Pin> pin(@RequestBody PinRequest request) {
+        if (isAuthenticated()) {
+            Pin pin = service.create(request.noteId(), UserContextHolder.get().id());
+            return ResponseEntity.ok(pin);
+        }
+
+        return ResponseEntity.status(UNAUTHORIZED).build();
+    }
+
+    @DeleteMapping("{id}")
+    public ResponseEntity<Void> delete(@PathVariable String id) {
+        if (isAuthenticated()) {
+            service.remove(id, UserContextHolder.get().id());
+        }
+
+        return ResponseEntity.ok().build();
+    }
+
+}

+ 4 - 0
src/main/java/com/lhamacorp/knotes/api/dto/PinRequest.java

@@ -0,0 +1,4 @@
+package com.lhamacorp.knotes.api.dto;
+
+public record PinRequest(String noteId) {
+}

+ 10 - 0
src/main/java/com/lhamacorp/knotes/domain/Pin.java

@@ -0,0 +1,10 @@
+package com.lhamacorp.knotes.domain;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.time.Instant;
+
+@Document("pins")
+public record Pin(@Id String id, String noteId, String userId, Instant createdAt) {
+}

+ 12 - 0
src/main/java/com/lhamacorp/knotes/exception/BadRequestException.java

@@ -0,0 +1,12 @@
+package com.lhamacorp.knotes.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(value = HttpStatus.BAD_REQUEST)
+public class BadRequestException extends RuntimeException {
+
+    public BadRequestException(String message) {
+        super(message);
+    }
+}

+ 16 - 0
src/main/java/com/lhamacorp/knotes/repository/PinRepository.java

@@ -0,0 +1,16 @@
+package com.lhamacorp.knotes.repository;
+
+import com.lhamacorp.knotes.domain.Pin;
+import org.springframework.data.mongodb.repository.MongoRepository;
+
+import java.util.List;
+
+public interface PinRepository extends MongoRepository<Pin, String> {
+
+    List<Pin> findAllByUserId(String userId);
+
+    void deleteAllByNoteId(String noteId);
+
+    void deletePinByUserIdAndNoteId(String noteId, String userId);
+
+}

+ 9 - 5
src/main/java/com/lhamacorp/knotes/service/NoteService.java

@@ -6,7 +6,7 @@ import com.lhamacorp.knotes.context.UserContext;
 import com.lhamacorp.knotes.context.UserContextHolder;
 import com.lhamacorp.knotes.domain.EncryptionMode;
 import com.lhamacorp.knotes.domain.Note;
-import com.lhamacorp.knotes.exception.NotFoundException;
+import com.lhamacorp.knotes.exception.BadRequestException;
 import com.lhamacorp.knotes.exception.UnauthorizedException;
 import com.lhamacorp.knotes.repository.NoteRepository;
 import org.springframework.cache.annotation.CacheEvict;
@@ -39,6 +39,10 @@ public class NoteService {
         return repository.existsById(id);
     }
 
+    public Note get(String id) {
+        return repository.findById(id).orElseThrow(() -> new BadRequestException("Note not found"));
+    }
+
     public List<String> findAll() {
         UserContext user = UserContextHolder.get();
         return ANONYMOUS.equals(user.id())
@@ -49,13 +53,13 @@ public class NoteService {
     @Cacheable(value = "content", key = "#id")
     public Note findById(String id) {
         return repository.findById(id)
-                .orElseThrow(() -> new NotFoundException(NOT_FOUND));
+                .orElseThrow(() -> new BadRequestException(NOT_FOUND));
     }
 
     @Cacheable(value = "metadata", key = "#id")
     public NoteMetadata findMetadataById(String id) {
         Note noteProjection = repository.findMetadataById(id)
-                .orElseThrow(() -> new NotFoundException(NOT_FOUND));
+                .orElseThrow(() -> new BadRequestException(NOT_FOUND));
         return from(noteProjection);
     }
 
@@ -69,7 +73,7 @@ public class NoteService {
 
     @CacheEvict(value = {"content", "metadata"}, key = "#id")
     public Note update(String id, String content, EncryptionMode encryptionMode, String password) {
-        Note existingNote = repository.findById(id).orElseThrow(() -> new NotFoundException(NOT_FOUND));
+        Note existingNote = repository.findById(id).orElseThrow(() -> new BadRequestException(NOT_FOUND));
         UserContext user = UserContextHolder.get();
 
         if (existingNote.encryptionMode() == PRIVATE && !existingNote.createdBy().equals(user.id())) {
@@ -90,7 +94,7 @@ public class NoteService {
 
     @CacheEvict(value = {"content", "metadata"}, key = "#id")
     public void delete(String id) {
-        Note note = repository.findById(id).orElseThrow(() -> new NotFoundException("Note not found"));
+        Note note = repository.findById(id).orElseThrow(() -> new BadRequestException("Note not found"));
         String userId = UserContextHolder.get().id();
 
         if (note.createdBy().equals(userId)) {

+ 52 - 0
src/main/java/com/lhamacorp/knotes/service/PinService.java

@@ -0,0 +1,52 @@
+package com.lhamacorp.knotes.service;
+
+import com.github.f4b6a3.ulid.Ulid;
+import com.lhamacorp.knotes.domain.Note;
+import com.lhamacorp.knotes.domain.Pin;
+import com.lhamacorp.knotes.exception.BadRequestException;
+import com.lhamacorp.knotes.exception.NotFoundException;
+import com.lhamacorp.knotes.repository.PinRepository;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+import static com.github.f4b6a3.ulid.UlidCreator.getUlid;
+import static com.lhamacorp.knotes.domain.EncryptionMode.PUBLIC;
+import static java.time.Instant.now;
+
+@Service
+public class PinService {
+
+    private final PinRepository repository;
+    private final NoteService noteService;
+
+    public PinService(PinRepository repository, NoteService noteService) {
+        this.repository = repository;
+        this.noteService = noteService;
+    }
+
+    public List<Pin> get(String userId) {
+        return repository.findAllByUserId(userId);
+    }
+
+    public Pin create(String noteId, String userId) {
+        Note note = noteService.get(noteId);
+
+        if (!PUBLIC.equals(note.encryptionMode())) {
+            throw new BadRequestException("Note cannot be pinned");
+        }
+
+        Ulid id = getUlid();
+
+        return repository.save(new Pin(id.toString(), note.id(), userId, now()));
+    }
+
+    public void remove(String id, String userId) {
+        Pin pin = repository.findById(id).orElseThrow(() -> new NotFoundException("Pin not found"));
+
+        if (pin.userId().equals(userId)) {
+            repository.deleteById(id);
+        }
+    }
+
+}

+ 4 - 4
src/test/java/com/lhamacorp/knotes/service/NoteServiceTest.java

@@ -5,7 +5,7 @@ import com.lhamacorp.knotes.context.UserContext;
 import com.lhamacorp.knotes.context.UserContextHolder;
 import com.lhamacorp.knotes.domain.EncryptionMode;
 import com.lhamacorp.knotes.domain.Note;
-import com.lhamacorp.knotes.exception.NotFoundException;
+import com.lhamacorp.knotes.exception.BadRequestException;
 import com.lhamacorp.knotes.repository.NoteRepository;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
@@ -112,7 +112,7 @@ class NoteServiceTest {
         when(repository.findById(testId)).thenReturn(Optional.empty());
 
         // When & Then
-        NotFoundException exception = assertThrows(NotFoundException.class,
+        BadRequestException exception = assertThrows(BadRequestException.class,
             () -> noteService.findById(testId));
 
         assertEquals("Note not found!", exception.getMessage());
@@ -141,7 +141,7 @@ class NoteServiceTest {
         when(repository.findMetadataById(testId)).thenReturn(Optional.empty());
 
         // When & Then
-        NotFoundException exception = assertThrows(NotFoundException.class,
+        BadRequestException exception = assertThrows(BadRequestException.class,
             () -> noteService.findMetadataById(testId));
 
         assertEquals("Note not found!", exception.getMessage());
@@ -247,7 +247,7 @@ class NoteServiceTest {
         when(repository.findById(testId)).thenReturn(Optional.empty());
 
         // When & Then
-        NotFoundException exception = assertThrows(NotFoundException.class,
+        BadRequestException exception = assertThrows(BadRequestException.class,
             () -> noteService.update(testId, updatedContent, null, null));
 
         assertEquals("Note not found!", exception.getMessage());