Skip to content
This repository was archived by the owner on May 21, 2026. It is now read-only.

Commit a5648d6

Browse files
Implement GraphQL mutations addMediaToUserLibrary and removeMediaFromUserLibrary with tests
Agent-Logs-Url: https://github.com/EspacoGeek-Teams/SpringAPI_EspacoGeek/sessions/18f5c822-c6fb-49ea-9fe5-aae0c61b4a39 Co-authored-by: AbigailGeovana <142514517+AbigailGeovana@users.noreply.github.com>
1 parent 8209973 commit a5648d6

10 files changed

Lines changed: 553 additions & 1 deletion

File tree

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.espacogeek.geek.controllers;
2+
3+
import java.util.List;
4+
5+
import org.springframework.graphql.data.method.annotation.Argument;
6+
import org.springframework.graphql.data.method.annotation.MutationMapping;
7+
import org.springframework.graphql.data.method.annotation.QueryMapping;
8+
import org.springframework.security.access.prepost.PreAuthorize;
9+
import org.springframework.security.core.Authentication;
10+
import org.springframework.stereotype.Controller;
11+
12+
import com.espacogeek.geek.models.UserLibraryModel;
13+
import com.espacogeek.geek.services.UserLibraryService;
14+
import com.espacogeek.geek.utils.UserUtils;
15+
16+
import lombok.RequiredArgsConstructor;
17+
18+
/**
19+
* GraphQL controller for user media library operations.
20+
* All endpoints require the user to be authenticated ({@code hasRole('user')}).
21+
*/
22+
@Controller
23+
@RequiredArgsConstructor
24+
public class UserLibraryController {
25+
26+
private final UserLibraryService userLibraryService;
27+
28+
/**
29+
* Returns all media entries in the authenticated user's library.
30+
*/
31+
@QueryMapping(name = "findUserMediaLibrary")
32+
@PreAuthorize("hasRole('user')")
33+
public List<UserLibraryModel> findUserMediaLibrary(Authentication authentication) {
34+
Integer userId = UserUtils.getUserID(authentication);
35+
return userLibraryService.findByUserId(userId);
36+
}
37+
38+
/**
39+
* Adds the specified media to the authenticated user's library.
40+
*
41+
* @param mediaId ID of the media to add
42+
* @return the newly created library entry
43+
*/
44+
@MutationMapping(name = "addMediaToUserLibrary")
45+
@PreAuthorize("hasRole('user')")
46+
public UserLibraryModel addMediaToUserLibrary(Authentication authentication, @Argument(name = "mediaId") Integer mediaId) {
47+
Integer userId = UserUtils.getUserID(authentication);
48+
return userLibraryService.addMedia(userId, mediaId);
49+
}
50+
51+
/**
52+
* Removes the specified media from the authenticated user's library.
53+
*
54+
* @param mediaId ID of the media to remove
55+
* @return {@code true} if the entry was removed, {@code false} if it was not found
56+
*/
57+
@MutationMapping(name = "removeMediaFromUserLibrary")
58+
@PreAuthorize("hasRole('user')")
59+
public Boolean removeMediaFromUserLibrary(Authentication authentication, @Argument(name = "mediaId") Integer mediaId) {
60+
Integer userId = UserUtils.getUserID(authentication);
61+
return userLibraryService.removeMedia(userId, mediaId);
62+
}
63+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.espacogeek.geek.repositories;
2+
3+
import org.springframework.data.jpa.repository.JpaRepository;
4+
import org.springframework.stereotype.Repository;
5+
6+
import com.espacogeek.geek.models.TypeStatusModel;
7+
8+
@Repository
9+
public interface TypeStatusRepository extends JpaRepository<TypeStatusModel, Integer> {
10+
}
Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
package com.espacogeek.geek.repositories;
22

3+
import java.util.List;
4+
import java.util.Optional;
5+
36
import org.springframework.data.jpa.repository.JpaRepository;
7+
import org.springframework.stereotype.Repository;
48

59
import com.espacogeek.geek.models.UserLibraryModel;
610

11+
@Repository
712
public interface UserLibraryRepository extends JpaRepository<UserLibraryModel, Integer> {
8-
13+
14+
List<UserLibraryModel> findByUserId(Integer userId);
15+
16+
Optional<UserLibraryModel> findByUserIdAndMediaId(Integer userId, Integer mediaId);
17+
18+
void deleteByUserIdAndMediaId(Integer userId, Integer mediaId);
919
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.espacogeek.geek.services;
2+
3+
import java.util.List;
4+
5+
import com.espacogeek.geek.models.UserLibraryModel;
6+
7+
/**
8+
* Service interface for managing a user's personal media library.
9+
*/
10+
public interface UserLibraryService {
11+
12+
/**
13+
* Retrieves all media entries in the given user's library.
14+
*
15+
* @param userId the ID of the user
16+
* @return list of {@link UserLibraryModel} entries belonging to the user
17+
*/
18+
List<UserLibraryModel> findByUserId(Integer userId);
19+
20+
/**
21+
* Adds a media item to the user's library with default values
22+
* (status = "Planning", progress = 0).
23+
*
24+
* @param userId the ID of the authenticated user
25+
* @param mediaId the ID of the media to add
26+
* @return the newly created {@link UserLibraryModel} entry
27+
*/
28+
UserLibraryModel addMedia(Integer userId, Integer mediaId);
29+
30+
/**
31+
* Removes a media item from the user's library.
32+
*
33+
* @param userId the ID of the authenticated user
34+
* @param mediaId the ID of the media to remove
35+
* @return {@code true} if the entry existed and was deleted,
36+
* {@code false} if no matching entry was found
37+
*/
38+
boolean removeMedia(Integer userId, Integer mediaId);
39+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.espacogeek.geek.services.impl;
2+
3+
import java.util.List;
4+
import java.util.Optional;
5+
6+
import org.springframework.http.HttpStatus;
7+
import org.springframework.stereotype.Service;
8+
import org.springframework.transaction.annotation.Transactional;
9+
10+
import com.espacogeek.geek.exception.GenericException;
11+
import com.espacogeek.geek.models.MediaModel;
12+
import com.espacogeek.geek.models.TypeStatusModel;
13+
import com.espacogeek.geek.models.UserLibraryModel;
14+
import com.espacogeek.geek.models.UserModel;
15+
import com.espacogeek.geek.repositories.MediaRepository;
16+
import com.espacogeek.geek.repositories.TypeStatusRepository;
17+
import com.espacogeek.geek.repositories.UserLibraryRepository;
18+
import com.espacogeek.geek.repositories.UserRepository;
19+
import com.espacogeek.geek.services.UserLibraryService;
20+
21+
import lombok.RequiredArgsConstructor;
22+
23+
/**
24+
* Implementation of {@link UserLibraryService}.
25+
*/
26+
@Service
27+
@RequiredArgsConstructor
28+
public class UserLibraryServiceImpl implements UserLibraryService {
29+
30+
/** ID of the default "Planning" status in the {@code types_status} table. */
31+
private static final int DEFAULT_STATUS_ID = 1;
32+
33+
private final UserLibraryRepository userLibraryRepository;
34+
private final UserRepository userRepository;
35+
private final MediaRepository mediaRepository;
36+
private final TypeStatusRepository typeStatusRepository;
37+
38+
@Override
39+
public List<UserLibraryModel> findByUserId(Integer userId) {
40+
return userLibraryRepository.findByUserId(userId);
41+
}
42+
43+
@Override
44+
@Transactional
45+
public UserLibraryModel addMedia(Integer userId, Integer mediaId) {
46+
UserModel user = userRepository.findById(userId)
47+
.orElseThrow(() -> new GenericException(HttpStatus.NOT_FOUND.toString()));
48+
49+
MediaModel media = mediaRepository.findById(mediaId)
50+
.orElseThrow(() -> new GenericException(HttpStatus.NOT_FOUND.toString()));
51+
52+
TypeStatusModel defaultStatus = typeStatusRepository.findById(DEFAULT_STATUS_ID)
53+
.orElseThrow(() -> new GenericException(HttpStatus.INTERNAL_SERVER_ERROR.toString()));
54+
55+
UserLibraryModel entry = new UserLibraryModel();
56+
entry.setUser(user);
57+
entry.setMedia(media);
58+
entry.setTypeStatus(defaultStatus);
59+
entry.setProgress(0);
60+
61+
return userLibraryRepository.save(entry);
62+
}
63+
64+
@Override
65+
@Transactional
66+
public boolean removeMedia(Integer userId, Integer mediaId) {
67+
Optional<UserLibraryModel> entry = userLibraryRepository.findByUserIdAndMediaId(userId, mediaId);
68+
if (entry.isEmpty()) {
69+
return false;
70+
}
71+
userLibraryRepository.deleteByUserIdAndMediaId(userId, mediaId);
72+
return true;
73+
}
74+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""
2+
Represents a media entry in the user's personal library
3+
"""
4+
type UserMediaList {
5+
"Unique library entry identifier"
6+
id: ID
7+
"Watching/reading progress"
8+
progress: Int
9+
"Date the media was added to the library"
10+
addedAt: Date
11+
"Current tracking status (e.g. Planning, Watching, Completed)"
12+
typeStatus: TypeStatus
13+
"The media associated with this entry"
14+
media: Media
15+
}
16+
17+
"""
18+
Represents a media tracking status (e.g. Planning, Watching, Completed)
19+
"""
20+
type TypeStatus {
21+
"Unique status identifier"
22+
id: ID
23+
"Status name"
24+
name: String
25+
}

src/main/resources/graphql/mutation.graphqls

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,19 @@ type Mutation {
7676
Returns: Success message
7777
"""
7878
verifyEmailChange(token: String!): String
79+
80+
"""
81+
Add a media item to the authenticated user's library. Requires authentication.
82+
Creates a new library entry with default status (Planning) and progress 0.
83+
Example: addMediaToUserLibrary(mediaId: 42)
84+
Returns: The newly created UserMediaList entry.
85+
"""
86+
addMediaToUserLibrary(mediaId: Int!): UserMediaList
87+
88+
"""
89+
Remove a media item from the authenticated user's library. Requires authentication.
90+
Example: removeMediaFromUserLibrary(mediaId: 42)
91+
Returns: true if the entry was removed, false if it was not found.
92+
"""
93+
removeMediaFromUserLibrary(mediaId: Int!): Boolean
7994
}

src/main/resources/graphql/query.graphqls

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,10 @@ type Query {
7575
Example: getBatchJobs(page: 0, size: 10, status: "FAILED")
7676
"""
7777
getBatchJobs(page: Int!, size: Int!, status: String): BatchJobPage!
78+
79+
"""
80+
Get the authenticated user's media library. Requires authentication via Authorization: Bearer header.
81+
Returns a list of all media entries tracked by the current user.
82+
"""
83+
findUserMediaLibrary: [UserMediaList]
7884
}

0 commit comments

Comments
 (0)