# Lemma Package Documentation Generated documentation for all packages in the Lemma project. ## Table of Contents - [cmd/server](#cmd-server) - [docs](#docs) - [internal/app](#internal-app) - [internal/auth](#internal-auth) - [internal/context](#internal-context) - [internal/db](#internal-db) - [internal/git](#internal-git) - [internal/handlers](#internal-handlers) - [internal/logging](#internal-logging) - [internal/models](#internal-models) - [internal/secrets](#internal-secrets) - [internal/storage](#internal-storage) - [internal/testenv](#internal-testenv) ## cmd/server ```go Package main provides the entry point for the application. It loads the configuration, initializes the server, and starts the server. ``` ## docs ```go package docs // import "lemma/docs" Package docs Code generated by swaggo/swag. DO NOT EDIT VARIABLES var SwaggerInfo = &swag.Spec{ Version: "1.0", Host: "", BasePath: "/api/v1", Schemes: []string{}, Title: "Lemma API", Description: "This is the API for Lemma markdown note taking app.", InfoInstanceName: "swagger", SwaggerTemplate: docTemplate, LeftDelim: "{{", RightDelim: "}}", } SwaggerInfo holds exported Swagger Info so clients can modify it ``` ## internal/app ```go package app // import "lemma/internal/app" Package app provides application-level functionality for initializing and running the server FUNCTIONS func ParseDBURL(dbURL string) (db.DBType, string, error) ParseDBURL parses a database URL and returns the driver name and data source TYPES type Config struct { DBURL string DBType db.DBType WorkDir string StaticPath string Port string Domain string CORSOrigins []string AdminEmail string AdminPassword string EncryptionKey string JWTSigningKey string RateLimitRequests int RateLimitWindow time.Duration IsDevelopment bool LogLevel logging.LogLevel } Config holds the configuration for the application func DefaultConfig() *Config DefaultConfig returns a new Config instance with default values func LoadConfig() (*Config, error) LoadConfig creates a new Config instance with values from environment variables func (c *Config) Redact() *Config Redact redacts sensitive fields from a Config instance type Options struct { Config *Config Database db.Database Storage storage.Manager JWTManager auth.JWTManager SessionManager auth.SessionManager CookieService auth.CookieManager } Options holds all dependencies and configuration for the server func DefaultOptions(cfg *Config) (*Options, error) DefaultOptions creates server options with default configuration type Server struct { // Has unexported fields. } Server represents the HTTP server and its dependencies func NewServer(options *Options) *Server NewServer creates a new server instance with the given options func (s *Server) Close() error Close handles graceful shutdown of server dependencies func (s *Server) Router() chi.Router Router returns the chi router for testing func (s *Server) Start() error Start configures and starts the HTTP server ``` ## internal/auth ```go package auth // import "lemma/internal/auth" Package auth provides JWT token generation and validation Package auth provides JWT token generation and validation FUNCTIONS func NewSessionService(db db.SessionStore, jwtManager JWTManager) *sessionManager NewSessionService creates a new session service with the given database and JWT manager revive:disable:unexported-return TYPES type Claims struct { jwt.RegisteredClaims // Embedded standard JWT claims UserID int `json:"uid"` // User identifier Role string `json:"role"` // User role (admin, editor, viewer) Type TokenType `json:"type"` // Token type (access or refresh) } Claims represents the custom claims we store in JWT tokens type CookieManager interface { GenerateAccessTokenCookie(token string) *http.Cookie GenerateRefreshTokenCookie(token string) *http.Cookie GenerateCSRFCookie(token string) *http.Cookie InvalidateCookie(cookieType string) *http.Cookie } CookieManager interface defines methods for generating cookies func NewCookieService(isDevelopment bool, domain string) CookieManager NewCookieService creates a new cookie service type JWTConfig struct { SigningKey string // Secret key used to sign tokens AccessTokenExpiry time.Duration // How long access tokens are valid RefreshTokenExpiry time.Duration // How long refresh tokens are valid } JWTConfig holds the configuration for the JWT service type JWTManager interface { GenerateAccessToken(userID int, role string, sessionID string) (string, error) GenerateRefreshToken(userID int, role string, sessionID string) (string, error) ValidateToken(tokenString string) (*Claims, error) } JWTManager defines the interface for managing JWT tokens func NewJWTService(config JWTConfig) (JWTManager, error) NewJWTService creates a new JWT service with the provided configuration Returns an error if the signing key is missing type Middleware struct { // Has unexported fields. } Middleware handles JWT authentication for protected routes func NewMiddleware(jwtManager JWTManager, sessionManager SessionManager, cookieManager CookieManager) *Middleware NewMiddleware creates a new authentication middleware func (m *Middleware) Authenticate(next http.Handler) http.Handler Authenticate middleware validates JWT tokens and sets user information in context func (m *Middleware) RequireRole(role string) func(http.Handler) http.Handler RequireRole returns a middleware that ensures the user has the required role func (m *Middleware) RequireWorkspaceAccess(next http.Handler) http.Handler RequireWorkspaceAccess returns a middleware that ensures the user has access to the workspace type SessionManager interface { CreateSession(userID int, role string) (*models.Session, string, error) RefreshSession(refreshToken string) (string, error) ValidateSession(sessionID string) (*models.Session, error) InvalidateSession(token string) error CleanExpiredSessions() error } SessionManager is an interface for managing user sessions type TokenType string TokenType represents the type of JWT token (access or refresh) const ( AccessToken TokenType = "access" // AccessToken - Short-lived token for API access RefreshToken TokenType = "refresh" // RefreshToken - Long-lived token for obtaining new access tokens ) ``` ## internal/context ```go package context // import "lemma/internal/context" Package context provides functions for managing request context CONSTANTS const ( // HandlerContextKey is the key used to store handler context in the request context HandlerContextKey contextKey = "handlerContext" ) FUNCTIONS func WithHandlerContext(r *http.Request, hctx *HandlerContext) *http.Request WithHandlerContext adds handler context to the request func WithUserContextMiddleware(next http.Handler) http.Handler WithUserContextMiddleware extracts user information from JWT claims and adds it to the request context func WithWorkspaceContextMiddleware(db db.WorkspaceReader) func(http.Handler) http.Handler WithWorkspaceContextMiddleware adds workspace information to the request context TYPES type HandlerContext struct { UserID int UserRole string Workspace *models.Workspace // Optional, only set for workspace routes } HandlerContext holds the request-specific data available to all handlers func GetRequestContext(w http.ResponseWriter, r *http.Request) (*HandlerContext, bool) GetRequestContext retrieves the handler context from the request type UserClaims struct { UserID int Role string } UserClaims represents user information from authentication func GetUserFromContext(ctx context.Context) (*UserClaims, error) GetUserFromContext retrieves user claims from the context ``` ## internal/db ```go package db // import "lemma/internal/db" Package db provides the database access layer for the application. It contains methods for interacting with the database, such as creating, updating, and deleting records. CONSTANTS const ( // JWTSecretKey is the key for the JWT secret in the system settings JWTSecretKey = "jwt_secret" ) TYPES type DBField struct { Name string Value any Type reflect.Type OriginalName string // Has unexported fields. } func StructTagsToFields(s any) ([]DBField, error) StructTagsToFields converts a struct to a slice of DBField instances type DBType string const ( DBTypeSQLite DBType = "sqlite3" DBTypePostgres DBType = "postgres" ) type Database interface { UserStore WorkspaceStore SessionStore SystemStore Begin() (*sql.Tx, error) Close() error Migrate() error } Database defines the methods for interacting with the database func Init(dbType DBType, dbURL string, secretsService secrets.Service) (Database, error) Init initializes the database connection type JoinType string const ( InnerJoin JoinType = "INNER JOIN" LeftJoin JoinType = "LEFT JOIN" RightJoin JoinType = "RIGHT JOIN" ) type Query struct { // Has unexported fields. } Query represents a SQL query with its parameters func NewQuery(dbType DBType, secretsService secrets.Service) *Query NewQuery creates a new Query instance func (q *Query) AddArgs(args ...any) *Query AddArgs adds arguments to the query func (q *Query) And(condition string) *Query And adds an AND condition func (q *Query) Args() []any Args returns the query arguments func (q *Query) Delete() *Query Delete starts a DELETE statement func (q *Query) EndGroup() *Query EndGroup ends a parenthetical group func (q *Query) From(table string) *Query From adds a FROM clause func (q *Query) GroupBy(columns ...string) *Query GroupBy adds a GROUP BY clause func (q *Query) Having(condition string) *Query Having adds a HAVING clause for filtering groups func (q *Query) Insert(table string, columns ...string) *Query Insert starts an INSERT statement func (q *Query) InsertStruct(s any, table string) (*Query, error) InsertStruct creates an INSERT query from a struct func (q *Query) Join(joinType JoinType, table, condition string) *Query Join adds a JOIN clause func (q *Query) Limit(limit int) *Query Limit adds a LIMIT clause func (q *Query) Offset(offset int) *Query Offset adds an OFFSET clause func (q *Query) Or(condition string) *Query Or adds an OR condition func (q *Query) OrderBy(columns ...string) *Query OrderBy adds an ORDER BY clause func (q *Query) Placeholder(arg any) *Query Placeholder adds a placeholder for a single argument func (q *Query) Placeholders(n int) *Query Placeholders adds n placeholders separated by commas func (q *Query) Returning(columns ...string) *Query Returning adds a RETURNING clause for both PostgreSQL and SQLite (3.35.0+) func (q *Query) Select(columns ...string) *Query Select adds a SELECT clause func (q *Query) SelectStruct(s any, table string) (*Query, error) SelectStruct creates a SELECT query from a struct func (q *Query) Set(column string) *Query Set adds a SET clause for updates func (q *Query) StartGroup() *Query StartGroup starts a parenthetical group func (q *Query) String() string String returns the formatted query string func (q *Query) Update(table string) *Query Update starts an UPDATE statement func (q *Query) UpdateStruct(s any, table string) (*Query, error) UpdateStruct creates an UPDATE query from a struct func (q *Query) Values(count int) *Query Values adds a VALUES clause func (q *Query) Where(condition string) *Query Where adds a WHERE clause func (q *Query) WhereIn(column string, count int) *Query WhereIn adds a WHERE IN clause func (q *Query) Write(s string) *Query Write adds a string to the query type Scanner interface { Scan(dest ...any) error } Scanner is an interface that both sql.Row and sql.Rows satisfy type SessionStore interface { CreateSession(session *models.Session) error GetSessionByRefreshToken(refreshToken string) (*models.Session, error) GetSessionByID(sessionID string) (*models.Session, error) DeleteSession(sessionID string) error CleanExpiredSessions() error } SessionStore defines the methods for interacting with jwt sessions in the database type SystemStore interface { GetSystemStats() (*UserStats, error) EnsureJWTSecret() (string, error) GetSystemSetting(key string) (string, error) SetSystemSetting(key, value string) error } SystemStore defines the methods for interacting with system settings and stats in the database type UserStats struct { TotalUsers int `json:"totalUsers"` TotalWorkspaces int `json:"totalWorkspaces"` ActiveUsers int `json:"activeUsers"` // Users with activity in last 30 days } UserStats represents system-wide statistics type UserStore interface { CreateUser(user *models.User) (*models.User, error) GetUserByEmail(email string) (*models.User, error) GetUserByID(userID int) (*models.User, error) GetAllUsers() ([]*models.User, error) UpdateUser(user *models.User) error DeleteUser(userID int) error UpdateLastWorkspace(userID int, workspaceName string) error GetLastWorkspaceName(userID int) (string, error) CountAdminUsers() (int, error) } UserStore defines the methods for interacting with user data in the database type WorkspaceReader interface { GetWorkspaceByID(workspaceID int) (*models.Workspace, error) GetWorkspaceByName(userID int, workspaceName string) (*models.Workspace, error) GetWorkspacesByUserID(userID int) ([]*models.Workspace, error) GetAllWorkspaces() ([]*models.Workspace, error) } WorkspaceReader defines the methods for reading workspace data from the database type WorkspaceStore interface { WorkspaceReader WorkspaceWriter } WorkspaceStore defines the methods for interacting with workspace data in the database type WorkspaceWriter interface { CreateWorkspace(workspace *models.Workspace) error UpdateWorkspace(workspace *models.Workspace) error DeleteWorkspace(workspaceID int) error UpdateWorkspaceSettings(workspace *models.Workspace) error DeleteWorkspaceTx(tx *sql.Tx, workspaceID int) error UpdateLastWorkspaceTx(tx *sql.Tx, userID, workspaceID int) error UpdateLastOpenedFile(workspaceID int, filePath string) error GetLastOpenedFile(workspaceID int) (string, error) } WorkspaceWriter defines the methods for writing workspace data to the database ``` ## internal/git ```go package git // import "lemma/internal/git" Package git provides functionalities to interact with Git repositories, including cloning, pulling, committing, and pushing changes. TYPES type Client interface { Clone() error Pull() error Commit(message string) (CommitHash, error) Push() error EnsureRepo() error } Client defines the interface for Git operations func New(url, username, token, workDir, commitName, commitEmail string) Client New creates a new git Client instance type CommitHash plumbing.Hash CommitHash represents a Git commit hash func (h CommitHash) String() string String returns the string representation of the CommitHash type Config struct { URL string Username string Token string WorkDir string CommitName string CommitEmail string } Config holds the configuration for a Git client ``` ## internal/handlers ```go package handlers // import "lemma/internal/handlers" Package handlers contains the request handlers for the api routes. TYPES type CommitRequest struct { Message string `json:"message" example:"Initial commit"` } CommitRequest represents a request to commit changes type CommitResponse struct { CommitHash string `json:"commitHash" example:"a1b2c3d4"` } CommitResponse represents a response to a commit request type CreateUserRequest struct { Email string `json:"email"` DisplayName string `json:"displayName"` Password string `json:"password"` Role models.UserRole `json:"role"` } CreateUserRequest holds the request fields for creating a new user type DeleteAccountRequest struct { Password string `json:"password"` } DeleteAccountRequest represents a user account deletion request type DeleteWorkspaceResponse struct { NextWorkspaceName string `json:"nextWorkspaceName"` } DeleteWorkspaceResponse contains the name of the next workspace after deleting the current one type ErrorResponse struct { Message string `json:"message"` } ErrorResponse is a generic error response type Handler struct { DB db.Database Storage storage.Manager } Handler provides common functionality for all handlers func NewHandler(db db.Database, s storage.Manager) *Handler NewHandler creates a new handler with the given dependencies func (h *Handler) AdminCreateUser() http.HandlerFunc AdminCreateUser godoc @Summary Create a new user @Description Create a new user as an admin @Tags Admin @Security CookieAuth @ID adminCreateUser @Accept json @Produce json @Param user body CreateUserRequest true "User details" @Success 200 {object} models.User @Failure 400 {object} ErrorResponse "Invalid request body" @Failure 400 {object} ErrorResponse "Email, password, and role are required" @Failure 400 {object} ErrorResponse "Password must be at least 8 characters" @Failure 409 {object} ErrorResponse "Email already exists" @Failure 500 {object} ErrorResponse "Failed to hash password" @Failure 500 {object} ErrorResponse "Failed to create user" @Failure 500 {object} ErrorResponse "Failed to initialize user workspace" @Router /admin/users [post] func (h *Handler) AdminDeleteUser() http.HandlerFunc AdminDeleteUser godoc @Summary Delete a specific user @Description Delete a specific user as an admin @Tags Admin @Security CookieAuth @ID adminDeleteUser @Param userId path int true "User ID" @Success 204 "No Content" @Failure 400 {object} ErrorResponse "Invalid user ID" @Failure 400 {object} ErrorResponse "Cannot delete your own account" @Failure 403 {object} ErrorResponse "Cannot delete other admin users" @Failure 404 {object} ErrorResponse "User not found" @Failure 500 {object} ErrorResponse "Failed to delete user" @Router /admin/users/{userId} [delete] func (h *Handler) AdminGetSystemStats() http.HandlerFunc AdminGetSystemStats godoc @Summary Get system statistics @Description Get system-wide statistics as an admin @Tags Admin @Security CookieAuth @ID adminGetSystemStats @Produce json @Success 200 {object} SystemStats @Failure 500 {object} ErrorResponse "Failed to get user stats" @Failure 500 {object} ErrorResponse "Failed to get file stats" @Router /admin/stats [get] func (h *Handler) AdminGetUser() http.HandlerFunc AdminGetUser godoc @Summary Get a specific user @Description Get a specific user as an admin @Tags Admin @Security CookieAuth @ID adminGetUser @Produce json @Param userId path int true "User ID" @Success 200 {object} models.User @Failure 400 {object} ErrorResponse "Invalid user ID" @Failure 404 {object} ErrorResponse "User not found" @Router /admin/users/{userId} [get] func (h *Handler) AdminListUsers() http.HandlerFunc AdminListUsers godoc @Summary List all users @Description Returns the list of all users @Tags Admin @Security CookieAuth @ID adminListUsers @Produce json @Success 200 {array} models.User @Failure 500 {object} ErrorResponse "Failed to list users" @Router /admin/users [get] func (h *Handler) AdminListWorkspaces() http.HandlerFunc AdminListWorkspaces godoc @Summary List all workspaces @Description List all workspaces and their stats as an admin @Tags Admin @Security CookieAuth @ID adminListWorkspaces @Produce json @Success 200 {array} WorkspaceStats @Failure 500 {object} ErrorResponse "Failed to list workspaces" @Failure 500 {object} ErrorResponse "Failed to get user" @Failure 500 {object} ErrorResponse "Failed to get file stats" @Router /admin/workspaces [get] func (h *Handler) AdminUpdateUser() http.HandlerFunc AdminUpdateUser godoc @Summary Update a specific user @Description Update a specific user as an admin @Tags Admin @Security CookieAuth @ID adminUpdateUser @Accept json @Produce json @Param userId path int true "User ID" @Param user body UpdateUserRequest true "User details" @Success 200 {object} models.User @Failure 400 {object} ErrorResponse "Invalid user ID" @Failure 400 {object} ErrorResponse "Invalid request body" @Failure 404 {object} ErrorResponse "User not found" @Failure 500 {object} ErrorResponse "Failed to hash password" @Failure 500 {object} ErrorResponse "Failed to update user" @Router /admin/users/{userId} [put] func (h *Handler) CreateWorkspace() http.HandlerFunc CreateWorkspace godoc @Summary Create workspace @Description Creates a new workspace @Tags workspaces @ID createWorkspace @Security CookieAuth @Accept json @Produce json @Param body body models.Workspace true "Workspace" @Success 200 {object} models.Workspace @Failure 400 {object} ErrorResponse "Invalid request body" @Failure 400 {object} ErrorResponse "Invalid workspace" @Failure 500 {object} ErrorResponse "Failed to create workspace" @Failure 500 {object} ErrorResponse "Failed to initialize workspace directory" @Failure 500 {object} ErrorResponse "Failed to setup git repo" @Router /workspaces [post] func (h *Handler) DeleteAccount() http.HandlerFunc DeleteAccount godoc @Summary Delete account @Description Deletes the user's account and all associated data @Tags users @ID deleteAccount @Security CookieAuth @Accept json @Produce json @Param body body DeleteAccountRequest true "Account deletion request" @Success 204 "No Content - Account deleted successfully" @Failure 400 {object} ErrorResponse "Invalid request body" @Failure 401 {object} ErrorResponse "Password is incorrect" @Failure 403 {object} ErrorResponse "Cannot delete the last admin account" @Failure 404 {object} ErrorResponse "User not found" @Failure 500 {object} ErrorResponse "Failed to verify admin status" @Failure 500 {object} ErrorResponse "Failed to delete account" @Router /profile [delete] func (h *Handler) DeleteFile() http.HandlerFunc DeleteFile godoc @Summary Delete file @Description Deletes a file in the user's workspace @Tags files @ID deleteFile @Security CookieAuth @Param workspace_name path string true "Workspace name" @Param file_path path string true "File path" @Success 204 "No Content - File deleted successfully" @Failure 400 {object} ErrorResponse "Invalid file path" @Failure 404 {object} ErrorResponse "File not found" @Failure 500 {object} ErrorResponse "Failed to delete file" @Router /workspaces/{workspace_name}/files/{file_path} [delete] func (h *Handler) DeleteWorkspace() http.HandlerFunc DeleteWorkspace godoc @Summary Delete workspace @Description Deletes the current workspace @Tags workspaces @ID deleteWorkspace @Security CookieAuth @Produce json @Param workspace_name path string true "Workspace name" @Success 200 {object} DeleteWorkspaceResponse @Failure 400 {object} ErrorResponse "Cannot delete the last workspace" @Failure 500 {object} ErrorResponse "Failed to get workspaces" @Failure 500 {object} ErrorResponse "Failed to start transaction" @Failure 500 {object} ErrorResponse "Failed to update last workspace" @Failure 500 {object} ErrorResponse "Failed to delete workspace" @Failure 500 {object} ErrorResponse "Failed to rollback transaction" @Failure 500 {object} ErrorResponse "Failed to commit transaction" @Router /workspaces/{workspace_name} [delete] func (h *Handler) GetCurrentUser() http.HandlerFunc GetCurrentUser godoc @Summary Get current user @Description Returns the current authenticated user @Tags auth @ID getCurrentUser @Security CookieAuth @Produce json @Success 200 {object} models.User @Failure 404 {object} ErrorResponse "User not found" @Router /auth/me [get] func (h *Handler) GetFileContent() http.HandlerFunc GetFileContent godoc @Summary Get file content @Description Returns the content of a file in the user's workspace @Tags files @ID getFileContent @Security CookieAuth @Produce plain @Param workspace_name path string true "Workspace name" @Param file_path path string true "File path" @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/{file_path} [get] func (h *Handler) GetLastOpenedFile() http.HandlerFunc GetLastOpenedFile godoc @Summary Get last opened file @Description Returns the path of the last opened file in the user's workspace @Tags files @ID getLastOpenedFile @Security CookieAuth @Produce json @Param workspace_name path string true "Workspace name" @Success 200 {object} LastOpenedFileResponse @Failure 400 {object} ErrorResponse "Invalid file path" @Failure 500 {object} ErrorResponse "Failed to get last opened file" @Router /workspaces/{workspace_name}/files/last [get] func (h *Handler) GetLastWorkspaceName() http.HandlerFunc GetLastWorkspaceName godoc @Summary Get last workspace name @Description Returns the name of the last opened workspace @Tags workspaces @ID getLastWorkspaceName @Security CookieAuth @Produce json @Success 200 {object} LastWorkspaceNameResponse @Failure 500 {object} ErrorResponse "Failed to get last workspace" @Router /workspaces/last [get] func (h *Handler) GetWorkspace() http.HandlerFunc GetWorkspace godoc @Summary Get workspace @Description Returns the current workspace @Tags workspaces @ID getWorkspace @Security CookieAuth @Produce json @Param workspace_name path string true "Workspace name" @Success 200 {object} models.Workspace @Failure 500 {object} ErrorResponse "Internal server error" @Router /workspaces/{workspace_name} [get] func (h *Handler) ListFiles() http.HandlerFunc ListFiles godoc @Summary List files @Description Lists all files in the user's workspace @Tags files @ID listFiles @Security CookieAuth @Produce json @Param workspace_name path string true "Workspace name" @Success 200 {array} storage.FileNode @Failure 500 {object} ErrorResponse "Failed to list files" @Router /workspaces/{workspace_name}/files [get] func (h *Handler) ListWorkspaces() http.HandlerFunc ListWorkspaces godoc @Summary List workspaces @Description Lists all workspaces for the current user @Tags workspaces @ID listWorkspaces @Security CookieAuth @Produce json @Success 200 {array} models.Workspace @Failure 500 {object} ErrorResponse "Failed to list workspaces" @Router /workspaces [get] func (h *Handler) Login(authManager auth.SessionManager, cookieService auth.CookieManager) http.HandlerFunc Login godoc @Summary Login @Description Logs in a user and returns a session with access and refresh tokens @Tags auth @Accept json @Produce json @Param body body LoginRequest true "Login request" @Success 200 {object} LoginResponse @Header 200 {string} X-CSRF-Token "CSRF token for future requests" @Failure 400 {object} ErrorResponse "Invalid request body" @Failure 400 {object} ErrorResponse "Email and password are required" @Failure 401 {object} ErrorResponse "Invalid credentials" @Failure 500 {object} ErrorResponse "Failed to create session" @Failure 500 {object} ErrorResponse "Failed to generate CSRF token" @Router /auth/login [post] func (h *Handler) Logout(authManager auth.SessionManager, cookieService auth.CookieManager) http.HandlerFunc Logout godoc @Summary Logout @Description Log out invalidates the user's session @Tags auth @ID logout @Success 204 "No Content" @Failure 400 {object} ErrorResponse "Session ID required" @Failure 500 {object} ErrorResponse "Failed to logout" @Router /auth/logout [post] func (h *Handler) LookupFileByName() http.HandlerFunc LookupFileByName godoc @Summary Lookup file by name @Description Returns the paths of files with the given name in the user's workspace @Tags files @ID lookupFileByName @Security CookieAuth @Produce json @Param workspace_name path string true "Workspace name" @Param filename query string true "File name" @Success 200 {object} LookupResponse @Failure 400 {object} ErrorResponse "Filename is required" @Failure 404 {object} ErrorResponse "File not found" @Router /workspaces/{workspace_name}/files/lookup [get] func (h *Handler) PullChanges() http.HandlerFunc PullChanges godoc @Summary Pull changes from remote @Description Pulls changes from the remote repository @Tags git @ID pullChanges @Security CookieAuth @Produce json @Param workspace_name path string true "Workspace name" @Success 200 {object} PullResponse @Failure 500 {object} ErrorResponse "Failed to pull changes" @Router /workspaces/{workspace_name}/git/pull [post] func (h *Handler) RefreshToken(authManager auth.SessionManager, cookieService auth.CookieManager) http.HandlerFunc RefreshToken godoc @Summary Refresh token @Description Refreshes the access token using the refresh token @Tags auth @ID refreshToken @Accept json @Produce json @Success 200 @Header 200 {string} X-CSRF-Token "New CSRF token" @Failure 400 {object} ErrorResponse "Refresh token required" @Failure 401 {object} ErrorResponse "Invalid refresh token" @Failure 500 {object} ErrorResponse "Failed to generate CSRF token" @Router /auth/refresh [post] func (h *Handler) SaveFile() http.HandlerFunc SaveFile godoc @Summary Save file @Description Saves the content of a file in the user's workspace @Tags files @ID saveFile @Security CookieAuth @Accept plain @Produce json @Param workspace_name path string true "Workspace name" @Param file_path path string true "File path" @Success 200 {object} SaveFileResponse @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/{file_path} [post] func (h *Handler) StageCommitAndPush() http.HandlerFunc StageCommitAndPush godoc @Summary Stage, commit, and push changes @Description Stages, commits, and pushes changes to the remote repository @Tags git @ID stageCommitAndPush @Security CookieAuth @Produce json @Param workspace_name path string true "Workspace name" @Param body body CommitRequest true "Commit request" @Success 200 {object} CommitResponse @Failure 400 {object} ErrorResponse "Invalid request body" @Failure 400 {object} ErrorResponse "Commit message is required" @Failure 500 {object} ErrorResponse "Failed to stage, commit, and push changes" @Router /workspaces/{workspace_name}/git/commit [post] func (h *Handler) UpdateLastOpenedFile() http.HandlerFunc UpdateLastOpenedFile godoc @Summary Update last opened file @Description Updates the last opened file in the user's workspace @Tags files @ID updateLastOpenedFile @Security CookieAuth @Accept json @Produce json @Param workspace_name path string true "Workspace name" @Param body body UpdateLastOpenedFileRequest true "Update last opened file request" @Success 204 "No Content - Last opened file updated successfully" @Failure 400 {object} ErrorResponse "Invalid request body" @Failure 400 {object} ErrorResponse "Invalid file path" @Failure 404 {object} ErrorResponse "File not found" @Failure 500 {object} ErrorResponse "Failed to update file" @Router /workspaces/{workspace_name}/files/last [put] func (h *Handler) UpdateLastWorkspaceName() http.HandlerFunc UpdateLastWorkspaceName godoc @Summary Update last workspace name @Description Updates the name of the last opened workspace @Tags workspaces @ID updateLastWorkspaceName @Security CookieAuth @Accept json @Produce json @Success 204 "No Content - Last workspace updated successfully" @Failure 400 {object} ErrorResponse "Invalid request body" @Failure 500 {object} ErrorResponse "Failed to update last workspace" @Router /workspaces/last [put] func (h *Handler) UpdateProfile() http.HandlerFunc UpdateProfile godoc @Summary Update profile @Description Updates the user's profile @Tags users @ID updateProfile @Security CookieAuth @Accept json @Produce json @Param body body UpdateProfileRequest true "Profile update request" @Success 200 {object} models.User @Failure 400 {object} ErrorResponse "Invalid request body" @Failure 400 {object} ErrorResponse "Current password is required to change password" @Failure 400 {object} ErrorResponse "New password must be at least 8 characters long" @Failure 400 {object} ErrorResponse "Current password is required to change email" @Failure 401 {object} ErrorResponse "Current password is incorrect" @Failure 404 {object} ErrorResponse "User not found" @Failure 409 {object} ErrorResponse "Email already in use" @Failure 500 {object} ErrorResponse "Failed to process new password" @Failure 500 {object} ErrorResponse "Failed to update profile" @Router /profile [put] func (h *Handler) UpdateWorkspace() http.HandlerFunc UpdateWorkspace godoc @Summary Update workspace @Description Updates the current workspace @Tags workspaces @ID updateWorkspace @Security CookieAuth @Accept json @Produce json @Param workspace_name path string true "Workspace name" @Param body body models.Workspace true "Workspace" @Success 200 {object} models.Workspace @Failure 400 {object} ErrorResponse "Invalid request body" @Failure 500 {object} ErrorResponse "Failed to update workspace" @Failure 500 {object} ErrorResponse "Failed to setup git repo" @Router /workspaces/{workspace_name} [put] type LastOpenedFileResponse struct { LastOpenedFilePath string `json:"lastOpenedFilePath"` } LastOpenedFileResponse represents a response to a last opened file request type LastWorkspaceNameResponse struct { LastWorkspaceName string `json:"lastWorkspaceName"` } LastWorkspaceNameResponse contains the name of the last opened workspace type LoginRequest struct { Email string `json:"email"` Password string `json:"password"` } LoginRequest represents a user login request type LoginResponse struct { User *models.User `json:"user"` SessionID string `json:"sessionId,omitempty"` ExpiresAt time.Time `json:"expiresAt,omitempty"` } LoginResponse represents a user login response type LookupResponse struct { Paths []string `json:"paths"` } LookupResponse represents a response to a file lookup request type PullResponse struct { Message string `json:"message" example:"Pulled changes from remote"` } PullResponse represents a response to a pull http request type SaveFileResponse struct { FilePath string `json:"filePath"` Size int64 `json:"size"` UpdatedAt time.Time `json:"updatedAt"` } SaveFileResponse represents a response to a save file request type StaticHandler struct { // Has unexported fields. } StaticHandler serves static files with support for SPA routing and pre-compressed files func NewStaticHandler(staticPath string) *StaticHandler NewStaticHandler creates a new StaticHandler with the given static path func (h *StaticHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) ServeHTTP serves the static files type SystemStats struct { *db.UserStats *storage.FileCountStats } SystemStats holds system-wide statistics type UpdateLastOpenedFileRequest struct { FilePath string `json:"filePath"` } UpdateLastOpenedFileRequest represents a request to update the last opened file type UpdateProfileRequest struct { DisplayName string `json:"displayName"` Email string `json:"email"` CurrentPassword string `json:"currentPassword"` NewPassword string `json:"newPassword"` } UpdateProfileRequest represents a user profile update request type UpdateUserRequest struct { Email string `json:"email,omitempty"` DisplayName string `json:"displayName,omitempty"` Password string `json:"password,omitempty"` Role models.UserRole `json:"role,omitempty"` } UpdateUserRequest holds the request fields for updating a user type WorkspaceStats struct { UserID int `json:"userID"` UserEmail string `json:"userEmail"` WorkspaceID int `json:"workspaceID"` WorkspaceName string `json:"workspaceName"` WorkspaceCreatedAt time.Time `json:"workspaceCreatedAt"` *storage.FileCountStats } WorkspaceStats holds workspace statistics ``` ## internal/logging ```go package logging // import "lemma/internal/logging" Package logging provides a simple logging interface for the server. FUNCTIONS func Debug(msg string, args ...any) Debug logs a debug message func Error(msg string, args ...any) Error logs an error message func Info(msg string, args ...any) Info logs an info message func Setup(minLevel LogLevel) Setup initializes the logger with the given minimum log level func Warn(msg string, args ...any) Warn logs a warning message TYPES type LogLevel slog.Level LogLevel represents the log level const ( DEBUG LogLevel = LogLevel(slog.LevelDebug) INFO LogLevel = LogLevel(slog.LevelInfo) WARN LogLevel = LogLevel(slog.LevelWarn) ERROR LogLevel = LogLevel(slog.LevelError) ) Log levels func ParseLogLevel(level string) LogLevel ParseLogLevel converts a string to a LogLevel type Logger interface { Debug(msg string, args ...any) Info(msg string, args ...any) Warn(msg string, args ...any) Error(msg string, args ...any) WithGroup(name string) Logger With(args ...any) Logger } Logger represents the interface for logging operations func With(args ...any) Logger With adds key-value pairs to the logger context func WithGroup(name string) Logger WithGroup adds a group to the logger context ``` ## internal/models ```go package models // import "lemma/internal/models" Package models contains the data models used throughout the application. These models are used to represent data in the database, as well as to validate and serialize data in the application. TYPES type Session struct { ID string `db:"id"` // Unique session identifier UserID int `db:"user_id"` // ID of the user this session belongs to RefreshToken string `db:"refresh_token"` // The refresh token associated with this session ExpiresAt time.Time `db:"expires_at"` // When this session expires CreatedAt time.Time `db:"created_at,default"` // When this session was created } Session represents a user session in the database type User struct { ID int `json:"id" db:"id,default" validate:"required,min=1"` Email string `json:"email" db:"email" validate:"required,email"` DisplayName string `json:"displayName" db:"display_name"` PasswordHash string `json:"-" db:"password_hash"` Role UserRole `json:"role" db:"role" validate:"required,oneof=admin editor viewer"` CreatedAt time.Time `json:"createdAt" db:"created_at,default"` LastWorkspaceID int `json:"lastWorkspaceId" db:"last_workspace_id"` } User represents a user in the system func (u *User) Validate() error Validate validates the user struct type UserRole string UserRole represents the role of a user in the system const ( RoleAdmin UserRole = "admin" RoleEditor UserRole = "editor" RoleViewer UserRole = "viewer" ) User roles type Workspace struct { ID int `json:"id" db:"id,default" validate:"required,min=1"` UserID int `json:"userId" db:"user_id" validate:"required,min=1"` Name string `json:"name" db:"name" validate:"required"` CreatedAt time.Time `json:"createdAt" db:"created_at,default"` LastOpenedFilePath string `json:"lastOpenedFilePath" db:"last_opened_file_path"` // Integrated settings Theme string `json:"theme" db:"theme" validate:"oneof=light dark"` AutoSave bool `json:"autoSave" db:"auto_save"` ShowHiddenFiles bool `json:"showHiddenFiles" db:"show_hidden_files"` GitEnabled bool `json:"gitEnabled" db:"git_enabled"` GitURL string `json:"gitUrl" db:"git_url,ommitempty" validate:"required_if=GitEnabled true"` GitUser string `json:"gitUser" db:"git_user,ommitempty" validate:"required_if=GitEnabled true"` GitToken string `json:"gitToken" db:"git_token,ommitempty,encrypted" validate:"required_if=GitEnabled true"` GitAutoCommit bool `json:"gitAutoCommit" db:"git_auto_commit"` GitCommitMsgTemplate string `json:"gitCommitMsgTemplate" db:"git_commit_msg_template"` GitCommitName string `json:"gitCommitName" db:"git_commit_name"` GitCommitEmail string `json:"gitCommitEmail" db:"git_commit_email" validate:"omitempty,required_if=GitEnabled true,email"` } Workspace represents a user's workspace in the system func (w *Workspace) SetDefaultSettings() SetDefaultSettings sets the default settings for the workspace func (w *Workspace) Validate() error Validate validates the workspace struct func (w *Workspace) ValidateGitSettings() error ValidateGitSettings validates the git settings if git is enabled ``` ## internal/secrets ```go package secrets // import "lemma/internal/secrets" Package secrets provides an Encryptor interface for encrypting and decrypting strings using AES-256-GCM. FUNCTIONS func ValidateKey(key string) error ValidateKey checks if the provided base64-encoded key is suitable for AES-256 TYPES type Service interface { Encrypt(plaintext string) (string, error) Decrypt(ciphertext string) (string, error) } Service is an interface for encrypting and decrypting strings func NewService(key string) (Service, error) NewService creates a new Encryptor instance with the provided base64-encoded key ``` ## internal/storage ```go package storage // import "lemma/internal/storage" Package storage provides functionalities to interact with the storage system (filesystem). FUNCTIONS func IsPathValidationError(err error) bool IsPathValidationError checks if the error is a PathValidationError TYPES type FileCountStats struct { TotalFiles int `json:"totalFiles"` TotalSize int64 `json:"totalSize"` } FileCountStats holds statistics about files in a workspace type FileManager interface { ListFilesRecursively(userID, workspaceID int) ([]FileNode, error) FindFileByName(userID, workspaceID int, filename string) ([]string, error) GetFileContent(userID, workspaceID int, filePath string) ([]byte, error) SaveFile(userID, workspaceID int, filePath string, content []byte) error DeleteFile(userID, workspaceID int, filePath string) error GetFileStats(userID, workspaceID int) (*FileCountStats, error) GetTotalFileStats() (*FileCountStats, error) } FileManager provides functionalities to interact with files in the storage. type FileNode struct { ID string `json:"id"` Name string `json:"name"` Path string `json:"path"` Children []FileNode `json:"children,omitempty"` } FileNode represents a file or directory in the storage. type Manager interface { FileManager WorkspaceManager RepositoryManager } Manager interface combines all storage interfaces. type Options struct { Fs fileSystem NewGitClient func(url, user, token, path, commitName, commitEmail string) git.Client } Options represents the options for the storage service. type PathValidationError struct { Path string Message string } PathValidationError represents a path validation error (e.g., path traversal attempt) func (e *PathValidationError) Error() string type RepositoryManager interface { SetupGitRepo(userID, workspaceID int, gitURL, gitUser, gitToken, commitName, commitEmail string) error DisableGitRepo(userID, workspaceID int) StageCommitAndPush(userID, workspaceID int, message string) (git.CommitHash, error) Pull(userID, workspaceID int) error } RepositoryManager defines the interface for managing Git repositories. type Service struct { RootDir string GitRepos map[int]map[int]git.Client // map[userID]map[workspaceID]*git.Client // Has unexported fields. } Service represents the file system structure. func NewService(rootDir string) *Service NewService creates a new Storage instance with the default options and the given rootDir root directory. func NewServiceWithOptions(rootDir string, options Options) *Service NewServiceWithOptions creates a new Storage instance with the given options and the given rootDir root directory. func (s *Service) DeleteFile(userID, workspaceID int, filePath string) error DeleteFile deletes the file at the given filePath. Path must be a relative path within the workspace directory given by userID and workspaceID. func (s *Service) DeleteUserWorkspace(userID, workspaceID int) error DeleteUserWorkspace deletes the workspace directory for the given userID and workspaceID. func (s *Service) DisableGitRepo(userID, workspaceID int) DisableGitRepo disables the Git repository for the given userID and workspaceID. func (s *Service) FindFileByName(userID, workspaceID int, filename string) ([]string, error) FindFileByName returns a list of file paths that match the given filename. Files are searched recursively in the workspace directory and its subdirectories. Workspace is identified by the given userID and workspaceID. func (s *Service) GetFileContent(userID, workspaceID int, filePath string) ([]byte, error) GetFileContent returns the content of the file at the given filePath. Path must be a relative path within the workspace directory given by userID and workspaceID. func (s *Service) GetFileStats(userID, workspaceID int) (*FileCountStats, error) GetFileStats returns the total number of files and related statistics in a workspace Workspace is identified by the given userID and workspaceID func (s *Service) GetTotalFileStats() (*FileCountStats, error) GetTotalFileStats returns the total file statistics for the storage. func (s *Service) GetWorkspacePath(userID, workspaceID int) string GetWorkspacePath returns the path to the workspace directory for the given userID and workspaceID. func (s *Service) InitializeUserWorkspace(userID, workspaceID int) error InitializeUserWorkspace creates the workspace directory for the given userID and workspaceID. func (s *Service) ListFilesRecursively(userID, workspaceID int) ([]FileNode, error) ListFilesRecursively returns a list of all files in the workspace directory and its subdirectories. Workspace is identified by the given userID and workspaceID. func (s *Service) Pull(userID, workspaceID int) error Pull pulls the changes from the remote Git repository. The git repository belongs to the given userID and is associated with the given workspaceID. func (s *Service) SaveFile(userID, workspaceID int, filePath string, content []byte) error SaveFile writes the content to the file at the given filePath. Path must be a relative path within the workspace directory given by userID and workspaceID. func (s *Service) SetupGitRepo(userID, workspaceID int, gitURL, gitUser, gitToken, commitName, commitEmail string) error SetupGitRepo sets up a Git repository for the given userID and workspaceID. The repository is cloned from the given gitURL using the given gitUser and gitToken. func (s *Service) StageCommitAndPush(userID, workspaceID int, message string) (git.CommitHash, error) StageCommitAndPush stages, commit with the message, and pushes the changes to the Git repository. The git repository belongs to the given userID and is associated with the given workspaceID. func (s *Service) ValidatePath(userID, workspaceID int, path string) (string, error) ValidatePath validates the if the given path is valid within the workspace directory. Workspace directory is defined as the directory for the given userID and workspaceID. type WorkspaceManager interface { ValidatePath(userID, workspaceID int, path string) (string, error) GetWorkspacePath(userID, workspaceID int) string InitializeUserWorkspace(userID, workspaceID int) error DeleteUserWorkspace(userID, workspaceID int) error } WorkspaceManager provides functionalities to interact with workspaces in the storage. ``` ## internal/testenv ```go package testenv // import "lemma/internal/testenv" Package testenv provides a setup for testing the application. ```