diff --git a/server/internal/db/sessions.go b/server/internal/db/sessions.go index 7df51c6..66c148b 100644 --- a/server/internal/db/sessions.go +++ b/server/internal/db/sessions.go @@ -10,11 +10,12 @@ import ( // CreateSession inserts a new session record into the database func (db *database) CreateSession(session *models.Session) error { - query := NewQuery(db.dbType). - Insert("sessions", "id", "user_id", "refresh_token", "expires_at", "created_at"). - Values(5). - AddArgs(session.ID, session.UserID, session.RefreshToken, session.ExpiresAt, session.CreatedAt) - _, err := db.Exec(query.String(), query.Args()...) + query, err := NewQuery(db.dbType). + InsertStruct(session, "sessions") + if err != nil { + return fmt.Errorf("failed to create query: %w", err) + } + _, err = db.Exec(query.String(), query.Args()...) if err != nil { return fmt.Errorf("failed to store session: %w", err) } diff --git a/server/internal/db/struct_query.go b/server/internal/db/struct_query.go index b465b52..c18b6d5 100644 --- a/server/internal/db/struct_query.go +++ b/server/internal/db/struct_query.go @@ -8,9 +8,10 @@ import ( ) type DBField struct { - Name string - Value any - Type reflect.Type + Name string + Value any + Type reflect.Type + useDefault bool } func StructTagsToFields(s any) ([]DBField, error) { @@ -47,14 +48,28 @@ func StructTagsToFields(s any) ([]DBField, error) { tag = toSnakeCase(f.Name) } - if strings.Contains(tag, "omitempty") && reflect.DeepEqual(v.Field(i).Interface(), reflect.Zero(f.Type).Interface()) { - continue + useDefault := false + if strings.Contains(tag, ",") { + parts := strings.Split(tag, ",") + tag = parts[0] + + for _, opt := range parts[1:] { + switch opt { + case "omitempty": + if reflect.DeepEqual(v.Field(i).Interface(), reflect.Zero(f.Type).Interface()) { + continue + } + case "default": + useDefault = true + } + } } fields = append(fields, DBField{ - Name: tag, - Value: v.Field(i).Interface(), - Type: f.Type, + Name: tag, + Value: v.Field(i).Interface(), + Type: f.Type, + useDefault: useDefault, }) } return fields, nil @@ -86,6 +101,10 @@ func (q *Query) InsertStruct(s any, table string) (*Query, error) { values := make([]any, 0, len(fields)) for _, f := range fields { + if f.useDefault { + continue + } + columns = append(columns, f.Name) values = append(values, f.Value) } diff --git a/server/internal/db/users.go b/server/internal/db/users.go index 5260a19..b326949 100644 --- a/server/internal/db/users.go +++ b/server/internal/db/users.go @@ -17,11 +17,14 @@ func (db *database) CreateUser(user *models.User) (*models.User, error) { } defer tx.Rollback() - query := NewQuery(db.dbType). - Insert("users", "email", "display_name", "password_hash", "role"). - Values(4). - AddArgs(user.Email, user.DisplayName, user.PasswordHash, user.Role). - Returning("id", "created_at") + query, err := NewQuery(db.dbType). + InsertStruct(user, "users") + + if err != nil { + return nil, fmt.Errorf("failed to create query: %w", err) + } + + query.Returning("id", "created_at") err = tx.QueryRow(query.String(), query.Args()...). Scan(&user.ID, &user.CreatedAt) @@ -69,23 +72,16 @@ func (db *database) CreateUser(user *models.User) (*models.User, error) { func (db *database) createWorkspaceTx(tx *sql.Tx, workspace *models.Workspace) error { log := getLogger().WithGroup("users") - insertQuery := NewQuery(db.dbType). - Insert("workspaces", - "user_id", "name", - "theme", "auto_save", "show_hidden_files", - "git_enabled", "git_url", "git_user", "git_token", - "git_auto_commit", "git_commit_msg_template", - "git_commit_name", "git_commit_email"). - Values(13). - AddArgs( - workspace.UserID, workspace.Name, - workspace.Theme, workspace.AutoSave, workspace.ShowHiddenFiles, - workspace.GitEnabled, workspace.GitURL, workspace.GitUser, workspace.GitToken, - workspace.GitAutoCommit, workspace.GitCommitMsgTemplate, - workspace.GitCommitName, workspace.GitCommitEmail). - Returning("id") + insertQuery, err := NewQuery(db.dbType). + InsertStruct(workspace, "workspaces") - err := tx.QueryRow(insertQuery.String(), insertQuery.Args()...).Scan(&workspace.ID) + if err != nil { + return fmt.Errorf("failed to create query: %w", err) + } + + insertQuery.Returning("id") + + err = tx.QueryRow(insertQuery.String(), insertQuery.Args()...).Scan(&workspace.ID) if err != nil { return fmt.Errorf("failed to insert workspace: %w", err) } diff --git a/server/internal/db/workspaces.go b/server/internal/db/workspaces.go index 06bff0c..dc07911 100644 --- a/server/internal/db/workspaces.go +++ b/server/internal/db/workspaces.go @@ -24,19 +24,16 @@ func (db *database) CreateWorkspace(workspace *models.Workspace) error { if err != nil { return fmt.Errorf("failed to encrypt token: %w", err) } + workspace.GitToken = encryptedToken - query := NewQuery(db.dbType). - Insert("workspaces", - "user_id", "name", "theme", "auto_save", "show_hidden_files", - "git_enabled", "git_url", "git_user", "git_token", - "git_auto_commit", "git_commit_msg_template", - "git_commit_name", "git_commit_email"). - Values(13). - AddArgs( - workspace.UserID, workspace.Name, workspace.Theme, workspace.AutoSave, workspace.ShowHiddenFiles, - workspace.GitEnabled, workspace.GitURL, workspace.GitUser, encryptedToken, - workspace.GitAutoCommit, workspace.GitCommitMsgTemplate, workspace.GitCommitName, workspace.GitCommitEmail). - Returning("id", "created_at") + query, err := NewQuery(db.dbType). + InsertStruct(workspace, "workspaces") + + if err != nil { + return fmt.Errorf("failed to create query: %w", err) + } + + query.Returning("id", "created_at") err = db.QueryRow(query.String(), query.Args()...). Scan(&workspace.ID, &workspace.CreatedAt) diff --git a/server/internal/models/session.go b/server/internal/models/session.go index d0b8119..305dd85 100644 --- a/server/internal/models/session.go +++ b/server/internal/models/session.go @@ -5,9 +5,9 @@ import "time" // Session represents a user session in the database type Session struct { - ID string // Unique session identifier - UserID int // ID of the user this session belongs to - RefreshToken string // The refresh token associated with this session - ExpiresAt time.Time // When this session expires - CreatedAt time.Time // When this session was created + ID string `db:"id,default"` // 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 } diff --git a/server/internal/models/user.go b/server/internal/models/user.go index 3832cda..11d6903 100644 --- a/server/internal/models/user.go +++ b/server/internal/models/user.go @@ -20,13 +20,13 @@ const ( // User represents a user in the system type User struct { - ID int `json:"id" validate:"required,min=1"` - Email string `json:"email" validate:"required,email"` - DisplayName string `json:"displayName"` - PasswordHash string `json:"-"` - Role UserRole `json:"role" validate:"required,oneof=admin editor viewer"` - CreatedAt time.Time `json:"createdAt"` - LastWorkspaceID int `json:"lastWorkspaceId"` + 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"` } // Validate validates the user struct diff --git a/server/internal/models/workspace.go b/server/internal/models/workspace.go index 6589957..886847b 100644 --- a/server/internal/models/workspace.go +++ b/server/internal/models/workspace.go @@ -6,24 +6,24 @@ import ( // Workspace represents a user's workspace in the system type Workspace struct { - ID int `json:"id" validate:"required,min=1"` - UserID int `json:"userId" validate:"required,min=1"` - Name string `json:"name" validate:"required"` - CreatedAt time.Time `json:"createdAt"` - LastOpenedFilePath string `json:"lastOpenedFilePath"` + 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" validate:"oneof=light dark"` - AutoSave bool `json:"autoSave"` - ShowHiddenFiles bool `json:"showHiddenFiles"` - GitEnabled bool `json:"gitEnabled"` - GitURL string `json:"gitUrl" validate:"required_if=GitEnabled true"` - GitUser string `json:"gitUser" validate:"required_if=GitEnabled true"` - GitToken string `json:"gitToken" validate:"required_if=GitEnabled true"` - GitAutoCommit bool `json:"gitAutoCommit"` - GitCommitMsgTemplate string `json:"gitCommitMsgTemplate"` - GitCommitName string `json:"gitCommitName"` - GitCommitEmail string `json:"gitCommitEmail" validate:"omitempty,required_if=GitEnabled true,email"` + 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" validate:"required_if=GitEnabled true"` + GitUser string `json:"gitUser" db:"git_user" validate:"required_if=GitEnabled true"` + GitToken string `json:"gitToken" db:"git_token" 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"` } // Validate validates the workspace struct