diff --git a/server/docs/docs.go b/server/docs/docs.go new file mode 100644 index 0000000..47dc923 --- /dev/null +++ b/server/docs/docs.go @@ -0,0 +1,1829 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/admin/stats": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get system-wide statistics as an admin", + "produces": [ + "application/json" + ], + "tags": [ + "Admin" + ], + "summary": "Get system statistics", + "operationId": "adminGetSystemStats", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.SystemStats" + } + }, + "500": { + "description": "Failed to get file stats", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/admin/users": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Returns the list of all users", + "produces": [ + "application/json" + ], + "tags": [ + "Admin" + ], + "summary": "List all users", + "operationId": "adminListUsers", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.User" + } + } + }, + "500": { + "description": "Failed to list users", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new user as an admin", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Admin" + ], + "summary": "Create a new user", + "operationId": "adminCreateUser", + "parameters": [ + { + "description": "User details", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateUserRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.User" + } + }, + "400": { + "description": "Password must be at least 8 characters", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "409": { + "description": "Email already exists", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to initialize user workspace", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/admin/users/{userId}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a specific user as an admin", + "produces": [ + "application/json" + ], + "tags": [ + "Admin" + ], + "summary": "Get a specific user", + "operationId": "adminGetUser", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.User" + } + }, + "400": { + "description": "Invalid user ID", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "404": { + "description": "User not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update a specific user as an admin", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Admin" + ], + "summary": "Update a specific user", + "operationId": "adminUpdateUser", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "path", + "required": true + }, + { + "description": "User details", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.UpdateUserRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.User" + } + }, + "400": { + "description": "Invalid request body", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "404": { + "description": "User not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to update user", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a specific user as an admin", + "tags": [ + "Admin" + ], + "summary": "Delete a specific user", + "operationId": "adminDeleteUser", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Cannot delete your own account", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "403": { + "description": "Cannot delete other admin users", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "404": { + "description": "User not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to delete user", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/admin/workspaces": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "List all workspaces and their stats as an admin", + "produces": [ + "application/json" + ], + "tags": [ + "Admin" + ], + "summary": "List all workspaces", + "operationId": "adminListWorkspaces", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.WorkspaceStats" + } + } + }, + "500": { + "description": "Failed to get file stats", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/auth/login": { + "post": { + "description": "Logs in a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Login", + "operationId": "login", + "parameters": [ + { + "description": "Login request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.LoginRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.LoginResponse" + } + }, + "400": { + "description": "Email and password are required", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "401": { + "description": "Invalid credentials", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to create session", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/auth/logout": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Log out invalidates the user's session", + "tags": [ + "auth" + ], + "summary": "Logout", + "operationId": "logout", + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Session ID required", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to logout", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/auth/me": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Returns the current authenticated user", + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Get current user", + "operationId": "getCurrentUser", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.User" + } + }, + "404": { + "description": "User not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/auth/refresh": { + "post": { + "description": "Refreshes the access token using the refresh token", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Refresh token", + "operationId": "refreshToken", + "parameters": [ + { + "description": "Refresh request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.RefreshRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.RefreshResponse" + } + }, + "400": { + "description": "Refresh token required", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "401": { + "description": "Invalid refresh token", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/profile": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Updates the user's profile", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "Update profile", + "operationId": "updateProfile", + "parameters": [ + { + "description": "Profile update request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.UpdateProfileRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.User" + } + }, + "400": { + "description": "Current password is required to change email", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "401": { + "description": "Current password is incorrect", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "404": { + "description": "User not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "409": { + "description": "Email already in use", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to update profile", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Deletes the user's account and all associated data", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "Delete account", + "operationId": "deleteAccount", + "parameters": [ + { + "description": "Account deletion request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.DeleteAccountRequest" + } + } + ], + "responses": { + "204": { + "description": "No Content - Account deleted successfully" + }, + "400": { + "description": "Invalid request body", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "401": { + "description": "Password is incorrect", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "403": { + "description": "Cannot delete the last admin account", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "404": { + "description": "User not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to delete account", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/workspaces": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Lists all workspaces for the current user", + "produces": [ + "application/json" + ], + "tags": [ + "workspaces" + ], + "summary": "List workspaces", + "operationId": "listWorkspaces", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Workspace" + } + } + }, + "500": { + "description": "Failed to list workspaces", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Creates a new workspace", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "workspaces" + ], + "summary": "Create workspace", + "operationId": "createWorkspace", + "parameters": [ + { + "description": "Workspace", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Workspace" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Workspace" + } + }, + "400": { + "description": "Invalid workspace", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to setup git repo", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/workspaces/last": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Returns the name of the last opened workspace", + "produces": [ + "application/json" + ], + "tags": [ + "workspaces" + ], + "summary": "Get last workspace name", + "operationId": "getLastWorkspaceName", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.LastWorkspaceNameResponse" + } + }, + "500": { + "description": "Failed to get last workspace", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Updates the name of the last opened workspace", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "workspaces" + ], + "summary": "Update last workspace name", + "operationId": "updateLastWorkspaceName", + "responses": { + "204": { + "description": "No Content - Last workspace updated successfully" + }, + "400": { + "description": "Invalid request body", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to update last workspace", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/workspaces/{workspace_name}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Returns the current workspace", + "produces": [ + "application/json" + ], + "tags": [ + "workspaces" + ], + "summary": "Get workspace", + "operationId": "getWorkspace", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Workspace" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Updates the current workspace", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "workspaces" + ], + "summary": "Update workspace", + "operationId": "updateWorkspace", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + }, + { + "description": "Workspace", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Workspace" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Workspace" + } + }, + "400": { + "description": "Invalid request body", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to setup git repo", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Deletes the current workspace", + "produces": [ + "application/json" + ], + "tags": [ + "workspaces" + ], + "summary": "Delete workspace", + "operationId": "deleteWorkspace", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.DeleteWorkspaceResponse" + } + }, + "400": { + "description": "Cannot delete the last workspace", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to commit transaction", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/workspaces/{workspace_name}/files": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Lists all files in the user's workspace", + "produces": [ + "application/json" + ], + "tags": [ + "files" + ], + "summary": "List files", + "operationId": "listFiles", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/storage.FileNode" + } + } + }, + "500": { + "description": "Failed to list files", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/workspaces/{workspace_name}/files/last": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Returns the path of the last opened file in the user's workspace", + "produces": [ + "application/json" + ], + "tags": [ + "files" + ], + "summary": "Get last opened file", + "operationId": "getLastOpenedFile", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.LastOpenedFileResponse" + } + }, + "400": { + "description": "Invalid file path", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to get last opened file", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Updates the last opened file in the user's workspace", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "files" + ], + "summary": "Update last opened file", + "operationId": "updateLastOpenedFile", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + }, + { + "description": "Update last opened file request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.UpdateLastOpenedFileRequest" + } + } + ], + "responses": { + "204": { + "description": "No Content - Last opened file updated successfully" + }, + "400": { + "description": "Invalid file path", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "404": { + "description": "File not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to update file", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/workspaces/{workspace_name}/files/lookup": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Returns the paths of files with the given name in the user's workspace", + "produces": [ + "application/json" + ], + "tags": [ + "files" + ], + "summary": "Lookup file by name", + "operationId": "lookupFileByName", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "File name", + "name": "filename", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.LookupResponse" + } + }, + "400": { + "description": "Filename is required", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "404": { + "description": "File not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/workspaces/{workspace_name}/files/{file_path}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Returns the content of a file in the user's workspace", + "produces": [ + "text/plain" + ], + "tags": [ + "files" + ], + "summary": "Get file content", + "operationId": "getFileContent", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "File path", + "name": "file_path", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Raw file content", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Invalid file path", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "404": { + "description": "File not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to write response", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Saves the content of a file in the user's workspace", + "consumes": [ + "text/plain" + ], + "produces": [ + "application/json" + ], + "tags": [ + "files" + ], + "summary": "Save file", + "operationId": "saveFile", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "File path", + "name": "file_path", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.SaveFileResponse" + } + }, + "400": { + "description": "Invalid file path", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to save file", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Deletes a file in the user's workspace", + "tags": [ + "files" + ], + "summary": "Delete file", + "operationId": "deleteFile", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "File path", + "name": "file_path", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content - File deleted successfully" + }, + "400": { + "description": "Invalid file path", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "404": { + "description": "File not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to write response", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/workspaces/{workspace_name}/git/commit": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Stages, commits, and pushes changes to the remote repository", + "produces": [ + "application/json" + ], + "tags": [ + "git" + ], + "summary": "Stage, commit, and push changes", + "operationId": "stageCommitAndPush", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + }, + { + "description": "Commit request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CommitRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CommitResponse" + } + }, + "400": { + "description": "Commit message is required", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to stage, commit, and push changes", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/workspaces/{workspace_name}/git/pull": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Pulls changes from the remote repository", + "produces": [ + "application/json" + ], + "tags": [ + "git" + ], + "summary": "Pull changes from remote", + "operationId": "pullChanges", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.PullResponse" + } + }, + "500": { + "description": "Failed to pull changes", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + } + }, + "definitions": { + "handlers.CommitRequest": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Initial commit" + } + } + }, + "handlers.CommitResponse": { + "type": "object", + "properties": { + "commitHash": { + "type": "string", + "example": "a1b2c3d4" + } + } + }, + "handlers.CreateUserRequest": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "role": { + "$ref": "#/definitions/models.UserRole" + } + } + }, + "handlers.DeleteAccountRequest": { + "type": "object", + "properties": { + "password": { + "type": "string" + } + } + }, + "handlers.DeleteWorkspaceResponse": { + "type": "object", + "properties": { + "nextWorkspaceName": { + "type": "string" + } + } + }, + "handlers.ErrorResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + }, + "handlers.LastOpenedFileResponse": { + "type": "object", + "properties": { + "lastOpenedFilePath": { + "type": "string" + } + } + }, + "handlers.LastWorkspaceNameResponse": { + "type": "object", + "properties": { + "lastWorkspaceName": { + "type": "string" + } + } + }, + "handlers.LoginRequest": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "handlers.LoginResponse": { + "type": "object", + "properties": { + "accessToken": { + "type": "string" + }, + "refreshToken": { + "type": "string" + }, + "session": { + "$ref": "#/definitions/models.Session" + }, + "user": { + "$ref": "#/definitions/models.User" + } + } + }, + "handlers.LookupResponse": { + "type": "object", + "properties": { + "paths": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "handlers.PullResponse": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Pulled changes from remote" + } + } + }, + "handlers.RefreshRequest": { + "type": "object", + "properties": { + "refreshToken": { + "type": "string" + } + } + }, + "handlers.RefreshResponse": { + "type": "object", + "properties": { + "accessToken": { + "type": "string" + } + } + }, + "handlers.SaveFileResponse": { + "type": "object", + "properties": { + "filePath": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "updatedAt": { + "type": "string" + } + } + }, + "handlers.SystemStats": { + "type": "object", + "properties": { + "activeUsers": { + "description": "Users with activity in last 30 days", + "type": "integer" + }, + "totalFiles": { + "type": "integer" + }, + "totalSize": { + "type": "integer" + }, + "totalUsers": { + "type": "integer" + }, + "totalWorkspaces": { + "type": "integer" + } + } + }, + "handlers.UpdateLastOpenedFileRequest": { + "type": "object", + "properties": { + "filePath": { + "type": "string" + } + } + }, + "handlers.UpdateProfileRequest": { + "type": "object", + "properties": { + "currentPassword": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "newPassword": { + "type": "string" + } + } + }, + "handlers.UpdateUserRequest": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "role": { + "$ref": "#/definitions/models.UserRole" + } + } + }, + "handlers.WorkspaceStats": { + "type": "object", + "properties": { + "totalFiles": { + "type": "integer" + }, + "totalSize": { + "type": "integer" + }, + "userEmail": { + "type": "string" + }, + "userID": { + "type": "integer" + }, + "workspaceCreatedAt": { + "type": "string" + }, + "workspaceID": { + "type": "integer" + }, + "workspaceName": { + "type": "string" + } + } + }, + "models.Session": { + "type": "object", + "properties": { + "createdAt": { + "description": "When this session was created", + "type": "string" + }, + "expiresAt": { + "description": "When this session expires", + "type": "string" + }, + "id": { + "description": "Unique session identifier", + "type": "string" + }, + "refreshToken": { + "description": "The refresh token associated with this session", + "type": "string" + }, + "userID": { + "description": "ID of the user this session belongs to", + "type": "integer" + } + } + }, + "models.User": { + "type": "object", + "required": [ + "email", + "id", + "role" + ], + "properties": { + "createdAt": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "integer", + "minimum": 1 + }, + "lastWorkspaceId": { + "type": "integer" + }, + "role": { + "enum": [ + "admin", + "editor", + "viewer" + ], + "allOf": [ + { + "$ref": "#/definitions/models.UserRole" + } + ] + } + } + }, + "models.UserRole": { + "type": "string", + "enum": [ + "admin", + "editor", + "viewer" + ], + "x-enum-varnames": [ + "RoleAdmin", + "RoleEditor", + "RoleViewer" + ] + }, + "models.Workspace": { + "type": "object", + "required": [ + "id", + "name", + "userId" + ], + "properties": { + "autoSave": { + "type": "boolean" + }, + "createdAt": { + "type": "string" + }, + "gitAutoCommit": { + "type": "boolean" + }, + "gitCommitEmail": { + "type": "string" + }, + "gitCommitMsgTemplate": { + "type": "string" + }, + "gitCommitName": { + "type": "string" + }, + "gitEnabled": { + "type": "boolean" + }, + "gitToken": { + "type": "string" + }, + "gitUrl": { + "type": "string" + }, + "gitUser": { + "type": "string" + }, + "id": { + "type": "integer", + "minimum": 1 + }, + "lastOpenedFilePath": { + "type": "string" + }, + "name": { + "type": "string" + }, + "showHiddenFiles": { + "type": "boolean" + }, + "theme": { + "description": "Integrated settings", + "type": "string", + "enum": [ + "light", + "dark" + ] + }, + "userId": { + "type": "integer", + "minimum": 1 + } + } + }, + "storage.FileNode": { + "type": "object", + "properties": { + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/storage.FileNode" + } + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + } + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "", + BasePath: "/api/v1", + Schemes: []string{}, + Title: "NovaMD API", + Description: "This is the API for NovaMD markdown note taking app.", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/server/docs/swagger.json b/server/docs/swagger.json new file mode 100644 index 0000000..52ca878 --- /dev/null +++ b/server/docs/swagger.json @@ -0,0 +1,1804 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is the API for NovaMD markdown note taking app.", + "title": "NovaMD API", + "contact": {}, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0" + }, + "basePath": "/api/v1", + "paths": { + "/admin/stats": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get system-wide statistics as an admin", + "produces": [ + "application/json" + ], + "tags": [ + "Admin" + ], + "summary": "Get system statistics", + "operationId": "adminGetSystemStats", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.SystemStats" + } + }, + "500": { + "description": "Failed to get file stats", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/admin/users": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Returns the list of all users", + "produces": [ + "application/json" + ], + "tags": [ + "Admin" + ], + "summary": "List all users", + "operationId": "adminListUsers", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.User" + } + } + }, + "500": { + "description": "Failed to list users", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new user as an admin", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Admin" + ], + "summary": "Create a new user", + "operationId": "adminCreateUser", + "parameters": [ + { + "description": "User details", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateUserRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.User" + } + }, + "400": { + "description": "Password must be at least 8 characters", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "409": { + "description": "Email already exists", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to initialize user workspace", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/admin/users/{userId}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a specific user as an admin", + "produces": [ + "application/json" + ], + "tags": [ + "Admin" + ], + "summary": "Get a specific user", + "operationId": "adminGetUser", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.User" + } + }, + "400": { + "description": "Invalid user ID", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "404": { + "description": "User not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update a specific user as an admin", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Admin" + ], + "summary": "Update a specific user", + "operationId": "adminUpdateUser", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "path", + "required": true + }, + { + "description": "User details", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.UpdateUserRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.User" + } + }, + "400": { + "description": "Invalid request body", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "404": { + "description": "User not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to update user", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a specific user as an admin", + "tags": [ + "Admin" + ], + "summary": "Delete a specific user", + "operationId": "adminDeleteUser", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Cannot delete your own account", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "403": { + "description": "Cannot delete other admin users", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "404": { + "description": "User not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to delete user", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/admin/workspaces": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "List all workspaces and their stats as an admin", + "produces": [ + "application/json" + ], + "tags": [ + "Admin" + ], + "summary": "List all workspaces", + "operationId": "adminListWorkspaces", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.WorkspaceStats" + } + } + }, + "500": { + "description": "Failed to get file stats", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/auth/login": { + "post": { + "description": "Logs in a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Login", + "operationId": "login", + "parameters": [ + { + "description": "Login request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.LoginRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.LoginResponse" + } + }, + "400": { + "description": "Email and password are required", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "401": { + "description": "Invalid credentials", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to create session", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/auth/logout": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Log out invalidates the user's session", + "tags": [ + "auth" + ], + "summary": "Logout", + "operationId": "logout", + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Session ID required", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to logout", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/auth/me": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Returns the current authenticated user", + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Get current user", + "operationId": "getCurrentUser", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.User" + } + }, + "404": { + "description": "User not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/auth/refresh": { + "post": { + "description": "Refreshes the access token using the refresh token", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Refresh token", + "operationId": "refreshToken", + "parameters": [ + { + "description": "Refresh request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.RefreshRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.RefreshResponse" + } + }, + "400": { + "description": "Refresh token required", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "401": { + "description": "Invalid refresh token", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/profile": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Updates the user's profile", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "Update profile", + "operationId": "updateProfile", + "parameters": [ + { + "description": "Profile update request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.UpdateProfileRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.User" + } + }, + "400": { + "description": "Current password is required to change email", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "401": { + "description": "Current password is incorrect", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "404": { + "description": "User not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "409": { + "description": "Email already in use", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to update profile", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Deletes the user's account and all associated data", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "Delete account", + "operationId": "deleteAccount", + "parameters": [ + { + "description": "Account deletion request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.DeleteAccountRequest" + } + } + ], + "responses": { + "204": { + "description": "No Content - Account deleted successfully" + }, + "400": { + "description": "Invalid request body", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "401": { + "description": "Password is incorrect", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "403": { + "description": "Cannot delete the last admin account", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "404": { + "description": "User not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to delete account", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/workspaces": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Lists all workspaces for the current user", + "produces": [ + "application/json" + ], + "tags": [ + "workspaces" + ], + "summary": "List workspaces", + "operationId": "listWorkspaces", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Workspace" + } + } + }, + "500": { + "description": "Failed to list workspaces", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Creates a new workspace", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "workspaces" + ], + "summary": "Create workspace", + "operationId": "createWorkspace", + "parameters": [ + { + "description": "Workspace", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Workspace" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Workspace" + } + }, + "400": { + "description": "Invalid workspace", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to setup git repo", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/workspaces/last": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Returns the name of the last opened workspace", + "produces": [ + "application/json" + ], + "tags": [ + "workspaces" + ], + "summary": "Get last workspace name", + "operationId": "getLastWorkspaceName", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.LastWorkspaceNameResponse" + } + }, + "500": { + "description": "Failed to get last workspace", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Updates the name of the last opened workspace", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "workspaces" + ], + "summary": "Update last workspace name", + "operationId": "updateLastWorkspaceName", + "responses": { + "204": { + "description": "No Content - Last workspace updated successfully" + }, + "400": { + "description": "Invalid request body", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to update last workspace", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/workspaces/{workspace_name}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Returns the current workspace", + "produces": [ + "application/json" + ], + "tags": [ + "workspaces" + ], + "summary": "Get workspace", + "operationId": "getWorkspace", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Workspace" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Updates the current workspace", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "workspaces" + ], + "summary": "Update workspace", + "operationId": "updateWorkspace", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + }, + { + "description": "Workspace", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Workspace" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Workspace" + } + }, + "400": { + "description": "Invalid request body", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to setup git repo", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Deletes the current workspace", + "produces": [ + "application/json" + ], + "tags": [ + "workspaces" + ], + "summary": "Delete workspace", + "operationId": "deleteWorkspace", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.DeleteWorkspaceResponse" + } + }, + "400": { + "description": "Cannot delete the last workspace", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to commit transaction", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/workspaces/{workspace_name}/files": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Lists all files in the user's workspace", + "produces": [ + "application/json" + ], + "tags": [ + "files" + ], + "summary": "List files", + "operationId": "listFiles", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/storage.FileNode" + } + } + }, + "500": { + "description": "Failed to list files", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/workspaces/{workspace_name}/files/last": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Returns the path of the last opened file in the user's workspace", + "produces": [ + "application/json" + ], + "tags": [ + "files" + ], + "summary": "Get last opened file", + "operationId": "getLastOpenedFile", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.LastOpenedFileResponse" + } + }, + "400": { + "description": "Invalid file path", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to get last opened file", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Updates the last opened file in the user's workspace", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "files" + ], + "summary": "Update last opened file", + "operationId": "updateLastOpenedFile", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + }, + { + "description": "Update last opened file request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.UpdateLastOpenedFileRequest" + } + } + ], + "responses": { + "204": { + "description": "No Content - Last opened file updated successfully" + }, + "400": { + "description": "Invalid file path", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "404": { + "description": "File not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to update file", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/workspaces/{workspace_name}/files/lookup": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Returns the paths of files with the given name in the user's workspace", + "produces": [ + "application/json" + ], + "tags": [ + "files" + ], + "summary": "Lookup file by name", + "operationId": "lookupFileByName", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "File name", + "name": "filename", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.LookupResponse" + } + }, + "400": { + "description": "Filename is required", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "404": { + "description": "File not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/workspaces/{workspace_name}/files/{file_path}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Returns the content of a file in the user's workspace", + "produces": [ + "text/plain" + ], + "tags": [ + "files" + ], + "summary": "Get file content", + "operationId": "getFileContent", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "File path", + "name": "file_path", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Raw file content", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Invalid file path", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "404": { + "description": "File not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to write response", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Saves the content of a file in the user's workspace", + "consumes": [ + "text/plain" + ], + "produces": [ + "application/json" + ], + "tags": [ + "files" + ], + "summary": "Save file", + "operationId": "saveFile", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "File path", + "name": "file_path", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.SaveFileResponse" + } + }, + "400": { + "description": "Invalid file path", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to save file", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Deletes a file in the user's workspace", + "tags": [ + "files" + ], + "summary": "Delete file", + "operationId": "deleteFile", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "File path", + "name": "file_path", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content - File deleted successfully" + }, + "400": { + "description": "Invalid file path", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "404": { + "description": "File not found", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to write response", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/workspaces/{workspace_name}/git/commit": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Stages, commits, and pushes changes to the remote repository", + "produces": [ + "application/json" + ], + "tags": [ + "git" + ], + "summary": "Stage, commit, and push changes", + "operationId": "stageCommitAndPush", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + }, + { + "description": "Commit request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CommitRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CommitResponse" + } + }, + "400": { + "description": "Commit message is required", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + }, + "500": { + "description": "Failed to stage, commit, and push changes", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + }, + "/workspaces/{workspace_name}/git/pull": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Pulls changes from the remote repository", + "produces": [ + "application/json" + ], + "tags": [ + "git" + ], + "summary": "Pull changes from remote", + "operationId": "pullChanges", + "parameters": [ + { + "type": "string", + "description": "Workspace name", + "name": "workspace_name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.PullResponse" + } + }, + "500": { + "description": "Failed to pull changes", + "schema": { + "$ref": "#/definitions/handlers.ErrorResponse" + } + } + } + } + } + }, + "definitions": { + "handlers.CommitRequest": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Initial commit" + } + } + }, + "handlers.CommitResponse": { + "type": "object", + "properties": { + "commitHash": { + "type": "string", + "example": "a1b2c3d4" + } + } + }, + "handlers.CreateUserRequest": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "role": { + "$ref": "#/definitions/models.UserRole" + } + } + }, + "handlers.DeleteAccountRequest": { + "type": "object", + "properties": { + "password": { + "type": "string" + } + } + }, + "handlers.DeleteWorkspaceResponse": { + "type": "object", + "properties": { + "nextWorkspaceName": { + "type": "string" + } + } + }, + "handlers.ErrorResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + }, + "handlers.LastOpenedFileResponse": { + "type": "object", + "properties": { + "lastOpenedFilePath": { + "type": "string" + } + } + }, + "handlers.LastWorkspaceNameResponse": { + "type": "object", + "properties": { + "lastWorkspaceName": { + "type": "string" + } + } + }, + "handlers.LoginRequest": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "handlers.LoginResponse": { + "type": "object", + "properties": { + "accessToken": { + "type": "string" + }, + "refreshToken": { + "type": "string" + }, + "session": { + "$ref": "#/definitions/models.Session" + }, + "user": { + "$ref": "#/definitions/models.User" + } + } + }, + "handlers.LookupResponse": { + "type": "object", + "properties": { + "paths": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "handlers.PullResponse": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Pulled changes from remote" + } + } + }, + "handlers.RefreshRequest": { + "type": "object", + "properties": { + "refreshToken": { + "type": "string" + } + } + }, + "handlers.RefreshResponse": { + "type": "object", + "properties": { + "accessToken": { + "type": "string" + } + } + }, + "handlers.SaveFileResponse": { + "type": "object", + "properties": { + "filePath": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "updatedAt": { + "type": "string" + } + } + }, + "handlers.SystemStats": { + "type": "object", + "properties": { + "activeUsers": { + "description": "Users with activity in last 30 days", + "type": "integer" + }, + "totalFiles": { + "type": "integer" + }, + "totalSize": { + "type": "integer" + }, + "totalUsers": { + "type": "integer" + }, + "totalWorkspaces": { + "type": "integer" + } + } + }, + "handlers.UpdateLastOpenedFileRequest": { + "type": "object", + "properties": { + "filePath": { + "type": "string" + } + } + }, + "handlers.UpdateProfileRequest": { + "type": "object", + "properties": { + "currentPassword": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "newPassword": { + "type": "string" + } + } + }, + "handlers.UpdateUserRequest": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "role": { + "$ref": "#/definitions/models.UserRole" + } + } + }, + "handlers.WorkspaceStats": { + "type": "object", + "properties": { + "totalFiles": { + "type": "integer" + }, + "totalSize": { + "type": "integer" + }, + "userEmail": { + "type": "string" + }, + "userID": { + "type": "integer" + }, + "workspaceCreatedAt": { + "type": "string" + }, + "workspaceID": { + "type": "integer" + }, + "workspaceName": { + "type": "string" + } + } + }, + "models.Session": { + "type": "object", + "properties": { + "createdAt": { + "description": "When this session was created", + "type": "string" + }, + "expiresAt": { + "description": "When this session expires", + "type": "string" + }, + "id": { + "description": "Unique session identifier", + "type": "string" + }, + "refreshToken": { + "description": "The refresh token associated with this session", + "type": "string" + }, + "userID": { + "description": "ID of the user this session belongs to", + "type": "integer" + } + } + }, + "models.User": { + "type": "object", + "required": [ + "email", + "id", + "role" + ], + "properties": { + "createdAt": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "integer", + "minimum": 1 + }, + "lastWorkspaceId": { + "type": "integer" + }, + "role": { + "enum": [ + "admin", + "editor", + "viewer" + ], + "allOf": [ + { + "$ref": "#/definitions/models.UserRole" + } + ] + } + } + }, + "models.UserRole": { + "type": "string", + "enum": [ + "admin", + "editor", + "viewer" + ], + "x-enum-varnames": [ + "RoleAdmin", + "RoleEditor", + "RoleViewer" + ] + }, + "models.Workspace": { + "type": "object", + "required": [ + "id", + "name", + "userId" + ], + "properties": { + "autoSave": { + "type": "boolean" + }, + "createdAt": { + "type": "string" + }, + "gitAutoCommit": { + "type": "boolean" + }, + "gitCommitEmail": { + "type": "string" + }, + "gitCommitMsgTemplate": { + "type": "string" + }, + "gitCommitName": { + "type": "string" + }, + "gitEnabled": { + "type": "boolean" + }, + "gitToken": { + "type": "string" + }, + "gitUrl": { + "type": "string" + }, + "gitUser": { + "type": "string" + }, + "id": { + "type": "integer", + "minimum": 1 + }, + "lastOpenedFilePath": { + "type": "string" + }, + "name": { + "type": "string" + }, + "showHiddenFiles": { + "type": "boolean" + }, + "theme": { + "description": "Integrated settings", + "type": "string", + "enum": [ + "light", + "dark" + ] + }, + "userId": { + "type": "integer", + "minimum": 1 + } + } + }, + "storage.FileNode": { + "type": "object", + "properties": { + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/storage.FileNode" + } + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/server/docs/swagger.yaml b/server/docs/swagger.yaml new file mode 100644 index 0000000..505cd38 --- /dev/null +++ b/server/docs/swagger.yaml @@ -0,0 +1,1168 @@ +basePath: /api/v1 +definitions: + handlers.CommitRequest: + properties: + message: + example: Initial commit + type: string + type: object + handlers.CommitResponse: + properties: + commitHash: + example: a1b2c3d4 + type: string + type: object + handlers.CreateUserRequest: + properties: + displayName: + type: string + email: + type: string + password: + type: string + role: + $ref: '#/definitions/models.UserRole' + type: object + handlers.DeleteAccountRequest: + properties: + password: + type: string + type: object + handlers.DeleteWorkspaceResponse: + properties: + nextWorkspaceName: + type: string + type: object + handlers.ErrorResponse: + properties: + message: + type: string + type: object + handlers.LastOpenedFileResponse: + properties: + lastOpenedFilePath: + type: string + type: object + handlers.LastWorkspaceNameResponse: + properties: + lastWorkspaceName: + type: string + type: object + handlers.LoginRequest: + properties: + email: + type: string + password: + type: string + type: object + handlers.LoginResponse: + properties: + accessToken: + type: string + refreshToken: + type: string + session: + $ref: '#/definitions/models.Session' + user: + $ref: '#/definitions/models.User' + type: object + handlers.LookupResponse: + properties: + paths: + items: + type: string + type: array + type: object + handlers.PullResponse: + properties: + message: + example: Pulled changes from remote + type: string + type: object + handlers.RefreshRequest: + properties: + refreshToken: + type: string + type: object + handlers.RefreshResponse: + properties: + accessToken: + type: string + type: object + handlers.SaveFileResponse: + properties: + filePath: + type: string + size: + type: integer + updatedAt: + type: string + type: object + handlers.SystemStats: + properties: + activeUsers: + description: Users with activity in last 30 days + type: integer + totalFiles: + type: integer + totalSize: + type: integer + totalUsers: + type: integer + totalWorkspaces: + type: integer + type: object + handlers.UpdateLastOpenedFileRequest: + properties: + filePath: + type: string + type: object + handlers.UpdateProfileRequest: + properties: + currentPassword: + type: string + displayName: + type: string + email: + type: string + newPassword: + type: string + type: object + handlers.UpdateUserRequest: + properties: + displayName: + type: string + email: + type: string + password: + type: string + role: + $ref: '#/definitions/models.UserRole' + type: object + handlers.WorkspaceStats: + properties: + totalFiles: + type: integer + totalSize: + type: integer + userEmail: + type: string + userID: + type: integer + workspaceCreatedAt: + type: string + workspaceID: + type: integer + workspaceName: + type: string + type: object + models.Session: + properties: + createdAt: + description: When this session was created + type: string + expiresAt: + description: When this session expires + type: string + id: + description: Unique session identifier + type: string + refreshToken: + description: The refresh token associated with this session + type: string + userID: + description: ID of the user this session belongs to + type: integer + type: object + models.User: + properties: + createdAt: + type: string + displayName: + type: string + email: + type: string + id: + minimum: 1 + type: integer + lastWorkspaceId: + type: integer + role: + allOf: + - $ref: '#/definitions/models.UserRole' + enum: + - admin + - editor + - viewer + required: + - email + - id + - role + type: object + models.UserRole: + enum: + - admin + - editor + - viewer + type: string + x-enum-varnames: + - RoleAdmin + - RoleEditor + - RoleViewer + models.Workspace: + properties: + autoSave: + type: boolean + createdAt: + type: string + gitAutoCommit: + type: boolean + gitCommitEmail: + type: string + gitCommitMsgTemplate: + type: string + gitCommitName: + type: string + gitEnabled: + type: boolean + gitToken: + type: string + gitUrl: + type: string + gitUser: + type: string + id: + minimum: 1 + type: integer + lastOpenedFilePath: + type: string + name: + type: string + showHiddenFiles: + type: boolean + theme: + description: Integrated settings + enum: + - light + - dark + type: string + userId: + minimum: 1 + type: integer + required: + - id + - name + - userId + type: object + storage.FileNode: + properties: + children: + items: + $ref: '#/definitions/storage.FileNode' + type: array + id: + type: string + name: + type: string + path: + type: string + type: object +info: + contact: {} + description: This is the API for NovaMD markdown note taking app. + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + title: NovaMD API + version: "1.0" +paths: + /admin/stats: + get: + description: Get system-wide statistics as an admin + operationId: adminGetSystemStats + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.SystemStats' + "500": + description: Failed to get file stats + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Get system statistics + tags: + - Admin + /admin/users: + get: + description: Returns the list of all users + operationId: adminListUsers + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.User' + type: array + "500": + description: Failed to list users + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: List all users + tags: + - Admin + post: + consumes: + - application/json + description: Create a new user as an admin + operationId: adminCreateUser + parameters: + - description: User details + in: body + name: user + required: true + schema: + $ref: '#/definitions/handlers.CreateUserRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.User' + "400": + description: Password must be at least 8 characters + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "409": + description: Email already exists + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "500": + description: Failed to initialize user workspace + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Create a new user + tags: + - Admin + /admin/users/{userId}: + delete: + description: Delete a specific user as an admin + operationId: adminDeleteUser + parameters: + - description: User ID + in: path + name: userId + required: true + type: integer + responses: + "204": + description: No Content + "400": + description: Cannot delete your own account + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "403": + description: Cannot delete other admin users + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "404": + description: User not found + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "500": + description: Failed to delete user + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Delete a specific user + tags: + - Admin + get: + description: Get a specific user as an admin + operationId: adminGetUser + parameters: + - description: User ID + in: path + name: userId + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.User' + "400": + description: Invalid user ID + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "404": + description: User not found + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Get a specific user + tags: + - Admin + put: + consumes: + - application/json + description: Update a specific user as an admin + operationId: adminUpdateUser + parameters: + - description: User ID + in: path + name: userId + required: true + type: integer + - description: User details + in: body + name: user + required: true + schema: + $ref: '#/definitions/handlers.UpdateUserRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.User' + "400": + description: Invalid request body + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "404": + description: User not found + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "500": + description: Failed to update user + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Update a specific user + tags: + - Admin + /admin/workspaces: + get: + description: List all workspaces and their stats as an admin + operationId: adminListWorkspaces + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/handlers.WorkspaceStats' + type: array + "500": + description: Failed to get file stats + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: List all workspaces + tags: + - Admin + /auth/login: + post: + consumes: + - application/json + description: Logs in a user + operationId: login + parameters: + - description: Login request + in: body + name: body + required: true + schema: + $ref: '#/definitions/handlers.LoginRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.LoginResponse' + "400": + description: Email and password are required + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "401": + description: Invalid credentials + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "500": + description: Failed to create session + schema: + $ref: '#/definitions/handlers.ErrorResponse' + summary: Login + tags: + - auth + /auth/logout: + post: + description: Log out invalidates the user's session + operationId: logout + responses: + "204": + description: No Content + "400": + description: Session ID required + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "500": + description: Failed to logout + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Logout + tags: + - auth + /auth/me: + get: + description: Returns the current authenticated user + operationId: getCurrentUser + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.User' + "404": + description: User not found + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Get current user + tags: + - auth + /auth/refresh: + post: + consumes: + - application/json + description: Refreshes the access token using the refresh token + operationId: refreshToken + parameters: + - description: Refresh request + in: body + name: body + required: true + schema: + $ref: '#/definitions/handlers.RefreshRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.RefreshResponse' + "400": + description: Refresh token required + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "401": + description: Invalid refresh token + schema: + $ref: '#/definitions/handlers.ErrorResponse' + summary: Refresh token + tags: + - auth + /profile: + delete: + consumes: + - application/json + description: Deletes the user's account and all associated data + operationId: deleteAccount + parameters: + - description: Account deletion request + in: body + name: body + required: true + schema: + $ref: '#/definitions/handlers.DeleteAccountRequest' + produces: + - application/json + responses: + "204": + description: No Content - Account deleted successfully + "400": + description: Invalid request body + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "401": + description: Password is incorrect + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "403": + description: Cannot delete the last admin account + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "404": + description: User not found + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "500": + description: Failed to delete account + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Delete account + tags: + - users + put: + consumes: + - application/json + description: Updates the user's profile + operationId: updateProfile + parameters: + - description: Profile update request + in: body + name: body + required: true + schema: + $ref: '#/definitions/handlers.UpdateProfileRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.User' + "400": + description: Current password is required to change email + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "401": + description: Current password is incorrect + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "404": + description: User not found + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "409": + description: Email already in use + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "500": + description: Failed to update profile + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Update profile + tags: + - users + /workspaces: + get: + description: Lists all workspaces for the current user + operationId: listWorkspaces + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Workspace' + type: array + "500": + description: Failed to list workspaces + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: List workspaces + tags: + - workspaces + post: + consumes: + - application/json + description: Creates a new workspace + operationId: createWorkspace + parameters: + - description: Workspace + in: body + name: body + required: true + schema: + $ref: '#/definitions/models.Workspace' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Workspace' + "400": + description: Invalid workspace + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "500": + description: Failed to setup git repo + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Create workspace + tags: + - workspaces + /workspaces/{workspace_name}: + delete: + description: Deletes the current workspace + operationId: deleteWorkspace + parameters: + - description: Workspace name + in: path + name: workspace_name + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.DeleteWorkspaceResponse' + "400": + description: Cannot delete the last workspace + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "500": + description: Failed to commit transaction + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Delete workspace + tags: + - workspaces + get: + description: Returns the current workspace + operationId: getWorkspace + parameters: + - description: Workspace name + in: path + name: workspace_name + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Workspace' + "500": + description: Internal server error + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Get workspace + tags: + - workspaces + put: + consumes: + - application/json + description: Updates the current workspace + operationId: updateWorkspace + parameters: + - description: Workspace name + in: path + name: workspace_name + required: true + type: string + - description: Workspace + in: body + name: body + required: true + schema: + $ref: '#/definitions/models.Workspace' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Workspace' + "400": + description: Invalid request body + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "500": + description: Failed to setup git repo + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Update workspace + tags: + - workspaces + /workspaces/{workspace_name}/files: + get: + description: Lists all files in the user's workspace + operationId: listFiles + parameters: + - description: Workspace name + in: path + name: workspace_name + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/storage.FileNode' + type: array + "500": + description: Failed to list files + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: List files + tags: + - files + /workspaces/{workspace_name}/files/{file_path}: + delete: + description: Deletes a file in the user's workspace + operationId: deleteFile + parameters: + - description: Workspace name + in: path + name: workspace_name + required: true + type: string + - description: File path + in: path + name: file_path + required: true + type: string + responses: + "204": + description: No Content - File deleted successfully + "400": + description: Invalid file path + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "404": + description: File not found + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "500": + description: Failed to write response + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Delete file + tags: + - files + get: + description: Returns the content of a file in the user's workspace + operationId: getFileContent + parameters: + - description: Workspace name + in: path + name: workspace_name + required: true + type: string + - description: File path + in: path + name: file_path + required: true + type: string + produces: + - text/plain + responses: + "200": + description: Raw file content + schema: + type: string + "400": + description: Invalid file path + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "404": + description: File not found + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "500": + description: Failed to write response + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Get file content + tags: + - files + post: + consumes: + - text/plain + description: Saves the content of a file in the user's workspace + operationId: saveFile + parameters: + - description: Workspace name + in: path + name: workspace_name + required: true + type: string + - description: File path + in: path + name: file_path + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.SaveFileResponse' + "400": + description: Invalid file path + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "500": + description: Failed to save file + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Save file + tags: + - files + /workspaces/{workspace_name}/files/last: + get: + description: Returns the path of the last opened file in the user's workspace + operationId: getLastOpenedFile + parameters: + - description: Workspace name + in: path + name: workspace_name + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.LastOpenedFileResponse' + "400": + description: Invalid file path + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "500": + description: Failed to get last opened file + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Get last opened file + tags: + - files + put: + consumes: + - application/json + description: Updates the last opened file in the user's workspace + operationId: updateLastOpenedFile + parameters: + - description: Workspace name + in: path + name: workspace_name + required: true + type: string + - description: Update last opened file request + in: body + name: body + required: true + schema: + $ref: '#/definitions/handlers.UpdateLastOpenedFileRequest' + produces: + - application/json + responses: + "204": + description: No Content - Last opened file updated successfully + "400": + description: Invalid file path + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "404": + description: File not found + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "500": + description: Failed to update file + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Update last opened file + tags: + - files + /workspaces/{workspace_name}/files/lookup: + get: + description: Returns the paths of files with the given name in the user's workspace + operationId: lookupFileByName + parameters: + - description: Workspace name + in: path + name: workspace_name + required: true + type: string + - description: File name + in: query + name: filename + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.LookupResponse' + "400": + description: Filename is required + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "404": + description: File not found + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Lookup file by name + tags: + - files + /workspaces/{workspace_name}/git/commit: + post: + description: Stages, commits, and pushes changes to the remote repository + operationId: stageCommitAndPush + parameters: + - description: Workspace name + in: path + name: workspace_name + required: true + type: string + - description: Commit request + in: body + name: body + required: true + schema: + $ref: '#/definitions/handlers.CommitRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.CommitResponse' + "400": + description: Commit message is required + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "500": + description: Failed to stage, commit, and push changes + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Stage, commit, and push changes + tags: + - git + /workspaces/{workspace_name}/git/pull: + post: + description: Pulls changes from the remote repository + operationId: pullChanges + parameters: + - description: Workspace name + in: path + name: workspace_name + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.PullResponse' + "500": + description: Failed to pull changes + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Pull changes from remote + tags: + - git + /workspaces/last: + get: + description: Returns the name of the last opened workspace + operationId: getLastWorkspaceName + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.LastWorkspaceNameResponse' + "500": + description: Failed to get last workspace + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Get last workspace name + tags: + - workspaces + put: + consumes: + - application/json + description: Updates the name of the last opened workspace + operationId: updateLastWorkspaceName + produces: + - application/json + responses: + "204": + description: No Content - Last workspace updated successfully + "400": + description: Invalid request body + schema: + $ref: '#/definitions/handlers.ErrorResponse' + "500": + description: Failed to update last workspace + schema: + $ref: '#/definitions/handlers.ErrorResponse' + security: + - BearerAuth: [] + summary: Update last workspace name + tags: + - workspaces +swagger: "2.0" diff --git a/server/go.mod b/server/go.mod index 90ae840..a2d3dbd 100644 --- a/server/go.mod +++ b/server/go.mod @@ -12,12 +12,15 @@ require ( github.com/google/uuid v1.6.0 github.com/mattn/go-sqlite3 v1.14.23 github.com/stretchr/testify v1.9.0 + github.com/swaggo/http-swagger v1.3.4 + github.com/swaggo/swag v1.16.4 github.com/unrolled/secure v1.17.0 golang.org/x/crypto v0.21.0 ) require ( dario.cat/mergo v1.0.0 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/ProtonMail/go-crypto v1.0.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -28,16 +31,23 @@ require ( github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/spec v0.20.6 // indirect + github.com/go-openapi/swag v0.19.15 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/mailru/easyjson v0.7.6 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/skeema/knownhosts v1.2.2 // indirect + github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.23.0 // indirect @@ -45,5 +55,6 @@ require ( golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.13.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/server/go.sum b/server/go.sum index 426c423..ea26479 100644 --- a/server/go.sum +++ b/server/go.sum @@ -1,5 +1,7 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= @@ -15,6 +17,7 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -42,6 +45,16 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ= +github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -60,6 +73,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -71,8 +86,13 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0= github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= @@ -90,9 +110,17 @@ github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc= +github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= +github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww= +github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ= +github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= +github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= github.com/unrolled/secure v1.17.0 h1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbWU= github.com/unrolled/secure v1.17.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= @@ -111,6 +139,7 @@ golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= @@ -162,12 +191,17 @@ golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/server/internal/app/routes.go b/server/internal/app/routes.go index 3b02d02..f3e9df7 100644 --- a/server/internal/app/routes.go +++ b/server/internal/app/routes.go @@ -11,6 +11,10 @@ import ( "github.com/go-chi/cors" "github.com/go-chi/httprate" "github.com/unrolled/secure" + + httpSwagger "github.com/swaggo/http-swagger" + + _ "novamd/docs" // Swagger docs ) // setupRouter creates and configures the chi router with middleware and routes @@ -49,6 +53,12 @@ func setupRouter(o Options) *chi.Mux { Storage: o.Storage, } + if o.Config.IsDevelopment { + r.Get("/swagger/*", httpSwagger.Handler( + httpSwagger.URL("/swagger/doc.json"), // The URL pointing to API definition + )) + } + // API routes r.Route("/api/v1", func(r chi.Router) { // Rate limiting for API routes diff --git a/server/internal/handlers/file_handlers.go b/server/internal/handlers/file_handlers.go index 5e6ad27..6104d98 100644 --- a/server/internal/handlers/file_handlers.go +++ b/server/internal/handlers/file_handlers.go @@ -108,12 +108,12 @@ func (h *Handler) LookupFileByName() http.HandlerFunc { // @Produce plain // @Param workspace_name path string true "Workspace name" // @Param file_path path string true "File path" -// @Success 200 {string} "File content" +// @Success 200 {string} string "Raw file content" // @Failure 400 {object} ErrorResponse "Invalid file path" // @Failure 404 {object} ErrorResponse "File not found" // @Failure 500 {object} ErrorResponse "Failed to read file" // @Failure 500 {object} ErrorResponse "Failed to write response" -// @Router /workspaces/{workspace_name}/files/* [get] +// @Router /workspaces/{workspace_name}/files/{file_path} [get] func (h *Handler) GetFileContent() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx, ok := context.GetRequestContext(w, r) @@ -162,7 +162,7 @@ func (h *Handler) GetFileContent() http.HandlerFunc { // @Failure 400 {object} ErrorResponse "Failed to read request body" // @Failure 400 {object} ErrorResponse "Invalid file path" // @Failure 500 {object} ErrorResponse "Failed to save file" -// @Router /workspaces/{workspace_name}/files/* [post] +// @Router /workspaces/{workspace_name}/files/{file_path} [post] func (h *Handler) SaveFile() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx, ok := context.GetRequestContext(w, r) @@ -205,7 +205,6 @@ func (h *Handler) SaveFile() http.HandlerFunc { // @Tags files // @ID deleteFile // @Security BearerAuth -// @Produce string // @Param workspace_name path string true "Workspace name" // @Param file_path path string true "File path" // @Success 204 "No Content - File deleted successfully" @@ -213,7 +212,7 @@ func (h *Handler) SaveFile() http.HandlerFunc { // @Failure 404 {object} ErrorResponse "File not found" // @Failure 500 {object} ErrorResponse "Failed to delete file" // @Failure 500 {object} ErrorResponse "Failed to write response" -// @Router /workspaces/{workspace_name}/files/* [delete] +// @Router /workspaces/{workspace_name}/files/{file_path} [delete] func (h *Handler) DeleteFile() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx, ok := context.GetRequestContext(w, r) diff --git a/server/internal/handlers/workspace_handlers.go b/server/internal/handlers/workspace_handlers.go index 9cfb61f..2b26d0d 100644 --- a/server/internal/handlers/workspace_handlers.go +++ b/server/internal/handlers/workspace_handlers.go @@ -14,8 +14,8 @@ type DeleteWorkspaceResponse struct { NextWorkspaceName string `json:"nextWorkspaceName"` } -// GetLastWorkspaceNameResponse contains the name of the last opened workspace -type GetLastWorkspaceNameResponse struct { +// LastWorkspaceNameResponse contains the name of the last opened workspace +type LastWorkspaceNameResponse struct { LastWorkspaceName string `json:"lastWorkspaceName"` } @@ -325,7 +325,7 @@ func (h *Handler) GetLastWorkspaceName() http.HandlerFunc { return } - respondJSON(w, &GetLastWorkspaceNameResponse{LastWorkspaceName: workspaceName}) + respondJSON(w, &LastWorkspaceNameResponse{LastWorkspaceName: workspaceName}) } }