package db import ( "me-fit/types" "me-fit/utils" "database/sql" "errors" "strings" "time" "github.com/google/uuid" ) var ( ErrUserNotFound = errors.New("User not found") ErrUserExists = errors.New("User already exists") ErrSessionNotFound = errors.New("Session not found") ) type User struct { Id uuid.UUID Email string EmailVerified bool EmailVerifiedAt *time.Time IsAdmin bool Password []byte Salt []byte CreateAt time.Time } func NewUser(id uuid.UUID, email string, emailVerified bool, emailVerifiedAt *time.Time, isAdmin bool, password []byte, salt []byte, createAt time.Time) *User { return &User{ Id: id, Email: email, EmailVerified: emailVerified, EmailVerifiedAt: emailVerifiedAt, IsAdmin: isAdmin, Password: password, Salt: salt, CreateAt: createAt, } } type Session struct { Id string UserId uuid.UUID CreatedAt time.Time } func NewSession(id string, userId uuid.UUID, createdAt time.Time) *Session { return &Session{ Id: id, UserId: userId, CreatedAt: createdAt, } } type AuthDb interface { InsertUser(user *User) error GetUser(email string) (*User, error) GetUserById(userId uuid.UUID) (*User, error) DeleteUser(userId uuid.UUID) error UpdateUserPassword(userId uuid.UUID, newHash []byte) error InsertEmailVerificationToken(userId uuid.UUID, token string) error GetEmailVerificationToken(userId uuid.UUID) (string, error) InsertSession(session *Session) error GetSession(sessionId string) (*Session, error) DeleteOldSessions(userId uuid.UUID) error DeleteSession(sessionId string) error } type AuthDbSqlite struct { db *sql.DB } func NewAuthDbSqlite(db *sql.DB) *AuthDbSqlite { return &AuthDbSqlite{db: db} } func (db AuthDbSqlite) InsertUser(user *User) error { _, err := db.db.Exec(` INSERT INTO user (user_uuid, email, email_verified, email_verified_at, is_admin, password, salt, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, user.Id, user.Email, user.EmailVerified, user.EmailVerifiedAt, user.IsAdmin, user.Password, user.Salt, user.CreateAt) if err != nil { if strings.Contains(err.Error(), "email") { return ErrUserExists } utils.LogError("SQL error InsertUser", err) return types.ErrInternal } return nil } func (db AuthDbSqlite) GetUser(email string) (*User, error) { var ( userId uuid.UUID emailVerified bool emailVerifiedAt *time.Time isAdmin bool password []byte salt []byte createdAt time.Time ) err := db.db.QueryRow(` SELECT user_uuid, email_verified, email_verified_at, password, salt, created_at FROM user WHERE email = ?`, email).Scan(&userId, &emailVerified, &emailVerifiedAt, &password, &salt, &createdAt) if err != nil { if err == sql.ErrNoRows { return nil, ErrUserNotFound } else { utils.LogError("SQL error GetUser", err) return nil, types.ErrInternal } } return NewUser(userId, email, emailVerified, emailVerifiedAt, isAdmin, password, salt, createdAt), nil } func (db AuthDbSqlite) GetUserById(userId uuid.UUID) (*User, error) { var ( email string emailVerified bool emailVerifiedAt *time.Time isAdmin bool password []byte salt []byte createdAt time.Time ) err := db.db.QueryRow(` SELECT email, email_verified, email_verified_at, password, salt, created_at FROM user WHERE user_uuid = ?`, userId).Scan(&email, &emailVerified, &emailVerifiedAt, &password, &salt, &createdAt) if err != nil { if err == sql.ErrNoRows { return nil, ErrUserNotFound } else { utils.LogError("SQL error GetUser", err) return nil, types.ErrInternal } } return NewUser(userId, email, emailVerified, emailVerifiedAt, isAdmin, password, salt, createdAt), nil } func (db AuthDbSqlite) DeleteUser(userId uuid.UUID) error { tx, err := db.db.Begin() if err != nil { utils.LogError("Could not start transaction", err) return types.ErrInternal } _, err = tx.Exec("DELETE FROM workout WHERE user_id = ?", userId) if err != nil { tx.Rollback() utils.LogError("Could not delete workouts", err) return types.ErrInternal } _, err = tx.Exec("DELETE FROM user_token WHERE user_uuid = ?", userId) if err != nil { tx.Rollback() utils.LogError("Could not delete user tokens", err) return types.ErrInternal } _, err = tx.Exec("DELETE FROM session WHERE user_uuid = ?", userId) if err != nil { tx.Rollback() utils.LogError("Could not delete sessions", err) return types.ErrInternal } _, err = tx.Exec("DELETE FROM user WHERE user_uuid = ?", userId) if err != nil { tx.Rollback() utils.LogError("Could not delete user", err) return types.ErrInternal } err = tx.Commit() if err != nil { utils.LogError("Could not commit transaction", err) return types.ErrInternal } return nil } func (db AuthDbSqlite) UpdateUserPassword(userId uuid.UUID, newHash []byte) error { _, err := db.db.Exec("UPDATE user SET password = ? WHERE user_uuid = ?", newHash, userId) if err != nil { utils.LogError("Could not update password", err) return types.ErrInternal } return nil } func (db AuthDbSqlite) InsertEmailVerificationToken(userId uuid.UUID, token string) error { _, err := db.db.Exec(` INSERT INTO user_token (user_uuid, type, token, created_at) VALUES (?, 'email_verify', ?, datetime())`, userId, token) if err != nil { utils.LogError("Could not insert token", err) return types.ErrInternal } return nil } func (db AuthDbSqlite) GetEmailVerificationToken(userId uuid.UUID) (string, error) { var token string err := db.db.QueryRow(` SELECT token FROM user_token WHERE user_uuid = ? AND type = 'email_verify'`, userId).Scan(&token) if err != nil && err != sql.ErrNoRows { utils.LogError("Could not get token", err) return "", types.ErrInternal } return token, nil } func (db AuthDbSqlite) InsertSession(session *Session) error { _, err := db.db.Exec(` INSERT INTO session (session_id, user_uuid, created_at) VALUES (?, ?, ?)`, session.Id, session.UserId, session.CreatedAt) if err != nil { utils.LogError("Could not insert new session", err) return types.ErrInternal } return nil } func (db AuthDbSqlite) GetSession(sessionId string) (*Session, error) { var ( userId uuid.UUID sessionCreatedAt time.Time ) err := db.db.QueryRow(` SELECT u.user_uuid, s.created_at FROM session s INNER JOIN user u ON s.user_uuid = u.user_uuid WHERE session_id = ?`, sessionId).Scan(&userId, &sessionCreatedAt) if err != nil { return nil, ErrSessionNotFound } return NewSession(sessionId, userId, sessionCreatedAt), nil } func (db AuthDbSqlite) DeleteOldSessions(userId uuid.UUID) error { // Delete old inactive sessions _, err := db.db.Exec("DELETE FROM session WHERE created_at < datetime('now','-8 hours') AND user_uuid = ?", userId) if err != nil { utils.LogError("Could not delete old sessions", err) return types.ErrInternal } return nil } func (db AuthDbSqlite) DeleteSession(sessionId string) error { if sessionId != "" { _, err := db.db.Exec("DELETE FROM session WHERE session_id = ?", sessionId) if err != nil { utils.LogError("Could not delete session", err) return types.ErrInternal } } return nil }