diff --git a/src/handlers/admin/admin_handler_calendars.erl b/src/handlers/admin/admin_handler_calendars.erl index 0b185a5..adcd9dd 100644 --- a/src/handlers/admin/admin_handler_calendars.erl +++ b/src/handlers/admin/admin_handler_calendars.erl @@ -2,6 +2,7 @@ -behaviour(cowboy_handler). -export([init/2]). -export([trails/0]). +-export([calendar_schema/0]). -include("records.hrl"). diff --git a/src/swagger/admin-swagger.json b/src/swagger/admin-swagger.json index 746c263..3c3a789 100644 --- a/src/swagger/admin-swagger.json +++ b/src/swagger/admin-swagger.json @@ -4,380 +4,60 @@ "title": "EventHub Admin API" }, "paths": { - "/admin/health": { - "get": { - "description": "Admin API health check", - "tags": [ - "Health" - ], - "responses": { - "200": { - "description": "API is healthy", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "status": { - "type": "string" - } - } - } - } - } - } - } - } - }, - "/v1/admin/admins": { - "get": { - "description": "List all admins (superadmin only)", - "parameters": [ - { - "in": "query", - "name": "role", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "status", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "limit", - "schema": { - "type": "integer" - } - }, - { - "in": "query", - "name": "offset", - "schema": { - "type": "integer" - } - } - ], - "tags": [ - "Admins" - ], - "responses": { - "200": { - "description": "Array of admins", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "active", - "blocked" - ] - }, - "role": { - "type": "string", - "enum": [ - "superadmin", - "admin", - "moderator", - "support" - ] - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "nickname": { - "type": "string", - "nullable": true - }, - "email": { - "type": "string", - "format": "email" - }, - "avatar_url": { - "type": "string", - "nullable": true - }, - "timezone": { - "type": "string", - "nullable": true - }, - "language": { - "type": "string", - "nullable": true - }, - "phone": { - "type": "string", - "nullable": true - }, - "preferences": { - "type": "object", - "nullable": true - }, - "last_login": { - "type": "string", - "format": "date-time" - }, - "updated_at": { - "type": "string", - "format": "date-time" - } - } - } - } - } - } - } - } - }, - "post": { - "description": "Create a new admin (superadmin only)", - "tags": [ - "Admins" - ], - "responses": { - "201": { - "description": "Admin created" - }, - "400": { - "description": "Invalid fields" - }, - "403": { - "description": "Only superadmin can create admins" - }, - "409": { - "description": "Email already exists" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "email", - "password", - "role" - ], - "properties": { - "role": { - "type": "string", - "enum": [ - "superadmin", - "admin", - "moderator", - "support" - ] - }, - "password": { - "type": "string" - }, - "email": { - "type": "string", - "format": "email" - } - } - } - } - } - } - } - }, - "/v1/admin/audit": { - "get": { - "description": "List audit records (superadmin only)", - "parameters": [ - { - "in": "query", - "name": "admin_id", - "description": "Filter by admin ID", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "action", - "description": "Filter by action", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "date_from", - "description": "Start timestamp (ISO8601)", - "schema": { - "type": "string", - "format": "date-time" - } - }, - { - "in": "query", - "name": "date_to", - "description": "End timestamp (ISO8601)", - "schema": { - "type": "string", - "format": "date-time" - } - }, - { - "in": "query", - "name": "limit", - "description": "Page size", - "schema": { - "type": "integer" - } - }, - { - "in": "query", - "name": "offset", - "description": "Offset", - "schema": { - "type": "integer" - } - } - ], - "tags": [ - "Audit" - ], - "responses": { - "200": { - "description": "Array of audit records", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "reason": { - "type": "string", - "nullable": true - }, - "timestamp": { - "type": "string", - "format": "date-time" - }, - "ip": { - "type": "string" - }, - "role": { - "type": "string" - }, - "email": { - "type": "string", - "format": "email" - }, - "entity_id": { - "type": "string" - }, - "action": { - "type": "string" - }, - "admin_id": { - "type": "string" - }, - "entity_type": { - "type": "string" - } - } - } - } - } - } - } - } - } - }, - "/v1/admin/banned-words": { - "get": { - "description": "List all banned words (admin)", - "parameters": [ - { - "in": "query", - "name": "limit", - "description": "Page size", - "schema": { - "type": "integer" - } - }, - { - "in": "query", - "name": "offset", - "description": "Offset", - "schema": { - "type": "integer" - } - } - ], + "/v1/admin/banned-words/{word}": { + "delete": { + "description": "Remove a banned word", "tags": [ "Banned Words" ], + "parameters": [ + { + "in": "path", + "name": "word", + "description": "The word to remove", + "schema": { + "type": "string" + }, + "required": true + } + ], "responses": { "200": { - "description": "Array of banned words", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "added_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "word": { - "type": "string" - }, - "added_by": { - "type": "string", - "nullable": true - } - } - } - } - } - } + "description": "Word removed" + }, + "404": { + "description": "Word not found" } } }, - "post": { - "description": "Add a new banned word", + "put": { + "description": "Update an existing banned word", "tags": [ "Banned Words" ], + "parameters": [ + { + "in": "path", + "name": "word", + "description": "Current word", + "schema": { + "type": "string" + }, + "required": true + } + ], "responses": { - "201": { - "description": "Word added" + "200": { + "description": "Word updated" + }, + "404": { + "description": "Word not found" }, "409": { - "description": "Word already exists" + "description": "New word already exists" } }, "requestBody": { - "required": true, "content": { "application/json": { "schema": { @@ -392,40 +72,17 @@ } } } - } - } - } - }, - "/v1/admin/banned-words/:word": { - "delete": { - "description": "Remove a banned word", - "parameters": [ - { - "in": "path", - "name": "word", - "description": "The word to remove", - "schema": { - "type": "string" - }, - "required": true - } - ], - "tags": [ - "Banned Words" - ], - "responses": { - "200": { - "description": "Word removed" }, - "404": { - "description": "Word not found" - } + "required": true } } }, - "/v1/admin/calendar/:id": { + "/v1/admin/review/:id": { "put": { - "description": "Moderate calendar - unfreeze", + "description": "Moderate review - unhide", + "tags": [ + "Moderation" + ], "parameters": [ { "in": "path", @@ -452,9 +109,6 @@ "required": true } ], - "tags": [ - "Moderation" - ], "responses": { "200": { "description": "Moderation applied successfully" @@ -467,7 +121,6 @@ } }, "requestBody": { - "required": true, "content": { "application/json": { "schema": { @@ -482,20 +135,135 @@ "action": { "type": "string", "enum": [ - "freeze", - "unfreeze" + "hide", + "unhide" ] } } } } + }, + "required": true + } + } + }, + "/v1/admin/me": { + "get": { + "description": "Get current admin profile", + "tags": [ + "Admins" + ], + "responses": { + "200": { + "description": "Admin profile", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "role": { + "type": "string" + }, + "email": { + "type": "string" + }, + "nickname": { + "type": "string", + "nullable": true + }, + "avatar_url": { + "type": "string", + "nullable": true + }, + "timezone": { + "type": "string", + "nullable": true + }, + "language": { + "type": "string", + "nullable": true + }, + "phone": { + "type": "string", + "nullable": true + }, + "preferences": { + "type": "object", + "nullable": true + }, + "last_login": { + "type": "string", + "format": "date-time" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + } + } } } + }, + "put": { + "description": "Update current admin profile", + "tags": [ + "Admins" + ], + "responses": { + "200": { + "description": "Updated profile" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "nickname": { + "type": "string" + }, + "avatar_url": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "language": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "preferences": { + "type": "object" + } + } + } + } + }, + "required": true + } } }, "/v1/admin/event/:id": { "put": { "description": "Moderate event - unfreeze", + "tags": [ + "Moderation" + ], "parameters": [ { "in": "path", @@ -522,9 +290,6 @@ "required": true } ], - "tags": [ - "Moderation" - ], "responses": { "200": { "description": "Moderation applied successfully" @@ -537,7 +302,6 @@ } }, "requestBody": { - "required": true, "content": { "application/json": { "schema": { @@ -559,13 +323,439 @@ } } } + }, + "required": true + } + } + }, + "/v1/admin/tickets/stats": { + "get": { + "description": "Get ticket statistics", + "tags": [ + "Tickets" + ], + "responses": { + "200": { + "description": "Ticket statistics", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "closed": { + "type": "integer", + "description": "Number of closed tickets" + }, + "open": { + "type": "integer", + "description": "Number of open tickets" + }, + "resolved": { + "type": "integer", + "description": "Number of resolved tickets" + }, + "in_progress": { + "type": "integer", + "description": "Number of in-progress tickets" + }, + "total_errors": { + "type": "integer", + "description": "Total error occurrences" + }, + "total_tickets": { + "type": "integer", + "description": "Total number of tickets" + } + } + } + } + } } } } }, + "/v1/admin/refresh": { + "post": { + "description": "Refresh admin access token using refresh token", + "tags": [ + "Admin" + ], + "responses": { + "200": { + "description": "New admin token pair (access + refresh)", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string", + "description": "JWT access token" + }, + "refresh_token": { + "type": "string", + "description": "Refresh token" + } + } + } + } + } + }, + "400": { + "description": "Missing refresh_token field or invalid JSON" + }, + "401": { + "description": "Refresh token expired or not found" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "refresh_token" + ], + "properties": { + "refresh_token": { + "type": "string" + } + } + } + } + }, + "required": true + } + } + }, + "/v1/admin/user/:id": { + "put": { + "description": "Moderate user - unblock", + "tags": [ + "Moderation" + ], + "parameters": [ + { + "in": "path", + "name": "target_type", + "description": "Entity type", + "schema": { + "type": "string", + "enum": [ + "calendar", + "event", + "review", + "user" + ] + }, + "required": true + }, + { + "in": "path", + "name": "id", + "description": "Entity ID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Moderation applied successfully" + }, + "400": { + "description": "Bad request" + }, + "404": { + "description": "Entity not found" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "action" + ], + "properties": { + "reason": { + "type": "string" + }, + "action": { + "type": "string", + "enum": [ + "block", + "unblock" + ] + } + } + } + } + }, + "required": true + } + } + }, + "/v1/admin/calendars/{id}": { + "delete": { + "description": "Delete calendar (admin)", + "tags": [ + "Admin Calendars" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Deleted" + }, + "404": { + "description": "Not found" + } + } + }, + "get": { + "description": "Get calendar by ID (admin)", + "tags": [ + "Admin Calendars" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Calendar object", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "active", + "frozen", + "deleted" + ] + }, + "type": { + "type": "string", + "enum": [ + "personal", + "commercial" + ] + }, + "description": { + "type": "string" + }, + "title": { + "type": "string" + }, + "category": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date_time" + }, + "updated_at": { + "type": "string", + "format": "date_time" + }, + "owner_id": { + "type": "string" + }, + "short_name": { + "type": "string" + }, + "color": { + "type": "string" + }, + "image_url": { + "type": "string" + }, + "settings": { + "type": "object" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "confirmation": { + "type": "string" + }, + "rating_avg": { + "type": "number" + }, + "rating_count": { + "type": "integer" + } + } + } + } + } + }, + "404": { + "description": "Not found" + } + } + }, + "put": { + "description": "Update calendar (admin)", + "tags": [ + "Admin Calendars" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Updated calendar", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "active", + "frozen", + "deleted" + ] + }, + "type": { + "type": "string", + "enum": [ + "personal", + "commercial" + ] + }, + "description": { + "type": "string" + }, + "title": { + "type": "string" + }, + "category": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date_time" + }, + "updated_at": { + "type": "string", + "format": "date_time" + }, + "owner_id": { + "type": "string" + }, + "short_name": { + "type": "string" + }, + "color": { + "type": "string" + }, + "image_url": { + "type": "string" + }, + "settings": { + "type": "object" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "confirmation": { + "type": "string" + }, + "rating_avg": { + "type": "number" + }, + "rating_count": { + "type": "integer" + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "404": { + "description": "Not found" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "title": { + "type": "string" + }, + "is_public": { + "type": "boolean" + } + } + } + } + }, + "required": true + } + } + }, "/v1/admin/events": { "get": { "description": "Search and list events (admin)", + "tags": [ + "Events" + ], "parameters": [ { "in": "query", @@ -662,9 +852,6 @@ "required": false } ], - "tags": [ - "Events" - ], "responses": { "200": { "description": "Array of events with Content-Range header", @@ -703,17 +890,30 @@ "duration": { "type": "integer" }, - "start_time": { - "type": "string", - "format": "date-time" - }, - "calendar_id": { - "type": "string" - }, "created_at": { "type": "string", "format": "date-time" }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "rating_avg": { + "type": "number", + "format": "float" + }, + "rating_count": { + "type": "integer" + }, + "calendar_id": { + "type": "string" + }, "event_type": { "type": "string", "enum": [ @@ -721,26 +921,20 @@ "recurring" ] }, + "start_time": { + "type": "string", + "format": "date-time" + }, "master_id": { "type": "string", "nullable": true }, - "specialist_id": { - "type": "string", - "nullable": true - }, - "updated_at": { - "type": "string", - "format": "date-time" - }, "is_instance": { "type": "boolean" }, - "tags": { - "type": "array", - "items": { - "type": "string" - } + "specialist_id": { + "type": "string", + "nullable": true }, "capacity": { "type": "integer", @@ -750,13 +944,6 @@ "type": "string", "nullable": true }, - "rating_avg": { - "type": "number", - "format": "float" - }, - "rating_count": { - "type": "integer" - }, "attachments": { "type": "array", "items": { @@ -787,1066 +974,91 @@ } } }, - "/v1/admin/events/:id": { - "delete": { - "description": "Soft-delete event (admin)", - "parameters": [ - { - "in": "path", - "name": "id", - "description": "Event ID", - "schema": { - "type": "string" - }, - "required": true - } - ], - "tags": [ - "Events" - ], - "responses": { - "200": { - "description": "Event status set to deleted" - } - } - }, + "/v1/admin/reviews/stats": { "get": { - "description": "Get event by ID (admin)", - "parameters": [ - { - "in": "path", - "name": "id", - "description": "Event ID", - "schema": { - "type": "string" - }, - "required": true - } - ], - "tags": [ - "Events" - ], - "responses": { - "200": { - "description": "Event details", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "reason": { - "type": "string", - "nullable": true - }, - "status": { - "type": "string", - "enum": [ - "active", - "cancelled", - "completed" - ] - }, - "description": { - "type": "string" - }, - "title": { - "type": "string" - }, - "location": { - "type": "object", - "nullable": true - }, - "duration": { - "type": "integer" - }, - "start_time": { - "type": "string", - "format": "date-time" - }, - "calendar_id": { - "type": "string" - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "event_type": { - "type": "string", - "enum": [ - "single", - "recurring" - ] - }, - "master_id": { - "type": "string", - "nullable": true - }, - "specialist_id": { - "type": "string", - "nullable": true - }, - "updated_at": { - "type": "string", - "format": "date-time" - }, - "is_instance": { - "type": "boolean" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - }, - "capacity": { - "type": "integer", - "nullable": true - }, - "online_link": { - "type": "string", - "nullable": true - }, - "rating_avg": { - "type": "number", - "format": "float" - }, - "rating_count": { - "type": "integer" - }, - "attachments": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true - }, - "edit_history": { - "type": "array", - "items": { - "type": "object" - }, - "nullable": true - }, - "recurrence": { - "type": "object", - "nullable": true - } - } - } - } - } - } - } - }, - "put": { - "description": "Update event (admin)", - "parameters": [ - { - "in": "path", - "name": "id", - "description": "Event ID", - "schema": { - "type": "string" - }, - "required": true - } - ], - "tags": [ - "Events" - ], - "responses": { - "200": { - "description": "Updated event" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": [ - "active", - "cancelled", - "completed" - ] - }, - "description": { - "type": "string" - }, - "title": { - "type": "string" - }, - "location": { - "type": "object", - "properties": { - "address": { - "type": "string" - }, - "lat": { - "type": "number", - "format": "float" - }, - "lon": { - "type": "number", - "format": "float" - } - } - }, - "duration": { - "type": "integer" - }, - "start_time": { - "type": "string", - "format": "date-time" - }, - "specialist_id": { - "type": "string" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - }, - "capacity": { - "type": "integer" - }, - "online_link": { - "type": "string" - } - } - } - } - } - } - } - }, - "/v1/admin/login": { - "post": { - "description": "Admin login", - "tags": [ - "Auth" - ], - "responses": { - "200": { - "description": "Login successful, returns token and user info" - }, - "401": { - "description": "Invalid credentials" - }, - "403": { - "description": "Insufficient permissions" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "email", - "password" - ], - "properties": { - "password": { - "type": "string", - "format": "password" - }, - "email": { - "type": "string", - "format": "email" - } - } - } - } - } - } - } - }, - "/v1/admin/me": { - "get": { - "description": "Get current admin profile", - "tags": [ - "Admins" - ], - "responses": { - "200": { - "description": "Admin profile", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "status": { - "type": "string" - }, - "role": { - "type": "string" - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "nickname": { - "type": "string", - "nullable": true - }, - "email": { - "type": "string" - }, - "avatar_url": { - "type": "string", - "nullable": true - }, - "timezone": { - "type": "string", - "nullable": true - }, - "language": { - "type": "string", - "nullable": true - }, - "phone": { - "type": "string", - "nullable": true - }, - "preferences": { - "type": "object", - "nullable": true - }, - "last_login": { - "type": "string", - "format": "date-time" - }, - "updated_at": { - "type": "string", - "format": "date-time" - } - } - } - } - } - } - } - }, - "put": { - "description": "Update current admin profile", - "tags": [ - "Admins" - ], - "responses": { - "200": { - "description": "Updated profile" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "nickname": { - "type": "string" - }, - "avatar_url": { - "type": "string" - }, - "timezone": { - "type": "string" - }, - "language": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "preferences": { - "type": "object" - } - } - } - } - } - } - } - }, - "/v1/admin/reports": { - "get": { - "description": "List all reports (admin)", - "parameters": [ - { - "in": "query", - "name": "status", - "description": "Filter by status", - "schema": { - "type": "string", - "enum": [ - "pending", - "reviewed", - "dismissed" - ] - } - }, - { - "in": "query", - "name": "target_type", - "description": "Filter by target type", - "schema": { - "type": "string", - "enum": [ - "calendar", - "event", - "review" - ] - } - }, - { - "in": "query", - "name": "q", - "description": "Search in reason", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "limit", - "description": "Page size", - "schema": { - "type": "integer" - } - }, - { - "in": "query", - "name": "offset", - "description": "Offset", - "schema": { - "type": "integer" - } - } - ], - "tags": [ - "Reports" - ], - "responses": { - "200": { - "description": "Array of reports", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "reason": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "pending", - "reviewed", - "dismissed" - ] - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "reporter_id": { - "type": "string" - }, - "target_type": { - "type": "string", - "enum": [ - "calendar", - "event", - "review" - ] - }, - "target_id": { - "type": "string" - }, - "resolved_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "resolved_by": { - "type": "string", - "nullable": true - } - } - } - } - } - } - } - } - } - }, - "/v1/admin/reports/:id": { - "get": { - "description": "Get report by ID (admin)", - "parameters": [ - { - "in": "path", - "name": "id", - "description": "Report ID", - "schema": { - "type": "string" - }, - "required": true - } - ], - "tags": [ - "Reports" - ], - "responses": { - "200": { - "description": "Report details", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "reason": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "pending", - "reviewed", - "dismissed" - ] - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "reporter_id": { - "type": "string" - }, - "target_type": { - "type": "string", - "enum": [ - "calendar", - "event", - "review" - ] - }, - "target_id": { - "type": "string" - }, - "resolved_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "resolved_by": { - "type": "string", - "nullable": true - } - } - } - } - } - }, - "404": { - "description": "Report not found" - } - } - }, - "put": { - "description": "Update report status (admin)", - "parameters": [ - { - "in": "path", - "name": "id", - "description": "Report ID", - "schema": { - "type": "string" - }, - "required": true - } - ], - "tags": [ - "Reports" - ], - "responses": { - "200": { - "description": "Updated report" - }, - "404": { - "description": "Report not found" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": [ - "reviewed", - "dismissed" - ] - } - } - } - } - } - } - } - }, - "/v1/admin/review/:id": { - "put": { - "description": "Moderate review - unhide", - "parameters": [ - { - "in": "path", - "name": "target_type", - "description": "Entity type", - "schema": { - "type": "string", - "enum": [ - "calendar", - "event", - "review", - "user" - ] - }, - "required": true - }, - { - "in": "path", - "name": "id", - "description": "Entity ID", - "schema": { - "type": "string" - }, - "required": true - } - ], - "tags": [ - "Moderation" - ], - "responses": { - "200": { - "description": "Moderation applied successfully" - }, - "400": { - "description": "Bad request" - }, - "404": { - "description": "Entity not found" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "action" - ], - "properties": { - "reason": { - "type": "string" - }, - "action": { - "type": "string", - "enum": [ - "hide", - "unhide" - ] - } - } - } - } - } - } - } - }, - "/v1/admin/reviews": { - "get": { - "description": "List all reviews (admin)", - "parameters": [ - { - "in": "query", - "name": "target_type", - "description": "calendar or event", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "target_id", - "description": "ID of target", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "user_id", - "description": "Filter by user", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "status", - "description": "visible, hidden, deleted, or all", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "limit", - "description": "Page size", - "schema": { - "type": "integer" - } - }, - { - "in": "query", - "name": "offset", - "description": "Offset", - "schema": { - "type": "integer" - } - } - ], - "tags": [ - "Reviews" - ], - "responses": { - "200": { - "description": "Array of reviews", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "reason": { - "type": "string", - "nullable": true - }, - "status": { - "type": "string", - "enum": [ - "visible", - "hidden", - "deleted" - ] - }, - "comment": { - "type": "string" - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "user_id": { - "type": "string" - }, - "updated_at": { - "type": "string", - "format": "date-time" - }, - "target_type": { - "type": "string", - "enum": [ - "calendar", - "event" - ] - }, - "target_id": { - "type": "string" - }, - "rating": { - "maximum": 5, - "type": "integer", - "minimum": 1 - }, - "likes": { - "type": "integer" - }, - "dislikes": { - "type": "integer" - } - } - } - } - } - } - } - } - }, - "patch": { - "description": "Bulk update review statuses", - "tags": [ - "Reviews" - ], - "responses": { - "200": { - "description": "Number of updated reviews" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "visible", - "hidden", - "deleted" - ] - } - } - } - } - } - } - } - } - }, - "/v1/admin/reviews/:id": { - "get": { - "description": "Get review by ID (admin)", - "parameters": [ - { - "in": "path", - "name": "id", - "description": "Review ID", - "schema": { - "type": "string" - }, - "required": true - } - ], - "tags": [ - "Reviews" - ], - "responses": { - "200": { - "description": "Review details", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "reason": { - "type": "string", - "nullable": true - }, - "status": { - "type": "string", - "enum": [ - "visible", - "hidden", - "deleted" - ] - }, - "comment": { - "type": "string" - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "user_id": { - "type": "string" - }, - "updated_at": { - "type": "string", - "format": "date-time" - }, - "target_type": { - "type": "string", - "enum": [ - "calendar", - "event" - ] - }, - "target_id": { - "type": "string" - }, - "rating": { - "maximum": 5, - "type": "integer", - "minimum": 1 - }, - "likes": { - "type": "integer" - }, - "dislikes": { - "type": "integer" - } - } - } - } - } - } - } - }, - "put": { - "description": "Update review (admin)", - "parameters": [ - { - "in": "path", - "name": "id", - "description": "Review ID", - "schema": { - "type": "string" - }, - "required": true - } - ], - "tags": [ - "Reviews" - ], - "responses": { - "200": { - "description": "Updated review" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "reason": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "visible", - "hidden", - "deleted" - ] - }, - "comment": { - "type": "string" - }, - "rating": { - "maximum": 5, - "type": "integer", - "minimum": 1 - } - } - } - } - } - } - } - }, - "/v1/admin/stats": { - "get": { - "description": "Get admin dashboard statistics", - "parameters": [ - { - "in": "query", - "name": "from", - "description": "Start date (ISO8601)", - "schema": { - "type": "string", - "format": "date-time" - } - }, - { - "in": "query", - "name": "to", - "description": "End date (ISO8601)", - "schema": { - "type": "string", - "format": "date-time" - } - } - ], + "description": "Get detailed review statistics (total, by target type, by status, top targets by reviews/positive/negative)", "tags": [ "Statistics" ], "responses": { "200": { - "description": "Statistics object", + "description": "Review statistics object", "content": { "application/json": { "schema": { "type": "object", "properties": { - "active_subscriptions": { - "type": "integer" + "reviews_by_status": { + "type": "object", + "description": "Number of reviews grouped by status (visible, hidden, deleted)", + "additionalProperties": { + "type": "integer" + } }, - "calendars": { - "type": "integer" + "reviews_by_target_type": { + "type": "object", + "description": "Number of reviews grouped by target type (event, calendar)", + "additionalProperties": { + "type": "integer" + } }, - "events": { - "type": "integer" + "top_targets_by_negative_reviews": { + "type": "array", + "description": "Top 10 targets by negative reviews (rating <= 2)", + "items": { + "type": "object", + "properties": { + "review_count": { + "type": "integer" + }, + "target_id": { + "type": "string" + }, + "target_type": { + "type": "string" + } + } + } }, - "reports": { - "type": "integer" + "top_targets_by_positive_reviews": { + "type": "array", + "description": "Top 10 targets by positive reviews (rating >= 4)", + "items": { + "type": "object", + "properties": { + "review_count": { + "type": "integer" + }, + "target_id": { + "type": "string" + }, + "target_type": { + "type": "string" + } + } + } }, - "reviews": { - "type": "integer" + "top_targets_by_reviews": { + "type": "array", + "description": "Top 10 targets by total number of reviews", + "items": { + "type": "object", + "properties": { + "review_count": { + "type": "integer" + }, + "target_id": { + "type": "string" + }, + "target_type": { + "type": "string" + } + } + } }, - "subscriptions": { - "type": "integer" - }, - "tickets": { - "type": "integer" - }, - "users": { + "total_reviews": { "type": "integer", - "description": "Total number of users" + "description": "Total number of reviews" } } } @@ -1859,34 +1071,42 @@ } } }, - "/v1/admin/subscriptions": { + "/v1/admin/banned-words": { "get": { - "description": "List all subscriptions (admin)", + "description": "List all banned words (admin)", + "tags": [ + "Banned Words" + ], "parameters": [ { "in": "query", - "name": "plan", - "description": "Filter by plan", + "name": "q", + "description": "Search by word", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "sort", + "description": "Sort field", "schema": { "type": "string", "enum": [ - "monthly", - "quarterly", - "biannual", - "annual" + "word", + "added_at" ] } }, { "in": "query", - "name": "status", - "description": "Filter by status", + "name": "order", + "description": "Sort direction", "schema": { "type": "string", "enum": [ - "active", - "expired", - "cancelled" + "asc", + "desc" ] } }, @@ -1907,12 +1127,9 @@ } } ], - "tags": [ - "Subscriptions" - ], "responses": { "200": { - "description": "Array of subscriptions", + "description": "Array of banned words", "content": { "application/json": { "schema": { @@ -1923,44 +1140,17 @@ "id": { "type": "string" }, - "status": { + "added_at": { "type": "string", - "enum": [ - "active", - "expired", - "cancelled" - ] + "format": "date-time", + "nullable": true }, - "started_at": { - "type": "string", - "format": "date-time" - }, - "plan": { - "type": "string", - "enum": [ - "monthly", - "quarterly", - "biannual", - "annual" - ] - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "user_id": { + "word": { "type": "string" }, - "updated_at": { + "added_by": { "type": "string", - "format": "date-time" - }, - "trial_used": { - "type": "boolean" - }, - "expires_at": { - "type": "string", - "format": "date-time" + "nullable": true } } } @@ -1969,295 +1159,46 @@ } } } - } - }, - "/v1/admin/subscriptions/:id": { - "delete": { - "description": "Delete subscription (admin)", - "parameters": [ - { - "in": "path", - "name": "id", - "description": "Subscription ID", - "schema": { - "type": "string" - }, - "required": true - } - ], - "tags": [ - "Subscriptions" - ], - "responses": { - "200": { - "description": "Subscription deleted" - }, - "404": { - "description": "Subscription not found" - } - } }, - "get": { - "description": "Get subscription by ID (admin)", - "parameters": [ - { - "in": "path", - "name": "id", - "description": "Subscription ID", - "schema": { - "type": "string" - }, - "required": true - } - ], + "post": { + "description": "Add a new banned word", "tags": [ - "Subscriptions" + "Banned Words" ], "responses": { - "200": { - "description": "Subscription details", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "active", - "expired", - "cancelled" - ] - }, - "started_at": { - "type": "string", - "format": "date-time" - }, - "plan": { - "type": "string", - "enum": [ - "monthly", - "quarterly", - "biannual", - "annual" - ] - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "user_id": { - "type": "string" - }, - "updated_at": { - "type": "string", - "format": "date-time" - }, - "trial_used": { - "type": "boolean" - }, - "expires_at": { - "type": "string", - "format": "date-time" - } - } - } - } - } + "201": { + "description": "Word added" }, - "404": { - "description": "Subscription not found" - } - } - }, - "put": { - "description": "Update subscription (admin)", - "parameters": [ - { - "in": "path", - "name": "id", - "description": "Subscription ID", - "schema": { - "type": "string" - }, - "required": true - } - ], - "tags": [ - "Subscriptions" - ], - "responses": { - "200": { - "description": "Updated subscription" - }, - "404": { - "description": "Subscription not found" + "409": { + "description": "Word already exists" } }, "requestBody": { - "required": true, "content": { "application/json": { "schema": { "type": "object", + "required": [ + "word" + ], "properties": { - "status": { - "type": "string", - "enum": [ - "active", - "expired", - "cancelled" - ] - }, - "plan": { - "type": "string", - "enum": [ - "monthly", - "quarterly", - "biannual", - "annual" - ] - }, - "trial_used": { - "type": "boolean" - }, - "expires_at": { - "type": "string", - "format": "date-time", - "description": "New expiration date" + "word": { + "type": "string" } } } } - } - } - } - }, - "/v1/admin/tickets": { - "get": { - "description": "List all tickets (admin)", - "parameters": [ - { - "in": "query", - "name": "status", - "description": "Filter by status", - "schema": { - "type": "string", - "enum": [ - "open", - "in_progress", - "resolved", - "closed" - ] - } }, - { - "in": "query", - "name": "assigned_to", - "description": "Filter by assigned admin ID", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "q", - "description": "Search in error message", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "limit", - "description": "Page size", - "schema": { - "type": "integer" - } - }, - { - "in": "query", - "name": "offset", - "description": "Offset", - "schema": { - "type": "integer" - } - } - ], - "tags": [ - "Tickets" - ], - "responses": { - "200": { - "description": "Array of tickets", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "count": { - "type": "integer" - }, - "id": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "open", - "in_progress", - "resolved", - "closed" - ] - }, - "context": { - "type": "string" - }, - "stacktrace": { - "type": "string" - }, - "reporter_id": { - "type": "string" - }, - "error_hash": { - "type": "string" - }, - "error_message": { - "type": "string" - }, - "first_seen": { - "type": "string", - "format": "date-time" - }, - "last_seen": { - "type": "string", - "format": "date-time" - }, - "assigned_to": { - "type": "string", - "nullable": true - }, - "resolution_note": { - "type": "string", - "nullable": true - } - } - } - } - } - } - } + "required": true } } }, "/v1/admin/tickets/:id": { "delete": { "description": "Delete ticket (admin)", + "tags": [ + "Tickets" + ], "parameters": [ { "in": "path", @@ -2269,9 +1210,6 @@ "required": true } ], - "tags": [ - "Tickets" - ], "responses": { "200": { "description": "Ticket deleted" @@ -2283,6 +1221,9 @@ }, "get": { "description": "Get ticket by ID (admin)", + "tags": [ + "Tickets" + ], "parameters": [ { "in": "path", @@ -2294,9 +1235,6 @@ "required": true } ], - "tags": [ - "Tickets" - ], "responses": { "200": { "description": "Ticket details", @@ -2363,6 +1301,9 @@ }, "put": { "description": "Update ticket (admin)", + "tags": [ + "Tickets" + ], "parameters": [ { "in": "path", @@ -2374,9 +1315,6 @@ "required": true } ], - "tags": [ - "Tickets" - ], "responses": { "200": { "description": "Updated ticket" @@ -2386,7 +1324,6 @@ } }, "requestBody": { - "required": true, "content": { "application/json": { "schema": { @@ -2410,43 +1347,181 @@ } } } - } + }, + "required": true } } }, - "/v1/admin/tickets/stats": { + "/v1/admin/calendars/stats": { "get": { - "description": "Get ticket statistics (admin)", + "description": "Calendar statistics", "tags": [ - "Tickets" + "Statistics" ], "responses": { "200": { - "description": "Ticket statistics", + "description": "Calendar statistics object", "content": { "application/json": { "schema": { "type": "object", "properties": { - "closed": { - "type": "integer", - "description": "Number of closed tickets" + "calendars_by_status": { + "type": "object", + "additionalProperties": { + "type": "integer" + } }, - "open": { - "type": "integer", - "description": "Number of open tickets" + "calendars_by_type": { + "type": "object", + "additionalProperties": { + "type": "integer" + } }, - "total": { - "type": "integer", - "description": "Total number of tickets" + "top_calendars_by_negative_reviews": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "rating_avg": { + "type": "number", + "format": "float" + }, + "review_count": { + "type": "integer" + }, + "title": { + "type": "string" + } + } + } }, - "resolved": { - "type": "integer", - "description": "Number of resolved tickets" + "top_calendars_by_positive_reviews": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "rating_avg": { + "type": "number", + "format": "float" + }, + "review_count": { + "type": "integer" + }, + "title": { + "type": "string" + } + } + } }, - "in_progress": { + "top_calendars_by_rating": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "rating_avg": { + "type": "number", + "format": "float" + }, + "review_count": { + "type": "integer" + }, + "title": { + "type": "string" + } + } + } + }, + "top_calendars_by_reviews": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "rating_avg": { + "type": "number", + "format": "float" + }, + "review_count": { + "type": "integer" + }, + "title": { + "type": "string" + } + } + } + }, + "total_calendars": { + "type": "integer" + } + } + } + } + } + } + } + } + }, + "/v1/admin/reports/stats": { + "get": { + "description": "Get detailed report statistics (total, by target type, by status, top 10 targets by reports)", + "tags": [ + "Statistics" + ], + "responses": { + "200": { + "description": "Report statistics object", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reports_by_status": { + "type": "object", + "description": "Number of reports grouped by status (pending, reviewed, dismissed)", + "additionalProperties": { + "type": "integer" + } + }, + "reports_by_target_type": { + "type": "object", + "description": "Number of reports grouped by target type (event, calendar, review)", + "additionalProperties": { + "type": "integer" + } + }, + "top_targets_by_reports": { + "type": "array", + "description": "Top 10 targets by number of reports", + "items": { + "type": "object", + "properties": { + "report_count": { + "type": "integer" + }, + "target_id": { + "type": "string" + }, + "target_type": { + "type": "string" + } + } + } + }, + "total_reports": { "type": "integer", - "description": "Number of tickets in progress" + "description": "Total number of reports" } } } @@ -2459,68 +1534,1329 @@ } } }, - "/v1/admin/user/:id": { - "put": { - "description": "Moderate user - unblock", + "/v1/admin/reviews": { + "get": { + "description": "List all reviews (admin)", + "tags": [ + "Reviews" + ], "parameters": [ { - "in": "path", + "in": "query", "name": "target_type", - "description": "Entity type", + "description": "calendar or event", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "target_id", + "description": "ID of target", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "user_id", + "description": "Filter by user", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "status", + "description": "visible, hidden, deleted, or all", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "sort", + "description": "Sort field", + "schema": { + "type": "string", + "enum": [ + "created_at", + "updated_at", + "rating" + ] + } + }, + { + "in": "query", + "name": "order", + "description": "Sort direction", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "in": "query", + "name": "limit", + "description": "Page size", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "description": "Offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of reviews", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "reason": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "enum": [ + "visible", + "hidden", + "deleted" + ] + }, + "comment": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "user_id": { + "type": "string" + }, + "target_type": { + "type": "string", + "enum": [ + "calendar", + "event" + ] + }, + "target_id": { + "type": "string" + }, + "rating": { + "maximum": 5, + "type": "integer", + "minimum": 1 + }, + "likes": { + "type": "integer" + }, + "dislikes": { + "type": "integer" + } + } + } + } + } + } + } + } + }, + "patch": { + "description": "Bulk update review statuses with a single reason", + "tags": [ + "Reviews" + ], + "responses": { + "200": { + "description": "Number of updated reviews" + }, + "400": { + "description": "Invalid request body or missing reason" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "operations", + "reason" + ], + "properties": { + "operations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "visible", + "hidden", + "deleted" + ] + } + } + } + }, + "reason": { + "type": "string", + "description": "Reason applied to all updates" + } + } + } + } + }, + "required": true + } + } + }, + "/v1/admin/reports": { + "get": { + "description": "List all reports (admin)", + "tags": [ + "Reports" + ], + "parameters": [ + { + "in": "query", + "name": "status", + "description": "Filter by status", + "schema": { + "type": "string", + "enum": [ + "pending", + "reviewed", + "dismissed" + ] + } + }, + { + "in": "query", + "name": "target_type", + "description": "Filter by target type", "schema": { "type": "string", "enum": [ "calendar", "event", - "review", - "user" + "review" ] - }, - "required": true + } }, + { + "in": "query", + "name": "q", + "description": "Search in reason", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "sort", + "description": "Sort field", + "schema": { + "type": "string", + "enum": [ + "created_at", + "status" + ] + } + }, + { + "in": "query", + "name": "order", + "description": "Sort direction", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "in": "query", + "name": "limit", + "description": "Page size", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "description": "Offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of reports", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "pending", + "reviewed", + "dismissed" + ] + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "target_type": { + "type": "string", + "enum": [ + "calendar", + "event", + "review" + ] + }, + "target_id": { + "type": "string" + }, + "reporter_id": { + "type": "string" + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resolved_by": { + "type": "string", + "nullable": true + } + } + } + } + } + } + } + } + } + }, + "/v1/admin/users/stats": { + "get": { + "description": "Get detailed user statistics (total, by role, by status, pending)", + "tags": [ + "Statistics" + ], + "responses": { + "200": { + "description": "User statistics object", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "pending_users": { + "type": "integer", + "description": "Number of unverified users" + }, + "total_users": { + "type": "integer", + "description": "Total number of users" + }, + "users_by_role": { + "type": "object", + "description": "Number of users grouped by role", + "additionalProperties": { + "type": "integer" + } + }, + "users_by_status": { + "type": "object", + "description": "Number of users grouped by status", + "additionalProperties": { + "type": "integer" + } + } + } + } + } + } + }, + "403": { + "description": "Admin access required" + } + } + } + }, + "/v1/admin/banned-words/batch": { + "post": { + "description": "Batch add banned words (only new words are added)", + "tags": [ + "Banned Words" + ], + "responses": { + "200": { + "description": "Batch operation completed" + }, + "400": { + "description": "Invalid request body" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "words" + ], + "properties": { + "words": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "required": true + } + } + }, + "/v1/admin/events/stats": { + "get": { + "description": "Get detailed event statistics (total, by type, by status, top 10 by rating)", + "tags": [ + "Statistics" + ], + "responses": { + "200": { + "description": "Event statistics object", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "events_by_status": { + "type": "object", + "description": "Number of events grouped by status (active, cancelled, completed)", + "additionalProperties": { + "type": "integer" + } + }, + "events_by_type": { + "type": "object", + "description": "Number of events grouped by type (single, recurring)", + "additionalProperties": { + "type": "integer" + } + }, + "top_events_by_rating": { + "type": "array", + "description": "Top 10 events by average rating", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "rating_avg": { + "type": "number", + "format": "float" + }, + "rating_count": { + "type": "integer" + }, + "title": { + "type": "string" + } + } + } + }, + "total_events": { + "type": "integer", + "description": "Total number of events (master, active)" + } + } + } + } + } + }, + "403": { + "description": "Admin access required" + } + } + } + }, + "/v1/admin/events/:id": { + "delete": { + "description": "Soft-delete event (admin)", + "tags": [ + "Events" + ], + "parameters": [ { "in": "path", "name": "id", - "description": "Entity ID", + "description": "Event ID", "schema": { "type": "string" }, "required": true } ], + "responses": { + "200": { + "description": "Event status set to deleted" + } + } + }, + "get": { + "description": "Get event by ID (admin)", "tags": [ - "Moderation" + "Events" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "description": "Event ID", + "schema": { + "type": "string" + }, + "required": true + } ], "responses": { "200": { - "description": "Moderation applied successfully" - }, - "400": { - "description": "Bad request" - }, - "404": { - "description": "Entity not found" + "description": "Event details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "reason": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "enum": [ + "active", + "cancelled", + "completed" + ] + }, + "description": { + "type": "string" + }, + "title": { + "type": "string" + }, + "location": { + "type": "object", + "nullable": true + }, + "duration": { + "type": "integer" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "rating_avg": { + "type": "number", + "format": "float" + }, + "rating_count": { + "type": "integer" + }, + "calendar_id": { + "type": "string" + }, + "event_type": { + "type": "string", + "enum": [ + "single", + "recurring" + ] + }, + "start_time": { + "type": "string", + "format": "date-time" + }, + "master_id": { + "type": "string", + "nullable": true + }, + "is_instance": { + "type": "boolean" + }, + "specialist_id": { + "type": "string", + "nullable": true + }, + "capacity": { + "type": "integer", + "nullable": true + }, + "online_link": { + "type": "string", + "nullable": true + }, + "attachments": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "edit_history": { + "type": "array", + "items": { + "type": "object" + }, + "nullable": true + }, + "recurrence": { + "type": "object", + "nullable": true + } + } + } + } + } + } + } + }, + "put": { + "description": "Update event (admin)", + "tags": [ + "Events" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "description": "Event ID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Updated event" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "active", + "cancelled", + "completed" + ] + }, + "description": { + "type": "string" + }, + "title": { + "type": "string" + }, + "location": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "lat": { + "type": "number", + "format": "float" + }, + "lon": { + "type": "number", + "format": "float" + } + } + }, + "duration": { + "type": "integer" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "start_time": { + "type": "string", + "format": "date-time" + }, + "specialist_id": { + "type": "string" + }, + "capacity": { + "type": "integer" + }, + "online_link": { + "type": "string" + } + } + } + } + }, + "required": true + } + } + }, + "/v1/admin/users/:id": { + "delete": { + "description": "Soft-delete user (admin)", + "tags": [ + "Users" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "description": "User ID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "User status set to deleted" + } + } + }, + "get": { + "description": "Get user by ID (admin)", + "tags": [ + "Users" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "description": "User ID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "User details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "reason": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "enum": [ + "active", + "frozen", + "deleted" + ] + }, + "role": { + "type": "string", + "enum": [ + "user", + "bot" + ] + }, + "email": { + "type": "string" + }, + "nickname": { + "type": "string", + "nullable": true + }, + "avatar_url": { + "type": "string", + "nullable": true + }, + "timezone": { + "type": "string", + "nullable": true + }, + "language": { + "type": "string", + "nullable": true + }, + "social_links": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "phone": { + "type": "string", + "nullable": true + }, + "preferences": { + "type": "object", + "nullable": true + }, + "last_login": { + "type": "string", + "format": "date-time" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + } + } + } + } + }, + "put": { + "description": "Update user (admin)", + "tags": [ + "Users" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "description": "User ID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Updated user" } }, "requestBody": { - "required": true, "content": { "application/json": { "schema": { "type": "object", - "required": [ - "action" - ], "properties": { "reason": { "type": "string" }, - "action": { + "status": { "type": "string", "enum": [ - "block", - "unblock" + "active", + "frozen", + "deleted" ] + }, + "role": { + "type": "string", + "enum": [ + "user", + "bot" + ] + } + } + } + } + }, + "required": true + } + } + }, + "/v1/admin/subscriptions": { + "get": { + "description": "List all subscriptions (admin)", + "tags": [ + "Subscriptions" + ], + "parameters": [ + { + "in": "query", + "name": "plan", + "description": "Filter by plan", + "schema": { + "type": "string", + "enum": [ + "monthly", + "quarterly", + "biannual", + "annual" + ] + } + }, + { + "in": "query", + "name": "status", + "description": "Filter by status", + "schema": { + "type": "string", + "enum": [ + "active", + "expired", + "cancelled" + ] + } + }, + { + "in": "query", + "name": "limit", + "description": "Page size", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "description": "Offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of subscriptions", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "active", + "expired", + "cancelled" + ] + }, + "started_at": { + "type": "string", + "format": "date-time" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "user_id": { + "type": "string" + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "plan": { + "type": "string", + "enum": [ + "monthly", + "quarterly", + "biannual", + "annual" + ] + }, + "trial_used": { + "type": "boolean" + } + } + } + } + } + } + } + } + } + }, + "/v1/admin/subscriptions/:id": { + "delete": { + "description": "Delete subscription (admin)", + "tags": [ + "Subscriptions" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "description": "Subscription ID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Subscription deleted" + }, + "404": { + "description": "Subscription not found" + } + } + }, + "get": { + "description": "Get subscription by ID (admin)", + "tags": [ + "Subscriptions" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "description": "Subscription ID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Subscription details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "active", + "expired", + "cancelled" + ] + }, + "started_at": { + "type": "string", + "format": "date-time" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "user_id": { + "type": "string" + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "plan": { + "type": "string", + "enum": [ + "monthly", + "quarterly", + "biannual", + "annual" + ] + }, + "trial_used": { + "type": "boolean" + } + } + } + } + } + }, + "404": { + "description": "Subscription not found" + } + } + }, + "put": { + "description": "Update subscription (admin)", + "tags": [ + "Subscriptions" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "description": "Subscription ID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Updated subscription" + }, + "404": { + "description": "Subscription not found" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "active", + "expired", + "cancelled" + ] + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "New expiration date" + }, + "plan": { + "type": "string", + "enum": [ + "monthly", + "quarterly", + "biannual", + "annual" + ] + }, + "trial_used": { + "type": "boolean" + } + } + } + } + }, + "required": true + } + } + }, + "/v1/admin/tickets": { + "get": { + "description": "List tickets with optional filtering and pagination (admin)", + "tags": [ + "Tickets" + ], + "parameters": [ + { + "in": "query", + "name": "status", + "description": "Filter by status", + "schema": { + "type": "string", + "enum": [ + "open", + "in_progress", + "resolved", + "closed" + ] + } + }, + { + "in": "query", + "name": "assigned_to", + "description": "Filter by assigned admin ID", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "q", + "description": "Search in error_message", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "sort", + "description": "Sort field", + "schema": { + "type": "string", + "enum": [ + "first_seen", + "last_seen", + "status" + ] + } + }, + { + "in": "query", + "name": "order", + "description": "Sort direction", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "in": "query", + "name": "limit", + "description": "Page size", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "description": "Offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of tickets", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "id": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "open", + "in_progress", + "resolved", + "closed" + ] + }, + "context": { + "type": "string" + }, + "stacktrace": { + "type": "string" + }, + "reporter_id": { + "type": "string" + }, + "error_hash": { + "type": "string" + }, + "error_message": { + "type": "string" + }, + "first_seen": { + "type": "string", + "format": "date-time" + }, + "last_seen": { + "type": "string", + "format": "date-time" + }, + "assigned_to": { + "type": "string", + "nullable": true + }, + "resolution_note": { + "type": "string", + "nullable": true + } + } } } } @@ -2532,6 +2868,9 @@ "/v1/admin/users": { "get": { "description": "List all users (admin)", + "tags": [ + "Users" + ], "parameters": [ { "in": "query", @@ -2564,6 +2903,31 @@ "type": "string" } }, + { + "in": "query", + "name": "sort", + "description": "Sort field", + "schema": { + "type": "string", + "enum": [ + "email", + "role", + "created_at" + ] + } + }, + { + "in": "query", + "name": "order", + "description": "Sort direction", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, { "in": "query", "name": "limit", @@ -2579,9 +2943,6 @@ } } ], - "tags": [ - "Users" - ], "responses": { "200": { "description": "Array of users", @@ -2614,18 +2975,14 @@ "bot" ] }, - "created_at": { + "email": { "type": "string", - "format": "date-time" + "format": "email" }, "nickname": { "type": "string", "nullable": true }, - "email": { - "type": "string", - "format": "email" - }, "avatar_url": { "type": "string", "nullable": true @@ -2657,6 +3014,10 @@ "type": "string", "format": "date-time" }, + "created_at": { + "type": "string", + "format": "date-time" + }, "updated_at": { "type": "string", "format": "date-time" @@ -2670,48 +3031,942 @@ } } }, - "/v1/admin/users/:id": { - "delete": { - "description": "Soft-delete user (admin)", + "/v1/admin/calendar/:id": { + "put": { + "description": "Moderate calendar - unfreeze", + "tags": [ + "Moderation" + ], "parameters": [ + { + "in": "path", + "name": "target_type", + "description": "Entity type", + "schema": { + "type": "string", + "enum": [ + "calendar", + "event", + "review", + "user" + ] + }, + "required": true + }, { "in": "path", "name": "id", - "description": "User ID", + "description": "Entity ID", "schema": { "type": "string" }, "required": true } ], + "responses": { + "200": { + "description": "Moderation applied successfully" + }, + "400": { + "description": "Bad request" + }, + "404": { + "description": "Entity not found" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "action" + ], + "properties": { + "reason": { + "type": "string" + }, + "action": { + "type": "string", + "enum": [ + "freeze", + "unfreeze" + ] + } + } + } + } + }, + "required": true + } + } + }, + "/v1/admin/reports/:id": { + "get": { + "description": "Get report by ID (admin)", "tags": [ - "Users" + "Reports" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "description": "Report ID", + "schema": { + "type": "string" + }, + "required": true + } ], "responses": { "200": { - "description": "User status set to deleted" + "description": "Report details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "pending", + "reviewed", + "dismissed" + ] + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "target_type": { + "type": "string", + "enum": [ + "calendar", + "event", + "review" + ] + }, + "target_id": { + "type": "string" + }, + "reporter_id": { + "type": "string" + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resolved_by": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "404": { + "description": "Report not found" } } }, - "get": { - "description": "Get user by ID (admin)", + "put": { + "description": "Update report status (admin)", + "tags": [ + "Reports" + ], "parameters": [ { "in": "path", "name": "id", - "description": "User ID", + "description": "Report ID", "schema": { "type": "string" }, "required": true } ], + "responses": { + "200": { + "description": "Updated report" + }, + "404": { + "description": "Report not found" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "reviewed", + "dismissed" + ] + } + } + } + } + }, + "required": true + } + } + }, + "/v1/admin/users/:id/verification-token": { + "get": { + "description": "Get or create verification token for user (admin)", "tags": [ "Users" ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true + } + ], "responses": { "200": { - "description": "User details", + "description": "Token" + } + } + } + }, + "/v1/admin/subscriptions/stats": { + "get": { + "description": "Get detailed subscription statistics (total, by plan, by status, trial count, ending paid subscriptions)", + "tags": [ + "Statistics" + ], + "responses": { + "200": { + "description": "Subscription statistics object", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ending_paid_subscriptions": { + "type": "array", + "description": "Paid subscriptions expiring within 30 days", + "items": { + "type": "object", + "properties": { + "expires_at": { + "type": "string", + "format": "date-time" + }, + "id": { + "type": "string" + }, + "plan": { + "type": "string" + }, + "user_id": { + "type": "string" + } + } + } + }, + "subscriptions_by_plan": { + "type": "object", + "description": "Number of subscriptions grouped by plan", + "additionalProperties": { + "type": "integer" + } + }, + "subscriptions_by_status": { + "type": "object", + "description": "Number of subscriptions grouped by status", + "additionalProperties": { + "type": "integer" + } + }, + "total_subscriptions": { + "type": "integer", + "description": "Total number of subscriptions" + }, + "trial_subscriptions": { + "type": "integer", + "description": "Number of subscriptions in trial period (trial_used = false)" + } + } + } + } + } + }, + "403": { + "description": "Admin access required" + } + } + } + }, + "/admin/health": { + "get": { + "description": "Admin API health check", + "tags": [ + "Health" + ], + "responses": { + "200": { + "description": "API is healthy", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v1/admin/stats": { + "get": { + "description": "Get admin dashboard statistics", + "tags": [ + "Statistics" + ], + "parameters": [ + { + "in": "query", + "name": "from", + "description": "Start date (ISO8601)", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "to", + "description": "End date (ISO8601)", + "schema": { + "type": "string", + "format": "date-time" + } + } + ], + "responses": { + "200": { + "description": "Statistics object", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "admin_activity": { + "type": "array", + "description": "Recent admin actions (superadmin only)", + "items": { + "type": "object" + } + }, + "avg_ticket_resolution_h": { + "type": "number", + "format": "float", + "description": "Average ticket resolution time in hours" + }, + "calendars_by_day": { + "type": "array", + "description": "Daily calendar creations", + "items": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "string" + } + } + } + }, + "calendars_total": { + "type": "integer", + "description": "Total number of calendars" + }, + "events_by_day": { + "type": "array", + "description": "Daily event creations", + "items": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "string" + } + } + } + }, + "events_total": { + "type": "integer", + "description": "Total number of events" + }, + "pending_users_by_day": { + "type": "array", + "description": "Daily pending user registrations", + "items": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "string" + } + } + } + }, + "pending_users_total": { + "type": "integer", + "description": "Number of users awaiting email verification" + }, + "registrations_by_day": { + "type": "array", + "description": "Daily user registrations", + "items": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "string" + } + } + } + }, + "reports_by_day": { + "type": "array", + "description": "Daily report creations", + "items": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "string" + } + } + } + }, + "reports_dismissed": { + "type": "integer", + "description": "Number of dismissed reports" + }, + "reports_pending": { + "type": "integer", + "description": "Number of pending reports" + }, + "reports_reviewed": { + "type": "integer", + "description": "Number of reviewed reports" + }, + "reports_total": { + "type": "integer", + "description": "Total number of reports (all statuses)" + }, + "reviews_by_day": { + "type": "array", + "description": "Daily review creations", + "items": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "string" + } + } + } + }, + "reviews_total": { + "type": "integer", + "description": "Total number of reviews" + }, + "subscriptions_by_day": { + "type": "array", + "description": "Daily subscription creations", + "items": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "string" + } + } + } + }, + "subscriptions_total": { + "type": "integer", + "description": "Total number of subscriptions" + }, + "tickets_by_day": { + "type": "array", + "description": "Daily ticket creations", + "items": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "string" + } + } + } + }, + "tickets_open": { + "type": "integer", + "description": "Number of open tickets" + }, + "tickets_total": { + "type": "integer", + "description": "Total number of tickets" + }, + "trial_subscriptions": { + "type": "integer", + "description": "Number of trial subscriptions" + }, + "users_total": { + "type": "integer", + "description": "Total number of users" + } + } + } + } + } + }, + "403": { + "description": "Admin access required" + } + } + } + }, + "/v1/admin/admins": { + "get": { + "description": "List all admins (any admin)", + "tags": [ + "Admins" + ], + "parameters": [ + { + "in": "query", + "name": "role", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "status", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "q", + "description": "Search by email or nickname", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "sort", + "description": "Sort field", + "schema": { + "type": "string", + "enum": [ + "email", + "role", + "created_at" + ] + } + }, + { + "in": "query", + "name": "order", + "description": "Sort order", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of admins", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "active", + "blocked" + ] + }, + "role": { + "type": "string", + "enum": [ + "superadmin", + "admin", + "moderator", + "support" + ] + }, + "email": { + "type": "string", + "format": "email" + }, + "nickname": { + "type": "string", + "nullable": true + }, + "avatar_url": { + "type": "string", + "nullable": true + }, + "timezone": { + "type": "string", + "nullable": true + }, + "language": { + "type": "string", + "nullable": true + }, + "phone": { + "type": "string", + "nullable": true + }, + "preferences": { + "type": "object", + "nullable": true + }, + "last_login": { + "type": "string", + "format": "date-time" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + } + } + } + } + } + }, + "post": { + "description": "Create a new admin (superadmin only)", + "tags": [ + "Admins" + ], + "responses": { + "201": { + "description": "Admin created" + }, + "400": { + "description": "Invalid fields" + }, + "403": { + "description": "Only superadmin can create admins" + }, + "409": { + "description": "Email already exists" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "email", + "password", + "role" + ], + "properties": { + "role": { + "type": "string", + "enum": [ + "superadmin", + "admin", + "moderator", + "support" + ] + }, + "password": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + } + } + } + } + }, + "required": true + } + } + }, + "/v1/admin/login": { + "post": { + "description": "Admin login", + "tags": [ + "Auth" + ], + "responses": { + "200": { + "description": "Login successful, returns token and user info" + }, + "401": { + "description": "Invalid credentials" + }, + "403": { + "description": "Insufficient permissions" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "email", + "password" + ], + "properties": { + "password": { + "type": "string", + "format": "password" + }, + "email": { + "type": "string", + "format": "email" + } + } + } + } + }, + "required": true + } + } + }, + "/v1/admin/audit": { + "get": { + "description": "List audit records (superadmin only)", + "tags": [ + "Audit" + ], + "parameters": [ + { + "in": "query", + "name": "admin_id", + "description": "Filter by admin ID", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "action", + "description": "Filter by action", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "date_from", + "description": "Start timestamp (ISO8601)", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "date_to", + "description": "End timestamp (ISO8601)", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "sort", + "description": "Sort field", + "schema": { + "type": "string", + "enum": [ + "timestamp", + "action", + "admin_id", + "entity_type" + ] + } + }, + { + "in": "query", + "name": "order", + "description": "Sort direction", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "in": "query", + "name": "limit", + "description": "Page size", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "description": "Offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of audit records", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "reason": { + "type": "string", + "nullable": true + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "ip": { + "type": "string" + }, + "role": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "admin_id": { + "type": "string" + }, + "action": { + "type": "string" + }, + "entity_type": { + "type": "string" + }, + "entity_id": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "/v1/admin/reviews/:id": { + "get": { + "description": "Get review by ID (admin)", + "tags": [ + "Reviews" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "description": "Review ID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Review details", "content": { "application/json": { "schema": { @@ -2727,29 +3982,339 @@ "status": { "type": "string", "enum": [ - "active", - "frozen", + "visible", + "hidden", "deleted" ] }, - "role": { - "type": "string", - "enum": [ - "user", - "bot" - ] + "comment": { + "type": "string" }, "created_at": { "type": "string", "format": "date-time" }, - "nickname": { + "updated_at": { "type": "string", - "nullable": true + "format": "date-time" + }, + "user_id": { + "type": "string" + }, + "target_type": { + "type": "string", + "enum": [ + "calendar", + "event" + ] + }, + "target_id": { + "type": "string" + }, + "rating": { + "maximum": 5, + "type": "integer", + "minimum": 1 + }, + "likes": { + "type": "integer" + }, + "dislikes": { + "type": "integer" + } + } + } + } + } + } + } + }, + "put": { + "description": "Update review (admin)", + "tags": [ + "Reviews" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "description": "Review ID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Updated review" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reason": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "visible", + "hidden", + "deleted" + ] + }, + "comment": { + "type": "string" + }, + "rating": { + "maximum": 5, + "type": "integer", + "minimum": 1 + } + } + } + } + }, + "required": true + } + } + }, + "/v1/admin/calendars": { + "get": { + "description": "List all calendars (admin)", + "tags": [ + "Admin Calendars" + ], + "parameters": [ + { + "in": "query", + "name": "status", + "description": "Filter by status", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "type", + "description": "Filter by type", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "q", + "description": "Search in title/description", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "sort_by", + "description": "Sort field (title, created_at, updated_at)", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "sort_order", + "description": "Sort direction", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "in": "query", + "name": "limit", + "description": "Page size", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "description": "Offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of calendars", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "active", + "frozen", + "deleted" + ] + }, + "type": { + "type": "string", + "enum": [ + "personal", + "commercial" + ] + }, + "description": { + "type": "string" + }, + "title": { + "type": "string" + }, + "category": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date_time" + }, + "updated_at": { + "type": "string", + "format": "date_time" + }, + "owner_id": { + "type": "string" + }, + "short_name": { + "type": "string" + }, + "color": { + "type": "string" + }, + "image_url": { + "type": "string" + }, + "settings": { + "type": "object" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "confirmation": { + "type": "string" + }, + "rating_avg": { + "type": "number" + }, + "rating_count": { + "type": "integer" + } + } + } + } + } + } + }, + "403": { + "description": "Forbidden" + } + } + } + }, + "/v1/admin/admins/:id": { + "delete": { + "description": "Delete admin (superadmin only)", + "tags": [ + "Admins" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Admin deleted" + }, + "404": { + "description": "Admin not found" + } + } + }, + "get": { + "description": "Get admin by ID (any admin)", + "tags": [ + "Admins" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Admin details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "active", + "blocked" + ] + }, + "role": { + "type": "string", + "enum": [ + "superadmin", + "admin", + "moderator", + "support" + ] }, "email": { "type": "string" }, + "nickname": { + "type": "string", + "nullable": true + }, "avatar_url": { "type": "string", "nullable": true @@ -2762,13 +4327,6 @@ "type": "string", "nullable": true }, - "social_links": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true - }, "phone": { "type": "string", "nullable": true @@ -2781,6 +4339,10 @@ "type": "string", "format": "date-time" }, + "created_at": { + "type": "string", + "format": "date-time" + }, "updated_at": { "type": "string", "format": "date-time" @@ -2789,58 +4351,190 @@ } } } + }, + "404": { + "description": "Admin not found" } } }, "put": { - "description": "Update user (admin)", + "description": "Update admin (superadmin only)", + "tags": [ + "Admins" + ], "parameters": [ { "in": "path", "name": "id", - "description": "User ID", "schema": { "type": "string" }, "required": true } ], - "tags": [ - "Users" - ], "responses": { "200": { - "description": "Updated user" + "description": "Admin updated" + }, + "404": { + "description": "Admin not found" } }, "requestBody": { - "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { - "reason": { + "nickname": { "type": "string" }, - "status": { - "type": "string", - "enum": [ - "active", - "frozen", - "deleted" - ] + "avatar_url": { + "type": "string" }, - "role": { - "type": "string", - "enum": [ - "user", - "bot" - ] + "timezone": { + "type": "string" + }, + "language": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "preferences": { + "type": "object" } } } } + }, + "required": true + } + } + }, + "/v1/admin/nodes/metrics": { + "get": { + "description": "Get historical node metrics", + "tags": [ + "Node Metrics" + ], + "parameters": [ + { + "in": "query", + "name": "node", + "description": "Node name, default current", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "from", + "schema": { + "type": "string", + "format": "date-time" + }, + "required": true + }, + { + "in": "query", + "name": "to", + "schema": { + "type": "string", + "format": "date-time" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Array of node metrics", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "active_sessions": { + "type": "integer", + "description": "Total number of active user and admin sessions" + }, + "io_in": { + "type": "integer", + "description": "Bytes received on sockets since last collection (delta)" + }, + "io_out": { + "type": "integer", + "description": "Bytes sent over sockets since last collection (delta)" + }, + "memory_atom": { + "type": "integer", + "description": "Memory used by atoms (bytes)" + }, + "memory_binary": { + "type": "integer", + "description": "Memory used by binaries (bytes)" + }, + "memory_ets": { + "type": "integer", + "description": "Memory used by ETS tables (bytes)" + }, + "memory_processes": { + "type": "integer", + "description": "Memory used by processes (bytes)" + }, + "memory_total": { + "type": "integer", + "description": "Total memory allocated by the Erlang VM (bytes)" + }, + "mnesia_commits": { + "type": "integer", + "description": "Number of successful Mnesia transactions since node start" + }, + "mnesia_failures": { + "type": "integer", + "description": "Number of failed Mnesia transactions since node start" + }, + "node": { + "type": "string", + "description": "Erlang node name" + }, + "process_count": { + "type": "integer", + "description": "Number of live Erlang processes" + }, + "run_queue": { + "type": "integer", + "description": "Length of the scheduler run queue" + }, + "table_sizes": { + "type": "object", + "description": "Map of Mnesia table names to their sizes (number of records)", + "additionalProperties": { + "type": "integer" + } + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Metric collection timestamp (ISO8601)" + }, + "ws_connections": { + "type": "integer", + "description": "Number of open WebSocket connections" + } + } + } + } + } + } + }, + "400": { + "description": "Missing required parameters" + }, + "403": { + "description": "Admin access required" } } } diff --git a/src/swagger/client-swagger.json b/src/swagger/client-swagger.json index bd249f9..784505f 100644 --- a/src/swagger/client-swagger.json +++ b/src/swagger/client-swagger.json @@ -1,7 +1,7 @@ { "info": { "version": "1.0.0", - "title": "EventHub Client API" + "title": "EventHub User API" }, "paths": { "/health": { @@ -29,9 +29,66 @@ } } }, + "/v1/auth/refresh": { + "post": { + "description": "Refresh user access token using refresh token", + "tags": [ + "Auth" + ], + "responses": { + "200": { + "description": "New user token pair (access + refresh)", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string", + "description": "JWT access token" + }, + "refresh_token": { + "type": "string", + "description": "Refresh token" + } + } + } + } + } + }, + "400": { + "description": "Missing refresh_token field or invalid JSON" + }, + "401": { + "description": "Refresh token expired or not found" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "refresh_token" + ], + "properties": { + "refresh_token": { + "type": "string" + } + } + } + } + }, + "required": true + } + } + }, "/v1/bookings/:id": { "delete": { "description": "Cancel booking (participant)", + "tags": [ + "Bookings" + ], "parameters": [ { "in": "path", @@ -43,9 +100,6 @@ "required": true } ], - "tags": [ - "Bookings" - ], "responses": { "200": { "description": "Booking cancelled" @@ -57,6 +111,9 @@ }, "get": { "description": "Get booking by ID", + "tags": [ + "Bookings" + ], "parameters": [ { "in": "path", @@ -68,9 +125,6 @@ "required": true } ], - "tags": [ - "Bookings" - ], "responses": { "200": { "description": "Booking details", @@ -94,15 +148,15 @@ "type": "string", "format": "date-time" }, - "event_id": { - "type": "string" + "updated_at": { + "type": "string", + "format": "date-time" }, "user_id": { "type": "string" }, - "updated_at": { - "type": "string", - "format": "date-time" + "event_id": { + "type": "string" }, "notes": { "type": "string", @@ -128,6 +182,9 @@ }, "put": { "description": "Confirm or decline a booking (owner)", + "tags": [ + "Bookings" + ], "parameters": [ { "in": "path", @@ -139,9 +196,6 @@ "required": true } ], - "tags": [ - "Bookings" - ], "responses": { "200": { "description": "Booking updated" @@ -154,7 +208,6 @@ } }, "requestBody": { - "required": true, "content": { "application/json": { "schema": { @@ -173,7 +226,8 @@ } } } - } + }, + "required": true } } }, @@ -229,6 +283,10 @@ "type": "string", "format": "date-time" }, + "updated_at": { + "type": "string", + "format": "date-time" + }, "owner_id": { "type": "string" }, @@ -236,23 +294,6 @@ "type": "string", "nullable": true }, - "updated_at": { - "type": "string", - "format": "date-time" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - }, - "rating_avg": { - "type": "number", - "format": "float" - }, - "rating_count": { - "type": "integer" - }, "color": { "type": "string", "nullable": true @@ -265,9 +306,22 @@ "type": "object", "nullable": true }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, "confirmation": { "type": "string", "description": "auto, manual, or {timeout, N}" + }, + "rating_avg": { + "type": "number", + "format": "float" + }, + "rating_count": { + "type": "integer" } } } @@ -297,7 +351,6 @@ } }, "requestBody": { - "required": true, "content": { "application/json": { "schema": { @@ -332,13 +385,17 @@ } } } - } + }, + "required": true } } }, "/v1/calendars/:calendar_id/events": { "get": { "description": "List events of a calendar with optional date range", + "tags": [ + "Events" + ], "parameters": [ { "in": "path", @@ -370,9 +427,6 @@ "required": false } ], - "tags": [ - "Events" - ], "responses": { "200": { "description": "Array of events", @@ -411,17 +465,30 @@ "duration": { "type": "integer" }, - "start_time": { - "type": "string", - "format": "date-time" - }, - "calendar_id": { - "type": "string" - }, "created_at": { "type": "string", "format": "date-time" }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "rating_avg": { + "type": "number", + "format": "float" + }, + "rating_count": { + "type": "integer" + }, + "calendar_id": { + "type": "string" + }, "event_type": { "type": "string", "enum": [ @@ -429,26 +496,20 @@ "recurring" ] }, + "start_time": { + "type": "string", + "format": "date-time" + }, "master_id": { "type": "string", "nullable": true }, - "specialist_id": { - "type": "string", - "nullable": true - }, - "updated_at": { - "type": "string", - "format": "date-time" - }, "is_instance": { "type": "boolean" }, - "tags": { - "type": "array", - "items": { - "type": "string" - } + "specialist_id": { + "type": "string", + "nullable": true }, "capacity": { "type": "integer", @@ -458,13 +519,6 @@ "type": "string", "nullable": true }, - "rating_avg": { - "type": "number", - "format": "float" - }, - "rating_count": { - "type": "integer" - }, "attachments": { "type": "array", "items": { @@ -499,6 +553,9 @@ }, "post": { "description": "Create a new event (single or recurring)", + "tags": [ + "Events" + ], "parameters": [ { "in": "path", @@ -510,9 +567,6 @@ "required": true } ], - "tags": [ - "Events" - ], "responses": { "201": { "description": "Event created" @@ -528,7 +582,6 @@ } }, "requestBody": { - "required": true, "content": { "application/json": { "schema": { @@ -565,16 +618,16 @@ "type": "integer", "description": "Duration in minutes" }, - "start_time": { - "type": "string", - "format": "date-time" - }, "tags": { "type": "array", "items": { "type": "string" } }, + "start_time": { + "type": "string", + "format": "date-time" + }, "capacity": { "type": "integer" }, @@ -588,13 +641,17 @@ } } } - } + }, + "required": true } } }, "/v1/calendars/:calendar_id/view": { "get": { "description": "Get calendar HTML view for a specific month", + "tags": [ + "Calendars" + ], "parameters": [ { "in": "path", @@ -616,9 +673,6 @@ "required": true } ], - "tags": [ - "Calendars" - ], "responses": { "200": { "description": "HTML calendar page", @@ -645,6 +699,9 @@ "/v1/calendars/:id": { "delete": { "description": "Delete calendar", + "tags": [ + "Calendars" + ], "parameters": [ { "in": "path", @@ -656,9 +713,6 @@ "required": true } ], - "tags": [ - "Calendars" - ], "responses": { "200": { "description": "Calendar deleted" @@ -667,6 +721,9 @@ }, "get": { "description": "Get calendar by ID", + "tags": [ + "Calendars" + ], "parameters": [ { "in": "path", @@ -678,9 +735,6 @@ "required": true } ], - "tags": [ - "Calendars" - ], "responses": { "200": { "description": "Calendar details", @@ -712,32 +766,32 @@ "type": "string", "format": "date-time" }, - "owner_id": { - "type": "string" - }, "updated_at": { "type": "string", "format": "date-time" }, + "owner_id": { + "type": "string" + }, "tags": { "type": "array", "items": { "type": "string" } }, - "rating_avg": { - "type": "number", - "format": "float" - }, - "rating_count": { - "type": "integer" - }, "confirmation": { "type": "string", "enum": [ "auto", "manual" ] + }, + "rating_avg": { + "type": "number", + "format": "float" + }, + "rating_count": { + "type": "integer" } } } @@ -748,6 +802,9 @@ }, "put": { "description": "Update calendar", + "tags": [ + "Calendars" + ], "parameters": [ { "in": "path", @@ -759,16 +816,12 @@ "required": true } ], - "tags": [ - "Calendars" - ], "responses": { "200": { "description": "Calendar updated" } }, "requestBody": { - "required": true, "content": { "application/json": { "schema": { @@ -795,13 +848,17 @@ } } } - } + }, + "required": true } } }, "/v1/events/:id": { "delete": { "description": "Delete event", + "tags": [ + "Events" + ], "parameters": [ { "in": "path", @@ -813,9 +870,6 @@ "required": true } ], - "tags": [ - "Events" - ], "responses": { "200": { "description": "Event deleted" @@ -830,6 +884,9 @@ }, "get": { "description": "Get event by ID", + "tags": [ + "Events" + ], "parameters": [ { "in": "path", @@ -841,9 +898,6 @@ "required": true } ], - "tags": [ - "Events" - ], "responses": { "200": { "description": "Event details", @@ -880,17 +934,30 @@ "duration": { "type": "integer" }, - "start_time": { - "type": "string", - "format": "date-time" - }, - "calendar_id": { - "type": "string" - }, "created_at": { "type": "string", "format": "date-time" }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "rating_avg": { + "type": "number", + "format": "float" + }, + "rating_count": { + "type": "integer" + }, + "calendar_id": { + "type": "string" + }, "event_type": { "type": "string", "enum": [ @@ -898,26 +965,20 @@ "recurring" ] }, + "start_time": { + "type": "string", + "format": "date-time" + }, "master_id": { "type": "string", "nullable": true }, - "specialist_id": { - "type": "string", - "nullable": true - }, - "updated_at": { - "type": "string", - "format": "date-time" - }, "is_instance": { "type": "boolean" }, - "tags": { - "type": "array", - "items": { - "type": "string" - } + "specialist_id": { + "type": "string", + "nullable": true }, "capacity": { "type": "integer", @@ -927,13 +988,6 @@ "type": "string", "nullable": true }, - "rating_avg": { - "type": "number", - "format": "float" - }, - "rating_count": { - "type": "integer" - }, "attachments": { "type": "array", "items": { @@ -967,6 +1021,9 @@ }, "put": { "description": "Update event", + "tags": [ + "Events" + ], "parameters": [ { "in": "path", @@ -978,9 +1035,6 @@ "required": true } ], - "tags": [ - "Events" - ], "responses": { "200": { "description": "Event updated" @@ -996,7 +1050,6 @@ } }, "requestBody": { - "required": true, "content": { "application/json": { "schema": { @@ -1035,6 +1088,12 @@ "duration": { "type": "integer" }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, "start_time": { "type": "string", "format": "date-time" @@ -1042,12 +1101,6 @@ "specialist_id": { "type": "string" }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - }, "capacity": { "type": "integer" }, @@ -1057,13 +1110,17 @@ } } } - } + }, + "required": true } } }, "/v1/events/:id/bookings": { "get": { "description": "List bookings for an event (owner only)", + "tags": [ + "Bookings" + ], "parameters": [ { "in": "path", @@ -1075,9 +1132,6 @@ "required": true } ], - "tags": [ - "Bookings" - ], "responses": { "200": { "description": "Array of bookings", @@ -1103,15 +1157,15 @@ "type": "string", "format": "date-time" }, - "event_id": { - "type": "string" + "updated_at": { + "type": "string", + "format": "date-time" }, "user_id": { "type": "string" }, - "updated_at": { - "type": "string", - "format": "date-time" + "event_id": { + "type": "string" }, "notes": { "type": "string", @@ -1141,6 +1195,9 @@ }, "post": { "description": "Create a booking for an event", + "tags": [ + "Bookings" + ], "parameters": [ { "in": "path", @@ -1152,9 +1209,6 @@ "required": true } ], - "tags": [ - "Bookings" - ], "responses": { "201": { "description": "Booking created", @@ -1178,15 +1232,15 @@ "type": "string", "format": "date-time" }, - "event_id": { - "type": "string" + "updated_at": { + "type": "string", + "format": "date-time" }, "user_id": { "type": "string" }, - "updated_at": { - "type": "string", - "format": "date-time" + "event_id": { + "type": "string" }, "notes": { "type": "string", @@ -1223,6 +1277,9 @@ "/v1/events/:id/occurrences": { "get": { "description": "Get event occurrences in a date range", + "tags": [ + "Events" + ], "parameters": [ { "in": "path", @@ -1254,9 +1311,6 @@ "required": true } ], - "tags": [ - "Events" - ], "responses": { "200": { "description": "Array of occurrences", @@ -1316,6 +1370,9 @@ "/v1/events/:id/occurrences/:start_time": { "delete": { "description": "Cancel a specific occurrence", + "tags": [ + "Events" + ], "parameters": [ { "in": "path", @@ -1337,9 +1394,6 @@ "required": true } ], - "tags": [ - "Events" - ], "responses": { "200": { "description": "Occurrence cancelled" @@ -1377,7 +1431,6 @@ } }, "requestBody": { - "required": true, "content": { "application/json": { "schema": { @@ -1398,56 +1451,20 @@ } } } - } - } - } - }, - "/v1/refresh": { - "post": { - "description": "Refresh access token using refresh token", - "tags": [ - "Auth" - ], - "responses": { - "200": { - "description": "New token pair (access + refresh)" }, - "400": { - "description": "Missing refresh_token field or invalid JSON" - }, - "401": { - "description": "Refresh token expired or not found" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "refresh_token" - ], - "properties": { - "refresh_token": { - "type": "string" - } - } - } - } - } + "required": true } } }, "/v1/register": { "post": { - "description": "Register a new user", + "description": "Register a new user (account will be pending until email verified)", "tags": [ "Auth" ], "responses": { "201": { - "description": "User registered, returns token and user info" + "description": "User registered successfully (pending verification)" }, "400": { "description": "Missing email or password, or invalid JSON" @@ -1457,7 +1474,6 @@ } }, "requestBody": { - "required": true, "content": { "application/json": { "schema": { @@ -1478,13 +1494,17 @@ } } } - } + }, + "required": true } } }, "/v1/reports": { "get": { "description": "List reports (admin/moderator only)", + "tags": [ + "Reports" + ], "parameters": [ { "in": "query", @@ -1503,9 +1523,6 @@ } } ], - "tags": [ - "Reports" - ], "responses": { "200": { "description": "Array of reports", @@ -1534,9 +1551,6 @@ "type": "string", "format": "date-time" }, - "reporter_id": { - "type": "string" - }, "target_type": { "type": "string", "enum": [ @@ -1548,6 +1562,9 @@ "target_id": { "type": "string" }, + "reporter_id": { + "type": "string" + }, "resolved_at": { "type": "string", "format": "date-time", @@ -1585,7 +1602,6 @@ } }, "requestBody": { - "required": true, "content": { "application/json": { "schema": { @@ -1612,13 +1628,17 @@ } } } - } + }, + "required": true } } }, "/v1/reviews": { "get": { "description": "List reviews for a target", + "tags": [ + "Reviews" + ], "parameters": [ { "in": "query", @@ -1643,9 +1663,6 @@ "required": true } ], - "tags": [ - "Reviews" - ], "responses": { "200": { "description": "Array of reviews", @@ -1678,13 +1695,13 @@ "type": "string", "format": "date-time" }, - "user_id": { - "type": "string" - }, "updated_at": { "type": "string", "format": "date-time" }, + "user_id": { + "type": "string" + }, "target_type": { "type": "string", "enum": [ @@ -1740,7 +1757,6 @@ } }, "requestBody": { - "required": true, "content": { "application/json": { "schema": { @@ -1773,13 +1789,17 @@ } } } - } + }, + "required": true } } }, "/v1/reviews/:id": { "delete": { "description": "Delete review", + "tags": [ + "Reviews" + ], "parameters": [ { "in": "path", @@ -1791,9 +1811,6 @@ "required": true } ], - "tags": [ - "Reviews" - ], "responses": { "200": { "description": "Review deleted" @@ -1808,6 +1825,9 @@ }, "get": { "description": "Get review by ID", + "tags": [ + "Reviews" + ], "parameters": [ { "in": "path", @@ -1819,9 +1839,6 @@ "required": true } ], - "tags": [ - "Reviews" - ], "responses": { "200": { "description": "Review details", @@ -1852,13 +1869,13 @@ "type": "string", "format": "date-time" }, - "user_id": { - "type": "string" - }, "updated_at": { "type": "string", "format": "date-time" }, + "user_id": { + "type": "string" + }, "target_type": { "type": "string", "enum": [ @@ -1895,6 +1912,9 @@ }, "put": { "description": "Update review", + "tags": [ + "Reviews" + ], "parameters": [ { "in": "path", @@ -1906,9 +1926,6 @@ "required": true } ], - "tags": [ - "Reviews" - ], "responses": { "200": { "description": "Review updated" @@ -1924,7 +1941,6 @@ } }, "requestBody": { - "required": true, "content": { "application/json": { "schema": { @@ -1941,13 +1957,17 @@ } } } - } + }, + "required": true } } }, "/v1/search": { "get": { "description": "Search calendars and events", + "tags": [ + "Search" + ], "parameters": [ { "in": "query", @@ -2063,9 +2083,6 @@ } } ], - "tags": [ - "Search" - ], "responses": { "200": { "description": "Search results with pagination", @@ -2132,6 +2149,21 @@ "type": "string", "format": "date-time" }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "user_id": { + "type": "string" + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, "plan": { "type": "string", "enum": [ @@ -2141,23 +2173,8 @@ "annual" ] }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "user_id": { - "type": "string" - }, - "updated_at": { - "type": "string", - "format": "date-time" - }, "trial_used": { "type": "boolean" - }, - "expires_at": { - "type": "string", - "format": "date-time" } } } @@ -2189,7 +2206,6 @@ } }, "requestBody": { - "required": true, "content": { "application/json": { "schema": { @@ -2198,6 +2214,13 @@ "action" ], "properties": { + "action": { + "type": "string", + "enum": [ + "start_trial", + "activate" + ] + }, "plan": { "type": "string", "enum": [ @@ -2207,13 +2230,6 @@ "annual" ] }, - "action": { - "type": "string", - "enum": [ - "start_trial", - "activate" - ] - }, "payment_info": { "type": "object", "description": "Payment information" @@ -2221,13 +2237,17 @@ } } } - } + }, + "required": true } } }, "/v1/tickets": { "get": { "description": "List tickets (admin sees all, user sees own)", + "tags": [ + "Tickets" + ], "parameters": [ { "in": "query", @@ -2246,9 +2266,6 @@ } } ], - "tags": [ - "Tickets" - ], "responses": { "200": { "description": "Array of tickets", @@ -2333,7 +2350,6 @@ } }, "requestBody": { - "required": true, "content": { "application/json": { "schema": { @@ -2354,13 +2370,17 @@ } } } - } + }, + "required": true } } }, "/v1/tickets/:id": { "get": { "description": "Get a user's own ticket by ID", + "tags": [ + "Tickets" + ], "parameters": [ { "in": "path", @@ -2372,9 +2392,6 @@ "required": true } ], - "tags": [ - "Tickets" - ], "responses": { "200": { "description": "Ticket details", @@ -2474,15 +2491,15 @@ "type": "string", "format": "date-time" }, - "event_id": { - "type": "string" + "updated_at": { + "type": "string", + "format": "date-time" }, "user_id": { "type": "string" }, - "updated_at": { - "type": "string", - "format": "date-time" + "event_id": { + "type": "string" }, "notes": { "type": "string", @@ -2544,18 +2561,14 @@ "bot" ] }, - "created_at": { + "email": { "type": "string", - "format": "date-time" + "format": "email" }, "nickname": { "type": "string", "nullable": true }, - "email": { - "type": "string", - "format": "email" - }, "avatar_url": { "type": "string", "nullable": true @@ -2587,6 +2600,10 @@ "type": "string", "format": "date-time" }, + "created_at": { + "type": "string", + "format": "date-time" + }, "updated_at": { "type": "string", "format": "date-time" @@ -2643,13 +2660,13 @@ "type": "string", "format": "date-time" }, - "user_id": { - "type": "string" - }, "updated_at": { "type": "string", "format": "date-time" }, + "user_id": { + "type": "string" + }, "target_type": { "type": "string", "enum": [ @@ -2682,6 +2699,47 @@ } } } + }, + "/v1/verify": { + "post": { + "description": "Verify user account using email verification token", + "tags": [ + "Auth" + ], + "responses": { + "200": { + "description": "Account verified successfully" + }, + "400": { + "description": "Missing token field or invalid JSON" + }, + "404": { + "description": "Token not found" + }, + "410": { + "description": "Token expired" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "string", + "description": "Verification token received by email" + } + } + } + } + }, + "required": true + } + } } }, "openapi": "3.0.3",