updates
This commit is contained in:
19
server/internal/service/category_service.go
Normal file
19
server/internal/service/category_service.go
Normal 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)
|
||||
}
|
||||
86
server/internal/service/daily_overview_service.go
Normal file
86
server/internal/service/daily_overview_service.go
Normal 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...)
|
||||
}
|
||||
19
server/internal/service/nutrient_service.go
Normal file
19
server/internal/service/nutrient_service.go
Normal 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)
|
||||
}
|
||||
43
server/internal/service/supplement_service.go
Normal file
43
server/internal/service/supplement_service.go
Normal 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
|
||||
}
|
||||
181
server/internal/service/todo_service.go
Normal file
181
server/internal/service/todo_service.go
Normal 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
|
||||
}
|
||||
|
||||
138
server/internal/service/user_service.go
Normal file
138
server/internal/service/user_service.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user