Files
lemma/server/documentation.md
2025-03-05 21:31:58 +01:00

48 KiB

Lemma Package Documentation

Generated documentation for all packages in the Lemma project.

Table of Contents

cmd/server

Package main provides the entry point for the application. It loads the
configuration, initializes the server, and starts the server.

docs

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

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

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

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

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

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

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

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

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

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

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

package testenv // import "lemma/internal/testenv"

Package testenv provides a setup for testing the application.