mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-05 15:44:21 +00:00
48 KiB
48 KiB
Lemma Package Documentation
Generated documentation for all packages in the Lemma project.
Table of Contents
- cmd/server
- docs
- internal/app
- internal/auth
- internal/context
- internal/db
- internal/git
- internal/handlers
- internal/logging
- internal/models
- internal/secrets
- internal/storage
- internal/testenv
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.