2024-02-12 12:40:49 +01:00
|
|
|
package database
|
|
|
|
|
|
|
|
import (
|
2024-02-28 03:30:05 +01:00
|
|
|
"embed"
|
2024-02-12 12:40:49 +01:00
|
|
|
"os"
|
2024-02-28 03:21:13 +01:00
|
|
|
"path/filepath"
|
2024-03-07 21:57:27 +01:00
|
|
|
"time"
|
2024-02-12 12:40:49 +01:00
|
|
|
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
|
|
)
|
|
|
|
|
2024-02-29 20:33:20 +01:00
|
|
|
// Interface for the database
|
|
|
|
type Database interface {
|
2024-03-12 20:44:40 +01:00
|
|
|
// Insert a new user into the database, password should be hashed before calling
|
2024-02-29 20:33:20 +01:00
|
|
|
AddUser(username string, password string) error
|
|
|
|
RemoveUser(username string) error
|
2024-03-07 13:21:47 +01:00
|
|
|
PromoteToAdmin(username string) error
|
2024-02-29 20:33:20 +01:00
|
|
|
GetUserId(username string) (int, error)
|
|
|
|
AddProject(name string, description string, username string) error
|
|
|
|
Migrate(dirname string) error
|
2024-03-14 13:27:57 +01:00
|
|
|
GetProjectId(projectname string) (int, error)
|
2024-03-14 13:39:56 +01:00
|
|
|
AddTimeReport(projectName string, userName string, start time.Time, end time.Time) error
|
|
|
|
AddUserToProject(username string, projectname string, role string) error
|
2024-03-14 13:47:04 +01:00
|
|
|
ChangeUserRole(username string, projectname string, role string) error
|
2024-02-29 20:33:20 +01:00
|
|
|
}
|
|
|
|
|
2024-02-27 05:00:04 +01:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2024-02-28 03:30:05 +01:00
|
|
|
//go:embed migrations
|
|
|
|
var scripts embed.FS
|
|
|
|
|
2024-03-14 13:39:56 +01:00
|
|
|
// TODO: Possibly break these out into separate files bundled with the embed package?
|
2024-02-27 05:00:04 +01:00
|
|
|
const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)"
|
2024-03-08 07:47:32 +01:00
|
|
|
const projectInsert = "INSERT INTO projects (name, description, owner_user_id) SELECT ?, ?, id FROM users WHERE username = ?"
|
2024-03-07 13:21:47 +01:00
|
|
|
const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?"
|
2024-03-14 13:39:56 +01:00
|
|
|
const addTimeReport = `WITH UserLookup AS (SELECT id FROM users WHERE username = ?),
|
|
|
|
ProjectLookup AS (SELECT id FROM projects WHERE name = ?)
|
|
|
|
INSERT INTO time_reports (project_id, user_id, start, end)
|
|
|
|
VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup), ?, ?);`
|
|
|
|
const addUserToProject = "INSERT INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)" // WIP
|
2024-03-14 13:47:04 +01:00
|
|
|
const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = ? AND project_id = ?"
|
2024-03-07 14:25:28 +01:00
|
|
|
|
2024-02-27 05:00:04 +01:00
|
|
|
// DbConnect connects to the database
|
2024-02-29 20:33:20 +01:00
|
|
|
func DbConnect(dbpath string) Database {
|
2024-02-12 12:40:49 +01:00
|
|
|
// Open the database
|
|
|
|
db, err := sqlx.Connect("sqlite3", dbpath)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2024-02-27 05:00:04 +01:00
|
|
|
// Ping forces the connection to be established
|
2024-02-12 12:40:49 +01:00
|
|
|
err = db.Ping()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2024-02-27 05:00:04 +01:00
|
|
|
return &Db{db}
|
|
|
|
}
|
|
|
|
|
2024-03-14 13:39:56 +01:00
|
|
|
func (d *Db) AddTimeReport(projectName string, userName string, start time.Time, end time.Time) error { // WIP
|
|
|
|
_, err := d.Exec(addTimeReport, userName, projectName, start, end)
|
2024-03-07 21:57:27 +01:00
|
|
|
return err
|
|
|
|
}
|
2024-03-14 13:27:57 +01:00
|
|
|
|
2024-03-07 23:24:54 +01:00
|
|
|
func (d *Db) AddUserToProject(username string, projectname string, role string) error { // WIP
|
|
|
|
var userid int
|
|
|
|
userid, err := d.GetUserId(username)
|
2024-03-14 13:27:57 +01:00
|
|
|
if err != nil {
|
2024-03-07 23:24:54 +01:00
|
|
|
panic(err)
|
2024-03-14 13:27:57 +01:00
|
|
|
}
|
|
|
|
|
2024-03-07 23:24:54 +01:00
|
|
|
var projectid int
|
|
|
|
projectid, err2 := d.GetProjectId(projectname)
|
|
|
|
if err2 != nil {
|
|
|
|
panic(err2)
|
|
|
|
}
|
2024-03-14 13:27:57 +01:00
|
|
|
|
2024-03-14 13:39:56 +01:00
|
|
|
_, err3 := d.Exec(addUserToProject, userid, projectid, role)
|
2024-03-07 23:24:54 +01:00
|
|
|
return err3
|
2024-03-07 21:57:27 +01:00
|
|
|
}
|
2024-03-07 20:58:50 +01:00
|
|
|
|
2024-03-14 13:39:56 +01:00
|
|
|
func (d *Db) ChangeUserRole(username string, projectname string, role string) error {
|
2024-03-07 23:24:54 +01:00
|
|
|
var userid int
|
|
|
|
userid, err := d.GetUserId(username)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2024-03-07 20:58:50 +01:00
|
|
|
|
2024-03-07 23:24:54 +01:00
|
|
|
var projectid int
|
|
|
|
projectid, err2 := d.GetProjectId(projectname)
|
|
|
|
if err2 != nil {
|
|
|
|
panic(err2)
|
|
|
|
}
|
|
|
|
|
2024-03-14 13:39:56 +01:00
|
|
|
_, err3 := d.Exec(changeUserRole, role, userid, projectid)
|
2024-03-07 23:24:54 +01:00
|
|
|
return err3
|
|
|
|
}
|
2024-03-07 20:58:50 +01:00
|
|
|
|
2024-02-27 05:00:04 +01:00
|
|
|
// 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
|
2024-02-12 12:40:49 +01:00
|
|
|
}
|
2024-02-27 05:51:16 +01:00
|
|
|
|
2024-03-07 13:21:47 +01:00
|
|
|
func (d *Db) PromoteToAdmin(username string) error {
|
|
|
|
_, err := d.Exec(promoteToAdmin, username)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-02-27 05:51:16 +01:00
|
|
|
func (d *Db) GetUserId(username string) (int, error) {
|
|
|
|
var id int
|
2024-03-07 23:24:54 +01:00
|
|
|
err := d.Get(&id, "SELECT id FROM users WHERE username = ?", username) // Borde det inte vara "user" i singular
|
|
|
|
return id, err
|
|
|
|
}
|
|
|
|
|
2024-03-14 13:27:57 +01:00
|
|
|
func (d *Db) GetProjectId(projectname string) (int, error) {
|
2024-03-07 23:24:54 +01:00
|
|
|
var id int
|
2024-03-14 13:27:57 +01:00
|
|
|
err := d.Get(&id, "SELECT id FROM projects WHERE name = ?", projectname)
|
2024-02-27 05:51:16 +01:00
|
|
|
return id, err
|
|
|
|
}
|
|
|
|
|
2024-02-27 07:59:42 +01:00
|
|
|
// Creates a new project in the database, associated with a user
|
2024-02-27 05:51:16 +01:00
|
|
|
func (d *Db) AddProject(name string, description string, username string) error {
|
2024-02-27 07:59:42 +01:00
|
|
|
_, err := d.Exec(projectInsert, name, description, username)
|
2024-02-27 05:51:16 +01:00
|
|
|
return err
|
|
|
|
}
|
2024-02-28 03:21:13 +01:00
|
|
|
|
|
|
|
// 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 {
|
2024-02-28 03:30:05 +01:00
|
|
|
// Read the embedded scripts directory
|
|
|
|
files, err := scripts.ReadDir("migrations")
|
2024-02-28 03:21:13 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-02-28 03:30:05 +01:00
|
|
|
// This is perhaps not the most elegant way to do this
|
|
|
|
sqlFile := filepath.Join("migrations", file.Name())
|
2024-02-28 03:21:13 +01:00
|
|
|
sqlBytes, err := os.ReadFile(sqlFile)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlQuery := string(sqlBytes)
|
|
|
|
_, err = tr.Exec(sqlQuery)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-02 04:29:50 +01:00
|
|
|
if tr.Commit() != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-02-28 03:21:13 +01:00
|
|
|
return nil
|
|
|
|
}
|