Initial commit: Homelab API
This commit is contained in:
282
internal/handler/auth.go
Normal file
282
internal/handler/auth.go
Normal file
@@ -0,0 +1,282 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/daniil/homelab-api/internal/middleware"
|
||||
"github.com/daniil/homelab-api/internal/model"
|
||||
"github.com/daniil/homelab-api/internal/repository"
|
||||
"github.com/daniil/homelab-api/internal/service"
|
||||
)
|
||||
|
||||
type AuthHandler struct {
|
||||
authService *service.AuthService
|
||||
}
|
||||
|
||||
func NewAuthHandler(authService *service.AuthService) *AuthHandler {
|
||||
return &AuthHandler{authService: authService}
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.RegisterRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Email == "" || req.Username == "" || req.Password == "" {
|
||||
writeError(w, "email, username and password are required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.authService.Register(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrUserExists) {
|
||||
writeError(w, "user with this email already exists", http.StatusConflict)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, service.ErrWeakPassword) {
|
||||
writeError(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
writeError(w, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, resp, http.StatusCreated)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.LoginRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Email == "" || req.Password == "" {
|
||||
writeError(w, "email and password are required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.authService.Login(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrInvalidCredentials) {
|
||||
writeError(w, "invalid email or password", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, service.ErrEmailNotVerified) {
|
||||
writeError(w, "please verify your email first", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
writeError(w, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, resp, http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Refresh(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.RefreshRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.RefreshToken == "" {
|
||||
writeError(w, "refresh_token is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.authService.Refresh(req.RefreshToken)
|
||||
if err != nil {
|
||||
writeError(w, "invalid refresh token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, resp, http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Me(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
|
||||
user, err := h.authService.GetUser(userID)
|
||||
if err != nil {
|
||||
writeError(w, "user not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, user, http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
|
||||
var req model.UpdateProfileRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.authService.UpdateProfile(userID, &req)
|
||||
if err != nil {
|
||||
writeError(w, "failed to update profile", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, user, http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) ChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
|
||||
var req model.ChangePasswordRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.OldPassword == "" || req.NewPassword == "" {
|
||||
writeError(w, "old_password and new_password are required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err := h.authService.ChangePassword(userID, &req)
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrInvalidCredentials) {
|
||||
writeError(w, "invalid old password", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, service.ErrWeakPassword) {
|
||||
writeError(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
writeError(w, "failed to change password", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, map[string]string{"message": "password changed successfully"}, http.StatusOK)
|
||||
}
|
||||
|
||||
// Email verification
|
||||
|
||||
func (h *AuthHandler) VerifyEmail(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.VerifyEmailRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Token == "" {
|
||||
writeError(w, "token is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err := h.authService.VerifyEmail(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrTokenNotFound) {
|
||||
writeError(w, "invalid token", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, repository.ErrTokenExpired) {
|
||||
writeError(w, "token expired", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, repository.ErrTokenUsed) {
|
||||
writeError(w, "token already used", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
writeError(w, "failed to verify email", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, map[string]string{"message": "email verified successfully"}, http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) ResendVerification(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.ResendVerificationRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Email == "" {
|
||||
writeError(w, "email is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Always return success to not reveal if user exists
|
||||
_ = h.authService.ResendVerification(&req)
|
||||
|
||||
writeJSON(w, map[string]string{"message": "if the email exists, a verification link has been sent"}, http.StatusOK)
|
||||
}
|
||||
|
||||
// Password reset
|
||||
|
||||
func (h *AuthHandler) ForgotPassword(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.ForgotPasswordRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Email == "" {
|
||||
writeError(w, "email is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Always return success to not reveal if user exists
|
||||
_ = h.authService.ForgotPassword(&req)
|
||||
|
||||
writeJSON(w, map[string]string{"message": "if the email exists, a password reset link has been sent"}, http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) ResetPassword(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.ResetPasswordRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Token == "" || req.NewPassword == "" {
|
||||
writeError(w, "token and new_password are required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err := h.authService.ResetPassword(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrTokenNotFound) {
|
||||
writeError(w, "invalid token", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, repository.ErrTokenExpired) {
|
||||
writeError(w, "token expired", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, repository.ErrTokenUsed) {
|
||||
writeError(w, "token already used", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, service.ErrWeakPassword) {
|
||||
writeError(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
writeError(w, "failed to reset password", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, map[string]string{"message": "password reset successfully"}, http.StatusOK)
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, data interface{}, status int) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
json.NewEncoder(w).Encode(data)
|
||||
}
|
||||
|
||||
func writeError(w http.ResponseWriter, message string, status int) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
json.NewEncoder(w).Encode(map[string]string{"error": message})
|
||||
}
|
||||
235
internal/handler/habits.go
Normal file
235
internal/handler/habits.go
Normal file
@@ -0,0 +1,235 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/daniil/homelab-api/internal/middleware"
|
||||
"github.com/daniil/homelab-api/internal/model"
|
||||
"github.com/daniil/homelab-api/internal/repository"
|
||||
"github.com/daniil/homelab-api/internal/service"
|
||||
)
|
||||
|
||||
type HabitHandler struct {
|
||||
habitService *service.HabitService
|
||||
}
|
||||
|
||||
func NewHabitHandler(habitService *service.HabitService) *HabitHandler {
|
||||
return &HabitHandler{habitService: habitService}
|
||||
}
|
||||
|
||||
func (h *HabitHandler) List(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
includeArchived := r.URL.Query().Get("archived") == "true"
|
||||
|
||||
habits, err := h.habitService.List(userID, includeArchived)
|
||||
if err != nil {
|
||||
writeError(w, "failed to fetch habits", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, habits, http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *HabitHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
|
||||
var req model.CreateHabitRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Name == "" {
|
||||
writeError(w, "name is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
habit, err := h.habitService.Create(userID, &req)
|
||||
if err != nil {
|
||||
writeError(w, "failed to create habit", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, habit, http.StatusCreated)
|
||||
}
|
||||
|
||||
func (h *HabitHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
habitID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
writeError(w, "invalid habit id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
habit, err := h.habitService.Get(habitID, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrHabitNotFound) {
|
||||
writeError(w, "habit not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
writeError(w, "failed to fetch habit", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, habit, http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *HabitHandler) Update(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
habitID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
writeError(w, "invalid habit id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var req model.UpdateHabitRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
habit, err := h.habitService.Update(habitID, userID, &req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrHabitNotFound) {
|
||||
writeError(w, "habit not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
writeError(w, "failed to update habit", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, habit, http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *HabitHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
habitID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
writeError(w, "invalid habit id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.habitService.Delete(habitID, userID); err != nil {
|
||||
if errors.Is(err, repository.ErrHabitNotFound) {
|
||||
writeError(w, "habit not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
writeError(w, "failed to delete habit", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *HabitHandler) Log(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
habitID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
writeError(w, "invalid habit id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var req model.LogHabitRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
// Allow empty body - means log today with count=1
|
||||
req = model.LogHabitRequest{}
|
||||
}
|
||||
|
||||
log, err := h.habitService.Log(habitID, userID, &req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrHabitNotFound) {
|
||||
writeError(w, "habit not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
writeError(w, "failed to log habit", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, log, http.StatusCreated)
|
||||
}
|
||||
|
||||
func (h *HabitHandler) GetLogs(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
habitID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
writeError(w, "invalid habit id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
days := 30
|
||||
if d := r.URL.Query().Get("days"); d != "" {
|
||||
if parsed, err := strconv.Atoi(d); err == nil && parsed > 0 {
|
||||
days = parsed
|
||||
}
|
||||
}
|
||||
|
||||
logs, err := h.habitService.GetLogs(habitID, userID, days)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrHabitNotFound) {
|
||||
writeError(w, "habit not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
writeError(w, "failed to fetch logs", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, logs, http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *HabitHandler) DeleteLog(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
logID, err := strconv.ParseInt(chi.URLParam(r, "logId"), 10, 64)
|
||||
if err != nil {
|
||||
writeError(w, "invalid log id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.habitService.DeleteLog(logID, userID); err != nil {
|
||||
if errors.Is(err, repository.ErrLogNotFound) {
|
||||
writeError(w, "log not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
writeError(w, "failed to delete log", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *HabitHandler) Stats(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
|
||||
stats, err := h.habitService.GetOverallStats(userID)
|
||||
if err != nil {
|
||||
writeError(w, "failed to fetch stats", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, stats, http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *HabitHandler) HabitStats(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
habitID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
writeError(w, "invalid habit id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
stats, err := h.habitService.GetHabitStats(habitID, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrHabitNotFound) {
|
||||
writeError(w, "habit not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
writeError(w, "failed to fetch stats", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, stats, http.StatusOK)
|
||||
}
|
||||
20
internal/handler/health.go
Normal file
20
internal/handler/health.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type HealthHandler struct{}
|
||||
|
||||
func NewHealthHandler() *HealthHandler {
|
||||
return &HealthHandler{}
|
||||
}
|
||||
|
||||
func (h *HealthHandler) Health(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ok",
|
||||
"service": "homelab-api",
|
||||
})
|
||||
}
|
||||
186
internal/handler/tasks.go
Normal file
186
internal/handler/tasks.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/daniil/homelab-api/internal/middleware"
|
||||
"github.com/daniil/homelab-api/internal/model"
|
||||
"github.com/daniil/homelab-api/internal/repository"
|
||||
"github.com/daniil/homelab-api/internal/service"
|
||||
)
|
||||
|
||||
type TaskHandler struct {
|
||||
taskService *service.TaskService
|
||||
}
|
||||
|
||||
func NewTaskHandler(taskService *service.TaskService) *TaskHandler {
|
||||
return &TaskHandler{taskService: taskService}
|
||||
}
|
||||
|
||||
func (h *TaskHandler) List(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
|
||||
var completed *bool
|
||||
if c := r.URL.Query().Get("completed"); c != "" {
|
||||
b := c == "true"
|
||||
completed = &b
|
||||
}
|
||||
|
||||
tasks, err := h.taskService.List(userID, completed)
|
||||
if err != nil {
|
||||
writeError(w, "failed to fetch tasks", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, tasks, http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *TaskHandler) Today(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
|
||||
tasks, err := h.taskService.GetTodayTasks(userID)
|
||||
if err != nil {
|
||||
writeError(w, "failed to fetch today tasks", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, tasks, http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *TaskHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
|
||||
var req model.CreateTaskRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Title == "" {
|
||||
writeError(w, "title is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
task, err := h.taskService.Create(userID, &req)
|
||||
if err != nil {
|
||||
writeError(w, "failed to create task", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, task, http.StatusCreated)
|
||||
}
|
||||
|
||||
func (h *TaskHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
taskID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
writeError(w, "invalid task id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
task, err := h.taskService.Get(taskID, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrTaskNotFound) {
|
||||
writeError(w, "task not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
writeError(w, "failed to fetch task", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, task, http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *TaskHandler) Update(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
taskID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
writeError(w, "invalid task id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var req model.UpdateTaskRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
task, err := h.taskService.Update(taskID, userID, &req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrTaskNotFound) {
|
||||
writeError(w, "task not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
writeError(w, "failed to update task", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, task, http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *TaskHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
taskID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
writeError(w, "invalid task id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.taskService.Delete(taskID, userID); err != nil {
|
||||
if errors.Is(err, repository.ErrTaskNotFound) {
|
||||
writeError(w, "task not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
writeError(w, "failed to delete task", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *TaskHandler) Complete(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
taskID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
writeError(w, "invalid task id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
task, err := h.taskService.Complete(taskID, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrTaskNotFound) {
|
||||
writeError(w, "task not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
writeError(w, "failed to complete task", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, task, http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *TaskHandler) Uncomplete(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
taskID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
writeError(w, "invalid task id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
task, err := h.taskService.Uncomplete(taskID, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrTaskNotFound) {
|
||||
writeError(w, "task not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
writeError(w, "failed to uncomplete task", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, task, http.StatusOK)
|
||||
}
|
||||
Reference in New Issue
Block a user