1396 lines
40 KiB
JSON
1396 lines
40 KiB
JSON
{
|
|
"openapi": "3.0.3",
|
|
"info": {
|
|
"title": "b2b API",
|
|
"description": "Authentication, user management, and repository time tracking API",
|
|
"version": "1.0.0",
|
|
"contact": {
|
|
"name": "API Support",
|
|
"email": "support@example.com"
|
|
}
|
|
},
|
|
"servers": [
|
|
{
|
|
"url": "http://localhost:3000",
|
|
"description": "Development server"
|
|
}
|
|
],
|
|
"tags": [
|
|
{
|
|
"name": "Health",
|
|
"description": "Health check endpoints"
|
|
},
|
|
{
|
|
"name": "Auth",
|
|
"description": "Authentication endpoints (under /api/v1/public/auth)"
|
|
},
|
|
{
|
|
"name": "Languages",
|
|
"description": "Language and translation endpoints"
|
|
},
|
|
{
|
|
"name": "Repo",
|
|
"description": "Repository time tracking data endpoints (under /api/v1/restricted/repo, requires authentication)"
|
|
},
|
|
{
|
|
"name": "Admin",
|
|
"description": "Admin-only endpoints"
|
|
},
|
|
{
|
|
"name": "Settings",
|
|
"description": "Application settings and configuration endpoints"
|
|
}
|
|
],
|
|
"paths": {
|
|
"/health": {
|
|
"get": {
|
|
"tags": ["Health"],
|
|
"summary": "Health check",
|
|
"description": "Returns the health status of the application",
|
|
"operationId": "getHealth",
|
|
"responses": {
|
|
"200": {
|
|
"description": "Successful response",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"status": {
|
|
"type": "string",
|
|
"example": "ok"
|
|
},
|
|
"app": {
|
|
"type": "string",
|
|
"example": "b2b"
|
|
},
|
|
"version": {
|
|
"type": "string",
|
|
"example": "1.0.0"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/langs": {
|
|
"get": {
|
|
"tags": ["Languages"],
|
|
"summary": "Get active languages",
|
|
"description": "Returns a list of all active languages",
|
|
"operationId": "getLanguages",
|
|
"responses": {
|
|
"200": {
|
|
"description": "Successful response",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/Language"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/translations": {
|
|
"get": {
|
|
"tags": ["Languages"],
|
|
"summary": "Get translations",
|
|
"description": "Returns translations from cache. Supports filtering by lang_id, scope, and components.",
|
|
"operationId": "getTranslations",
|
|
"parameters": [
|
|
{
|
|
"name": "lang_id",
|
|
"in": "query",
|
|
"description": "Filter by language ID",
|
|
"required": false,
|
|
"schema": {
|
|
"type": "integer"
|
|
}
|
|
},
|
|
{
|
|
"name": "scope",
|
|
"in": "query",
|
|
"description": "Filter by scope (e.g., 'be', 'frontend')",
|
|
"required": false,
|
|
"schema": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
{
|
|
"name": "components",
|
|
"in": "query",
|
|
"description": "Filter by component name",
|
|
"required": false,
|
|
"schema": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Successful response",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"status": {
|
|
"type": "string",
|
|
"example": "success"
|
|
},
|
|
"translations": {
|
|
"type": "object",
|
|
"description": "Translation data keyed by language ID, scope, component, and key"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Invalid request parameters",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/translations/reload": {
|
|
"get": {
|
|
"tags": ["Languages"],
|
|
"summary": "Reload translations",
|
|
"description": "Reloads translations from the database into the cache",
|
|
"operationId": "reloadTranslations",
|
|
"responses": {
|
|
"200": {
|
|
"description": "Translations reloaded successfully",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"status": {
|
|
"type": "string",
|
|
"example": "success"
|
|
},
|
|
"message": {
|
|
"type": "string",
|
|
"example": "Translations reloaded successfully"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Failed to reload translations",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/public/auth/login": {
|
|
"post": {
|
|
"tags": ["Auth"],
|
|
"summary": "User login",
|
|
"description": "Authenticate a user with email and password. Sets HTTPOnly cookies (access_token, refresh_token, is_authenticated) on success.",
|
|
"operationId": "login",
|
|
"requestBody": {
|
|
"required": true,
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/LoginRequest"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Login successful",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/AuthResponse"
|
|
}
|
|
}
|
|
},
|
|
"headers": {
|
|
"Set-Cookie": {
|
|
"schema": {
|
|
"type": "string"
|
|
},
|
|
"description": "HTTPOnly cookies: access_token, refresh_token (opaque), is_authenticated (non-HTTPOnly flag)"
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Invalid request body or missing fields",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Invalid credentials",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"403": {
|
|
"description": "Account inactive or email not verified",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/public/auth/register": {
|
|
"post": {
|
|
"tags": ["Auth"],
|
|
"summary": "User registration",
|
|
"description": "Register a new user account. Sends a verification email. first_name and last_name are required.",
|
|
"operationId": "register",
|
|
"requestBody": {
|
|
"required": true,
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/RegisterRequest"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"responses": {
|
|
"201": {
|
|
"description": "Registration successful",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"message": {
|
|
"type": "string",
|
|
"example": "registration successful, please verify your email"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Invalid request, missing required fields, or invalid password format",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"409": {
|
|
"description": "Email already exists",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/public/auth/complete-registration": {
|
|
"post": {
|
|
"tags": ["Auth"],
|
|
"summary": "Complete registration",
|
|
"description": "Complete registration after email verification using the token sent by email. Sets auth cookies on success.",
|
|
"operationId": "completeRegistration",
|
|
"requestBody": {
|
|
"required": true,
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/CompleteRegistrationRequest"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"responses": {
|
|
"201": {
|
|
"description": "Registration completed successfully",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/AuthResponse"
|
|
}
|
|
}
|
|
},
|
|
"headers": {
|
|
"Set-Cookie": {
|
|
"schema": {
|
|
"type": "string"
|
|
},
|
|
"description": "HTTPOnly cookies: access_token, refresh_token, is_authenticated"
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Invalid or expired token",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/public/auth/forgot-password": {
|
|
"post": {
|
|
"tags": ["Auth"],
|
|
"summary": "Request password reset",
|
|
"description": "Request a password reset email. Always returns success to prevent email enumeration.",
|
|
"operationId": "forgotPassword",
|
|
"requestBody": {
|
|
"required": true,
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "object",
|
|
"required": ["email"],
|
|
"properties": {
|
|
"email": {
|
|
"type": "string",
|
|
"format": "email",
|
|
"description": "User's email address"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Password reset email sent if account exists",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"message": {
|
|
"type": "string",
|
|
"example": "if an account with that email exists, a password reset link has been sent"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Invalid request or missing email",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/public/auth/reset-password": {
|
|
"post": {
|
|
"tags": ["Auth"],
|
|
"summary": "Reset password",
|
|
"description": "Reset password using reset token from email. Also revokes all existing refresh tokens for the user.",
|
|
"operationId": "resetPassword",
|
|
"requestBody": {
|
|
"required": true,
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ResetPasswordRequest"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Password reset successfully",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"message": {
|
|
"type": "string",
|
|
"example": "password reset successfully"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Invalid or expired token, or invalid password format",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/public/auth/logout": {
|
|
"post": {
|
|
"tags": ["Auth"],
|
|
"summary": "User logout",
|
|
"description": "Revokes the refresh token from the database and clears all authentication cookies.",
|
|
"operationId": "logout",
|
|
"responses": {
|
|
"200": {
|
|
"description": "Logout successful",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"message": {
|
|
"type": "string",
|
|
"example": "logged out successfully"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/public/auth/refresh": {
|
|
"post": {
|
|
"tags": ["Auth"],
|
|
"summary": "Refresh access token",
|
|
"description": "Get a new access token using the refresh token. The refresh token is read from the HTTPOnly cookie first, then from the request body as fallback. Rotates the refresh token on success.",
|
|
"operationId": "refreshToken",
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"refresh_token": {
|
|
"type": "string",
|
|
"description": "Opaque refresh token (fallback if cookie not available)"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Token refreshed successfully",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/AuthResponse"
|
|
}
|
|
}
|
|
},
|
|
"headers": {
|
|
"Set-Cookie": {
|
|
"schema": {
|
|
"type": "string"
|
|
},
|
|
"description": "Rotated HTTPOnly cookies: access_token, refresh_token, is_authenticated"
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Refresh token required",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Invalid or expired refresh token",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/public/auth/me": {
|
|
"get": {
|
|
"tags": ["Auth"],
|
|
"summary": "Get current user",
|
|
"description": "Returns the currently authenticated user's session information. Requires authentication via cookie.",
|
|
"operationId": "getMe",
|
|
"security": [
|
|
{
|
|
"CookieAuth": []
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Current user info",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"user": {
|
|
"$ref": "#/components/schemas/UserSession"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Not authenticated",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/public/auth/google": {
|
|
"get": {
|
|
"tags": ["Auth"],
|
|
"summary": "Google OAuth2 login",
|
|
"description": "Redirects the user to Google's OAuth2 consent page. Sets a short-lived oauth_state cookie for CSRF protection.",
|
|
"operationId": "googleLogin",
|
|
"responses": {
|
|
"302": {
|
|
"description": "Redirect to Google OAuth2 consent page",
|
|
"headers": {
|
|
"Location": {
|
|
"schema": {
|
|
"type": "string"
|
|
},
|
|
"description": "Google OAuth2 authorization URL"
|
|
},
|
|
"Set-Cookie": {
|
|
"schema": {
|
|
"type": "string"
|
|
},
|
|
"description": "HTTPOnly oauth_state cookie for CSRF protection (10 min expiry)"
|
|
}
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Failed to generate OAuth state",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/public/auth/google/callback": {
|
|
"get": {
|
|
"tags": ["Auth"],
|
|
"summary": "Google OAuth2 callback",
|
|
"description": "Handles the OAuth2 callback from Google. Validates state, exchanges code for tokens, creates or updates user, sets auth cookies, and redirects to the app.",
|
|
"operationId": "googleCallback",
|
|
"parameters": [
|
|
{
|
|
"name": "code",
|
|
"in": "query",
|
|
"description": "Authorization code from Google",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
{
|
|
"name": "state",
|
|
"in": "query",
|
|
"description": "State token for CSRF validation",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"302": {
|
|
"description": "Redirect to app after successful authentication",
|
|
"headers": {
|
|
"Location": {
|
|
"schema": {
|
|
"type": "string"
|
|
},
|
|
"description": "Redirect to /{lang} (user's preferred language)"
|
|
},
|
|
"Set-Cookie": {
|
|
"schema": {
|
|
"type": "string"
|
|
},
|
|
"description": "HTTPOnly cookies: access_token, refresh_token, is_authenticated"
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Invalid state (CSRF) or missing authorization code",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Google OAuth callback processing error",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/restricted/repo/get-repos": {
|
|
"get": {
|
|
"tags": ["Repo"],
|
|
"summary": "Get accessible repositories",
|
|
"description": "Returns a list of repository IDs that the authenticated user has access to.",
|
|
"operationId": "getRepos",
|
|
"security": [
|
|
{
|
|
"CookieAuth": []
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "List of repository IDs",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "integer",
|
|
"format": "uint"
|
|
},
|
|
"example": [1, 2, 5]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Invalid user session",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Not authenticated",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/restricted/repo/get-years": {
|
|
"get": {
|
|
"tags": ["Repo"],
|
|
"summary": "Get available years for a repository",
|
|
"description": "Returns a list of years for which tracked time data exists in the given repository. User must have access to the repository.",
|
|
"operationId": "getYears",
|
|
"security": [
|
|
{
|
|
"CookieAuth": []
|
|
}
|
|
],
|
|
"parameters": [
|
|
{
|
|
"name": "repoID",
|
|
"in": "query",
|
|
"description": "Repository ID",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "integer",
|
|
"format": "uint"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "List of years with tracked time data",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "integer",
|
|
"format": "uint"
|
|
},
|
|
"example": [2023, 2024, 2025]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Invalid repoID parameter or user does not have access to the repository",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Not authenticated",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/restricted/repo/get-quarters": {
|
|
"get": {
|
|
"tags": ["Repo"],
|
|
"summary": "Get quarterly time data for a repository",
|
|
"description": "Returns time tracked per quarter for the given repository and year. All 4 quarters are returned; quarters with no data have time=0. User must have access to the repository.",
|
|
"operationId": "getQuarters",
|
|
"security": [
|
|
{
|
|
"CookieAuth": []
|
|
}
|
|
],
|
|
"parameters": [
|
|
{
|
|
"name": "repoID",
|
|
"in": "query",
|
|
"description": "Repository ID",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "integer",
|
|
"format": "uint"
|
|
}
|
|
},
|
|
{
|
|
"name": "year",
|
|
"in": "query",
|
|
"description": "Year to retrieve quarterly data for",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "integer",
|
|
"format": "uint",
|
|
"example": 2024
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Quarterly time data",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/QuarterData"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Invalid repoID or year parameter, or user does not have access to the repository",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Not authenticated",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/restricted/repo/get-issues": {
|
|
"get": {
|
|
"tags": ["Repo"],
|
|
"summary": "Get issues with time summaries",
|
|
"description": "Returns a paginated list of issues with time tracking summaries for the given repository, year, and quarter. User must have access to the repository.",
|
|
"operationId": "getIssues",
|
|
"security": [
|
|
{
|
|
"CookieAuth": []
|
|
}
|
|
],
|
|
"parameters": [
|
|
{
|
|
"name": "repoID",
|
|
"in": "query",
|
|
"description": "Repository ID",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "integer",
|
|
"format": "uint"
|
|
}
|
|
},
|
|
{
|
|
"name": "year",
|
|
"in": "query",
|
|
"description": "Year to filter issues by",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "integer",
|
|
"format": "uint",
|
|
"example": 2024
|
|
}
|
|
},
|
|
{
|
|
"name": "quarter",
|
|
"in": "query",
|
|
"description": "Quarter number (1-4) to filter issues by",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "integer",
|
|
"format": "uint",
|
|
"minimum": 1,
|
|
"maximum": 4,
|
|
"example": 2
|
|
}
|
|
},
|
|
{
|
|
"name": "page_number",
|
|
"in": "query",
|
|
"description": "Page number for pagination (1-based)",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "integer",
|
|
"format": "uint",
|
|
"example": 1
|
|
}
|
|
},
|
|
{
|
|
"name": "elements_per_page",
|
|
"in": "query",
|
|
"description": "Number of items per page",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "integer",
|
|
"format": "uint",
|
|
"example": 30
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Paginated list of issues with time summaries",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/PaginatedIssues"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Invalid parameters or user does not have access to the repository",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Not authenticated",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Error"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/settings": {
|
|
"get": {
|
|
"tags": ["Settings"],
|
|
"summary": "Get application settings",
|
|
"description": "Returns public application settings and configuration",
|
|
"operationId": "getSettings",
|
|
"responses": {
|
|
"200": {
|
|
"description": "Settings retrieved successfully",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/SettingsResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"components": {
|
|
"schemas": {
|
|
"LoginRequest": {
|
|
"type": "object",
|
|
"required": ["email", "password"],
|
|
"properties": {
|
|
"email": {
|
|
"type": "string",
|
|
"format": "email",
|
|
"description": "User's email address"
|
|
},
|
|
"password": {
|
|
"type": "string",
|
|
"format": "password",
|
|
"description": "User's password"
|
|
}
|
|
}
|
|
},
|
|
"RegisterRequest": {
|
|
"type": "object",
|
|
"required": [
|
|
"email",
|
|
"password",
|
|
"confirm_password",
|
|
"first_name",
|
|
"last_name"
|
|
],
|
|
"properties": {
|
|
"email": {
|
|
"type": "string",
|
|
"format": "email",
|
|
"description": "User's email address"
|
|
},
|
|
"password": {
|
|
"type": "string",
|
|
"format": "password",
|
|
"description": "User's password (must meet complexity requirements: min 8 chars, uppercase, lowercase, digit)"
|
|
},
|
|
"confirm_password": {
|
|
"type": "string",
|
|
"format": "password",
|
|
"description": "Password confirmation"
|
|
},
|
|
"first_name": {
|
|
"type": "string",
|
|
"description": "User's first name (required)"
|
|
},
|
|
"last_name": {
|
|
"type": "string",
|
|
"description": "User's last name (required)"
|
|
},
|
|
"lang": {
|
|
"type": "string",
|
|
"description": "User's preferred language ISO code (e.g., 'en', 'pl', 'cs')"
|
|
}
|
|
}
|
|
},
|
|
"CompleteRegistrationRequest": {
|
|
"type": "object",
|
|
"required": ["token"],
|
|
"properties": {
|
|
"token": {
|
|
"type": "string",
|
|
"description": "Email verification token received via email"
|
|
}
|
|
}
|
|
},
|
|
"ResetPasswordRequest": {
|
|
"type": "object",
|
|
"required": ["token", "password"],
|
|
"properties": {
|
|
"token": {
|
|
"type": "string",
|
|
"description": "Password reset token received via email"
|
|
},
|
|
"password": {
|
|
"type": "string",
|
|
"format": "password",
|
|
"description": "New password (must meet complexity requirements)"
|
|
}
|
|
}
|
|
},
|
|
"AuthResponse": {
|
|
"type": "object",
|
|
"properties": {
|
|
"access_token": {
|
|
"type": "string",
|
|
"description": "JWT access token"
|
|
},
|
|
"token_type": {
|
|
"type": "string",
|
|
"example": "Bearer"
|
|
},
|
|
"expires_in": {
|
|
"type": "integer",
|
|
"description": "Access token expiration in seconds"
|
|
},
|
|
"user": {
|
|
"$ref": "#/components/schemas/UserSession"
|
|
}
|
|
}
|
|
},
|
|
"UserSession": {
|
|
"type": "object",
|
|
"properties": {
|
|
"user_id": {
|
|
"type": "integer",
|
|
"format": "uint",
|
|
"description": "User ID"
|
|
},
|
|
"email": {
|
|
"type": "string",
|
|
"format": "email"
|
|
},
|
|
"username": {
|
|
"type": "string"
|
|
},
|
|
"role": {
|
|
"type": "string",
|
|
"enum": ["user", "admin"],
|
|
"description": "User role"
|
|
},
|
|
"first_name": {
|
|
"type": "string"
|
|
},
|
|
"last_name": {
|
|
"type": "string"
|
|
},
|
|
"lang": {
|
|
"type": "string",
|
|
"description": "User's preferred language ISO code (e.g., 'en', 'pl', 'cs')"
|
|
}
|
|
}
|
|
},
|
|
"Error": {
|
|
"type": "object",
|
|
"properties": {
|
|
"error": {
|
|
"type": "string",
|
|
"description": "Translated error message"
|
|
}
|
|
}
|
|
},
|
|
"Language": {
|
|
"type": "object",
|
|
"properties": {
|
|
"id": {
|
|
"type": "integer",
|
|
"format": "uint64",
|
|
"description": "Language ID"
|
|
},
|
|
"name": {
|
|
"type": "string",
|
|
"description": "Language name"
|
|
},
|
|
"iso_code": {
|
|
"type": "string",
|
|
"description": "ISO 639-1 code (e.g., 'en', 'pl')"
|
|
},
|
|
"lang_code": {
|
|
"type": "string",
|
|
"description": "Full language code (e.g., 'en-US', 'pl-PL')"
|
|
},
|
|
"date_format": {
|
|
"type": "string",
|
|
"description": "Date format string"
|
|
},
|
|
"date_format_short": {
|
|
"type": "string",
|
|
"description": "Short date format string"
|
|
},
|
|
"rtl": {
|
|
"type": "boolean",
|
|
"description": "Right-to-left language"
|
|
},
|
|
"is_default": {
|
|
"type": "boolean",
|
|
"description": "Is default language"
|
|
},
|
|
"active": {
|
|
"type": "boolean",
|
|
"description": "Is active"
|
|
},
|
|
"flag": {
|
|
"type": "string",
|
|
"description": "Flag emoji or code"
|
|
}
|
|
}
|
|
},
|
|
"QuarterData": {
|
|
"type": "object",
|
|
"description": "Time tracked in a specific quarter",
|
|
"properties": {
|
|
"time": {
|
|
"type": "number",
|
|
"format": "double",
|
|
"description": "Total hours tracked in this quarter"
|
|
},
|
|
"quarter": {
|
|
"type": "string",
|
|
"description": "Quarter identifier in format YYYY_QN (e.g., '2024_Q1')",
|
|
"example": "2024_Q1"
|
|
}
|
|
}
|
|
},
|
|
"IssueTimeSummary": {
|
|
"type": "object",
|
|
"description": "Time tracking summary for a single issue",
|
|
"properties": {
|
|
"issue_id": {
|
|
"type": "integer",
|
|
"format": "uint",
|
|
"description": "Issue ID"
|
|
},
|
|
"issue_name": {
|
|
"type": "string",
|
|
"description": "Issue title/name"
|
|
},
|
|
"user_id": {
|
|
"type": "integer",
|
|
"format": "uint",
|
|
"description": "ID of the user who tracked time"
|
|
},
|
|
"initials": {
|
|
"type": "string",
|
|
"description": "Abbreviated initials of the user (e.g., 'J.D.')"
|
|
},
|
|
"created_date": {
|
|
"type": "string",
|
|
"format": "date",
|
|
"description": "Date when time was tracked"
|
|
},
|
|
"total_hours_spent": {
|
|
"type": "number",
|
|
"format": "double",
|
|
"description": "Total hours spent on this issue on the given date (rounded to 2 decimal places)"
|
|
}
|
|
}
|
|
},
|
|
"PaginatedIssues": {
|
|
"type": "object",
|
|
"description": "Paginated list of issue time summaries",
|
|
"properties": {
|
|
"items": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/IssueTimeSummary"
|
|
},
|
|
"description": "List of issue time summaries for the current page"
|
|
},
|
|
"items_count": {
|
|
"type": "integer",
|
|
"format": "uint",
|
|
"description": "Total number of items across all pages",
|
|
"example": 56
|
|
}
|
|
}
|
|
},
|
|
"SettingsResponse": {
|
|
"type": "object",
|
|
"properties": {
|
|
"app": {
|
|
"$ref": "#/components/schemas/AppSettings"
|
|
},
|
|
"server": {
|
|
"$ref": "#/components/schemas/ServerSettings"
|
|
},
|
|
"auth": {
|
|
"$ref": "#/components/schemas/AuthSettings"
|
|
},
|
|
"features": {
|
|
"$ref": "#/components/schemas/FeatureFlags"
|
|
},
|
|
"version": {
|
|
"$ref": "#/components/schemas/VersionInfo"
|
|
}
|
|
}
|
|
},
|
|
"AppSettings": {
|
|
"type": "object",
|
|
"properties": {
|
|
"name": {
|
|
"type": "string",
|
|
"description": "Application name"
|
|
},
|
|
"environment": {
|
|
"type": "string",
|
|
"description": "Application environment (e.g., 'development', 'production')"
|
|
},
|
|
"base_url": {
|
|
"type": "string",
|
|
"description": "Base URL of the application"
|
|
},
|
|
"password_regex": {
|
|
"type": "string",
|
|
"description": "Regular expression for password validation"
|
|
}
|
|
}
|
|
},
|
|
"ServerSettings": {
|
|
"type": "object",
|
|
"properties": {
|
|
"port": {
|
|
"type": "integer",
|
|
"description": "Server port"
|
|
},
|
|
"host": {
|
|
"type": "string",
|
|
"description": "Server host"
|
|
}
|
|
}
|
|
},
|
|
"AuthSettings": {
|
|
"type": "object",
|
|
"properties": {
|
|
"jwt_expiration": {
|
|
"type": "integer",
|
|
"description": "JWT access token expiration in seconds"
|
|
},
|
|
"refresh_expiration": {
|
|
"type": "integer",
|
|
"description": "Refresh token expiration in seconds"
|
|
}
|
|
}
|
|
},
|
|
"FeatureFlags": {
|
|
"type": "object",
|
|
"properties": {
|
|
"email_enabled": {
|
|
"type": "boolean",
|
|
"description": "Whether email functionality is enabled"
|
|
},
|
|
"oauth_google": {
|
|
"type": "boolean",
|
|
"description": "Whether Google OAuth is enabled"
|
|
}
|
|
}
|
|
},
|
|
"VersionInfo": {
|
|
"type": "object",
|
|
"properties": {
|
|
"version": {
|
|
"type": "string",
|
|
"description": "Application version (git tag or commit hash)"
|
|
},
|
|
"commit": {
|
|
"type": "string",
|
|
"description": "Short git commit hash"
|
|
},
|
|
"build_date": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"description": "Build date in RFC3339 format"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"securitySchemes": {
|
|
"CookieAuth": {
|
|
"type": "apiKey",
|
|
"in": "cookie",
|
|
"name": "access_token",
|
|
"description": "HTTPOnly JWT access token cookie set during login, registration, or token refresh"
|
|
},
|
|
"BearerAuth": {
|
|
"type": "http",
|
|
"scheme": "bearer",
|
|
"bearerFormat": "JWT",
|
|
"description": "JWT token obtained from login response (alternative to cookie-based auth)"
|
|
}
|
|
}
|
|
}
|
|
}
|