Compare commits

..

6 Commits

Author SHA1 Message Date
Gustavo Henrique Miranda b0bd489af9
Merge f43c609e04 into 86da601ad6 2025-12-07 05:34:41 +00:00
Gustavo Henrique Miranda f43c609e04
Merge pull request #21 from gmbrax/feature/Implement-AlbumArt-Management
Feature/implement album art management
2025-12-07 02:34:38 -03:00
Gustavo Henrique Miranda dc1f6dfbc8
Update src/main/java/com/mediamanager/model/AlbumArt.java
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-12-07 02:30:51 -03:00
Gustavo Henrique Miranda 0aac0f2f14
Update src/main/proto/albumart.proto
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-12-07 02:21:47 -03:00
Gustavo Henrique Santos Souza de Miranda 2fb890a485 Register AlbumArtService with service locator and update gitignore
This commit completes the AlbumArt feature integration by registering
the service with the dependency injection system and updating repository
ignore rules.

Service Registration:
- Initialize AlbumArtRepository with EntityManagerFactory
- Create AlbumArtService instance with repository dependency
- Register AlbumArtService with ServiceLocator for handler injection
- Ensures all AlbumArt action handlers can resolve dependencies

The service follows the established initialization pattern used by other
entities (SamplingRate, BitDepth, BitRate, etc.) ensuring consistency
across the application architecture.

Repository Configuration:
- Add src/scripts/* to .gitignore for local script files
- Add local-repo/ to .gitignore for local Maven repository cache
- Prevents accidental commits of development artifacts and dependencies

These changes complete the AlbumArt feature, making it fully operational
and accessible through the action handler system.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-07 02:17:29 -03:00
Gustavo Henrique Santos Souza de Miranda 745ba7e5fc Implement AlbumArt management with full CRUD operations
This commit introduces comprehensive AlbumArt entity management following
the established repository pattern used for other entities like SamplingRate.

Key components implemented:

Model Layer:
- AlbumArt entity with id and filepath fields
- JPA annotations for database persistence

Repository Layer:
- AlbumArtRepository with EntityManager-based operations
- Full transaction management with proper rollback handling
- Methods: save, findAll, findById, update, deleteById

Service Layer:
- AlbumArtService with business logic and validation
- Input validation for null/empty filepath values
- ID validation for all operations requiring entity lookup
- Comprehensive logging using Log4j2

Mapper Layer:
- AlbumArtMapper for bidirectional entity/protobuf conversion
- Null safety checks and validation
- Proper handling of optional ID field

Action Handlers:
- CreateAlbumArtHandler (albumart.create)
- GetAlbumArtHandler (albumart.getAll)
- GetAlbumArtByIdHandler (albumart.getById)
- UpdateAlbumArtHandler (albumart.update)
- DeleteAlbumArtHandler (albumart.delete)
- HTTP status code handling: 200 (success), 400 (validation),
  404 (not found), 500 (server error)

Protocol Buffers:
- Fixed java_outer_classname from "BitDepthMessages" to "AlbumArtMessages"
- All CRUD message definitions (Create, Get, GetById, Update, Delete)

The implementation follows best practices with proper error handling,
logging, validation, and consistency with existing codebase patterns.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-07 02:15:57 -03:00
12 changed files with 581 additions and 1 deletions

2
.gitignore vendored
View File

@ -52,3 +52,5 @@ src/main/resources/application.properties
# Allow example configuration files to be committed
!src/main/resources/*.properties.example
src/scripts/*
local-repo/

View File

@ -0,0 +1,35 @@
package com.mediamanager.mapper;
import com.mediamanager.model.AlbumArt;
import com.mediamanager.protocol.messages.AlbumArtMessages;
public class AlbumArtMapper {
public static AlbumArtMessages.AlbumArt toProtobuf(AlbumArt entity) {
if (entity == null) {
return null;
}
String filepath = entity.getFilepath();
if (filepath == null || filepath.isEmpty()) {
throw new IllegalArgumentException("Filepath cannot be null or empty");
}
AlbumArtMessages.AlbumArt.Builder builder = AlbumArtMessages.AlbumArt.newBuilder()
.setFilepath(filepath);
Integer id = entity.getId();
if (id != null) {
builder.setId(id);
}
return builder.build();
}
public static AlbumArt toEntity(AlbumArtMessages.AlbumArt protobuf) {
if (protobuf == null) {
return null;
}
AlbumArt entity = new AlbumArt();
if (protobuf.getId() >0) {
entity.setId(protobuf.getId());
}
entity.setFilepath(protobuf.getFilepath());
return entity;
}
}

View File

@ -0,0 +1,33 @@
package com.mediamanager.model;
import jakarta.persistence.*;
@Entity
@Table(name = "albumArt")
public class AlbumArt {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(nullable = false)
private String filepath;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFilepath() {
return filepath;
}
public void setFilepath(String filepath) {
this.filepath = filepath;
}
}

View File

@ -0,0 +1,103 @@
package com.mediamanager.repository;
import com.mediamanager.model.AlbumArt;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.List;
import java.util.Optional;
public class AlbumArtRepository {
private static final Logger logger = LogManager.getLogger(AlbumArtRepository.class);
private final EntityManagerFactory entityManagerFactory;
public AlbumArtRepository(EntityManagerFactory entityManagerFactory) {
this.entityManagerFactory = entityManagerFactory;
}
public AlbumArt save(AlbumArt albumArt) {
logger.debug("Saving AlbumArt: {}", albumArt.getFilepath());
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
try {
em.persist(albumArt);
em.getTransaction().commit();
logger.debug("AlbumArt has been saved successfully");
return albumArt;
} catch (Exception e) {
em.getTransaction().rollback();
logger.error("Error while saving AlbumArt: {}", e.getMessage());
throw e;
} finally {
if (em.isOpen()) em.close();
}
}
public List<AlbumArt> findAll() {
logger.debug("Finding All AlbumArt");
EntityManager em = entityManagerFactory.createEntityManager();
try{
return em.createQuery("select a from AlbumArt a", AlbumArt.class).getResultList();
}finally {
if (em.isOpen()) em.close();
}
}
public Optional<AlbumArt> findById(Integer id) {
logger.debug("Finding AlbumArt with id: {}", id);
EntityManager em = entityManagerFactory.createEntityManager();
try{
AlbumArt albumArt = em.find(AlbumArt.class, id);
return Optional.ofNullable(albumArt);
}finally {
if (em.isOpen()) em.close();
}
}
public AlbumArt update(AlbumArt albumArt) {
logger.debug("Updating AlbumArt: {}", albumArt.getFilepath());
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
try {
AlbumArt updated = em.merge(albumArt);
em.getTransaction().commit();
logger.debug("AlbumArt has been updated successfully");
return updated;
} catch (Exception e) {
em.getTransaction().rollback();
logger.error("Error while updating AlbumArt: {}", e.getMessage());
throw e;
} finally {
if (em.isOpen()) em.close();
}
}
public boolean deleteById(Integer id){
logger.debug("Deleting AlbumArt with id: {}", id);
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
try{
AlbumArt albumArt = em.find(AlbumArt.class, id);
if (albumArt == null) {
em.getTransaction().rollback();
return false;
}
em.remove(albumArt);
em.getTransaction().commit();
logger.debug("AlbumArt has been deleted successfully");
return true;
} catch (Exception e) {
em.getTransaction().rollback();
logger.error("Error while deleting AlbumArt: {}", e.getMessage());
throw e;
} finally {
if (em.isOpen()) em.close();
}
}
}

View File

@ -0,0 +1,69 @@
package com.mediamanager.service.albumart;
import com.mediamanager.model.AlbumArt;
import com.mediamanager.repository.AlbumArtRepository;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.List;
import java.util.Optional;
public class AlbumArtService {
private static final Logger logger = LogManager.getLogger(AlbumArtService.class);
private final AlbumArtRepository repository;
public AlbumArtService(AlbumArtRepository repository) {
this.repository = repository;
}
public AlbumArt createAlbumArt(String filepath) {
logger.debug("Creating album art:{}", filepath);
if (filepath == null || filepath.trim().isEmpty()) {
throw new IllegalArgumentException("AlbumArt filepath cannot be null or empty");
}
AlbumArt albumArt = new AlbumArt();
albumArt.setFilepath(filepath);
return repository.save(albumArt);
}
public List<AlbumArt> getAllAlbumArts() {
logger.info("Getting all album arts");
return repository.findAll();
}
public Optional<AlbumArt> getAlbumArtById(Integer id) {
if (id == null) {
throw new IllegalArgumentException("ID cannot be null");
}
logger.info("Getting album art by id:{}", id);
return repository.findById(id);
}
public Optional<AlbumArt> updateAlbumArt(Integer id, String filepath) {
if (id == null) {
throw new IllegalArgumentException("ID cannot be null");
}
logger.info("Updating album art:{}", filepath);
if (filepath == null || filepath.trim().isEmpty()) {
throw new IllegalArgumentException("AlbumArt filepath cannot be null or empty");
}
Optional<AlbumArt> existingAlbumArt = repository.findById(id);
if(existingAlbumArt.isEmpty()) {
logger.warn("Album art not found with id:{}", id);
return Optional.empty();
}
AlbumArt albumArt = existingAlbumArt.get();
albumArt.setFilepath(filepath);
AlbumArt updatedAlbumArt = repository.update(albumArt);
return Optional.of(updatedAlbumArt);
}
public boolean deleteAlbumArt(Integer id) {
if (id == null) {
throw new IllegalArgumentException("Album art id cannot be null");
}
logger.info("Deleting album art:{}", id);
return repository.deleteById(id);
}
}

View File

@ -3,6 +3,7 @@ package com.mediamanager.service.delegate;
import com.google.protobuf.ByteString;
import com.mediamanager.protocol.TransportProtocol;
import com.mediamanager.repository.*;
import com.mediamanager.service.albumart.AlbumArtService;
import com.mediamanager.service.bitdepth.BitDepthService;
import com.mediamanager.service.bitrate.BitRateService;
import com.mediamanager.service.composer.ComposerService;
@ -75,6 +76,10 @@ public class DelegateActionManager {
SamplingRateService samplingRateService = new SamplingRateService(samplingRateRepository);
serviceLocator.register(SamplingRateService.class, samplingRateService);
AlbumArtRepository albumArtRepository = new AlbumArtRepository(entityManagerFactory);
AlbumArtService albumArtService = new AlbumArtService(albumArtRepository);
serviceLocator.register(AlbumArtService.class, albumArtService);
serviceLocator.logRegisteredServices();
logger.info("Services initialized successfully");

View File

@ -0,0 +1,49 @@
package com.mediamanager.service.delegate.handler.albumart;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.mediamanager.mapper.AlbumArtMapper;
import com.mediamanager.model.AlbumArt;
import com.mediamanager.protocol.TransportProtocol;
import com.mediamanager.protocol.messages.AlbumArtMessages;
import com.mediamanager.service.delegate.ActionHandler;
import com.mediamanager.service.delegate.annotation.Action;
import com.mediamanager.service.albumart.AlbumArtService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@Action("albumart.create")
public class CreateAlbumArtHandler implements ActionHandler {
private static final Logger logger = LogManager.getLogger(CreateAlbumArtHandler.class);
private final AlbumArtService albumArtService;
public CreateAlbumArtHandler(AlbumArtService albumArtService) {
this.albumArtService = albumArtService;
}
@Override
public TransportProtocol.Response.Builder handle(ByteString requestPayload) throws InvalidProtocolBufferException {
try{
AlbumArtMessages.CreateAlbumArtRequest createRequest =
AlbumArtMessages.CreateAlbumArtRequest.parseFrom(requestPayload);
AlbumArt albumArt = albumArtService.createAlbumArt(createRequest.getFilepath());
AlbumArtMessages.AlbumArt albumArtProto = AlbumArtMapper.toProtobuf(albumArt);
AlbumArtMessages.CreateAlbumArtResponse createAlbumArtResponse = AlbumArtMessages.CreateAlbumArtResponse.newBuilder()
.setAlbumart(albumArtProto)
.build();
return TransportProtocol.Response.newBuilder()
.setPayload(createAlbumArtResponse.toByteString());
} catch (IllegalArgumentException e) {
logger.error("Validation error", e);
return TransportProtocol.Response.newBuilder()
.setStatusCode(400)
.setPayload(ByteString.copyFromUtf8("Validation error: " + e.getMessage()));
} catch (Exception e) {
logger.error("Error creating album art", e);
return TransportProtocol.Response.newBuilder()
.setStatusCode(500)
.setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage()));
}
}
}

View File

@ -0,0 +1,62 @@
package com.mediamanager.service.delegate.handler.albumart;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.mediamanager.protocol.TransportProtocol;
import com.mediamanager.protocol.messages.AlbumArtMessages;
import com.mediamanager.service.delegate.ActionHandler;
import com.mediamanager.service.delegate.annotation.Action;
import com.mediamanager.service.albumart.AlbumArtService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@Action("albumart.delete")
public class DeleteAlbumArtHandler implements ActionHandler {
private static final Logger logger = LogManager.getLogger(DeleteAlbumArtHandler.class);
private final AlbumArtService albumArtService;
public DeleteAlbumArtHandler(AlbumArtService albumArtService) {
this.albumArtService = albumArtService;
}
@Override
public TransportProtocol.Response.Builder handle(ByteString requestPayload)
throws InvalidProtocolBufferException {
try {
AlbumArtMessages.DeleteAlbumArtRequest deleteRequest =
AlbumArtMessages.DeleteAlbumArtRequest.parseFrom(requestPayload);
int id = deleteRequest.getId();
boolean success = albumArtService.deleteAlbumArt(id);
AlbumArtMessages.DeleteAlbumArtResponse deleteResponse;
if (success) {
deleteResponse = AlbumArtMessages.DeleteAlbumArtResponse.newBuilder()
.setSuccess(true)
.setMessage("Album art deleted successfully")
.build();
return TransportProtocol.Response.newBuilder()
.setPayload(deleteResponse.toByteString());
} else {
deleteResponse = AlbumArtMessages.DeleteAlbumArtResponse.newBuilder()
.setSuccess(false)
.setMessage("Album art not found")
.build();
return TransportProtocol.Response.newBuilder()
.setStatusCode(404)
.setPayload(deleteResponse.toByteString());
}
} catch (Exception e) {
logger.error("Error deleting album art", e);
AlbumArtMessages.DeleteAlbumArtResponse deleteResponse =
AlbumArtMessages.DeleteAlbumArtResponse.newBuilder()
.setSuccess(false)
.setMessage("Error: " + e.getMessage())
.build();
return TransportProtocol.Response.newBuilder()
.setStatusCode(500)
.setPayload(deleteResponse.toByteString());
}
}
}

View File

@ -0,0 +1,56 @@
package com.mediamanager.service.delegate.handler.albumart;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.mediamanager.mapper.AlbumArtMapper;
import com.mediamanager.model.AlbumArt;
import com.mediamanager.protocol.TransportProtocol;
import com.mediamanager.protocol.messages.AlbumArtMessages;
import com.mediamanager.service.delegate.ActionHandler;
import com.mediamanager.service.delegate.annotation.Action;
import com.mediamanager.service.albumart.AlbumArtService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Optional;
@Action(value = "albumart.getById")
public class GetAlbumArtByIdHandler implements ActionHandler {
private static final Logger logger = LogManager.getLogger(GetAlbumArtByIdHandler.class);
private final AlbumArtService albumArtService;
public GetAlbumArtByIdHandler(AlbumArtService albumArtService) {
this.albumArtService = albumArtService;
}
@Override
public TransportProtocol.Response.Builder handle(ByteString requestPayload)
throws InvalidProtocolBufferException{
try{
AlbumArtMessages.GetAlbumArtByIdRequest getByIdRequest =
AlbumArtMessages.GetAlbumArtByIdRequest.parseFrom(requestPayload);
int id = getByIdRequest.getId();
Optional<AlbumArt> albumArtOpt = albumArtService.getAlbumArtById(id);
if (albumArtOpt.isEmpty()){
logger.warn("AlbumArt not found with ID: {}", id);
return TransportProtocol.Response.newBuilder()
.setStatusCode(404)
.setPayload(ByteString.copyFromUtf8("AlbumArt not found"));
}
AlbumArtMessages.AlbumArt albumArtProto = AlbumArtMapper.toProtobuf(albumArtOpt.get());
AlbumArtMessages.GetAlbumArtByIdResponse getByIdResponse = AlbumArtMessages.GetAlbumArtByIdResponse.newBuilder()
.setAlbumart(albumArtProto)
.build();
return TransportProtocol.Response.newBuilder()
.setPayload(getByIdResponse.toByteString());
} catch (Exception e) {
logger.error("Error getting album art by ID", e);
return TransportProtocol.Response.newBuilder()
.setStatusCode(500)
.setPayload(ByteString.copyFromUtf8("Error: "+ e.getMessage()));
}
}
}

View File

@ -0,0 +1,48 @@
package com.mediamanager.service.delegate.handler.albumart;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.mediamanager.mapper.AlbumArtMapper;
import com.mediamanager.model.AlbumArt;
import com.mediamanager.protocol.TransportProtocol;
import com.mediamanager.protocol.messages.AlbumArtMessages;
import com.mediamanager.service.delegate.ActionHandler;
import com.mediamanager.service.delegate.annotation.Action;
import com.mediamanager.service.albumart.AlbumArtService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.List;
@Action("albumart.getAll")
public class GetAlbumArtHandler implements ActionHandler {
private static final Logger logger = LogManager.getLogger(GetAlbumArtHandler.class);
private final AlbumArtService albumArtService;
public GetAlbumArtHandler(AlbumArtService albumArtService){this.albumArtService = albumArtService;}
@Override
public TransportProtocol.Response.Builder handle(ByteString requestPayload) throws InvalidProtocolBufferException {
try{
List<AlbumArt> albumArts = albumArtService.getAllAlbumArts();
AlbumArtMessages.GetAlbumArtsResponse.Builder responseBuilder = AlbumArtMessages.GetAlbumArtsResponse.newBuilder();
for (AlbumArt albumArt : albumArts) {
AlbumArtMessages.AlbumArt albumArtProto = AlbumArtMapper.toProtobuf(albumArt);
responseBuilder.addAlbumarts(albumArtProto);
}
AlbumArtMessages.GetAlbumArtsResponse getAlbumArtsResponse = responseBuilder.build();
return TransportProtocol.Response.newBuilder()
.setPayload(getAlbumArtsResponse.toByteString());
}catch (Exception e){
logger.error("Error getting album arts", e);
return TransportProtocol.Response.newBuilder()
.setStatusCode(500)
.setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage()));
}
}
}

View File

@ -0,0 +1,65 @@
package com.mediamanager.service.delegate.handler.albumart;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.mediamanager.mapper.AlbumArtMapper;
import com.mediamanager.model.AlbumArt;
import com.mediamanager.protocol.TransportProtocol;
import com.mediamanager.protocol.messages.AlbumArtMessages;
import com.mediamanager.service.delegate.ActionHandler;
import com.mediamanager.service.delegate.annotation.Action;
import com.mediamanager.service.albumart.AlbumArtService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Optional;
@Action("albumart.update")
public class UpdateAlbumArtHandler implements ActionHandler {
private static final Logger logger = LogManager.getLogger(UpdateAlbumArtHandler.class);
private final AlbumArtService albumArtService;
public UpdateAlbumArtHandler(AlbumArtService albumArtService) {
this.albumArtService = albumArtService;
}
@Override
public TransportProtocol.Response.Builder handle(ByteString requestPayload) throws InvalidProtocolBufferException {
try{
AlbumArtMessages.UpdateAlbumArtRequest updateRequest =
AlbumArtMessages.UpdateAlbumArtRequest.parseFrom(requestPayload);
int id = updateRequest.getId();
String newFilepath = updateRequest.getFilepath();
Optional<AlbumArt> albumArtOpt = albumArtService.updateAlbumArt(id, newFilepath);
if(albumArtOpt.isEmpty()){
logger.warn("AlbumArt not found with ID: {}", id);
return TransportProtocol.Response.newBuilder()
.setStatusCode(404)
.setPayload(ByteString.copyFromUtf8("AlbumArt not found"));
}
AlbumArtMessages.AlbumArt albumArtProto = AlbumArtMapper.toProtobuf(albumArtOpt.get());
AlbumArtMessages.UpdateAlbumArtResponse updateResponse = AlbumArtMessages.UpdateAlbumArtResponse.newBuilder()
.setAlbumart(albumArtProto)
.build();
return TransportProtocol.Response.newBuilder()
.setPayload(updateResponse.toByteString());
} catch (IllegalArgumentException e){
logger.error("Validation error", e);
return TransportProtocol.Response.newBuilder()
.setStatusCode(400)
.setPayload(ByteString.copyFromUtf8("Validation error: " + e.getMessage()));
} catch (Exception e) {
logger.error("Error updating album art", e);
return TransportProtocol.Response.newBuilder()
.setStatusCode(500)
.setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage()));
}
}
}

View File

@ -0,0 +1,53 @@
syntax = "proto3";
option java_package = "com.mediamanager.protocol.messages";
option java_outer_classname = "AlbumArtMessages";
package mediamanager.messages;
message AlbumArt {
int32 id = 1;
string filepath = 2;
}
message CreateAlbumArtRequest {
string filepath = 1;
}
message CreateAlbumArtResponse {
AlbumArt albumart = 1;
}
message GetAlbumArtsRequest {
}
message GetAlbumArtsResponse {
repeated AlbumArt albumarts = 1;
}
message GetAlbumArtByIdRequest {
int32 id = 1;
}
message GetAlbumArtByIdResponse {
AlbumArt albumart = 1;
}
message UpdateAlbumArtRequest {
int32 id = 1;
string filepath = 2;
}
message UpdateAlbumArtResponse {
AlbumArt albumart = 1;
}
message DeleteAlbumArtRequest {
int32 id = 1;
}
message DeleteAlbumArtResponse {
bool success = 1;
string message = 2;
}