From 6c8abf1f539be9b8a3df7360da444c23b142af6d Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Thu, 14 Mar 2024 13:39:56 +0100 Subject: [PATCH] Database refactor and test coverage --- backend/internal/database/db.go | 39 +++++++++---- backend/internal/database/db_test.go | 56 ++++++++++++++++++- .../database/migrations/0020_projects.sql | 2 - .../database/migrations/0030_time_reports.sql | 3 +- 4 files changed, 84 insertions(+), 16 deletions(-) diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 5a88873..ce329af 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -20,8 +20,8 @@ type Database interface { 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 + AddTimeReport(projectName string, userName string, start time.Time, end time.Time) error + AddUserToProject(username string, projectname string, role 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 @@ -37,12 +37,16 @@ type Db struct { //go:embed migrations var scripts embed.FS +// TODO: Possibly break these out into separate files bundled with the embed package? 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 -// const changeUserRole = "" +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 +const changeUserRole = "UPDATE project_member SET role = ? WHERE user_id = ? AND project_id = ?" // DbConnect connects to the database func DbConnect(dbpath string) Database { @@ -61,8 +65,8 @@ func DbConnect(dbpath string) Database { return &Db{db} } -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) +func (d *Db) AddTimeReport(projectName string, userName string, start time.Time, end time.Time) error { // WIP + _, err := d.Exec(addTimeReport, userName, projectName, start, end) return err } @@ -79,13 +83,26 @@ func (d *Db) AddUserToProject(username string, projectname string, role string) panic(err2) } - _, err3 := d.Exec(addUserToProject, projectid, userid, role) + _, err3 := d.Exec(addUserToProject, userid, projectid, role) return err3 } -// func (d *Db) ChangeUserRole(username string, projectname string, role string) error { +func (d *Db) ChangeUserRole(username string, projectname string, role string) error { + 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(changeUserRole, role, userid, projectid) + return err3 +} // AddUser adds a user to the database func (d *Db) AddUser(username string, password string) error { @@ -112,7 +129,7 @@ func (d *Db) GetUserId(username string) (int, error) { func (d *Db) GetProjectId(projectname string) (int, error) { // WIP, denna kan vara goof var id int - err := d.Get(&id, "SELECT id FROM project WHERE project_name = ?", projectname) + err := d.Get(&id, "SELECT id FROM projects WHERE name = ?", projectname) return id, err } diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index 96eb9b7..7d431b9 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -2,6 +2,7 @@ package database import ( "testing" + "time" ) // Tests are not guaranteed to be sequential @@ -92,9 +93,60 @@ func TestPromoteToAdmin(t *testing.T) { } } -// func TestAddTimeReport(t *testing.T) { +func TestAddTimeReport(t *testing.T) { + db, err := setupState() + if err != nil { + t.Error("setupState failed:", err) + } -// } + err = db.AddUser("testuser", "password") + if err != nil { + t.Error("AddUser failed:", err) + } + + err = db.AddProject("testproject", "description", "testuser") + if err != nil { + t.Error("AddProject failed:", err) + } + + var now = time.Now() + var then = now.Add(time.Hour) + + err = db.AddTimeReport("testproject", "testuser", now, then) + if err != nil { + t.Error("AddTimeReport failed:", err) + } +} + +func TestAddUserToProject(t *testing.T) { + db, err := setupState() + if err != nil { + t.Error("setupState failed:", err) + } + + err = db.AddUser("testuser", "password") + if err != nil { + t.Error("AddUser failed:", err) + } + + err = db.AddProject("testproject", "description", "testuser") + if err != nil { + t.Error("AddProject failed:", err) + } + + var now = time.Now() + var then = now.Add(time.Hour) + + err = db.AddTimeReport("testproject", "testuser", now, then) + if err != nil { + t.Error("AddTimeReport failed:", err) + } + + err = db.AddUserToProject("testuser", "testproject", "user") + if err != nil { + t.Error("AddUserToProject failed:", err) + } +} // func TestAddUserToProject(t *testing.T) { diff --git a/backend/internal/database/migrations/0020_projects.sql b/backend/internal/database/migrations/0020_projects.sql index adfb818..58d8e97 100644 --- a/backend/internal/database/migrations/0020_projects.sql +++ b/backend/internal/database/migrations/0020_projects.sql @@ -1,11 +1,9 @@ CREATE TABLE IF NOT EXISTS projects ( id INTEGER PRIMARY KEY, - projectId TEXT DEFAULT (HEX(RANDOMBLOB(4))) NOT NULL UNIQUE, name VARCHAR(255) NOT NULL UNIQUE, description TEXT NOT NULL, owner_user_id INTEGER NOT NULL, FOREIGN KEY (owner_user_id) REFERENCES users (id) ); -CREATE INDEX IF NOT EXISTS projects_projectId_index ON projects (projectId); CREATE INDEX IF NOT EXISTS projects_user_id_index ON projects (owner_user_id); \ No newline at end of file diff --git a/backend/internal/database/migrations/0030_time_reports.sql b/backend/internal/database/migrations/0030_time_reports.sql index e8f3ec1..76812a1 100644 --- a/backend/internal/database/migrations/0030_time_reports.sql +++ b/backend/internal/database/migrations/0030_time_reports.sql @@ -1,10 +1,11 @@ CREATE TABLE IF NOT EXISTS time_reports ( id INTEGER PRIMARY KEY, - reportId TEXT DEFAULT (HEX(RANDOMBLOB(6))) NOT NULL UNIQUE, project_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, start DATETIME NOT NULL, end DATETIME NOT NULL, FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ); CREATE TRIGGER IF NOT EXISTS time_reports_start_before_end