first commit
This commit is contained in:
62
internal/service/admin_service.go
Normal file
62
internal/service/admin_service.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"homework-manager/internal/models"
|
||||
"homework-manager/internal/repository"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCannotDeleteSelf = errors.New("cannot delete yourself")
|
||||
ErrCannotChangeSelfRole = errors.New("cannot change your own role")
|
||||
)
|
||||
|
||||
type AdminService struct {
|
||||
userRepo *repository.UserRepository
|
||||
}
|
||||
|
||||
func NewAdminService() *AdminService {
|
||||
return &AdminService{
|
||||
userRepo: repository.NewUserRepository(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AdminService) GetAllUsers() ([]models.User, error) {
|
||||
return s.userRepo.FindAll()
|
||||
}
|
||||
|
||||
func (s *AdminService) GetUserByID(id uint) (*models.User, error) {
|
||||
return s.userRepo.FindByID(id)
|
||||
}
|
||||
|
||||
func (s *AdminService) DeleteUser(adminID, targetID uint) error {
|
||||
if adminID == targetID {
|
||||
return ErrCannotDeleteSelf
|
||||
}
|
||||
|
||||
_, err := s.userRepo.FindByID(targetID)
|
||||
if err != nil {
|
||||
return ErrUserNotFound
|
||||
}
|
||||
|
||||
return s.userRepo.Delete(targetID)
|
||||
}
|
||||
|
||||
func (s *AdminService) ChangeRole(adminID, targetID uint, newRole string) error {
|
||||
if adminID == targetID {
|
||||
return ErrCannotChangeSelfRole
|
||||
}
|
||||
|
||||
if newRole != "admin" && newRole != "user" {
|
||||
return errors.New("invalid role")
|
||||
}
|
||||
|
||||
user, err := s.userRepo.FindByID(targetID)
|
||||
if err != nil {
|
||||
return ErrUserNotFound
|
||||
}
|
||||
|
||||
user.Role = newRole
|
||||
return s.userRepo.Update(user)
|
||||
}
|
||||
89
internal/service/api_key_service.go
Normal file
89
internal/service/api_key_service.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"homework-manager/internal/database"
|
||||
"homework-manager/internal/models"
|
||||
)
|
||||
|
||||
type APIKeyService struct{}
|
||||
|
||||
func NewAPIKeyService() *APIKeyService {
|
||||
return &APIKeyService{}
|
||||
}
|
||||
|
||||
|
||||
func (s *APIKeyService) generateRandomKey() (string, error) {
|
||||
bytes := make([]byte, 32)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "hm_" + hex.EncodeToString(bytes), nil
|
||||
}
|
||||
|
||||
func (s *APIKeyService) hashKey(key string) string {
|
||||
hash := sha256.Sum256([]byte(key))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func (s *APIKeyService) CreateAPIKey(userID uint, name string) (string, *models.APIKey, error) {
|
||||
if name == "" {
|
||||
return "", nil, errors.New("キー名を入力してください")
|
||||
}
|
||||
|
||||
plainKey, err := s.generateRandomKey()
|
||||
if err != nil {
|
||||
return "", nil, errors.New("キーの生成に失敗しました")
|
||||
}
|
||||
|
||||
apiKey := &models.APIKey{
|
||||
UserID: userID,
|
||||
Name: name,
|
||||
KeyHash: s.hashKey(plainKey),
|
||||
}
|
||||
|
||||
if err := database.GetDB().Create(apiKey).Error; err != nil {
|
||||
return "", nil, errors.New("キーの保存に失敗しました")
|
||||
}
|
||||
|
||||
return plainKey, apiKey, nil
|
||||
}
|
||||
|
||||
func (s *APIKeyService) ValidateAPIKey(plainKey string) (uint, error) {
|
||||
hash := s.hashKey(plainKey)
|
||||
|
||||
var apiKey models.APIKey
|
||||
if err := database.GetDB().Where("key_hash = ?", hash).First(&apiKey).Error; err != nil {
|
||||
return 0, errors.New("無効なAPIキーです")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
database.GetDB().Model(&apiKey).Update("last_used", now)
|
||||
|
||||
return apiKey.UserID, nil
|
||||
}
|
||||
|
||||
func (s *APIKeyService) GetAllAPIKeys() ([]models.APIKey, error) {
|
||||
var keys []models.APIKey
|
||||
err := database.GetDB().Preload("User").Order("created_at desc").Find(&keys).Error
|
||||
return keys, err
|
||||
}
|
||||
|
||||
func (s *APIKeyService) GetAPIKeysByUser(userID uint) ([]models.APIKey, error) {
|
||||
var keys []models.APIKey
|
||||
err := database.GetDB().Where("user_id = ?", userID).Order("created_at desc").Find(&keys).Error
|
||||
return keys, err
|
||||
}
|
||||
|
||||
func (s *APIKeyService) DeleteAPIKey(id uint) error {
|
||||
result := database.GetDB().Delete(&models.APIKey{}, id)
|
||||
if result.RowsAffected == 0 {
|
||||
return errors.New("APIキーが見つかりません")
|
||||
}
|
||||
return result.Error
|
||||
}
|
||||
269
internal/service/assignment_service.go
Normal file
269
internal/service/assignment_service.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"homework-manager/internal/models"
|
||||
"homework-manager/internal/repository"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAssignmentNotFound = errors.New("assignment not found")
|
||||
ErrUnauthorized = errors.New("unauthorized")
|
||||
)
|
||||
|
||||
type PaginatedResult struct {
|
||||
Assignments []models.Assignment
|
||||
TotalCount int64
|
||||
TotalPages int
|
||||
CurrentPage int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
type AssignmentService struct {
|
||||
assignmentRepo *repository.AssignmentRepository
|
||||
}
|
||||
|
||||
func NewAssignmentService() *AssignmentService {
|
||||
return &AssignmentService{
|
||||
assignmentRepo: repository.NewAssignmentRepository(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AssignmentService) Create(userID uint, title, description, subject, priority string, dueDate time.Time) (*models.Assignment, error) {
|
||||
if priority == "" {
|
||||
priority = "medium"
|
||||
}
|
||||
assignment := &models.Assignment{
|
||||
UserID: userID,
|
||||
Title: title,
|
||||
Description: description,
|
||||
Subject: subject,
|
||||
Priority: priority,
|
||||
DueDate: dueDate,
|
||||
IsCompleted: false,
|
||||
}
|
||||
|
||||
if err := s.assignmentRepo.Create(assignment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return assignment, nil
|
||||
}
|
||||
|
||||
func (s *AssignmentService) GetByID(userID, assignmentID uint) (*models.Assignment, error) {
|
||||
assignment, err := s.assignmentRepo.FindByID(assignmentID)
|
||||
if err != nil {
|
||||
return nil, ErrAssignmentNotFound
|
||||
}
|
||||
|
||||
if assignment.UserID != userID {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
return assignment, nil
|
||||
}
|
||||
|
||||
func (s *AssignmentService) GetAllByUser(userID uint) ([]models.Assignment, error) {
|
||||
return s.assignmentRepo.FindByUserID(userID)
|
||||
}
|
||||
|
||||
func (s *AssignmentService) GetPendingByUser(userID uint) ([]models.Assignment, error) {
|
||||
return s.assignmentRepo.FindPendingByUserID(userID, 0, 0)
|
||||
}
|
||||
|
||||
func (s *AssignmentService) GetPendingByUserPaginated(userID uint, page, pageSize int) (*PaginatedResult, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
assignments, err := s.assignmentRepo.FindPendingByUserID(userID, pageSize, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
totalCount, _ := s.assignmentRepo.CountPendingByUserID(userID)
|
||||
totalPages := int((totalCount + int64(pageSize) - 1) / int64(pageSize))
|
||||
|
||||
return &PaginatedResult{
|
||||
Assignments: assignments,
|
||||
TotalCount: totalCount,
|
||||
TotalPages: totalPages,
|
||||
CurrentPage: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *AssignmentService) GetCompletedByUser(userID uint) ([]models.Assignment, error) {
|
||||
return s.assignmentRepo.FindCompletedByUserID(userID, 0, 0)
|
||||
}
|
||||
|
||||
func (s *AssignmentService) GetCompletedByUserPaginated(userID uint, page, pageSize int) (*PaginatedResult, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
assignments, err := s.assignmentRepo.FindCompletedByUserID(userID, pageSize, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
totalCount, _ := s.assignmentRepo.CountCompletedByUserID(userID)
|
||||
totalPages := int((totalCount + int64(pageSize) - 1) / int64(pageSize))
|
||||
|
||||
return &PaginatedResult{
|
||||
Assignments: assignments,
|
||||
TotalCount: totalCount,
|
||||
TotalPages: totalPages,
|
||||
CurrentPage: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *AssignmentService) GetDueTodayByUser(userID uint) ([]models.Assignment, error) {
|
||||
return s.assignmentRepo.FindDueTodayByUserID(userID)
|
||||
}
|
||||
|
||||
func (s *AssignmentService) GetDueThisWeekByUser(userID uint) ([]models.Assignment, error) {
|
||||
return s.assignmentRepo.FindDueThisWeekByUserID(userID)
|
||||
}
|
||||
|
||||
func (s *AssignmentService) GetOverdueByUser(userID uint) ([]models.Assignment, error) {
|
||||
return s.assignmentRepo.FindOverdueByUserID(userID, 0, 0)
|
||||
}
|
||||
|
||||
func (s *AssignmentService) GetOverdueByUserPaginated(userID uint, page, pageSize int) (*PaginatedResult, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
assignments, err := s.assignmentRepo.FindOverdueByUserID(userID, pageSize, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
totalCount, _ := s.assignmentRepo.CountOverdueByUserID(userID)
|
||||
totalPages := int((totalCount + int64(pageSize) - 1) / int64(pageSize))
|
||||
|
||||
return &PaginatedResult{
|
||||
Assignments: assignments,
|
||||
TotalCount: totalCount,
|
||||
TotalPages: totalPages,
|
||||
CurrentPage: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *AssignmentService) SearchAssignments(userID uint, query, priority, filter string, page, pageSize int) (*PaginatedResult, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
|
||||
assignments, totalCount, err := s.assignmentRepo.Search(userID, query, priority, filter, page, pageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
totalPages := int((totalCount + int64(pageSize) - 1) / int64(pageSize))
|
||||
|
||||
return &PaginatedResult{
|
||||
Assignments: assignments,
|
||||
TotalCount: totalCount,
|
||||
TotalPages: totalPages,
|
||||
CurrentPage: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *AssignmentService) Update(userID, assignmentID uint, title, description, subject, priority string, dueDate time.Time) (*models.Assignment, error) {
|
||||
assignment, err := s.GetByID(userID, assignmentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
assignment.Title = title
|
||||
assignment.Description = description
|
||||
assignment.Subject = subject
|
||||
assignment.Priority = priority
|
||||
assignment.DueDate = dueDate
|
||||
|
||||
if err := s.assignmentRepo.Update(assignment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return assignment, nil
|
||||
}
|
||||
|
||||
func (s *AssignmentService) ToggleComplete(userID, assignmentID uint) (*models.Assignment, error) {
|
||||
assignment, err := s.GetByID(userID, assignmentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
assignment.IsCompleted = !assignment.IsCompleted
|
||||
if assignment.IsCompleted {
|
||||
now := time.Now()
|
||||
assignment.CompletedAt = &now
|
||||
} else {
|
||||
assignment.CompletedAt = nil
|
||||
}
|
||||
|
||||
if err := s.assignmentRepo.Update(assignment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return assignment, nil
|
||||
}
|
||||
|
||||
func (s *AssignmentService) Delete(userID, assignmentID uint) error {
|
||||
assignment, err := s.GetByID(userID, assignmentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.assignmentRepo.Delete(assignment.ID)
|
||||
}
|
||||
|
||||
func (s *AssignmentService) GetSubjectsByUser(userID uint) ([]string, error) {
|
||||
return s.assignmentRepo.GetSubjectsByUserID(userID)
|
||||
}
|
||||
|
||||
type DashboardStats struct {
|
||||
TotalPending int64
|
||||
DueToday int
|
||||
DueThisWeek int
|
||||
Overdue int
|
||||
Subjects []string
|
||||
}
|
||||
|
||||
func (s *AssignmentService) GetDashboardStats(userID uint) (*DashboardStats, error) {
|
||||
pending, _ := s.assignmentRepo.CountPendingByUserID(userID)
|
||||
dueToday, _ := s.assignmentRepo.FindDueTodayByUserID(userID)
|
||||
dueThisWeek, _ := s.assignmentRepo.FindDueThisWeekByUserID(userID)
|
||||
overdueCount, _ := s.assignmentRepo.CountOverdueByUserID(userID)
|
||||
subjects, _ := s.assignmentRepo.GetSubjectsByUserID(userID)
|
||||
|
||||
return &DashboardStats{
|
||||
TotalPending: pending,
|
||||
DueToday: len(dueToday),
|
||||
DueThisWeek: len(dueThisWeek),
|
||||
Overdue: int(overdueCount),
|
||||
Subjects: subjects,
|
||||
}, nil
|
||||
}
|
||||
106
internal/service/auth_service.go
Normal file
106
internal/service/auth_service.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"homework-manager/internal/models"
|
||||
"homework-manager/internal/repository"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUserNotFound = errors.New("user not found")
|
||||
ErrEmailAlreadyExists = errors.New("email already exists")
|
||||
ErrInvalidCredentials = errors.New("invalid credentials")
|
||||
)
|
||||
|
||||
type AuthService struct {
|
||||
userRepo *repository.UserRepository
|
||||
}
|
||||
|
||||
func NewAuthService() *AuthService {
|
||||
return &AuthService{
|
||||
userRepo: repository.NewUserRepository(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AuthService) Register(email, password, name string) (*models.User, error) {
|
||||
// Check if email already exists
|
||||
_, err := s.userRepo.FindByEmail(email)
|
||||
if err == nil {
|
||||
return nil, ErrEmailAlreadyExists
|
||||
}
|
||||
|
||||
// Hash password
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Determine role (first user is admin)
|
||||
role := "user"
|
||||
count, _ := s.userRepo.Count()
|
||||
if count == 0 {
|
||||
role = "admin"
|
||||
}
|
||||
|
||||
user := &models.User{
|
||||
Email: email,
|
||||
PasswordHash: string(hashedPassword),
|
||||
Name: name,
|
||||
Role: role,
|
||||
}
|
||||
|
||||
if err := s.userRepo.Create(user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *AuthService) Login(email, password string) (*models.User, error) {
|
||||
user, err := s.userRepo.FindByEmail(email)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidCredentials
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
|
||||
return nil, ErrInvalidCredentials
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *AuthService) GetUserByID(id uint) (*models.User, error) {
|
||||
return s.userRepo.FindByID(id)
|
||||
}
|
||||
|
||||
func (s *AuthService) ChangePassword(userID uint, oldPassword, newPassword string) error {
|
||||
user, err := s.userRepo.FindByID(userID)
|
||||
if err != nil {
|
||||
return ErrUserNotFound
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(oldPassword)); err != nil {
|
||||
return ErrInvalidCredentials
|
||||
}
|
||||
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user.PasswordHash = string(hashedPassword)
|
||||
return s.userRepo.Update(user)
|
||||
}
|
||||
|
||||
func (s *AuthService) UpdateProfile(userID uint, name string) error {
|
||||
user, err := s.userRepo.FindByID(userID)
|
||||
if err != nil {
|
||||
return ErrUserNotFound
|
||||
}
|
||||
|
||||
user.Name = name
|
||||
return s.userRepo.Update(user)
|
||||
}
|
||||
Reference in New Issue
Block a user