bbe0757ab6
Implement complete refresh token flow for improved security: - Short-lived access tokens (configurable, default 1h) - Long-lived refresh tokens (configurable, default 7d) - Automatic token rotation on refresh - Token revocation for proper logout Domain layer: - Add RefreshToken entity with validation and revocation - Add RefreshTokenRepository interface Application layer: - Add RefreshTokenHandler for token refresh operations - Add RevokeTokenHandler for single token revocation - Add RevokeAllTokensHandler for user-wide revocation Infrastructure layer: - Implement SQLite RefreshTokenRepository - Add refresh_tokens table migration with indexes - Add parseDuration helper for flexible time configuration HTTP layer: - Add POST /api/v1/auth/refresh endpoint - Add POST /api/v1/auth/logout endpoint - Update login/register to return refresh tokens - Improve Swagger documentation with clear descriptions Configuration: - Update .env.example with secure token expiry defaults - Add support for minute/hour/day duration formats Tests: - Fix test suite to work with new signatures - All existing tests passing
1226 lines
41 KiB
Go
1226 lines
41 KiB
Go
// Package docs Code generated by swaggo/swag. DO NOT EDIT
|
|
package docs
|
|
|
|
import "github.com/swaggo/swag"
|
|
|
|
const docTemplate = `{
|
|
"schemes": {{ marshal .Schemes }},
|
|
"swagger": "2.0",
|
|
"info": {
|
|
"description": "{{escape .Description}}",
|
|
"title": "{{.Title}}",
|
|
"termsOfService": "http://swagger.io/terms/",
|
|
"contact": {
|
|
"name": "API Support",
|
|
"url": "https://github.com/davidfolch/apocapoc-api"
|
|
},
|
|
"license": {
|
|
"name": "MIT",
|
|
"url": "https://opensource.org/licenses/MIT"
|
|
},
|
|
"version": "{{.Version}}"
|
|
},
|
|
"host": "{{.Host}}",
|
|
"basePath": "{{.BasePath}}",
|
|
"paths": {
|
|
"/auth/login": {
|
|
"post": {
|
|
"description": "Authenticate user with email and password. Returns both access token and refresh token. The access token is used for API requests, the refresh token is used to obtain new access tokens when they expire.",
|
|
"consumes": [
|
|
"application/json"
|
|
],
|
|
"produces": [
|
|
"application/json"
|
|
],
|
|
"tags": [
|
|
"auth"
|
|
],
|
|
"summary": "Login user",
|
|
"parameters": [
|
|
{
|
|
"description": "Login credentials",
|
|
"name": "request",
|
|
"in": "body",
|
|
"required": true,
|
|
"schema": {
|
|
"$ref": "#/definitions/http.LoginRequest"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Returns access token, refresh token, and user ID",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.AuthResponse"
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Invalid request body",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Invalid email or password",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Internal server error",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/auth/logout": {
|
|
"post": {
|
|
"description": "Revoke the refresh token to invalidate the user session. After logout, the refresh token cannot be used to obtain new access tokens. The user will need to login again. Always call this endpoint before clearing tokens from client storage to ensure proper session termination.",
|
|
"consumes": [
|
|
"application/json"
|
|
],
|
|
"produces": [
|
|
"application/json"
|
|
],
|
|
"tags": [
|
|
"auth"
|
|
],
|
|
"summary": "Logout user",
|
|
"parameters": [
|
|
{
|
|
"description": "Refresh token to revoke",
|
|
"name": "request",
|
|
"in": "body",
|
|
"required": true,
|
|
"schema": {
|
|
"$ref": "#/definitions/http.LogoutRequest"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Successfully logged out - the refresh token is now invalid",
|
|
"schema": {
|
|
"type": "object",
|
|
"additionalProperties": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Invalid request body or missing refresh token",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"404": {
|
|
"description": "Refresh token not found (already revoked or never existed)",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Internal server error",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/auth/refresh": {
|
|
"post": {
|
|
"description": "Exchange a valid refresh token for a new access token and refresh token pair. IMPORTANT: The old refresh token is automatically revoked and you receive a NEW refresh token - always update both tokens in storage. Use this endpoint when the access token expires to maintain the user session without requiring re-login.",
|
|
"consumes": [
|
|
"application/json"
|
|
],
|
|
"produces": [
|
|
"application/json"
|
|
],
|
|
"tags": [
|
|
"auth"
|
|
],
|
|
"summary": "Refresh access token",
|
|
"parameters": [
|
|
{
|
|
"description": "Current refresh token",
|
|
"name": "request",
|
|
"in": "body",
|
|
"required": true,
|
|
"schema": {
|
|
"$ref": "#/definitions/http.RefreshRequest"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Returns NEW access token and NEW refresh token - the old refresh token is now invalid",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.AuthResponse"
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Invalid request body",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Invalid or expired refresh token",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Internal server error",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/auth/register": {
|
|
"post": {
|
|
"description": "Create a new user account and receive both access token and refresh token. Store both tokens securely - the refresh token is used to obtain new access tokens when they expire.",
|
|
"consumes": [
|
|
"application/json"
|
|
],
|
|
"produces": [
|
|
"application/json"
|
|
],
|
|
"tags": [
|
|
"auth"
|
|
],
|
|
"summary": "Register a new user",
|
|
"parameters": [
|
|
{
|
|
"description": "Registration data (password requires: min 8 chars, uppercase, lowercase, digit, special char)",
|
|
"name": "request",
|
|
"in": "body",
|
|
"required": true,
|
|
"schema": {
|
|
"$ref": "#/definitions/http.RegisterRequest"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"201": {
|
|
"description": "Returns access token, refresh token, and user ID",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.AuthResponse"
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Invalid input: email format, password requirements, or timezone",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"409": {
|
|
"description": "Email already registered",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Internal server error",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/habits": {
|
|
"get": {
|
|
"security": [
|
|
{
|
|
"BearerAuth": []
|
|
}
|
|
],
|
|
"description": "Get all active habits for the authenticated user",
|
|
"produces": [
|
|
"application/json"
|
|
],
|
|
"tags": [
|
|
"habits"
|
|
],
|
|
"summary": "Get all user habits",
|
|
"responses": {
|
|
"200": {
|
|
"description": "OK",
|
|
"schema": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/definitions/http.UserHabitResponse"
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Unauthorized",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Internal Server Error",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"post": {
|
|
"security": [
|
|
{
|
|
"BearerAuth": []
|
|
}
|
|
],
|
|
"description": "Create a new habit for the authenticated user",
|
|
"consumes": [
|
|
"application/json"
|
|
],
|
|
"produces": [
|
|
"application/json"
|
|
],
|
|
"tags": [
|
|
"habits"
|
|
],
|
|
"summary": "Create a new habit",
|
|
"parameters": [
|
|
{
|
|
"description": "Habit data",
|
|
"name": "request",
|
|
"in": "body",
|
|
"required": true,
|
|
"schema": {
|
|
"$ref": "#/definitions/http.CreateHabitRequest"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"201": {
|
|
"description": "Created",
|
|
"schema": {
|
|
"type": "object",
|
|
"additionalProperties": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Bad Request",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Unauthorized",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Internal Server Error",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/habits/today": {
|
|
"get": {
|
|
"security": [
|
|
{
|
|
"BearerAuth": []
|
|
}
|
|
],
|
|
"description": "Get all habits scheduled for today for the authenticated user",
|
|
"produces": [
|
|
"application/json"
|
|
],
|
|
"tags": [
|
|
"habits"
|
|
],
|
|
"summary": "Get today's habits",
|
|
"responses": {
|
|
"200": {
|
|
"description": "OK",
|
|
"schema": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/definitions/http.TodaysHabitResponse"
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Unauthorized",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Internal Server Error",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/habits/{id}": {
|
|
"get": {
|
|
"security": [
|
|
{
|
|
"BearerAuth": []
|
|
}
|
|
],
|
|
"description": "Get a specific habit by ID",
|
|
"produces": [
|
|
"application/json"
|
|
],
|
|
"tags": [
|
|
"habits"
|
|
],
|
|
"summary": "Get habit by ID",
|
|
"parameters": [
|
|
{
|
|
"type": "string",
|
|
"description": "Habit ID",
|
|
"name": "id",
|
|
"in": "path",
|
|
"required": true
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "OK",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.UserHabitResponse"
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Unauthorized",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"403": {
|
|
"description": "Forbidden",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"404": {
|
|
"description": "Not Found",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Internal Server Error",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"put": {
|
|
"security": [
|
|
{
|
|
"BearerAuth": []
|
|
}
|
|
],
|
|
"description": "Update an existing habit",
|
|
"consumes": [
|
|
"application/json"
|
|
],
|
|
"produces": [
|
|
"application/json"
|
|
],
|
|
"tags": [
|
|
"habits"
|
|
],
|
|
"summary": "Update habit",
|
|
"parameters": [
|
|
{
|
|
"type": "string",
|
|
"description": "Habit ID",
|
|
"name": "id",
|
|
"in": "path",
|
|
"required": true
|
|
},
|
|
{
|
|
"description": "Update data",
|
|
"name": "request",
|
|
"in": "body",
|
|
"required": true,
|
|
"schema": {
|
|
"$ref": "#/definitions/http.UpdateHabitRequest"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "OK",
|
|
"schema": {
|
|
"type": "object",
|
|
"additionalProperties": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Bad Request",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Unauthorized",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"403": {
|
|
"description": "Forbidden",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"404": {
|
|
"description": "Not Found",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Internal Server Error",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"delete": {
|
|
"security": [
|
|
{
|
|
"BearerAuth": []
|
|
}
|
|
],
|
|
"description": "Archive (soft delete) a habit",
|
|
"produces": [
|
|
"application/json"
|
|
],
|
|
"tags": [
|
|
"habits"
|
|
],
|
|
"summary": "Archive habit",
|
|
"parameters": [
|
|
{
|
|
"type": "string",
|
|
"description": "Habit ID",
|
|
"name": "id",
|
|
"in": "path",
|
|
"required": true
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "OK",
|
|
"schema": {
|
|
"type": "object",
|
|
"additionalProperties": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Unauthorized",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"403": {
|
|
"description": "Forbidden",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"404": {
|
|
"description": "Not Found",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Internal Server Error",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/habits/{id}/entries": {
|
|
"get": {
|
|
"security": [
|
|
{
|
|
"BearerAuth": []
|
|
}
|
|
],
|
|
"description": "Get entries (completion history) for a habit with optional date filtering and pagination",
|
|
"produces": [
|
|
"application/json"
|
|
],
|
|
"tags": [
|
|
"habits"
|
|
],
|
|
"summary": "Get habit entries",
|
|
"parameters": [
|
|
{
|
|
"type": "string",
|
|
"description": "Habit ID",
|
|
"name": "id",
|
|
"in": "path",
|
|
"required": true
|
|
},
|
|
{
|
|
"type": "string",
|
|
"description": "Start date (YYYY-MM-DD)",
|
|
"name": "from",
|
|
"in": "query"
|
|
},
|
|
{
|
|
"type": "string",
|
|
"description": "End date (YYYY-MM-DD)",
|
|
"name": "to",
|
|
"in": "query"
|
|
},
|
|
{
|
|
"type": "integer",
|
|
"description": "Page number",
|
|
"name": "page",
|
|
"in": "query"
|
|
},
|
|
{
|
|
"type": "integer",
|
|
"description": "Page size (max 100)",
|
|
"name": "limit",
|
|
"in": "query"
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "OK",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.HabitEntriesResponse"
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Bad Request",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Unauthorized",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"403": {
|
|
"description": "Forbidden",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"404": {
|
|
"description": "Not Found",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Internal Server Error",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/habits/{id}/entries/{date}": {
|
|
"delete": {
|
|
"security": [
|
|
{
|
|
"BearerAuth": []
|
|
}
|
|
],
|
|
"description": "Delete a habit entry (unmark completion)",
|
|
"produces": [
|
|
"application/json"
|
|
],
|
|
"tags": [
|
|
"habits"
|
|
],
|
|
"summary": "Unmark habit",
|
|
"parameters": [
|
|
{
|
|
"type": "string",
|
|
"description": "Habit ID",
|
|
"name": "id",
|
|
"in": "path",
|
|
"required": true
|
|
},
|
|
{
|
|
"type": "string",
|
|
"description": "Date (YYYY-MM-DD)",
|
|
"name": "date",
|
|
"in": "path",
|
|
"required": true
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "OK",
|
|
"schema": {
|
|
"type": "object",
|
|
"additionalProperties": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Bad Request",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Unauthorized",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"403": {
|
|
"description": "Forbidden",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"404": {
|
|
"description": "Not Found",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Internal Server Error",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/habits/{id}/mark": {
|
|
"post": {
|
|
"security": [
|
|
{
|
|
"BearerAuth": []
|
|
}
|
|
],
|
|
"description": "Mark a habit as completed for a specific date",
|
|
"consumes": [
|
|
"application/json"
|
|
],
|
|
"produces": [
|
|
"application/json"
|
|
],
|
|
"tags": [
|
|
"habits"
|
|
],
|
|
"summary": "Mark habit as complete",
|
|
"parameters": [
|
|
{
|
|
"type": "string",
|
|
"description": "Habit ID",
|
|
"name": "id",
|
|
"in": "path",
|
|
"required": true
|
|
},
|
|
{
|
|
"description": "Mark data",
|
|
"name": "request",
|
|
"in": "body",
|
|
"required": true,
|
|
"schema": {
|
|
"$ref": "#/definitions/http.MarkHabitRequest"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "OK",
|
|
"schema": {
|
|
"type": "object",
|
|
"additionalProperties": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Bad Request",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"404": {
|
|
"description": "Not Found",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"409": {
|
|
"description": "Conflict",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Internal Server Error",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/health": {
|
|
"get": {
|
|
"description": "Get API health status including database connectivity and uptime",
|
|
"produces": [
|
|
"application/json"
|
|
],
|
|
"tags": [
|
|
"system"
|
|
],
|
|
"summary": "Health check",
|
|
"responses": {
|
|
"200": {
|
|
"description": "OK",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.HealthResponse"
|
|
}
|
|
},
|
|
"503": {
|
|
"description": "Service Unavailable",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.HealthResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/stats/habits/{id}": {
|
|
"get": {
|
|
"security": [
|
|
{
|
|
"BearerAuth": []
|
|
}
|
|
],
|
|
"description": "Get statistics for a specific habit including streaks and completion rates",
|
|
"produces": [
|
|
"application/json"
|
|
],
|
|
"tags": [
|
|
"stats"
|
|
],
|
|
"summary": "Get habit statistics",
|
|
"parameters": [
|
|
{
|
|
"type": "string",
|
|
"description": "Habit ID",
|
|
"name": "id",
|
|
"in": "path",
|
|
"required": true
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "OK",
|
|
"schema": {
|
|
"$ref": "#/definitions/queries.HabitStatsDTO"
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Unauthorized",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"403": {
|
|
"description": "Forbidden",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"404": {
|
|
"description": "Not Found",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Internal Server Error",
|
|
"schema": {
|
|
"$ref": "#/definitions/http.ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"definitions": {
|
|
"http.AuthResponse": {
|
|
"type": "object",
|
|
"properties": {
|
|
"refresh_token": {
|
|
"type": "string"
|
|
},
|
|
"token": {
|
|
"type": "string"
|
|
},
|
|
"user_id": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"http.CreateHabitRequest": {
|
|
"type": "object",
|
|
"properties": {
|
|
"carry_over": {
|
|
"type": "boolean"
|
|
},
|
|
"description": {
|
|
"type": "string"
|
|
},
|
|
"frequency": {
|
|
"$ref": "#/definitions/value_objects.Frequency"
|
|
},
|
|
"is_negative": {
|
|
"type": "boolean"
|
|
},
|
|
"name": {
|
|
"type": "string"
|
|
},
|
|
"specific_dates": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "integer"
|
|
}
|
|
},
|
|
"specific_days": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "integer"
|
|
}
|
|
},
|
|
"target_value": {
|
|
"type": "number"
|
|
},
|
|
"type": {
|
|
"$ref": "#/definitions/value_objects.HabitType"
|
|
}
|
|
}
|
|
},
|
|
"http.ErrorResponse": {
|
|
"type": "object",
|
|
"properties": {
|
|
"error": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"http.HabitEntriesResponse": {
|
|
"type": "object",
|
|
"properties": {
|
|
"entries": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/definitions/http.HabitEntryResponse"
|
|
}
|
|
},
|
|
"limit": {
|
|
"type": "integer"
|
|
},
|
|
"page": {
|
|
"type": "integer"
|
|
},
|
|
"total": {
|
|
"type": "integer"
|
|
}
|
|
}
|
|
},
|
|
"http.HabitEntryResponse": {
|
|
"type": "object",
|
|
"properties": {
|
|
"completed_at": {
|
|
"type": "string"
|
|
},
|
|
"habit_id": {
|
|
"type": "string"
|
|
},
|
|
"id": {
|
|
"type": "string"
|
|
},
|
|
"scheduled_date": {
|
|
"type": "string"
|
|
},
|
|
"value": {
|
|
"type": "number"
|
|
}
|
|
}
|
|
},
|
|
"http.HealthResponse": {
|
|
"type": "object",
|
|
"properties": {
|
|
"database": {
|
|
"type": "string"
|
|
},
|
|
"status": {
|
|
"type": "string"
|
|
},
|
|
"uptime": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"http.LoginRequest": {
|
|
"type": "object",
|
|
"properties": {
|
|
"email": {
|
|
"type": "string"
|
|
},
|
|
"password": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"http.LogoutRequest": {
|
|
"type": "object",
|
|
"properties": {
|
|
"refresh_token": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"http.MarkHabitRequest": {
|
|
"type": "object",
|
|
"properties": {
|
|
"scheduled_date": {
|
|
"type": "string"
|
|
},
|
|
"value": {
|
|
"type": "number"
|
|
}
|
|
}
|
|
},
|
|
"http.RefreshRequest": {
|
|
"type": "object",
|
|
"properties": {
|
|
"refresh_token": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"http.RegisterRequest": {
|
|
"type": "object",
|
|
"properties": {
|
|
"email": {
|
|
"type": "string"
|
|
},
|
|
"password": {
|
|
"type": "string"
|
|
},
|
|
"timezone": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"http.TodaysHabitResponse": {
|
|
"type": "object",
|
|
"properties": {
|
|
"id": {
|
|
"type": "string"
|
|
},
|
|
"is_carried_over": {
|
|
"type": "boolean"
|
|
},
|
|
"is_negative": {
|
|
"type": "boolean"
|
|
},
|
|
"name": {
|
|
"type": "string"
|
|
},
|
|
"scheduled_date": {
|
|
"type": "string"
|
|
},
|
|
"target_value": {
|
|
"type": "number"
|
|
},
|
|
"type": {
|
|
"$ref": "#/definitions/value_objects.HabitType"
|
|
}
|
|
}
|
|
},
|
|
"http.UpdateHabitRequest": {
|
|
"type": "object",
|
|
"properties": {
|
|
"carry_over": {
|
|
"type": "boolean"
|
|
},
|
|
"description": {
|
|
"type": "string"
|
|
},
|
|
"name": {
|
|
"type": "string"
|
|
},
|
|
"specific_dates": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "integer"
|
|
}
|
|
},
|
|
"specific_days": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "integer"
|
|
}
|
|
},
|
|
"target_value": {
|
|
"type": "number"
|
|
}
|
|
}
|
|
},
|
|
"http.UserHabitResponse": {
|
|
"type": "object",
|
|
"properties": {
|
|
"carry_over": {
|
|
"type": "boolean"
|
|
},
|
|
"frequency": {
|
|
"$ref": "#/definitions/value_objects.Frequency"
|
|
},
|
|
"id": {
|
|
"type": "string"
|
|
},
|
|
"is_negative": {
|
|
"type": "boolean"
|
|
},
|
|
"name": {
|
|
"type": "string"
|
|
},
|
|
"specific_days": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "integer"
|
|
}
|
|
},
|
|
"target_value": {
|
|
"type": "number"
|
|
},
|
|
"type": {
|
|
"$ref": "#/definitions/value_objects.HabitType"
|
|
}
|
|
}
|
|
},
|
|
"queries.HabitStatsDTO": {
|
|
"type": "object",
|
|
"properties": {
|
|
"completion_rate": {
|
|
"type": "number"
|
|
},
|
|
"completions_this_month": {
|
|
"type": "integer"
|
|
},
|
|
"completions_this_week": {
|
|
"type": "integer"
|
|
},
|
|
"current_streak": {
|
|
"type": "integer"
|
|
},
|
|
"habit_id": {
|
|
"type": "string"
|
|
},
|
|
"habit_name": {
|
|
"type": "string"
|
|
},
|
|
"longest_streak": {
|
|
"type": "integer"
|
|
},
|
|
"total_completions": {
|
|
"type": "integer"
|
|
}
|
|
}
|
|
},
|
|
"value_objects.Frequency": {
|
|
"type": "string",
|
|
"enum": [
|
|
"DAILY",
|
|
"WEEKLY",
|
|
"MONTHLY"
|
|
],
|
|
"x-enum-varnames": [
|
|
"FrequencyDaily",
|
|
"FrequencyWeekly",
|
|
"FrequencyMonthly"
|
|
]
|
|
},
|
|
"value_objects.HabitType": {
|
|
"type": "string",
|
|
"enum": [
|
|
"BOOLEAN",
|
|
"COUNTER",
|
|
"VALUE"
|
|
],
|
|
"x-enum-varnames": [
|
|
"HabitTypeBoolean",
|
|
"HabitTypeCounter",
|
|
"HabitTypeValue"
|
|
]
|
|
}
|
|
},
|
|
"securityDefinitions": {
|
|
"BearerAuth": {
|
|
"description": "Type \"Bearer\" followed by a space and JWT token.",
|
|
"type": "apiKey",
|
|
"name": "Authorization",
|
|
"in": "header"
|
|
}
|
|
}
|
|
}`
|
|
|
|
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
|
var SwaggerInfo = &swag.Spec{
|
|
Version: "1.0",
|
|
Host: "",
|
|
BasePath: "/api/v1",
|
|
Schemes: []string{},
|
|
Title: "Apocapoc API",
|
|
Description: "Self-hosted habit tracking service",
|
|
InfoInstanceName: "swagger",
|
|
SwaggerTemplate: docTemplate,
|
|
LeftDelim: "{{",
|
|
RightDelim: "}}",
|
|
}
|
|
|
|
func init() {
|
|
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
|
}
|