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:
+1056
File diff suppressed because it is too large
Load Diff
+1032
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,675 @@
|
|||||||
|
basePath: /
|
||||||
|
definitions:
|
||||||
|
handler.AnswerInput:
|
||||||
|
properties:
|
||||||
|
answer:
|
||||||
|
type: string
|
||||||
|
question_id:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
handler.AnswerResponse:
|
||||||
|
properties:
|
||||||
|
answer:
|
||||||
|
type: string
|
||||||
|
question_id:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
handler.Auth:
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
handler.ChangePasswordRequest:
|
||||||
|
properties:
|
||||||
|
new_password:
|
||||||
|
type: string
|
||||||
|
old_password:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
handler.CreateFormRequest:
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
questions:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/handler.QuestionInput'
|
||||||
|
type: array
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
handler.FormResponse:
|
||||||
|
properties:
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
questions:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/handler.QuestionResponse'
|
||||||
|
type: array
|
||||||
|
response_count:
|
||||||
|
type: integer
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
updated_at:
|
||||||
|
type: string
|
||||||
|
user_id:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
handler.LogoutRequest:
|
||||||
|
properties:
|
||||||
|
refresh_token:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
handler.QuestionInput:
|
||||||
|
properties:
|
||||||
|
options:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/handler.QuestionOptionInput'
|
||||||
|
type: array
|
||||||
|
position:
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
type: boolean
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
handler.QuestionOptionInput:
|
||||||
|
properties:
|
||||||
|
label:
|
||||||
|
type: string
|
||||||
|
position:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
handler.QuestionOptionResponse:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
label:
|
||||||
|
type: string
|
||||||
|
position:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
handler.QuestionResponse:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
options:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/handler.QuestionOptionResponse'
|
||||||
|
type: array
|
||||||
|
position:
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
type: boolean
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
handler.RefreshRequest:
|
||||||
|
properties:
|
||||||
|
refresh_token:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
handler.SubmitResponseRequest:
|
||||||
|
properties:
|
||||||
|
answers:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/handler.AnswerInput'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
handler.SubmitResponseResponse:
|
||||||
|
properties:
|
||||||
|
answers:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/handler.AnswerResponse'
|
||||||
|
type: array
|
||||||
|
form_id:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
submitted_at:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
handler.UpdateFormRequest:
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
questions:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/handler.QuestionInput'
|
||||||
|
type: array
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
host: localhost:8080
|
||||||
|
info:
|
||||||
|
contact: {}
|
||||||
|
description: REST API for Ristek Task Backend
|
||||||
|
title: Ristek Task API
|
||||||
|
version: "1.0"
|
||||||
|
paths:
|
||||||
|
/api/auth/login:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Authenticate with email and password to receive access and refresh
|
||||||
|
tokens
|
||||||
|
parameters:
|
||||||
|
- description: Login credentials
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handler.Auth'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: access_token, refresh_token, expires_in
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad request
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"401":
|
||||||
|
description: Unauthorized (invalid credentials)
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Internal server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
summary: Login
|
||||||
|
tags:
|
||||||
|
- auth
|
||||||
|
/api/auth/logout:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Invalidate the given refresh token to log out the current session
|
||||||
|
parameters:
|
||||||
|
- description: Refresh token to invalidate
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handler.LogoutRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"204":
|
||||||
|
description: No Content
|
||||||
|
"400":
|
||||||
|
description: Bad request
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Internal server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
summary: Logout
|
||||||
|
tags:
|
||||||
|
- auth
|
||||||
|
/api/auth/logout/all:
|
||||||
|
delete:
|
||||||
|
description: Invalidate all refresh tokens for the authenticated user
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"204":
|
||||||
|
description: No Content
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Internal server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Logout all sessions
|
||||||
|
tags:
|
||||||
|
- auth
|
||||||
|
/api/auth/me:
|
||||||
|
get:
|
||||||
|
description: Return profile information for the authenticated user
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: id, email, created_at, updated_at
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Internal server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Get current user
|
||||||
|
tags:
|
||||||
|
- auth
|
||||||
|
/api/auth/me/password:
|
||||||
|
patch:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Update the password of the currently authenticated user
|
||||||
|
parameters:
|
||||||
|
- description: Old and new password
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handler.ChangePasswordRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"204":
|
||||||
|
description: No Content
|
||||||
|
"400":
|
||||||
|
description: Bad request (e.g. incorrect old password)
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Internal server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Change password
|
||||||
|
tags:
|
||||||
|
- auth
|
||||||
|
/api/auth/refresh:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Exchange a valid refresh token for a new access token and refresh
|
||||||
|
token pair
|
||||||
|
parameters:
|
||||||
|
- description: Refresh token payload
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handler.RefreshRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: access_token, refresh_token, expires_in
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad request
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"401":
|
||||||
|
description: Unauthorized (invalid or expired refresh token)
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Internal server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
summary: Refresh access token
|
||||||
|
tags:
|
||||||
|
- auth
|
||||||
|
/api/auth/register:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Create a new user account with email and password
|
||||||
|
parameters:
|
||||||
|
- description: Register credentials
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handler.Auth'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Created
|
||||||
|
"400":
|
||||||
|
description: Bad request (e.g. email already exists, password too short)
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Internal server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
summary: Register a new user
|
||||||
|
tags:
|
||||||
|
- auth
|
||||||
|
/api/form:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Create a new form with title, description, and questions for the
|
||||||
|
authenticated user
|
||||||
|
parameters:
|
||||||
|
- description: Form creation payload
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handler.CreateFormRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Created form
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handler.FormResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad request (e.g. title is required)
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Internal server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Create a form
|
||||||
|
tags:
|
||||||
|
- forms
|
||||||
|
/api/form/{id}:
|
||||||
|
delete:
|
||||||
|
description: Delete a form by ID. Only the form owner can delete it. Deletion
|
||||||
|
is blocked if the form already has responses
|
||||||
|
parameters:
|
||||||
|
- description: Form ID (UUID)
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"204":
|
||||||
|
description: No Content
|
||||||
|
"400":
|
||||||
|
description: Invalid form ID
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"403":
|
||||||
|
description: Forbidden (not the form owner)
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"404":
|
||||||
|
description: Form not found
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"409":
|
||||||
|
description: Conflict (form already has responses)
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Internal server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Delete a form
|
||||||
|
tags:
|
||||||
|
- forms
|
||||||
|
get:
|
||||||
|
description: Retrieve a form and its questions by form ID (publicly accessible)
|
||||||
|
parameters:
|
||||||
|
- description: Form ID (UUID)
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Form details
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handler.FormResponse'
|
||||||
|
"400":
|
||||||
|
description: Invalid form ID
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"404":
|
||||||
|
description: Form not found
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Internal server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
summary: Get a form
|
||||||
|
tags:
|
||||||
|
- forms
|
||||||
|
put:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Replace a form's title, description, and questions. Only the form
|
||||||
|
owner can update it
|
||||||
|
parameters:
|
||||||
|
- description: Form ID (UUID)
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Form update payload
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handler.UpdateFormRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Updated form
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handler.FormResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad request (e.g. title is required)
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"403":
|
||||||
|
description: Forbidden (not the form owner)
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"404":
|
||||||
|
description: Form not found
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Internal server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Update a form
|
||||||
|
tags:
|
||||||
|
- forms
|
||||||
|
/api/form/{id}/response:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
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
|
||||||
|
parameters:
|
||||||
|
- description: Form ID (UUID)
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Response answers payload
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handler.SubmitResponseRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Submitted response
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handler.SubmitResponseResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad request (e.g. invalid answer, missing required question)
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"404":
|
||||||
|
description: Form not found
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Internal server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Submit a form response
|
||||||
|
tags:
|
||||||
|
- forms
|
||||||
|
/api/form/{id}/responses:
|
||||||
|
get:
|
||||||
|
description: Retrieve all submitted responses for a form. Only the form owner
|
||||||
|
can access this
|
||||||
|
parameters:
|
||||||
|
- description: Form ID (UUID)
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: List of form responses with answers
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/handler.SubmitResponseResponse'
|
||||||
|
type: array
|
||||||
|
"400":
|
||||||
|
description: Invalid form ID
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"403":
|
||||||
|
description: Forbidden (not the form owner)
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"404":
|
||||||
|
description: Form not found
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Internal server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Get form responses
|
||||||
|
tags:
|
||||||
|
- forms
|
||||||
|
/api/forms:
|
||||||
|
get:
|
||||||
|
description: Retrieve all forms belonging to the authenticated user, with optional
|
||||||
|
search, status filter, and sort options
|
||||||
|
parameters:
|
||||||
|
- description: Filter by title (case-insensitive)
|
||||||
|
in: query
|
||||||
|
name: search
|
||||||
|
type: string
|
||||||
|
- description: 'Filter by response status: has_responses | no_responses'
|
||||||
|
in: query
|
||||||
|
name: status
|
||||||
|
type: string
|
||||||
|
- description: 'Sort field: created_at (default) | updated_at'
|
||||||
|
in: query
|
||||||
|
name: sort_by
|
||||||
|
type: string
|
||||||
|
- description: 'Sort direction: newest (default) | oldest'
|
||||||
|
in: query
|
||||||
|
name: sort_dir
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: List of forms
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/handler.FormResponse'
|
||||||
|
type: array
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Internal server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: List forms
|
||||||
|
tags:
|
||||||
|
- forms
|
||||||
|
/health:
|
||||||
|
get:
|
||||||
|
description: Returns 200 OK if the server is running
|
||||||
|
produces:
|
||||||
|
- text/plain
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
summary: Health check
|
||||||
|
tags:
|
||||||
|
- health
|
||||||
|
securityDefinitions:
|
||||||
|
BearerAuth:
|
||||||
|
description: 'Enter your bearer token in the format: Bearer {token}'
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
|
type: apiKey
|
||||||
|
swagger: "2.0"
|
||||||
@@ -11,20 +11,37 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
|
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||||
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
|
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||||
|
github.com/go-openapi/spec v0.20.6 // indirect
|
||||||
|
github.com/go-openapi/swag v0.19.15 // indirect
|
||||||
github.com/goccy/go-json v0.10.3 // indirect
|
github.com/goccy/go-json v0.10.3 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
|
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
|
||||||
github.com/lestrrat-go/dsig v1.0.0 // indirect
|
github.com/lestrrat-go/dsig v1.0.0 // indirect
|
||||||
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect
|
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect
|
||||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||||
github.com/lestrrat-go/httprc/v3 v3.0.2 // indirect
|
github.com/lestrrat-go/httprc/v3 v3.0.2 // indirect
|
||||||
github.com/lestrrat-go/option/v2 v2.0.0 // indirect
|
github.com/lestrrat-go/option/v2 v2.0.0 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.6 // indirect
|
||||||
github.com/segmentio/asm v1.2.1 // indirect
|
github.com/segmentio/asm v1.2.1 // indirect
|
||||||
|
github.com/swaggo/files v1.0.1 // indirect
|
||||||
|
github.com/swaggo/files/v2 v2.0.0 // indirect
|
||||||
|
github.com/swaggo/http-swagger/v2 v2.0.2 // indirect
|
||||||
|
github.com/swaggo/swag v1.16.6 // indirect
|
||||||
github.com/valyala/fastjson v1.6.7 // indirect
|
github.com/valyala/fastjson v1.6.7 // indirect
|
||||||
|
golang.org/x/mod v0.32.0 // indirect
|
||||||
|
golang.org/x/net v0.49.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.34.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
|
golang.org/x/tools v0.41.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,29 @@
|
|||||||
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
|
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||||
|
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||||
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
|
||||||
|
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||||
|
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
|
||||||
|
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
|
||||||
|
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
|
||||||
|
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||||
|
github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ=
|
||||||
|
github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
|
||||||
|
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
|
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||||
|
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
@@ -17,6 +38,12 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
|
|||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
|
github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
|
||||||
github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
|
github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
|
||||||
github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38=
|
github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38=
|
||||||
@@ -31,27 +58,83 @@ github.com/lestrrat-go/jwx/v3 v3.0.13 h1:AdHKiPIYeCSnOJtvdpipPg/0SuFh9rdkN+HF3O0
|
|||||||
github.com/lestrrat-go/jwx/v3 v3.0.13/go.mod h1:2m0PV1A9tM4b/jVLMx8rh6rBl7F6WGb3EG2hufN9OQU=
|
github.com/lestrrat-go/jwx/v3 v3.0.13/go.mod h1:2m0PV1A9tM4b/jVLMx8rh6rBl7F6WGb3EG2hufN9OQU=
|
||||||
github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=
|
github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=
|
||||||
github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg=
|
github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||||
|
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
|
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
|
||||||
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||||
|
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
||||||
|
github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=
|
||||||
|
github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
|
||||||
|
github.com/swaggo/http-swagger/v2 v2.0.2 h1:FKCdLsl+sFCx60KFsyM0rDarwiUSZ8DqbfSyIKC9OBg=
|
||||||
|
github.com/swaggo/http-swagger/v2 v2.0.2/go.mod h1:r7/GBkAWIfK6E/OLnE8fXnviHiDeAHmgIyooa4xm3AQ=
|
||||||
|
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||||
|
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||||
github.com/valyala/fastjson v1.6.7 h1:ZE4tRy0CIkh+qDc5McjatheGX2czdn8slQjomexVpBM=
|
github.com/valyala/fastjson v1.6.7 h1:ZE4tRy0CIkh+qDc5McjatheGX2czdn8slQjomexVpBM=
|
||||||
github.com/valyala/fastjson v1.6.7/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
github.com/valyala/fastjson v1.6.7/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||||
|
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||||
|
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||||
|
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -30,6 +30,18 @@ func isDuplicateError(err error) bool {
|
|||||||
return false
|
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) {
|
func (h *Handler) RegisterPost(w http.ResponseWriter, r *http.Request) {
|
||||||
var register Auth
|
var register Auth
|
||||||
if err := json.NewDecoder(r.Body).Decode(®ister); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(®ister); err != nil {
|
||||||
@@ -83,6 +95,19 @@ type RefreshRequest struct {
|
|||||||
RefreshToken string `json:"refresh_token"`
|
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) {
|
func (h *Handler) RefreshPost(w http.ResponseWriter, r *http.Request) {
|
||||||
var req RefreshRequest
|
var req RefreshRequest
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
@@ -164,6 +189,18 @@ type LogoutRequest struct {
|
|||||||
RefreshToken string `json:"refresh_token"`
|
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) {
|
func (h *Handler) LogoutPost(w http.ResponseWriter, r *http.Request) {
|
||||||
var req LogoutRequest
|
var req LogoutRequest
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
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)
|
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) {
|
func (h *Handler) LogoutAllDelete(w http.ResponseWriter, r *http.Request) {
|
||||||
userIDStr, ok := r.Context().Value(middleware.UserIDKey).(string)
|
userIDStr, ok := r.Context().Value(middleware.UserIDKey).(string)
|
||||||
if !ok || userIDStr == "" {
|
if !ok || userIDStr == "" {
|
||||||
@@ -214,6 +262,17 @@ func (h *Handler) LogoutAllDelete(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.WriteHeader(http.StatusNoContent)
|
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) {
|
func (h *Handler) MeGet(w http.ResponseWriter, r *http.Request) {
|
||||||
userIDStr, ok := r.Context().Value(middleware.UserIDKey).(string)
|
userIDStr, ok := r.Context().Value(middleware.UserIDKey).(string)
|
||||||
if !ok || userIDStr == "" {
|
if !ok || userIDStr == "" {
|
||||||
@@ -256,6 +315,20 @@ type ChangePasswordRequest struct {
|
|||||||
NewPassword string `json:"new_password"`
|
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) {
|
func (h *Handler) MePasswordPatch(w http.ResponseWriter, r *http.Request) {
|
||||||
userIDStr, ok := r.Context().Value(middleware.UserIDKey).(string)
|
userIDStr, ok := r.Context().Value(middleware.UserIDKey).(string)
|
||||||
if !ok || userIDStr == "" {
|
if !ok || userIDStr == "" {
|
||||||
@@ -320,6 +393,19 @@ func (h *Handler) MePasswordPatch(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.WriteHeader(http.StatusNoContent)
|
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) {
|
func (h *Handler) LoginPost(w http.ResponseWriter, r *http.Request) {
|
||||||
var login Auth
|
var login Auth
|
||||||
if err := json.NewDecoder(r.Body).Decode(&login); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&login); err != nil {
|
||||||
|
|||||||
@@ -137,6 +137,21 @@ func isChoiceType(t repository.QuestionType) bool {
|
|||||||
return false
|
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) {
|
func (h *Handler) FormsGet(w http.ResponseWriter, r *http.Request) {
|
||||||
userID, ok := h.currentUserID(r)
|
userID, ok := h.currentUserID(r)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -223,6 +238,20 @@ func (h *Handler) FormsGet(w http.ResponseWriter, r *http.Request) {
|
|||||||
_ = json.NewEncoder(w).Encode(items)
|
_ = 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) {
|
func (h *Handler) FormsPost(w http.ResponseWriter, r *http.Request) {
|
||||||
userID, ok := h.currentUserID(r)
|
userID, ok := h.currentUserID(r)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -270,6 +299,18 @@ func (h *Handler) FormsPost(w http.ResponseWriter, r *http.Request) {
|
|||||||
_ = json.NewEncoder(w).Encode(buildFormResponse(form, questions, options))
|
_ = 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) {
|
func (h *Handler) FormGet(w http.ResponseWriter, r *http.Request) {
|
||||||
formID, err := uuid.Parse(r.PathValue("id"))
|
formID, err := uuid.Parse(r.PathValue("id"))
|
||||||
if err != nil {
|
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))
|
_ = 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) {
|
func (h *Handler) FormPut(w http.ResponseWriter, r *http.Request) {
|
||||||
userID, ok := h.currentUserID(r)
|
userID, ok := h.currentUserID(r)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -389,6 +447,22 @@ func (h *Handler) FormPut(w http.ResponseWriter, r *http.Request) {
|
|||||||
_ = json.NewEncoder(w).Encode(buildFormResponse(updated, questions, options))
|
_ = 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) {
|
func (h *Handler) FormDelete(w http.ResponseWriter, r *http.Request) {
|
||||||
userID, ok := h.currentUserID(r)
|
userID, ok := h.currentUserID(r)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -508,6 +582,21 @@ type SubmitResponseResponse struct {
|
|||||||
Answers []AnswerResponse `json:"answers"`
|
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) {
|
func (h *Handler) FormResponsesPost(w http.ResponseWriter, r *http.Request) {
|
||||||
formID, err := uuid.Parse(r.PathValue("id"))
|
formID, err := uuid.Parse(r.PathValue("id"))
|
||||||
if err != nil {
|
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) {
|
func (h *Handler) FormResponsesGet(w http.ResponseWriter, r *http.Request) {
|
||||||
userID, ok := h.currentUserID(r)
|
userID, ok := h.currentUserID(r)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ func internalServerError(w http.ResponseWriter, err error) {
|
|||||||
_, _ = w.Write([]byte(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) {
|
func (h *Handler) HealthGet(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
_, _ = w.Write([]byte("OK"))
|
_, _ = w.Write([]byte("OK"))
|
||||||
|
|||||||
@@ -3,10 +3,13 @@ package server
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
_ "ristek-task-be/docs"
|
||||||
"ristek-task-be/internal/db/sqlc/repository"
|
"ristek-task-be/internal/db/sqlc/repository"
|
||||||
"ristek-task-be/internal/handler"
|
"ristek-task-be/internal/handler"
|
||||||
"ristek-task-be/internal/jwt"
|
"ristek-task-be/internal/jwt"
|
||||||
"ristek-task-be/internal/middleware"
|
"ristek-task-be/internal/middleware"
|
||||||
|
|
||||||
|
httpSwagger "github.com/swaggo/http-swagger/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
@@ -30,6 +33,7 @@ func router(repository *repository.Queries, jwt *jwt.JWT) *http.ServeMux {
|
|||||||
h := handler.New(repository, jwt)
|
h := handler.New(repository, jwt)
|
||||||
|
|
||||||
r.HandleFunc("GET /health", h.HealthGet)
|
r.HandleFunc("GET /health", h.HealthGet)
|
||||||
|
r.Handle("/swagger/", httpSwagger.Handler(httpSwagger.URL("/swagger/doc.json")))
|
||||||
|
|
||||||
authRoute := http.NewServeMux()
|
authRoute := http.NewServeMux()
|
||||||
r.Handle("/api/auth/", http.StripPrefix("/api/auth", authRoute))
|
r.Handle("/api/auth/", http.StripPrefix("/api/auth", authRoute))
|
||||||
|
|||||||
@@ -14,6 +14,16 @@ import (
|
|||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// @title Ristek Task API
|
||||||
|
// @version 1.0
|
||||||
|
// @description REST API for Ristek Task Backend
|
||||||
|
// @securityDefinitions.apikey BearerAuth
|
||||||
|
// @in header
|
||||||
|
// @name Authorization
|
||||||
|
// @description Enter your bearer token in the format: Bearer {token}
|
||||||
|
|
||||||
|
// @host localhost:8080
|
||||||
|
// @BasePath /
|
||||||
func main() {
|
func main() {
|
||||||
log.SetOutput(os.Stdout)
|
log.SetOutput(os.Stdout)
|
||||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||||
|
|||||||
Reference in New Issue
Block a user