feat: implement offline sync endpoints with Last-Write-Wins strategy
Add comprehensive offline synchronization support for habits and entries: ## Infrastructure (Phase 1) - Add UpdatedAt and DeletedAt timestamps to Habit and HabitEntry entities - Implement soft delete with Delete(), Touch(), and IsDeleted() methods - Create SQL migration with optimized composite indexes for sync queries - Add GetChangesSince() and SoftDelete() to both repositories - Update all Find* methods to exclude soft-deleted records - 13 comprehensive TDD tests for sync repository methods ## HTTP Endpoints (Phase 2) - GET /api/v1/sync/changes: retrieve all changes since timestamp - POST /api/v1/sync/batch: apply client changes with conflict resolution - Implement Last-Write-Wins strategy using UpdatedAt timestamps - Add authentication and rate limiting (100 req/min) - Validate user ownership for all sync operations - 9 tests for sync handlers (3 queries + 6 commands) ## Technical Details - Composite indexes: (user_id, updated_at) for optimal query performance - No pagination: atomic sync operations for data consistency - Upsert behavior: create resources if not found on server - DTOs with full entity state including timestamps - Swagger documentation updated for new endpoints All 220+ tests passing ✓
This commit is contained in:
+846
-12
@@ -24,6 +24,67 @@ const docTemplate = `{
|
||||
"host": "{{.Host}}",
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {
|
||||
"/auth/forgot-password": {
|
||||
"post": {
|
||||
"description": "Request a password reset email with a reset token",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Request password reset",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "User email",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ForgotPasswordRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Reset email sent successfully",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid email",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Email not verified",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "User not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/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.",
|
||||
@@ -67,6 +128,12 @@ const docTemplate = `{
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Email not verified",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
@@ -185,7 +252,7 @@ const docTemplate = `{
|
||||
},
|
||||
"/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.",
|
||||
"description": "Create a new user account. If email verification is enabled, you will receive a verification email. Otherwise, you can login immediately.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -209,13 +276,19 @@ const docTemplate = `{
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Returns access token, refresh token, and user ID",
|
||||
"description": "Returns user ID and message about next steps",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.AuthResponse"
|
||||
"$ref": "#/definitions/http.RegisterResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid input: email format, password requirements, or timezone",
|
||||
"description": "Invalid input: email format or password requirements",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ValidationErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Registration is closed",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
@@ -235,6 +308,220 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/resend-verification": {
|
||||
"post": {
|
||||
"description": "Resend the email verification link to the user's email address",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Resend verification email",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "User email",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ResendVerificationRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Verification email sent",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid email",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "User not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "Email already verified",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/reset-password": {
|
||||
"post": {
|
||||
"description": "Reset user password using the reset token from email",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Reset password",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Reset token and new password",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ResetPasswordRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Password reset successfully",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid token or password requirements not met",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "User not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/verify-email": {
|
||||
"post": {
|
||||
"description": "Verify user email address using the token sent via email",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Verify email address",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Verification token",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.VerifyEmailRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Email verified successfully",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid or expired token",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "Email already verified",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/export": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Export all user habits and entries in JSON format with gzip compression. Limited to 1 export per hour.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"export"
|
||||
],
|
||||
"summary": "Export user data",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Compressed JSON export",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/queries.ExportUserDataResult"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"429": {
|
||||
"description": "Rate limit exceeded",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/habits": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -242,7 +529,7 @@ const docTemplate = `{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Get all active habits for the authenticated user",
|
||||
"description": "Get all active habits for the authenticated user with optional pagination and filters",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -250,14 +537,49 @@ const docTemplate = `{
|
||||
"habits"
|
||||
],
|
||||
"summary": "Get all user habits",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page size (default: 50, max: 100)",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Filter by type (BOOLEAN, COUNTER, VALUE)",
|
||||
"name": "type",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Filter by frequency (DAILY, WEEKLY, MONTHLY)",
|
||||
"name": "frequency",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "Include archived habits (default: false)",
|
||||
"name": "archived",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search by name or description",
|
||||
"name": "search",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/http.UserHabitResponse"
|
||||
}
|
||||
"$ref": "#/definitions/http.GetUserHabitsResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
@@ -340,7 +662,7 @@ const docTemplate = `{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Get all habits scheduled for today for the authenticated user",
|
||||
"description": "Get all habits scheduled for today for the authenticated user. Includes the entry for today if it exists. Requires timezone as query parameter (e.g., ?timezone=America/New_York).",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -348,6 +670,15 @@ const docTemplate = `{
|
||||
"habits"
|
||||
],
|
||||
"summary": "Get today's habits",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "IANA timezone (e.g., 'America/New_York', 'Europe/Madrid', 'UTC')",
|
||||
"name": "timezone",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
@@ -358,6 +689,12 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid or missing timezone",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
@@ -885,6 +1222,167 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/sync/batch": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Apply a batch of changes from the client for offline sync (Last-Write-Wins)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"sync"
|
||||
],
|
||||
"summary": "Apply sync batch",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Sync batch data",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.SyncBatchRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/sync/changes": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Get all changes (habits and entries) since a given timestamp for offline sync",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"sync"
|
||||
],
|
||||
"summary": "Get sync changes",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "ISO 8601 timestamp (e.g., 2025-01-01T00:00:00Z)",
|
||||
"name": "since",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.SyncChangesResponse"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users/me": {
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Permanently delete the authenticated user's account and all associated data (habits, entries, tokens). This action cannot be undone.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"summary": "Delete user account",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Account deleted successfully",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized - invalid or missing token",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "User not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
@@ -940,6 +1438,29 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.EntryChangesDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/http.SyncHabitEntryDTO"
|
||||
}
|
||||
},
|
||||
"deleted": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"updated": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/http.SyncHabitEntryDTO"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.ErrorResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -948,6 +1469,51 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.ForgotPasswordRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.GetUserHabitsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/http.UserHabitResponse"
|
||||
}
|
||||
},
|
||||
"pagination": {
|
||||
"$ref": "#/definitions/pagination.Response"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.HabitChangesDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/http.SyncHabitDTO"
|
||||
}
|
||||
},
|
||||
"deleted": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"updated": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/http.SyncHabitDTO"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.HabitEntriesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -994,6 +1560,9 @@ const docTemplate = `{
|
||||
"database": {
|
||||
"type": "string"
|
||||
},
|
||||
"smtp": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -1048,15 +1617,157 @@ const docTemplate = `{
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"timezone": {
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.RegisterResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.ResendVerificationRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.ResetPasswordRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"new_password": {
|
||||
"type": "string"
|
||||
},
|
||||
"token": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.SyncBatchRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"entries": {
|
||||
"$ref": "#/definitions/http.EntryChangesDTO"
|
||||
},
|
||||
"habits": {
|
||||
"$ref": "#/definitions/http.HabitChangesDTO"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.SyncChangesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"entries": {
|
||||
"$ref": "#/definitions/http.EntryChangesDTO"
|
||||
},
|
||||
"habits": {
|
||||
"$ref": "#/definitions/http.HabitChangesDTO"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.SyncHabitDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"archived_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"carry_over": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"frequency": {
|
||||
"$ref": "#/definitions/value_objects.Frequency"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.SyncHabitEntryDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"completed_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"habit_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"scheduled_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.TodaysHabitEntryResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"completed_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.TodaysHabitResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"entry": {
|
||||
"$ref": "#/definitions/http.TodaysHabitEntryResponse"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -1141,6 +1852,129 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.ValidationErrorResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"field": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.VerifyEmailRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"token": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pagination.Response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"page_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_pages": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"queries.ExportEntryDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"completed_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"habit_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"scheduled_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"queries.ExportHabitDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"archived_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"carry_over": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"frequency": {
|
||||
"$ref": "#/definitions/value_objects.Frequency"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"queries.ExportUserDataResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"entries": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/queries.ExportEntryDTO"
|
||||
}
|
||||
},
|
||||
"exported_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"habits": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/queries.ExportHabitDTO"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"queries.HabitStatsDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
+846
-12
@@ -16,6 +16,67 @@
|
||||
},
|
||||
"basePath": "/api/v1",
|
||||
"paths": {
|
||||
"/auth/forgot-password": {
|
||||
"post": {
|
||||
"description": "Request a password reset email with a reset token",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Request password reset",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "User email",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ForgotPasswordRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Reset email sent successfully",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid email",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Email not verified",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "User not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/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.",
|
||||
@@ -59,6 +120,12 @@
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Email not verified",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
@@ -177,7 +244,7 @@
|
||||
},
|
||||
"/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.",
|
||||
"description": "Create a new user account. If email verification is enabled, you will receive a verification email. Otherwise, you can login immediately.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -201,13 +268,19 @@
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Returns access token, refresh token, and user ID",
|
||||
"description": "Returns user ID and message about next steps",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.AuthResponse"
|
||||
"$ref": "#/definitions/http.RegisterResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid input: email format, password requirements, or timezone",
|
||||
"description": "Invalid input: email format or password requirements",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ValidationErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Registration is closed",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
@@ -227,6 +300,220 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/resend-verification": {
|
||||
"post": {
|
||||
"description": "Resend the email verification link to the user's email address",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Resend verification email",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "User email",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ResendVerificationRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Verification email sent",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid email",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "User not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "Email already verified",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/reset-password": {
|
||||
"post": {
|
||||
"description": "Reset user password using the reset token from email",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Reset password",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Reset token and new password",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ResetPasswordRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Password reset successfully",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid token or password requirements not met",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "User not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/verify-email": {
|
||||
"post": {
|
||||
"description": "Verify user email address using the token sent via email",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Verify email address",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Verification token",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.VerifyEmailRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Email verified successfully",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid or expired token",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "Email already verified",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/export": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Export all user habits and entries in JSON format with gzip compression. Limited to 1 export per hour.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"export"
|
||||
],
|
||||
"summary": "Export user data",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Compressed JSON export",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/queries.ExportUserDataResult"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"429": {
|
||||
"description": "Rate limit exceeded",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/habits": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -234,7 +521,7 @@
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Get all active habits for the authenticated user",
|
||||
"description": "Get all active habits for the authenticated user with optional pagination and filters",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -242,14 +529,49 @@
|
||||
"habits"
|
||||
],
|
||||
"summary": "Get all user habits",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page size (default: 50, max: 100)",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Filter by type (BOOLEAN, COUNTER, VALUE)",
|
||||
"name": "type",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Filter by frequency (DAILY, WEEKLY, MONTHLY)",
|
||||
"name": "frequency",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "Include archived habits (default: false)",
|
||||
"name": "archived",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search by name or description",
|
||||
"name": "search",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/http.UserHabitResponse"
|
||||
}
|
||||
"$ref": "#/definitions/http.GetUserHabitsResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
@@ -332,7 +654,7 @@
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Get all habits scheduled for today for the authenticated user",
|
||||
"description": "Get all habits scheduled for today for the authenticated user. Includes the entry for today if it exists. Requires timezone as query parameter (e.g., ?timezone=America/New_York).",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -340,6 +662,15 @@
|
||||
"habits"
|
||||
],
|
||||
"summary": "Get today's habits",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "IANA timezone (e.g., 'America/New_York', 'Europe/Madrid', 'UTC')",
|
||||
"name": "timezone",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
@@ -350,6 +681,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid or missing timezone",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
@@ -877,6 +1214,167 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/sync/batch": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Apply a batch of changes from the client for offline sync (Last-Write-Wins)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"sync"
|
||||
],
|
||||
"summary": "Apply sync batch",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Sync batch data",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.SyncBatchRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/sync/changes": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Get all changes (habits and entries) since a given timestamp for offline sync",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"sync"
|
||||
],
|
||||
"summary": "Get sync changes",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "ISO 8601 timestamp (e.g., 2025-01-01T00:00:00Z)",
|
||||
"name": "since",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.SyncChangesResponse"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users/me": {
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Permanently delete the authenticated user's account and all associated data (habits, entries, tokens). This action cannot be undone.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"summary": "Delete user account",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Account deleted successfully",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized - invalid or missing token",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "User not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
@@ -932,6 +1430,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.EntryChangesDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/http.SyncHabitEntryDTO"
|
||||
}
|
||||
},
|
||||
"deleted": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"updated": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/http.SyncHabitEntryDTO"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.ErrorResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -940,6 +1461,51 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.ForgotPasswordRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.GetUserHabitsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/http.UserHabitResponse"
|
||||
}
|
||||
},
|
||||
"pagination": {
|
||||
"$ref": "#/definitions/pagination.Response"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.HabitChangesDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/http.SyncHabitDTO"
|
||||
}
|
||||
},
|
||||
"deleted": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"updated": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/http.SyncHabitDTO"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.HabitEntriesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -986,6 +1552,9 @@
|
||||
"database": {
|
||||
"type": "string"
|
||||
},
|
||||
"smtp": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -1040,15 +1609,157 @@
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"timezone": {
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.RegisterResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.ResendVerificationRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.ResetPasswordRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"new_password": {
|
||||
"type": "string"
|
||||
},
|
||||
"token": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.SyncBatchRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"entries": {
|
||||
"$ref": "#/definitions/http.EntryChangesDTO"
|
||||
},
|
||||
"habits": {
|
||||
"$ref": "#/definitions/http.HabitChangesDTO"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.SyncChangesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"entries": {
|
||||
"$ref": "#/definitions/http.EntryChangesDTO"
|
||||
},
|
||||
"habits": {
|
||||
"$ref": "#/definitions/http.HabitChangesDTO"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.SyncHabitDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"archived_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"carry_over": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"frequency": {
|
||||
"$ref": "#/definitions/value_objects.Frequency"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.SyncHabitEntryDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"completed_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"habit_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"scheduled_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.TodaysHabitEntryResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"completed_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.TodaysHabitResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"entry": {
|
||||
"$ref": "#/definitions/http.TodaysHabitEntryResponse"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -1133,6 +1844,129 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.ValidationErrorResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"field": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.VerifyEmailRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"token": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pagination.Response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"page_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_pages": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"queries.ExportEntryDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"completed_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"habit_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"scheduled_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"queries.ExportHabitDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"archived_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"carry_over": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"frequency": {
|
||||
"$ref": "#/definitions/value_objects.Frequency"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"queries.ExportUserDataResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"entries": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/queries.ExportEntryDTO"
|
||||
}
|
||||
},
|
||||
"exported_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"habits": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/queries.ExportHabitDTO"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"queries.HabitStatsDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
+557
-12
@@ -34,11 +34,55 @@ definitions:
|
||||
type:
|
||||
$ref: '#/definitions/value_objects.HabitType'
|
||||
type: object
|
||||
http.EntryChangesDTO:
|
||||
properties:
|
||||
created:
|
||||
items:
|
||||
$ref: '#/definitions/http.SyncHabitEntryDTO'
|
||||
type: array
|
||||
deleted:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
updated:
|
||||
items:
|
||||
$ref: '#/definitions/http.SyncHabitEntryDTO'
|
||||
type: array
|
||||
type: object
|
||||
http.ErrorResponse:
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
type: object
|
||||
http.ForgotPasswordRequest:
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
type: object
|
||||
http.GetUserHabitsResponse:
|
||||
properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/http.UserHabitResponse'
|
||||
type: array
|
||||
pagination:
|
||||
$ref: '#/definitions/pagination.Response'
|
||||
type: object
|
||||
http.HabitChangesDTO:
|
||||
properties:
|
||||
created:
|
||||
items:
|
||||
$ref: '#/definitions/http.SyncHabitDTO'
|
||||
type: array
|
||||
deleted:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
updated:
|
||||
items:
|
||||
$ref: '#/definitions/http.SyncHabitDTO'
|
||||
type: array
|
||||
type: object
|
||||
http.HabitEntriesResponse:
|
||||
properties:
|
||||
entries:
|
||||
@@ -69,6 +113,8 @@ definitions:
|
||||
properties:
|
||||
database:
|
||||
type: string
|
||||
smtp:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
uptime:
|
||||
@@ -104,11 +150,103 @@ definitions:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
timezone:
|
||||
type: object
|
||||
http.RegisterResponse:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
user_id:
|
||||
type: string
|
||||
type: object
|
||||
http.ResendVerificationRequest:
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
type: object
|
||||
http.ResetPasswordRequest:
|
||||
properties:
|
||||
new_password:
|
||||
type: string
|
||||
token:
|
||||
type: string
|
||||
type: object
|
||||
http.SyncBatchRequest:
|
||||
properties:
|
||||
entries:
|
||||
$ref: '#/definitions/http.EntryChangesDTO'
|
||||
habits:
|
||||
$ref: '#/definitions/http.HabitChangesDTO'
|
||||
type: object
|
||||
http.SyncChangesResponse:
|
||||
properties:
|
||||
entries:
|
||||
$ref: '#/definitions/http.EntryChangesDTO'
|
||||
habits:
|
||||
$ref: '#/definitions/http.HabitChangesDTO'
|
||||
type: object
|
||||
http.SyncHabitDTO:
|
||||
properties:
|
||||
archived_at:
|
||||
type: string
|
||||
carry_over:
|
||||
type: boolean
|
||||
created_at:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
frequency:
|
||||
$ref: '#/definitions/value_objects.Frequency'
|
||||
id:
|
||||
type: string
|
||||
is_negative:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
specific_dates:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
specific_days:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
target_value:
|
||||
type: number
|
||||
type:
|
||||
$ref: '#/definitions/value_objects.HabitType'
|
||||
updated_at:
|
||||
type: string
|
||||
user_id:
|
||||
type: string
|
||||
type: object
|
||||
http.SyncHabitEntryDTO:
|
||||
properties:
|
||||
completed_at:
|
||||
type: string
|
||||
habit_id:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
scheduled_date:
|
||||
type: string
|
||||
updated_at:
|
||||
type: string
|
||||
value:
|
||||
type: number
|
||||
type: object
|
||||
http.TodaysHabitEntryResponse:
|
||||
properties:
|
||||
completed_at:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
value:
|
||||
type: number
|
||||
type: object
|
||||
http.TodaysHabitResponse:
|
||||
properties:
|
||||
entry:
|
||||
$ref: '#/definitions/http.TodaysHabitEntryResponse'
|
||||
id:
|
||||
type: string
|
||||
is_carried_over:
|
||||
@@ -164,6 +302,86 @@ definitions:
|
||||
type:
|
||||
$ref: '#/definitions/value_objects.HabitType'
|
||||
type: object
|
||||
http.ValidationErrorResponse:
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
field:
|
||||
type: string
|
||||
type: object
|
||||
http.VerifyEmailRequest:
|
||||
properties:
|
||||
token:
|
||||
type: string
|
||||
type: object
|
||||
pagination.Response:
|
||||
properties:
|
||||
page:
|
||||
type: integer
|
||||
page_size:
|
||||
type: integer
|
||||
total_items:
|
||||
type: integer
|
||||
total_pages:
|
||||
type: integer
|
||||
type: object
|
||||
queries.ExportEntryDTO:
|
||||
properties:
|
||||
completed_at:
|
||||
type: string
|
||||
habit_id:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
scheduled_date:
|
||||
type: string
|
||||
value:
|
||||
type: number
|
||||
type: object
|
||||
queries.ExportHabitDTO:
|
||||
properties:
|
||||
archived_at:
|
||||
type: string
|
||||
carry_over:
|
||||
type: boolean
|
||||
created_at:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
frequency:
|
||||
$ref: '#/definitions/value_objects.Frequency'
|
||||
id:
|
||||
type: string
|
||||
is_negative:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
specific_dates:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
specific_days:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
target_value:
|
||||
type: number
|
||||
type:
|
||||
$ref: '#/definitions/value_objects.HabitType'
|
||||
type: object
|
||||
queries.ExportUserDataResult:
|
||||
properties:
|
||||
entries:
|
||||
items:
|
||||
$ref: '#/definitions/queries.ExportEntryDTO'
|
||||
type: array
|
||||
exported_at:
|
||||
type: string
|
||||
habits:
|
||||
items:
|
||||
$ref: '#/definitions/queries.ExportHabitDTO'
|
||||
type: array
|
||||
type: object
|
||||
queries.HabitStatsDTO:
|
||||
properties:
|
||||
completion_rate:
|
||||
@@ -215,6 +433,46 @@ info:
|
||||
termsOfService: http://swagger.io/terms/
|
||||
title: Apocapoc API
|
||||
paths:
|
||||
/auth/forgot-password:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Request a password reset email with a reset token
|
||||
parameters:
|
||||
- description: User email
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/http.ForgotPasswordRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Reset email sent successfully
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"400":
|
||||
description: Invalid email
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
"403":
|
||||
description: Email not verified
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
"404":
|
||||
description: User not found
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
summary: Request password reset
|
||||
tags:
|
||||
- auth
|
||||
/auth/login:
|
||||
post:
|
||||
consumes:
|
||||
@@ -244,6 +502,10 @@ paths:
|
||||
description: Invalid email or password
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
"403":
|
||||
description: Email not verified
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
@@ -333,9 +595,8 @@ paths:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
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.
|
||||
description: Create a new user account. If email verification is enabled, you
|
||||
will receive a verification email. Otherwise, you can login immediately.
|
||||
parameters:
|
||||
- description: 'Registration data (password requires: min 8 chars, uppercase,
|
||||
lowercase, digit, special char)'
|
||||
@@ -348,11 +609,15 @@ paths:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Returns access token, refresh token, and user ID
|
||||
description: Returns user ID and message about next steps
|
||||
schema:
|
||||
$ref: '#/definitions/http.AuthResponse'
|
||||
$ref: '#/definitions/http.RegisterResponse'
|
||||
"400":
|
||||
description: 'Invalid input: email format, password requirements, or timezone'
|
||||
description: 'Invalid input: email format or password requirements'
|
||||
schema:
|
||||
$ref: '#/definitions/http.ValidationErrorResponse'
|
||||
"403":
|
||||
description: Registration is closed
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
"409":
|
||||
@@ -366,18 +631,182 @@ paths:
|
||||
summary: Register a new user
|
||||
tags:
|
||||
- auth
|
||||
/auth/resend-verification:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Resend the email verification link to the user's email address
|
||||
parameters:
|
||||
- description: User email
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/http.ResendVerificationRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Verification email sent
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"400":
|
||||
description: Invalid email
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
"404":
|
||||
description: User not found
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
"409":
|
||||
description: Email already verified
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
summary: Resend verification email
|
||||
tags:
|
||||
- auth
|
||||
/auth/reset-password:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Reset user password using the reset token from email
|
||||
parameters:
|
||||
- description: Reset token and new password
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/http.ResetPasswordRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Password reset successfully
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"400":
|
||||
description: Invalid token or password requirements not met
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
"404":
|
||||
description: User not found
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
summary: Reset password
|
||||
tags:
|
||||
- auth
|
||||
/auth/verify-email:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Verify user email address using the token sent via email
|
||||
parameters:
|
||||
- description: Verification token
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/http.VerifyEmailRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Email verified successfully
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"400":
|
||||
description: Invalid or expired token
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
"409":
|
||||
description: Email already verified
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
summary: Verify email address
|
||||
tags:
|
||||
- auth
|
||||
/export:
|
||||
get:
|
||||
description: Export all user habits and entries in JSON format with gzip compression.
|
||||
Limited to 1 export per hour.
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Compressed JSON export
|
||||
schema:
|
||||
$ref: '#/definitions/queries.ExportUserDataResult'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
"429":
|
||||
description: Rate limit exceeded
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Export user data
|
||||
tags:
|
||||
- export
|
||||
/habits:
|
||||
get:
|
||||
description: Get all active habits for the authenticated user
|
||||
description: Get all active habits for the authenticated user with optional
|
||||
pagination and filters
|
||||
parameters:
|
||||
- description: 'Page number (default: 1)'
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
- description: 'Page size (default: 50, max: 100)'
|
||||
in: query
|
||||
name: page_size
|
||||
type: integer
|
||||
- description: Filter by type (BOOLEAN, COUNTER, VALUE)
|
||||
in: query
|
||||
name: type
|
||||
type: string
|
||||
- description: Filter by frequency (DAILY, WEEKLY, MONTHLY)
|
||||
in: query
|
||||
name: frequency
|
||||
type: string
|
||||
- description: 'Include archived habits (default: false)'
|
||||
in: query
|
||||
name: archived
|
||||
type: boolean
|
||||
- description: Search by name or description
|
||||
in: query
|
||||
name: search
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/http.UserHabitResponse'
|
||||
type: array
|
||||
$ref: '#/definitions/http.GetUserHabitsResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
@@ -708,7 +1137,15 @@ paths:
|
||||
- habits
|
||||
/habits/today:
|
||||
get:
|
||||
description: Get all habits scheduled for today for the authenticated user
|
||||
description: Get all habits scheduled for today for the authenticated user.
|
||||
Includes the entry for today if it exists. Requires timezone as query parameter
|
||||
(e.g., ?timezone=America/New_York).
|
||||
parameters:
|
||||
- description: IANA timezone (e.g., 'America/New_York', 'Europe/Madrid', 'UTC')
|
||||
in: query
|
||||
name: timezone
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
@@ -718,6 +1155,10 @@ paths:
|
||||
items:
|
||||
$ref: '#/definitions/http.TodaysHabitResponse'
|
||||
type: array
|
||||
"400":
|
||||
description: Invalid or missing timezone
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
@@ -786,6 +1227,110 @@ paths:
|
||||
summary: Get habit statistics
|
||||
tags:
|
||||
- stats
|
||||
/sync/batch:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Apply a batch of changes from the client for offline sync (Last-Write-Wins)
|
||||
parameters:
|
||||
- description: Sync batch data
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/http.SyncBatchRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"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'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Apply sync batch
|
||||
tags:
|
||||
- sync
|
||||
/sync/changes:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get all changes (habits and entries) since a given timestamp for
|
||||
offline sync
|
||||
parameters:
|
||||
- description: ISO 8601 timestamp (e.g., 2025-01-01T00:00:00Z)
|
||||
in: query
|
||||
name: since
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/http.SyncChangesResponse'
|
||||
"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'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Get sync changes
|
||||
tags:
|
||||
- sync
|
||||
/users/me:
|
||||
delete:
|
||||
description: Permanently delete the authenticated user's account and all associated
|
||||
data (habits, entries, tokens). This action cannot be undone.
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Account deleted successfully
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"401":
|
||||
description: Unauthorized - invalid or missing token
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
"404":
|
||||
description: User not found
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/http.ErrorResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Delete user account
|
||||
tags:
|
||||
- users
|
||||
securityDefinitions:
|
||||
BearerAuth:
|
||||
description: Type "Bearer" followed by a space and JWT token.
|
||||
|
||||
Reference in New Issue
Block a user