This commit is contained in:
2025-11-03 12:24:01 +02:00
commit 0806865287
177 changed files with 18453 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
package service
import (
"context"
"go-server/internal/models"
"go-server/internal/repository"
)
type CategoryService struct {
categoryRepo *repository.CategoryRepository
}
func NewCategoryService(categoryRepo *repository.CategoryRepository) *CategoryService {
return &CategoryService{categoryRepo: categoryRepo}
}
func (s *CategoryService) GetAll(ctx context.Context) ([]models.Category, error) {
return s.categoryRepo.GetAll(ctx)
}

View File

@@ -0,0 +1,86 @@
package service
import (
"context"
"go-server/internal/repository"
)
type DailyOverviewService struct {
dailyRepo *repository.DailyOverviewRepository
}
func NewDailyOverviewService(dailyRepo *repository.DailyOverviewRepository) *DailyOverviewService {
return &DailyOverviewService{
dailyRepo: dailyRepo,
}
}
// DailyNutrientSummary represents the complete daily overview
type DailyNutrientSummary struct {
NutrientTotals []repository.NutrientTotal `json:"nutrientTotals"`
ByCategory map[string][]repository.NutrientTotal `json:"byCategory"`
SupplementBreakdown []repository.SupplementNutrientDetails `json:"supplementBreakdown"`
Summary DailySummaryStats `json:"summary"`
}
type DailySummaryStats struct {
TotalNutrients int `json:"totalNutrients"`
TotalSupplements int `json:"totalSupplements"`
CategoriesCount int `json:"categoriesCount"`
}
// GetDailyOverview returns a comprehensive overview of daily nutrient intake
func (s *DailyOverviewService) GetDailyOverview(ctx context.Context) (*DailyNutrientSummary, error) {
// Get nutrient totals
totals, err := s.dailyRepo.GetNutrientTotals(ctx)
if err != nil {
return nil, err
}
// Get breakdown by category
byCategory, err := s.dailyRepo.GetNutrientsByCategory(ctx)
if err != nil {
return nil, err
}
// Get supplement breakdown
breakdown, err := s.dailyRepo.GetSupplementBreakdown(ctx)
if err != nil {
return nil, err
}
// Calculate summary stats
supplementMap := make(map[string]bool)
for _, item := range breakdown {
supplementMap[item.SupplementName] = true
}
summary := DailySummaryStats{
TotalNutrients: len(totals),
TotalSupplements: len(supplementMap),
CategoriesCount: len(byCategory),
}
return &DailyNutrientSummary{
NutrientTotals: totals,
ByCategory: byCategory,
SupplementBreakdown: breakdown,
Summary: summary,
}, nil
}
// GetNutrientTotals returns just the aggregated totals
func (s *DailyOverviewService) GetNutrientTotals(ctx context.Context) ([]repository.NutrientTotal, error) {
return s.dailyRepo.GetNutrientTotals(ctx)
}
// GetSupplementBreakdown returns detailed breakdown by supplement
func (s *DailyOverviewService) GetSupplementBreakdown(ctx context.Context) ([]repository.SupplementNutrientDetails, error) {
return s.dailyRepo.GetSupplementBreakdown(ctx)
}
// ExecuteCustomQuery allows executing custom SQL queries
// This gives you maximum flexibility for complex analytics
func (s *DailyOverviewService) ExecuteCustomQuery(ctx context.Context, query string, args ...interface{}) ([]map[string]interface{}, error) {
return s.dailyRepo.ExecuteRawQueryWithResult(ctx, query, args...)
}

View File

@@ -0,0 +1,19 @@
package service
import (
"context"
"go-server/internal/models"
"go-server/internal/repository"
)
type NutrientService struct {
repo *repository.NutrientRepository
}
func NewNutrientService(repo *repository.NutrientRepository) *NutrientService {
return &NutrientService{repo: repo}
}
func (s *NutrientService) GetAll(ctx context.Context) ([]*models.Nutrient, error) {
return s.repo.GetAll(ctx)
}

View File

@@ -0,0 +1,43 @@
package service
import (
"context"
"go-server/internal/models"
"go-server/internal/repository"
)
type SupplementService struct {
repo *repository.SupplementRepository
nutrientRepo *repository.NutrientRepository
}
func NewSupplementService(repo *repository.SupplementRepository, nutrientRepo *repository.NutrientRepository) *SupplementService {
return &SupplementService{repo: repo, nutrientRepo: nutrientRepo}
}
func (s *SupplementService) GetAll(ctx context.Context) ([]*models.Supplement, error) {
return s.repo.GetAll(ctx)
}
func (s *SupplementService) GetDailySupplementsOverview(ctx context.Context) (*models.DailySupplementsOverview, error) {
supplements, err := s.repo.GetAll(ctx)
if err != nil {
return nil, err
}
nutrients, err := s.nutrientRepo.GetAll(ctx)
if err != nil {
return nil, err
}
overview, err := s.repo.GetDailySupplementsOverview(ctx)
if err != nil {
return nil, err
}
return &models.DailySupplementsOverview{
Supplements: supplements,
Nutrients: nutrients,
Overview: overview,
}, nil
}

View File

@@ -0,0 +1,181 @@
package service
import (
"context"
"errors"
"go-server/internal/models"
"go-server/internal/repository"
"time"
)
type TodoService struct {
todoRepo *repository.TodoRepository
}
func NewTodoService(todoRepo *repository.TodoRepository) *TodoService {
return &TodoService{
todoRepo: todoRepo,
}
}
// CreateTodoRequest represents the request to create a new todo
type CreateTodoRequest struct {
Title string `json:"title" validate:"required,min=1,max=200"`
Description string `json:"description" validate:"max=1000"`
Color string `json:"color" validate:"required,hexcolor"`
}
// UpdateTodoRequest represents the request to update a todo
type UpdateTodoRequest struct {
Title string `json:"title" validate:"required,min=1,max=200"`
Description string `json:"description" validate:"max=1000"`
Color string `json:"color" validate:"required,hexcolor"`
}
// CompleteTodoRequest represents the request to complete a todo
type CompleteTodoRequest struct {
Description string `json:"description" validate:"max=1000"`
}
// CreateTodo creates a new todo for a user
func (s *TodoService) CreateTodo(ctx context.Context, userID models.ULID, req CreateTodoRequest) (*models.Todo, error) {
todo := &models.Todo{
UserID: userID,
Title: req.Title,
Description: req.Description,
Color: req.Color,
IsActive: true,
}
err := s.todoRepo.CreateTodo(ctx, todo)
if err != nil {
return nil, err
}
return todo, nil
}
// GetTodosWithStats gets all todos for a user with completion statistics
func (s *TodoService) GetTodosWithStats(ctx context.Context, userID models.ULID) ([]models.TodoWithStats, error) {
return s.todoRepo.GetTodosWithStats(ctx, userID)
}
// GetTodoByID gets a todo by ID (with user verification)
func (s *TodoService) GetTodoByID(ctx context.Context, todoID, userID models.ULID) (*models.Todo, error) {
return s.todoRepo.GetTodoByID(ctx, todoID, userID)
}
// UpdateTodo updates a todo
func (s *TodoService) UpdateTodo(ctx context.Context, todoID, userID models.ULID, req UpdateTodoRequest) (*models.Todo, error) {
todo, err := s.todoRepo.GetTodoByID(ctx, todoID, userID)
if err != nil {
return nil, err
}
todo.Title = req.Title
todo.Description = req.Description
todo.Color = req.Color
err = s.todoRepo.UpdateTodo(ctx, todo)
if err != nil {
return nil, err
}
return todo, nil
}
// DeleteTodo deletes (deactivates) a todo
func (s *TodoService) DeleteTodo(ctx context.Context, todoID, userID models.ULID) error {
return s.todoRepo.DeleteTodo(ctx, todoID, userID)
}
// CompleteTodo marks a todo as completed for today
func (s *TodoService) CompleteTodo(ctx context.Context, todoID, userID models.ULID, req CompleteTodoRequest) error {
// First check if the todo exists and belongs to the user
todo, err := s.todoRepo.GetTodoByID(ctx, todoID, userID)
if err != nil {
return err
}
// Check if already completed today
completedToday, err := s.todoRepo.CheckTodoCompletedToday(ctx, todoID, userID)
if err != nil {
return err
}
if completedToday {
return errors.New("todo already completed today")
}
// Create completion record
completion := &models.TodoCompletion{
TodoID: todo.ID,
UserID: userID,
CompletedAt: time.Now(),
Description: req.Description,
}
return s.todoRepo.CompleteTodo(ctx, completion)
}
// GetTodaysSummary gets today's todo summary
func (s *TodoService) GetTodaysSummary(ctx context.Context, userID models.ULID) (*models.DailyTodoSummary, error) {
todos, err := s.todoRepo.GetTodosWithStats(ctx, userID)
if err != nil {
return nil, err
}
completedCount := 0
for _, todo := range todos {
if todo.CompletedToday {
completedCount++
}
}
summary := &models.DailyTodoSummary{
Date: time.Now(),
Todos: todos,
CompletedCount: completedCount,
TotalCount: len(todos),
}
return summary, nil
}
// GetActivityLog gets the user's activity log with pagination
func (s *TodoService) GetActivityLog(ctx context.Context, userID models.ULID, limit, offset int) ([]models.TodoCompletion, error) {
if limit <= 0 {
limit = 50 // Default limit
}
if limit > 200 {
limit = 200 // Max limit
}
return s.todoRepo.GetActivityLog(ctx, userID, limit, offset)
}
// GetActivityLogByDate gets activity log for a specific date
func (s *TodoService) GetActivityLogByDate(ctx context.Context, userID models.ULID, date time.Time) ([]models.TodoCompletion, error) {
return s.todoRepo.GetActivityLogByDate(ctx, userID, date)
}
// GetWeeklySummary gets a summary of the past 7 days
func (s *TodoService) GetWeeklySummary(ctx context.Context, userID models.ULID) (map[string][]models.TodoCompletion, error) {
endDate := time.Now().Add(24 * time.Hour) // Include today
startDate := endDate.Add(-7 * 24 * time.Hour) // 7 days ago
completions, err := s.todoRepo.GetCompletionsByDateRange(ctx, userID, startDate, endDate)
if err != nil {
return nil, err
}
// Group by date
dailyCompletions := make(map[string][]models.TodoCompletion)
for _, completion := range completions {
dateKey := completion.CompletedAt.Format("2006-01-02")
dailyCompletions[dateKey] = append(dailyCompletions[dateKey], completion)
}
return dailyCompletions, nil
}

View File

@@ -0,0 +1,138 @@
package service
import (
"context"
"errors"
"fmt"
"go-server/internal/models"
"go-server/internal/repository"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
)
type UserService struct {
repo *repository.UserRepository
}
type CreateUserInput struct {
Email string
Password string
FirstName string
LastName string
}
type UpdateUserInput struct {
FirstName *string
LastName *string
}
type ChangePasswordInput struct {
Password string
}
func NewUserService(repo *repository.UserRepository) *UserService {
return &UserService{repo: repo}
}
func (s *UserService) GetByID(ctx context.Context, id uuid.UUID) (*models.User, error) {
return s.repo.GetByID(ctx, id)
}
func (s *UserService) GetAll(ctx context.Context) ([]*models.User, error) {
return s.repo.GetAll(ctx)
}
func (s *UserService) Create(ctx context.Context, input CreateUserInput) (*models.User, error) {
hashedPassword, err := hashPassword(input.Password)
if err != nil {
return nil, fmt.Errorf("password hashing failed: %w", err)
}
user := &models.User{
Email: input.Email,
Password: hashedPassword,
FirstName: input.FirstName,
LastName: input.LastName,
PlatformRole: models.PlatformRoleUser,
}
if err := s.repo.Create(ctx, user); err != nil {
return nil, fmt.Errorf("failed to create user: %w", err)
}
return user, nil
}
func (s *UserService) Update(ctx context.Context, id uuid.UUID, input UpdateUserInput) (*models.User, error) {
user, err := s.repo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to fetch user: %w", err)
}
if user == nil {
return nil, errors.New("user not found")
}
if input.FirstName != nil {
user.FirstName = *input.FirstName
}
if input.LastName != nil {
user.LastName = *input.LastName
}
if err := s.repo.Update(ctx, user); err != nil {
return nil, fmt.Errorf("failed to update user: %w", err)
}
return user, nil
}
func (s *UserService) UpdateLastLogin(ctx context.Context, id uuid.UUID) error {
user, err := s.repo.GetByID(ctx, id)
if err != nil {
return fmt.Errorf("failed to fetch user: %w", err)
}
if user == nil {
return errors.New("user not found")
}
if err := s.repo.Update(ctx, user); err != nil {
return fmt.Errorf("failed to update last login: %w", err)
}
return nil
}
func (s *UserService) Delete(ctx context.Context, id uuid.UUID) error {
return s.repo.Delete(ctx, id)
}
func (s *UserService) ChangePassword(ctx context.Context, id uuid.UUID, input ChangePasswordInput) error {
hashedPassword, err := hashPassword(input.Password)
if err != nil {
return fmt.Errorf("password hashing failed: %w", err)
}
user, err := s.repo.GetByID(ctx, id)
if err != nil {
return fmt.Errorf("failed to fetch user: %w", err)
}
if user == nil {
return errors.New("user not found")
}
user.Password = hashedPassword
if err := s.repo.Update(ctx, user); err != nil {
return fmt.Errorf("failed to update user: %w", err)
}
return nil
}
// hashPassword generates a bcrypt hash for a given password.
func hashPassword(password string) (string, error) {
hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hashed), nil
}