TTime/backend/internal/database/db.go

185 lines
5.1 KiB
Go

package database
import (
"database/sql"
"embed"
"fmt"
"os"
"path/filepath"
"time"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
)
// Interface for the database
type Database interface {
// Insert a new user into the database, password should be hashed before calling
AddUser(username string, password string) error
RemoveUser(username string) error
PromoteToAdmin(username string) error
GetUserId(username string) (int, error)
AddProject(name string, description string, username string) error
Migrate(dirname string) error
// AddTimeReport(projectname string, start time.Time, end time.Time) error
// AddUserToProject(username string, projectname string) error
// ChangeUserRole(username string, projectname string, role string) error
// AddTimeReport(projectname string, start time.Time, end time.Time) error
// AddUserToProject(username string, projectname string) error
ChangeUserRole(username string, projectname string, role string) error
GetProjectId(projectname string) (int, error)
}
// This struct is a wrapper type that holds the database connection
// Internally DB holds a connection pool, so it's safe for concurrent use
type Db struct {
*sqlx.DB
}
//go:embed migrations
var scripts embed.FS
const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)"
const projectInsert = "INSERT INTO projects (name, description, owner_user_id) SELECT ?, ?, id FROM users WHERE username = ?"
const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?"
const addTimeReport = "INSERT INTO activity (report_id, activity_nbr, start_time, end_time, break, comment) VALUES (?, ?, ?, ?, ?, ?)" // WIP
const addUserToProject = "INSERT INTO project_member (project_id, user_id, role) VALUES (?, ?, ?)" // WIP
// DbConnect connects to the database
func DbConnect(dbpath string) Database {
// Open the database
db, err := sqlx.Connect("sqlite3", dbpath)
if err != nil {
panic(err)
}
// Ping forces the connection to be established
err = db.Ping()
if err != nil {
panic(err)
}
return &Db{db}
}
func (d *Db) ChangeUserRole(username string, projectname string, role string) error {
userID, err := d.GetUserId(username)
if err != nil {
return err
}
projectID, err := d.GetProjectId(projectname)
if err != nil {
return err
}
// Update user role in the project using the correct table name
_, err = d.Exec("INSERT OR REPLACE INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)", userID, projectID, role)
return err
}
func (d *Db) AddTimeReport(projectname string, start time.Time, end time.Time, breakTime uint32) error { // WIP
_, err := d.Exec(addTimeReport, projectname, 0, start, end, breakTime, false)
return err
}
func (d *Db) AddUserToProject(username string, projectname string, role string) error { // WIP
var userid int
userid, err := d.GetUserId(username)
if err != nil {
panic(err)
}
var projectid int
projectid, err2 := d.GetProjectId(projectname)
if err2 != nil {
panic(err2)
}
_, err3 := d.Exec(addUserToProject, projectid, userid, role)
return err3
}
// func (d *Db) ChangeUserRole(username string, projectname string, role string) error {
// }
// AddUser adds a user to the database
func (d *Db) AddUser(username string, password string) error {
_, err := d.Exec(userInsert, username, password)
return err
}
// Removes a user from the database
func (d *Db) RemoveUser(username string) error {
_, err := d.Exec("DELETE FROM users WHERE username = ?", username)
return err
}
func (d *Db) PromoteToAdmin(username string) error {
_, err := d.Exec(promoteToAdmin, username)
return err
}
func (d *Db) GetUserId(username string) (int, error) {
var id int
err := d.Get(&id, "SELECT id FROM users WHERE username = ?", username) // Borde det inte vara "user" i singular
return id, err
}
func (d *Db) GetProjectId(projectname string) (int, error) {
var id int
err := d.Get(&id, "SELECT id FROM projects WHERE name = ?", projectname)
if err != nil {
if err == sql.ErrNoRows {
return 0, fmt.Errorf("project '%s' not found", projectname)
}
return 0, err
}
return id, nil
}
// Creates a new project in the database, associated with a user
func (d *Db) AddProject(name string, description string, username string) error {
_, err := d.Exec(projectInsert, name, description, username)
return err
}
// Reads a directory of migration files and applies them to the database.
// This will eventually be used on an embedded directory
func (d *Db) Migrate(dirname string) error {
// Read the embedded scripts directory
files, err := scripts.ReadDir("migrations")
if err != nil {
return err
}
tr := d.MustBegin()
// Iterate over each SQL file and execute it
for _, file := range files {
if file.IsDir() || filepath.Ext(file.Name()) != ".sql" {
continue
}
// This is perhaps not the most elegant way to do this
sqlFile := filepath.Join("migrations", file.Name())
sqlBytes, err := os.ReadFile(sqlFile)
if err != nil {
return err
}
sqlQuery := string(sqlBytes)
_, err = tr.Exec(sqlQuery)
if err != nil {
return err
}
}
if tr.Commit() != nil {
return err
}
return nil
}