Implement remaining handlers and fix admin login

- Fix admin login bcrypt hash in database migrations
- Add static data handler (GET /api/static_data/, POST /api/static_data/refresh/)
- Add user handler (list users, get user, list profiles in shared residences)
- Add generate tasks report endpoint for residences
- Remove all placeholder handlers from router
- Add seeding documentation to README

New files:
- internal/handlers/static_data_handler.go
- internal/handlers/user_handler.go
- internal/services/user_service.go
- internal/dto/responses/user.go

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-11-26 23:11:49 -06:00
parent fff5d8c206
commit 9ec1bddd99
11 changed files with 604 additions and 22 deletions

View File

@@ -379,3 +379,102 @@ func (s *ResidenceService) GetResidenceTypes() ([]responses.ResidenceTypeRespons
return result, nil
}
// TaskReportData represents task data for a report
type TaskReportData struct {
ID uint `json:"id"`
Title string `json:"title"`
Description string `json:"description,omitempty"`
Category string `json:"category"`
Priority string `json:"priority"`
Status string `json:"status"`
DueDate *time.Time `json:"due_date,omitempty"`
IsCompleted bool `json:"is_completed"`
IsCancelled bool `json:"is_cancelled"`
IsArchived bool `json:"is_archived"`
}
// TasksReportResponse represents the generated tasks report
type TasksReportResponse struct {
ResidenceID uint `json:"residence_id"`
ResidenceName string `json:"residence_name"`
GeneratedAt time.Time `json:"generated_at"`
TotalTasks int `json:"total_tasks"`
Completed int `json:"completed"`
Pending int `json:"pending"`
Overdue int `json:"overdue"`
Tasks []TaskReportData `json:"tasks"`
}
// GenerateTasksReport generates a report of all tasks for a residence
func (s *ResidenceService) GenerateTasksReport(residenceID, userID uint) (*TasksReportResponse, error) {
// Check access
hasAccess, err := s.residenceRepo.HasAccess(residenceID, userID)
if err != nil {
return nil, err
}
if !hasAccess {
return nil, ErrResidenceAccessDenied
}
// Get residence details
residence, err := s.residenceRepo.FindByIDSimple(residenceID)
if err != nil {
return nil, ErrResidenceNotFound
}
// Get all tasks for the residence
tasks, err := s.residenceRepo.GetTasksForReport(residenceID)
if err != nil {
return nil, err
}
now := time.Now().UTC()
report := &TasksReportResponse{
ResidenceID: residence.ID,
ResidenceName: residence.Name,
GeneratedAt: now,
TotalTasks: len(tasks),
Tasks: make([]TaskReportData, len(tasks)),
}
for i, task := range tasks {
// Determine if task is completed (has completions)
isCompleted := len(task.Completions) > 0
taskData := TaskReportData{
ID: task.ID,
Title: task.Title,
Description: task.Description,
IsCompleted: isCompleted,
IsCancelled: task.IsCancelled,
IsArchived: task.IsArchived,
}
if task.Category != nil {
taskData.Category = task.Category.Name
}
if task.Priority != nil {
taskData.Priority = task.Priority.Name
}
if task.Status != nil {
taskData.Status = task.Status.Name
}
if task.DueDate != nil {
taskData.DueDate = task.DueDate
}
report.Tasks[i] = taskData
if isCompleted {
report.Completed++
} else if !task.IsCancelled && !task.IsArchived {
report.Pending++
if task.DueDate != nil && task.DueDate.Before(now) {
report.Overdue++
}
}
}
return report, nil
}

View File

@@ -0,0 +1,85 @@
package services
import (
"errors"
"github.com/treytartt/mycrib-api/internal/dto/responses"
"github.com/treytartt/mycrib-api/internal/repositories"
)
var (
ErrUserNotFound = errors.New("user not found")
)
// UserService handles user-related business logic
type UserService struct {
userRepo *repositories.UserRepository
}
// NewUserService creates a new user service
func NewUserService(userRepo *repositories.UserRepository) *UserService {
return &UserService{
userRepo: userRepo,
}
}
// ListUsersInSharedResidences returns users that share residences with the given user
func (s *UserService) ListUsersInSharedResidences(userID uint) ([]responses.UserSummary, error) {
users, err := s.userRepo.FindUsersInSharedResidences(userID)
if err != nil {
return nil, err
}
var result []responses.UserSummary
for _, u := range users {
result = append(result, responses.UserSummary{
ID: u.ID,
Username: u.Username,
Email: u.Email,
FirstName: u.FirstName,
LastName: u.LastName,
})
}
return result, nil
}
// GetUserIfSharedResidence returns a user if they share a residence with the requesting user
func (s *UserService) GetUserIfSharedResidence(targetUserID, requestingUserID uint) (*responses.UserSummary, error) {
user, err := s.userRepo.FindUserIfSharedResidence(targetUserID, requestingUserID)
if err != nil {
return nil, err
}
if user == nil {
return nil, ErrUserNotFound
}
return &responses.UserSummary{
ID: user.ID,
Username: user.Username,
Email: user.Email,
FirstName: user.FirstName,
LastName: user.LastName,
}, nil
}
// ListProfilesInSharedResidences returns user profiles for users in shared residences
func (s *UserService) ListProfilesInSharedResidences(userID uint) ([]responses.UserProfileSummary, error) {
profiles, err := s.userRepo.FindProfilesInSharedResidences(userID)
if err != nil {
return nil, err
}
var result []responses.UserProfileSummary
for _, p := range profiles {
result = append(result, responses.UserProfileSummary{
ID: p.ID,
UserID: p.UserID,
Bio: p.Bio,
ProfilePicture: p.ProfilePicture,
PhoneNumber: p.PhoneNumber,
})
}
return result, nil
}