first commit

This commit is contained in:
2026-06-22 14:31:01 +05:00
commit 109e01a656
35 changed files with 2120 additions and 0 deletions
+177
View File
@@ -0,0 +1,177 @@
package handler
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"secunda-test/internal/domain"
"secunda-test/internal/httpx"
"secunda-test/internal/service"
)
type Handler struct {
auth *service.AuthService
teams *service.TeamService
tasks *service.TaskService
mw *httpx.Middleware
}
func New(auth *service.AuthService, teams *service.TeamService, tasks *service.TaskService, mw *httpx.Middleware) *Handler {
return &Handler{auth: auth, teams: teams, tasks: tasks, mw: mw}
}
func (h *Handler) Routes() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("POST /api/v1/register", h.register)
mux.HandleFunc("POST /api/v1/login", h.login)
mux.Handle("POST /api/v1/teams", h.mw.Auth(http.HandlerFunc(h.createTeam)))
mux.Handle("GET /api/v1/teams", h.mw.Auth(http.HandlerFunc(h.listTeams)))
mux.Handle("POST /api/v1/teams/", h.mw.Auth(http.HandlerFunc(h.teamSubroutes)))
mux.Handle("POST /api/v1/tasks", h.mw.Auth(http.HandlerFunc(h.createTask)))
mux.Handle("GET /api/v1/tasks", h.mw.Auth(http.HandlerFunc(h.listTasks)))
mux.Handle("PUT /api/v1/tasks/", h.mw.Auth(http.HandlerFunc(h.taskSubroutes)))
mux.Handle("GET /api/v1/tasks/", h.mw.Auth(http.HandlerFunc(h.taskSubroutes)))
mux.Handle("GET /api/v1/reports/team-summary", h.mw.Auth(http.HandlerFunc(h.teamSummary)))
mux.Handle("GET /api/v1/reports/top-creators", h.mw.Auth(http.HandlerFunc(h.topCreators)))
mux.Handle("GET /api/v1/reports/invalid-assignees", h.mw.Auth(http.HandlerFunc(h.invalidAssignees)))
return h.mw.Chain(mux)
}
func (h *Handler) register(w http.ResponseWriter, r *http.Request) {
var req struct{ Email, Password, Name string }
if !decode(w, r, &req) {
return
}
id, err := h.auth.Register(r.Context(), req.Email, req.Password, req.Name)
if err != nil {
httpx.WriteError(w, httpx.StatusFromError(err), err.Error())
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]int64{"id": id})
}
func (h *Handler) login(w http.ResponseWriter, r *http.Request) {
var req struct{ Email, Password string }
if !decode(w, r, &req) {
return
}
token, err := h.auth.Login(r.Context(), req.Email, req.Password)
if err != nil {
httpx.WriteError(w, httpx.StatusFromError(err), err.Error())
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]string{"token": token})
}
func (h *Handler) createTeam(w http.ResponseWriter, r *http.Request) {
var req struct{ Name string }
if !decode(w, r, &req) {
return
}
team, err := h.teams.Create(r.Context(), mustUserID(r), req.Name)
respond(w, team, http.StatusCreated, err)
}
func (h *Handler) listTeams(w http.ResponseWriter, r *http.Request) {
teams, err := h.teams.List(r.Context(), mustUserID(r))
respond(w, teams, http.StatusOK, err)
}
func (h *Handler) teamSubroutes(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/v1/teams/"), "/")
if len(parts) == 2 && parts[1] == "invite" {
teamID, _ := strconv.ParseInt(parts[0], 10, 64)
var req struct{ Email string }
if !decode(w, r, &req) {
return
}
err := h.teams.Invite(r.Context(), mustUserID(r), teamID, req.Email)
respond(w, map[string]string{"status": "invited"}, http.StatusOK, err)
return
}
http.NotFound(w, r)
}
func (h *Handler) createTask(w http.ResponseWriter, r *http.Request) {
var req domain.Task
if !decode(w, r, &req) {
return
}
task, err := h.tasks.Create(r.Context(), mustUserID(r), req)
respond(w, task, http.StatusCreated, err)
}
func (h *Handler) listTasks(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
teamID, _ := strconv.ParseInt(q.Get("team_id"), 10, 64)
page, _ := strconv.Atoi(q.Get("page"))
pageSize, _ := strconv.Atoi(q.Get("page_size"))
var assignee *int64
if q.Get("assignee_id") != "" {
v, _ := strconv.ParseInt(q.Get("assignee_id"), 10, 64)
assignee = &v
}
tasks, err := h.tasks.List(r.Context(), mustUserID(r), domain.TaskFilter{
TeamID: teamID, Status: domain.TaskStatus(q.Get("status")), AssigneeID: assignee, Page: page, PageSize: pageSize,
})
respond(w, tasks, http.StatusOK, err)
}
func (h *Handler) taskSubroutes(w http.ResponseWriter, r *http.Request) {
path := strings.TrimPrefix(r.URL.Path, "/api/v1/tasks/")
parts := strings.Split(path, "/")
id, _ := strconv.ParseInt(parts[0], 10, 64)
if len(parts) == 2 && parts[1] == "history" && r.Method == http.MethodGet {
history, err := h.tasks.History(r.Context(), mustUserID(r), id)
respond(w, history, http.StatusOK, err)
return
}
if len(parts) == 1 && r.Method == http.MethodPut {
var req domain.Task
if !decode(w, r, &req) {
return
}
task, err := h.tasks.Update(r.Context(), mustUserID(r), id, req)
respond(w, task, http.StatusOK, err)
return
}
http.NotFound(w, r)
}
func (h *Handler) teamSummary(w http.ResponseWriter, r *http.Request) {
out, err := h.tasks.TeamSummary(r.Context())
respond(w, out, http.StatusOK, err)
}
func (h *Handler) topCreators(w http.ResponseWriter, r *http.Request) {
out, err := h.tasks.TopCreators(r.Context())
respond(w, out, http.StatusOK, err)
}
func (h *Handler) invalidAssignees(w http.ResponseWriter, r *http.Request) {
out, err := h.tasks.InvalidAssignees(r.Context())
respond(w, out, http.StatusOK, err)
}
func decode(w http.ResponseWriter, r *http.Request, v any) bool {
if err := json.NewDecoder(r.Body).Decode(v); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid json")
return false
}
return true
}
func respond(w http.ResponseWriter, v any, status int, err error) {
if err != nil {
httpx.WriteError(w, httpx.StatusFromError(err), err.Error())
return
}
httpx.WriteJSON(w, status, v)
}
func mustUserID(r *http.Request) int64 {
id, _ := httpx.UserID(r.Context())
return id
}