Files
CalendarApi/internal/service/tag.go
Michilis bd24545b7b Fix BASE_URL config loading, add tasks/projects; robust .env path resolution
- Config: try ENV_FILE, .env, ../.env for loading; trim trailing slash from BaseURL
- Log BASE_URL at server startup for verification
- .env.example: document BASE_URL
- Tasks, projects, tags, migrations and related API/handlers

Made-with: Cursor
2026-03-09 18:57:51 +00:00

160 lines
4.0 KiB
Go

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),
})
}