package service import ( "context" "github.com/calendarapi/internal/models" "github.com/calendarapi/internal/repository" "github.com/calendarapi/internal/utils" "github.com/google/uuid" "github.com/jackc/pgx/v5" ) type TagService struct { queries *repository.Queries } func NewTagService(queries *repository.Queries) *TagService { return &TagService{queries: queries} } type CreateTagRequest struct { Name string Color *string } type UpdateTagRequest struct { Name *string Color *string } func (s *TagService) Create(ctx context.Context, userID uuid.UUID, req CreateTagRequest) (*models.Tag, error) { if len(req.Name) < 1 || len(req.Name) > 50 { return nil, models.NewValidationError("tag name must be 1-50 characters") } color := "#6B7280" if req.Color != nil { if err := utils.ValidateColor(*req.Color); err != nil { return nil, err } color = *req.Color } id := uuid.New() t, err := s.queries.CreateTag(ctx, repository.CreateTagParams{ ID: utils.ToPgUUID(id), OwnerID: utils.ToPgUUID(userID), Name: req.Name, Column4: color, }) if err != nil { return nil, models.ErrInternal } tag := tagFromDB(t) return &tag, nil } func (s *TagService) Get(ctx context.Context, userID uuid.UUID, tagID uuid.UUID) (*models.Tag, error) { t, err := s.queries.GetTagByID(ctx, repository.GetTagByIDParams{ ID: utils.ToPgUUID(tagID), OwnerID: utils.ToPgUUID(userID), }) if err != nil { if err == pgx.ErrNoRows { return nil, models.ErrNotFound } return nil, models.ErrInternal } tag := tagFromDB(t) return &tag, nil } func (s *TagService) List(ctx context.Context, userID uuid.UUID) ([]models.Tag, error) { rows, err := s.queries.ListTagsByOwner(ctx, utils.ToPgUUID(userID)) if err != nil { return nil, models.ErrInternal } tags := make([]models.Tag, 0, len(rows)) for _, r := range rows { tags = append(tags, tagFromDB(r)) } return tags, nil } func (s *TagService) Update(ctx context.Context, userID uuid.UUID, tagID uuid.UUID, req UpdateTagRequest) (*models.Tag, error) { if req.Name != nil && (len(*req.Name) < 1 || len(*req.Name) > 50) { return nil, models.NewValidationError("tag name must be 1-50 characters") } if req.Color != nil { if err := utils.ValidateColor(*req.Color); err != nil { return nil, err } } updateParams := repository.UpdateTagParams{ ID: utils.ToPgUUID(tagID), OwnerID: utils.ToPgUUID(userID), } if req.Name != nil { updateParams.Name = utils.ToPgTextPtr(req.Name) } if req.Color != nil { updateParams.Color = utils.ToPgTextPtr(req.Color) } t, err := s.queries.UpdateTag(ctx, updateParams) if err != nil { if err == pgx.ErrNoRows { return nil, models.ErrNotFound } return nil, models.ErrInternal } tag := tagFromDB(t) return &tag, nil } func (s *TagService) Delete(ctx context.Context, userID uuid.UUID, tagID uuid.UUID) error { err := s.queries.DeleteTag(ctx, repository.DeleteTagParams{ ID: utils.ToPgUUID(tagID), OwnerID: utils.ToPgUUID(userID), }) if err != nil { return models.ErrInternal } return nil } func (s *TagService) AttachToTask(ctx context.Context, userID uuid.UUID, tagID uuid.UUID, taskID uuid.UUID) error { if _, err := s.queries.GetTagByID(ctx, repository.GetTagByIDParams{ ID: utils.ToPgUUID(tagID), OwnerID: utils.ToPgUUID(userID), }); err != nil { if err == pgx.ErrNoRows { return models.ErrNotFound } return models.ErrInternal } return s.queries.AttachTagToTask(ctx, repository.AttachTagToTaskParams{ TaskID: utils.ToPgUUID(taskID), TagID: utils.ToPgUUID(tagID), }) } func (s *TagService) DetachFromTask(ctx context.Context, userID uuid.UUID, tagID uuid.UUID, taskID uuid.UUID) error { if _, err := s.queries.GetTagByID(ctx, repository.GetTagByIDParams{ ID: utils.ToPgUUID(tagID), OwnerID: utils.ToPgUUID(userID), }); err != nil { if err == pgx.ErrNoRows { return models.ErrNotFound } return models.ErrInternal } return s.queries.DetachTagFromTask(ctx, repository.DetachTagFromTaskParams{ TaskID: utils.ToPgUUID(taskID), TagID: utils.ToPgUUID(tagID), }) }