feat: add swagger docs
Docker Build and Push / Build and Push Docker Image (push) Successful in 17m14s
Docker Build and Push / Build and Push Docker Image (push) Successful in 17m14s
This commit is contained in:
@@ -30,6 +30,18 @@ func isDuplicateError(err error) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// RegisterPost registers a new user account
|
||||
//
|
||||
// @Summary Register a new user
|
||||
// @Description Create a new user account with email and password
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body Auth true "Register credentials"
|
||||
// @Success 201
|
||||
// @Failure 400 {string} string "Bad request (e.g. email already exists, password too short)"
|
||||
// @Failure 500 {string} string "Internal server error"
|
||||
// @Router /api/auth/register [post]
|
||||
func (h *Handler) RegisterPost(w http.ResponseWriter, r *http.Request) {
|
||||
var register Auth
|
||||
if err := json.NewDecoder(r.Body).Decode(®ister); err != nil {
|
||||
@@ -83,6 +95,19 @@ type RefreshRequest struct {
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
// RefreshPost refreshes the access token using a refresh token
|
||||
//
|
||||
// @Summary Refresh access token
|
||||
// @Description Exchange a valid refresh token for a new access token and refresh token pair
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body RefreshRequest true "Refresh token payload"
|
||||
// @Success 200 {object} map[string]interface{} "access_token, refresh_token, expires_in"
|
||||
// @Failure 400 {string} string "Bad request"
|
||||
// @Failure 401 {string} string "Unauthorized (invalid or expired refresh token)"
|
||||
// @Failure 500 {string} string "Internal server error"
|
||||
// @Router /api/auth/refresh [post]
|
||||
func (h *Handler) RefreshPost(w http.ResponseWriter, r *http.Request) {
|
||||
var req RefreshRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
@@ -164,6 +189,18 @@ type LogoutRequest struct {
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
// LogoutPost logs out the current session by invalidating the refresh token
|
||||
//
|
||||
// @Summary Logout
|
||||
// @Description Invalidate the given refresh token to log out the current session
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body LogoutRequest true "Refresh token to invalidate"
|
||||
// @Success 204
|
||||
// @Failure 400 {string} string "Bad request"
|
||||
// @Failure 500 {string} string "Internal server error"
|
||||
// @Router /api/auth/logout [post]
|
||||
func (h *Handler) LogoutPost(w http.ResponseWriter, r *http.Request) {
|
||||
var req LogoutRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
@@ -189,6 +226,17 @@ func (h *Handler) LogoutPost(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// LogoutAllDelete logs out all sessions for the authenticated user
|
||||
//
|
||||
// @Summary Logout all sessions
|
||||
// @Description Invalidate all refresh tokens for the authenticated user
|
||||
// @Tags auth
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 204
|
||||
// @Failure 401 {string} string "Unauthorized"
|
||||
// @Failure 500 {string} string "Internal server error"
|
||||
// @Router /api/auth/logout/all [delete]
|
||||
func (h *Handler) LogoutAllDelete(w http.ResponseWriter, r *http.Request) {
|
||||
userIDStr, ok := r.Context().Value(middleware.UserIDKey).(string)
|
||||
if !ok || userIDStr == "" {
|
||||
@@ -214,6 +262,17 @@ func (h *Handler) LogoutAllDelete(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// MeGet returns the profile of the currently authenticated user
|
||||
//
|
||||
// @Summary Get current user
|
||||
// @Description Return profile information for the authenticated user
|
||||
// @Tags auth
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} map[string]interface{} "id, email, created_at, updated_at"
|
||||
// @Failure 401 {string} string "Unauthorized"
|
||||
// @Failure 500 {string} string "Internal server error"
|
||||
// @Router /api/auth/me [get]
|
||||
func (h *Handler) MeGet(w http.ResponseWriter, r *http.Request) {
|
||||
userIDStr, ok := r.Context().Value(middleware.UserIDKey).(string)
|
||||
if !ok || userIDStr == "" {
|
||||
@@ -256,6 +315,20 @@ type ChangePasswordRequest struct {
|
||||
NewPassword string `json:"new_password"`
|
||||
}
|
||||
|
||||
// MePasswordPatch changes the password of the authenticated user
|
||||
//
|
||||
// @Summary Change password
|
||||
// @Description Update the password of the currently authenticated user
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param body body ChangePasswordRequest true "Old and new password"
|
||||
// @Success 204
|
||||
// @Failure 400 {string} string "Bad request (e.g. incorrect old password)"
|
||||
// @Failure 401 {string} string "Unauthorized"
|
||||
// @Failure 500 {string} string "Internal server error"
|
||||
// @Router /api/auth/me/password [patch]
|
||||
func (h *Handler) MePasswordPatch(w http.ResponseWriter, r *http.Request) {
|
||||
userIDStr, ok := r.Context().Value(middleware.UserIDKey).(string)
|
||||
if !ok || userIDStr == "" {
|
||||
@@ -320,6 +393,19 @@ func (h *Handler) MePasswordPatch(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// LoginPost authenticates a user and returns tokens
|
||||
//
|
||||
// @Summary Login
|
||||
// @Description Authenticate with email and password to receive access and refresh tokens
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body Auth true "Login credentials"
|
||||
// @Success 200 {object} map[string]interface{} "access_token, refresh_token, expires_in"
|
||||
// @Failure 400 {string} string "Bad request"
|
||||
// @Failure 401 {string} string "Unauthorized (invalid credentials)"
|
||||
// @Failure 500 {string} string "Internal server error"
|
||||
// @Router /api/auth/login [post]
|
||||
func (h *Handler) LoginPost(w http.ResponseWriter, r *http.Request) {
|
||||
var login Auth
|
||||
if err := json.NewDecoder(r.Body).Decode(&login); err != nil {
|
||||
|
||||
@@ -137,6 +137,21 @@ func isChoiceType(t repository.QuestionType) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// FormsGet returns all forms owned by the authenticated user
|
||||
//
|
||||
// @Summary List forms
|
||||
// @Description Retrieve all forms belonging to the authenticated user, with optional search, status filter, and sort options
|
||||
// @Tags forms
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param search query string false "Filter by title (case-insensitive)"
|
||||
// @Param status query string false "Filter by response status: has_responses | no_responses"
|
||||
// @Param sort_by query string false "Sort field: created_at (default) | updated_at"
|
||||
// @Param sort_dir query string false "Sort direction: newest (default) | oldest"
|
||||
// @Success 200 {array} FormResponse "List of forms"
|
||||
// @Failure 401 {string} string "Unauthorized"
|
||||
// @Failure 500 {string} string "Internal server error"
|
||||
// @Router /api/forms [get]
|
||||
func (h *Handler) FormsGet(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := h.currentUserID(r)
|
||||
if !ok {
|
||||
@@ -223,6 +238,20 @@ func (h *Handler) FormsGet(w http.ResponseWriter, r *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(items)
|
||||
}
|
||||
|
||||
// FormsPost creates a new form
|
||||
//
|
||||
// @Summary Create a form
|
||||
// @Description Create a new form with title, description, and questions for the authenticated user
|
||||
// @Tags forms
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param body body CreateFormRequest true "Form creation payload"
|
||||
// @Success 201 {object} FormResponse "Created form"
|
||||
// @Failure 400 {string} string "Bad request (e.g. title is required)"
|
||||
// @Failure 401 {string} string "Unauthorized"
|
||||
// @Failure 500 {string} string "Internal server error"
|
||||
// @Router /api/form [post]
|
||||
func (h *Handler) FormsPost(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := h.currentUserID(r)
|
||||
if !ok {
|
||||
@@ -270,6 +299,18 @@ func (h *Handler) FormsPost(w http.ResponseWriter, r *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(buildFormResponse(form, questions, options))
|
||||
}
|
||||
|
||||
// FormGet retrieves a single form by ID
|
||||
//
|
||||
// @Summary Get a form
|
||||
// @Description Retrieve a form and its questions by form ID (publicly accessible)
|
||||
// @Tags forms
|
||||
// @Produce json
|
||||
// @Param id path string true "Form ID (UUID)"
|
||||
// @Success 200 {object} FormResponse "Form details"
|
||||
// @Failure 400 {string} string "Invalid form ID"
|
||||
// @Failure 404 {string} string "Form not found"
|
||||
// @Failure 500 {string} string "Internal server error"
|
||||
// @Router /api/form/{id} [get]
|
||||
func (h *Handler) FormGet(w http.ResponseWriter, r *http.Request) {
|
||||
formID, err := uuid.Parse(r.PathValue("id"))
|
||||
if err != nil {
|
||||
@@ -310,6 +351,23 @@ func (h *Handler) FormGet(w http.ResponseWriter, r *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(buildFormResponse(form, questions, options))
|
||||
}
|
||||
|
||||
// FormPut updates an existing form
|
||||
//
|
||||
// @Summary Update a form
|
||||
// @Description Replace a form's title, description, and questions. Only the form owner can update it
|
||||
// @Tags forms
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Form ID (UUID)"
|
||||
// @Param body body UpdateFormRequest true "Form update payload"
|
||||
// @Success 200 {object} FormResponse "Updated form"
|
||||
// @Failure 400 {string} string "Bad request (e.g. title is required)"
|
||||
// @Failure 401 {string} string "Unauthorized"
|
||||
// @Failure 403 {string} string "Forbidden (not the form owner)"
|
||||
// @Failure 404 {string} string "Form not found"
|
||||
// @Failure 500 {string} string "Internal server error"
|
||||
// @Router /api/form/{id} [put]
|
||||
func (h *Handler) FormPut(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := h.currentUserID(r)
|
||||
if !ok {
|
||||
@@ -389,6 +447,22 @@ func (h *Handler) FormPut(w http.ResponseWriter, r *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(buildFormResponse(updated, questions, options))
|
||||
}
|
||||
|
||||
// FormDelete deletes a form
|
||||
//
|
||||
// @Summary Delete a form
|
||||
// @Description Delete a form by ID. Only the form owner can delete it. Deletion is blocked if the form already has responses
|
||||
// @Tags forms
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Form ID (UUID)"
|
||||
// @Success 204
|
||||
// @Failure 400 {string} string "Invalid form ID"
|
||||
// @Failure 401 {string} string "Unauthorized"
|
||||
// @Failure 403 {string} string "Forbidden (not the form owner)"
|
||||
// @Failure 404 {string} string "Form not found"
|
||||
// @Failure 409 {string} string "Conflict (form already has responses)"
|
||||
// @Failure 500 {string} string "Internal server error"
|
||||
// @Router /api/form/{id} [delete]
|
||||
func (h *Handler) FormDelete(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := h.currentUserID(r)
|
||||
if !ok {
|
||||
@@ -508,6 +582,21 @@ type SubmitResponseResponse struct {
|
||||
Answers []AnswerResponse `json:"answers"`
|
||||
}
|
||||
|
||||
// FormResponsesPost submits a response to a form
|
||||
//
|
||||
// @Summary Submit a form response
|
||||
// @Description Submit answers to a form's questions. Authentication is optional (anonymous submissions allowed). Required questions must be answered. Choice answers must match valid options
|
||||
// @Tags forms
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Form ID (UUID)"
|
||||
// @Param body body SubmitResponseRequest true "Response answers payload"
|
||||
// @Success 201 {object} SubmitResponseResponse "Submitted response"
|
||||
// @Failure 400 {string} string "Bad request (e.g. invalid answer, missing required question)"
|
||||
// @Failure 404 {string} string "Form not found"
|
||||
// @Failure 500 {string} string "Internal server error"
|
||||
// @Router /api/form/{id}/response [post]
|
||||
func (h *Handler) FormResponsesPost(w http.ResponseWriter, r *http.Request) {
|
||||
formID, err := uuid.Parse(r.PathValue("id"))
|
||||
if err != nil {
|
||||
@@ -668,6 +757,21 @@ func (h *Handler) FormResponsesPost(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// FormResponsesGet retrieves all responses for a form
|
||||
//
|
||||
// @Summary Get form responses
|
||||
// @Description Retrieve all submitted responses for a form. Only the form owner can access this
|
||||
// @Tags forms
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Form ID (UUID)"
|
||||
// @Success 200 {array} SubmitResponseResponse "List of form responses with answers"
|
||||
// @Failure 400 {string} string "Invalid form ID"
|
||||
// @Failure 401 {string} string "Unauthorized"
|
||||
// @Failure 403 {string} string "Forbidden (not the form owner)"
|
||||
// @Failure 404 {string} string "Form not found"
|
||||
// @Failure 500 {string} string "Internal server error"
|
||||
// @Router /api/form/{id}/responses [get]
|
||||
func (h *Handler) FormResponsesGet(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := h.currentUserID(r)
|
||||
if !ok {
|
||||
|
||||
@@ -31,6 +31,14 @@ func internalServerError(w http.ResponseWriter, err error) {
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
}
|
||||
|
||||
// HealthGet returns the health status of the server
|
||||
//
|
||||
// @Summary Health check
|
||||
// @Description Returns 200 OK if the server is running
|
||||
// @Tags health
|
||||
// @Produce plain
|
||||
// @Success 200 {string} string "OK"
|
||||
// @Router /health [get]
|
||||
func (h *Handler) HealthGet(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("OK"))
|
||||
|
||||
@@ -3,10 +3,13 @@ package server
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
_ "ristek-task-be/docs"
|
||||
"ristek-task-be/internal/db/sqlc/repository"
|
||||
"ristek-task-be/internal/handler"
|
||||
"ristek-task-be/internal/jwt"
|
||||
"ristek-task-be/internal/middleware"
|
||||
|
||||
httpSwagger "github.com/swaggo/http-swagger/v2"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
@@ -30,6 +33,7 @@ func router(repository *repository.Queries, jwt *jwt.JWT) *http.ServeMux {
|
||||
h := handler.New(repository, jwt)
|
||||
|
||||
r.HandleFunc("GET /health", h.HealthGet)
|
||||
r.Handle("/swagger/", httpSwagger.Handler(httpSwagger.URL("/swagger/doc.json")))
|
||||
|
||||
authRoute := http.NewServeMux()
|
||||
r.Handle("/api/auth/", http.StripPrefix("/api/auth", authRoute))
|
||||
|
||||
Reference in New Issue
Block a user