From a22dcb9f4e642f0450afd85627e76d8b21368d0e Mon Sep 17 00:00:00 2001 From: borean Date: Mon, 4 Mar 2024 02:50:02 +0100 Subject: [PATCH 01/37] first push, broken import, WIP --- backend/internal/model/timereport | 19 ++++ backend/internal/model/user.go | 139 ++++++++++++++++++++++++++++++ go.work.sum | 5 ++ 3 files changed, 163 insertions(+) create mode 100644 backend/internal/model/timereport create mode 100644 backend/internal/model/user.go create mode 100644 go.work.sum diff --git a/backend/internal/model/timereport b/backend/internal/model/timereport new file mode 100644 index 0000000..d48c5fb --- /dev/null +++ b/backend/internal/model/timereport @@ -0,0 +1,19 @@ +package model + +type TimeReport struct { + reportId string + projectName string + userName string + userRole string + reportDate string + timeWorked uint64 + isSigned bool + reportStatus string // Example "draft", "signed", "unsigned" +} + +type Project struct { + timeReports []TimeReport + projectName string + projectmembers map[string]User + projectRoles map[string]User +} diff --git a/backend/internal/model/user.go b/backend/internal/model/user.go new file mode 100644 index 0000000..c125a60 --- /dev/null +++ b/backend/internal/model/user.go @@ -0,0 +1,139 @@ +package model + +import ( + "errors" +) + +type Account struct { + fullName string + userName string +} + +type Administrator struct { + projects map[string]Project + Account + ProjectMember // comp +} + +// Administrator reciever functions +func (administrator Administrator) addUser(project *Project, user *User) error { + // WIP + return errors.New("WIP") +} + +func (administrator Administrator) removerUser(project *Project, user *User) error { + // WIP + return errors.New("WIP") +} + +func (administrator Administrator) deleteProject(project *Project) error { + // WIP + return errors.New("WIP") +} + +func (administrator Administrator) changeUserRole(project *Project, user *User) error { + // WIP + return errors.New("WIP") +} + +func (administrator *Administrator) login() error { + // WIP + return errors.New("WIP") +} + +func (administrator *Administrator) logout() error { + // WIP + return errors.New("WIP") +} + +type ProjectManager struct { + managedProjects map[string]Project // projekt som förvaltas av projektledaren + totalTime uint64 // total totalt tid arbetat av projektledaren + Account + ProjectMember // comp +} + +// ProjectManager reciever functions +func (projectManager ProjectManager) signReport(timeReport *TimeReport, user User) error { + // WIP + return errors.New("WIP") +} + +func (projectManager ProjectManager) unsignReport(timeReport *TimeReport, user User) error { + // WIP + return errors.New("WIP") +} + +func (projectManager ProjectManager) getAllReports(project *Project) ([]TimeReport, error) { + // WIP + return project.timeReports, errors.New("WIP") +} + +func (projectManager ProjectManager) assignRole(user *User, project *Project, newRole string) error { + // WIP + return errors.New("WIP") +} + +func (projectManager ProjectManager) removeMember(project *Project, user *User) error { + // WIP + return errors.New("WIP") +} + +func (projectManager ProjectManager) getTotalTime(project *Project) (uint64, error) { + // WIP + return 0, errors.New("WIP") +} + +func (projectManager *ProjectManager) login() error { + // WIP + return errors.New("WIP") +} + +func (projectManager *ProjectManager) logout() error { + // WIP + return errors.New("WIP") +} + +type ProjectMember struct { + timereports []TimeReport + role string // ????? + Account // comp +} + +// User reciever functions + +// function used to create a time report, returning a *TimeReport is questionable +func (ProjectMember *ProjectMember) createTimeReport(Project *Project) (*TimeReport, error) { + // WIP + return &TimeReport{}, errors.New("WIP") +} + +func (ProjectMember ProjectMember) getTimeReport(timereports *[]TimeReport) (*TimeReport, error) { + // WIP + return &TimeReport{}, errors.New("WIP") +} + +func (ProjectMember *ProjectMember) editTimeReport(timereport *TimeReport) { + // timereport.editReport() + // WIP +} + +func (projectUser ProjectMember) deleteTimeReport(timeReport *TimeReport) error { // Ska bara project manager kunna göra detta? fråga ledarna! + // WIP + return errors.New("WIP") +} + +func (projectUser *ProjectMember) login() error { + // WIP + return errors.New("WIP") +} + +func (projectUser *ProjectMember) logout() error { + // WIP + return errors.New("WIP") +} + +type User interface { + login() + logout() +} diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000..f0a8e9c --- /dev/null +++ b/go.work.sum @@ -0,0 +1,5 @@ +github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= +github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= From ae9ee91bc4bd809f97bf71f6958b0a9fa26987cd Mon Sep 17 00:00:00 2001 From: borean Date: Mon, 4 Mar 2024 19:34:35 +0100 Subject: [PATCH 02/37] file correction --- backend/internal/model/timereport.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 backend/internal/model/timereport.go diff --git a/backend/internal/model/timereport.go b/backend/internal/model/timereport.go new file mode 100644 index 0000000..d48c5fb --- /dev/null +++ b/backend/internal/model/timereport.go @@ -0,0 +1,19 @@ +package model + +type TimeReport struct { + reportId string + projectName string + userName string + userRole string + reportDate string + timeWorked uint64 + isSigned bool + reportStatus string // Example "draft", "signed", "unsigned" +} + +type Project struct { + timeReports []TimeReport + projectName string + projectmembers map[string]User + projectRoles map[string]User +} From 3175c62e6dacdb1c02de9162f9708fd96c583359 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Thu, 7 Mar 2024 11:33:06 +0100 Subject: [PATCH 03/37] Carmack level testing instructions --- backend/internal/handlers/global_state.go | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/internal/handlers/global_state.go b/backend/internal/handlers/global_state.go index 5dc8895..ffe2072 100644 --- a/backend/internal/handlers/global_state.go +++ b/backend/internal/handlers/global_state.go @@ -115,6 +115,7 @@ func (gs *GState) Login(c *fiber.Ctx) error { // LoginRenew is a simple handler that renews the token func (gs *GState) LoginRenew(c *fiber.Ctx) error { + // For testing: curl localhost:3000/restricted -H "Authorization: Bearer " user := c.Locals("user").(*jwt.Token) claims := user.Claims.(jwt.MapClaims) claims["exp"] = time.Now().Add(time.Hour * 72).Unix() From acff254aa576bab4cd3ca4e641bb0d63b903bd22 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Thu, 7 Mar 2024 12:13:29 +0100 Subject: [PATCH 04/37] Indirect -> direct dependency in go.mod --- backend/go.mod | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/go.mod b/backend/go.mod index ab9be66..94d5519 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -10,8 +10,8 @@ require ( require ( github.com/MicahParks/keyfunc/v2 v2.1.0 // indirect - github.com/gofiber/contrib/jwt v1.0.8 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/gofiber/contrib/jwt v1.0.8 + github.com/golang-jwt/jwt/v5 v5.2.1 ) // These are all for fiber From a202eaa610edec14b965c5412d10fcf25ccbdf55 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Thu, 7 Mar 2024 12:26:08 +0100 Subject: [PATCH 05/37] Readme wsl notes --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc55126..d75861a 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ My recommendation would be to make WSL your primary development environment if y You should consult the [WSL documentation](https://docs.microsoft.com/en-us/windows/wsl/install), but for any recent version of windows, installation essentially boils down to running the following command in **PowerShell as an administrator**: ```powershell -wsl --install +wsl --install -d Ubuntu-22.04 # To get a somewhat recent version of Go ``` If you get any errors related to virtualization, you will need to enable virtualization in the BIOS. This is a common issue, and you can find a guide for your specific motherboard online. This is a one-time operation and will not affect your windows installation. This setting is usually called "VT-x" or "AMD-V" and is usually found in the CPU settings. If you can't find it, shoot me a message and I'll find it for you. From 3adf0b7ef58b16761419f29ab84078b2c4debe20 Mon Sep 17 00:00:00 2001 From: dDogge <> Date: Thu, 7 Mar 2024 13:09:09 +0100 Subject: [PATCH 06/37] Admin SQL table added --- backend/internal/database/migrations/0060_site_admin.sql | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 backend/internal/database/migrations/0060_site_admin.sql diff --git a/backend/internal/database/migrations/0060_site_admin.sql b/backend/internal/database/migrations/0060_site_admin.sql new file mode 100644 index 0000000..14de60d --- /dev/null +++ b/backend/internal/database/migrations/0060_site_admin.sql @@ -0,0 +1,4 @@ +CREATE TABLE IF NOT EXISTS site_admin ( + admin_id INTEGER PRIMARY KEY, + FOREIGN KEY (admin_id) REFERENCES users (id) ON DELETE CASCADE +) \ No newline at end of file From 18eefab292200f9a72ebad475c1cb25738ee104a Mon Sep 17 00:00:00 2001 From: dDogge <> Date: Thu, 7 Mar 2024 13:21:47 +0100 Subject: [PATCH 07/37] Implemented PromoteToAdmin and corresponding Test --- backend/internal/database/db.go | 7 +++++++ backend/internal/database/db_test.go | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 6e86641..68dcf13 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -14,6 +14,7 @@ import ( type Database interface { 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 @@ -30,6 +31,7 @@ var scripts embed.FS const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)" const projectInsert = "INSERT INTO projects (name, description, user_id) SELECT ?, ?, id FROM users WHERE username = ?" +const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?" // DbConnect connects to the database func DbConnect(dbpath string) Database { @@ -60,6 +62,11 @@ func (d *Db) RemoveUser(username string) error { 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) diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index 6830668..7c7c003 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -74,3 +74,20 @@ func TestDbRemoveUser(t *testing.T) { t.Error("RemoveUser failed:", err) } } + +func TestPromoteToAdmin(t *testing.T) { + db, err := setupState() + if err != nil { + t.Error("setupState failed:", err) + } + + err = db.AddUser("test", "password") + if err != nil { + t.Error("AddUser failed:", err) + } + + err = db.PromoteToAdmin("test") + if err != nil { + t.Error("PromoteToAdmin failed:", err) + } +} From c9406b8e50a010abc0855d56f13effcb6ef74608 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Thu, 7 Mar 2024 14:16:50 +0100 Subject: [PATCH 08/37] Ported Justfile to Makefile --- Makefile | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..668ccf1 --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +# Builds a release container +build-container-release: + podman build -t ttime-server -f container/Containerfile . + +# Builds a release container and runs it +start-release: build-container-release remove-podman-containers + podman run -d -e DATABASE_URL=sqlite:release.db -p 8080:8080 --name ttime ttime-server + @echo "Started production ttime-server on http://localhost:8080" + +# Removes and stops any containers related to the project +remove-podman-containers: + podman container rm -fi ttime + +# Tests every part of the project +testall: + cd frontend && npm test + cd frontend && npm run lint + cd backend && make test + cd backend && make lint + +# Cleans up everything related to the project +clean: remove-podman-containers + podman image rm -fi ttime-server + rm -rf frontend/dist + rm -rf frontend/node_modules + rm -f ttime-server.tar.gz + cd backend && make clean + @echo "Cleaned up!" + +# Cleans up everything related to podman, not just the project. Make sure you understand what this means. +podman-clean: + podman system reset --force + +# Installs the linter, which is not included in the ubuntu repo +install-linter: + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.56.2 + +# This installs just, a make alternative, which is slightly more ergonomic to use +install-just: + @echo "Installing just" + @curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin \ No newline at end of file From bd434c3da71a220bc5616dd0afd420b548e9cc92 Mon Sep 17 00:00:00 2001 From: dDogge <> Date: Thu, 7 Mar 2024 14:25:28 +0100 Subject: [PATCH 09/37] Added template for various new functions in db.go and corresponding tests in db_test.go --- backend/internal/database/db.go | 19 +++++++++++++++++++ backend/internal/database/db_test.go | 12 ++++++++++++ 2 files changed, 31 insertions(+) diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 68dcf13..84937f1 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -18,6 +18,9 @@ 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 + // ChangeUserRole(username string, projectname string, role string) error } // This struct is a wrapper type that holds the database connection @@ -33,6 +36,10 @@ const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)" const projectInsert = "INSERT INTO projects (name, description, user_id) SELECT ?, ?, id FROM users WHERE username = ?" const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?" +// const addTimeReport = "" +// const addUserToProject = "" +// const changeUserRole = "" + // DbConnect connects to the database func DbConnect(dbpath string) Database { // Open the database @@ -50,6 +57,18 @@ func DbConnect(dbpath string) Database { return &Db{db} } +// func (d *Db) AddTimeReport(projectname string, start time.Time, end time.Time) error { + +// } + +// func (d *Db) AddUserToProject(username string, projectname string) error { + +// } + +// 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) diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index 7c7c003..96eb9b7 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -91,3 +91,15 @@ func TestPromoteToAdmin(t *testing.T) { t.Error("PromoteToAdmin failed:", err) } } + +// func TestAddTimeReport(t *testing.T) { + +// } + +// func TestAddUserToProject(t *testing.T) { + +// } + +// func TestChangeUserRole(t *testing.T) { + +// } From d7cf29183687bc040fb12bb183f96a309f742343 Mon Sep 17 00:00:00 2001 From: Peter KW Date: Thu, 7 Mar 2024 17:48:43 +0100 Subject: [PATCH 10/37] added new page for admin and path in main --- .../src/Pages/AdminPages/AdminMenuPage.tsx | 27 +++++++++++++++++++ frontend/src/main.tsx | 5 ++++ 2 files changed, 32 insertions(+) create mode 100644 frontend/src/Pages/AdminPages/AdminMenuPage.tsx diff --git a/frontend/src/Pages/AdminPages/AdminMenuPage.tsx b/frontend/src/Pages/AdminPages/AdminMenuPage.tsx new file mode 100644 index 0000000..1b32ed4 --- /dev/null +++ b/frontend/src/Pages/AdminPages/AdminMenuPage.tsx @@ -0,0 +1,27 @@ +import { Link } from "react-router-dom"; +import BasicWindow from "../../Components/BasicWindow"; + +function AdminMenuPage(): JSX.Element { + const content = ( + <> +

Administrator Menu

+
+ +

+ Manage Users +

+ + +

+ Manage Projects +

+ +
+ + ); + + const buttons = <>; + + return ; +} +export default AdminMenuPage; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index d5616c7..03091a2 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -5,6 +5,7 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom"; import LoginPage from "./Pages/LoginPage.tsx"; import YourProjectsPage from "./Pages/YourProjectsPage.tsx"; import UserProjectPage from "./Pages/UserPages/UserProjectPage.tsx"; +import AdminMenuPage from "./Pages/AdminPages/AdminMenuPage.tsx"; // This is where the routes are mounted const router = createBrowserRouter([ @@ -20,6 +21,10 @@ const router = createBrowserRouter([ path: "/project", element: , }, + { + path: "/admin-menu", + element: , + }, ]); // Semi-hacky way to get the root element From 7ed986e4ebe3c1cd28d723a1de96a838db83c6af Mon Sep 17 00:00:00 2001 From: borean Date: Thu, 7 Mar 2024 20:58:50 +0100 Subject: [PATCH 11/37] update --- Justfile | 11 +- Makefile | 41 +++++++ README.md | 2 +- backend/Makefile | 4 + backend/cmd/main.go | 18 ++- backend/go.mod | 20 ++-- backend/go.sum | 20 ++++ backend/internal/database/db.go | 26 +++++ backend/internal/database/db_test.go | 29 +++++ .../database/migrations/0060_site_admin.sql | 4 + backend/internal/handlers/global_state.go | 103 +++++++++++++++++- frontend/package-lock.json | 100 ++++++++++++++++- frontend/package.json | 6 +- frontend/src/App.css | 42 ------- frontend/src/App.tsx | 31 ------ frontend/src/Components/TaskList.tsx | 36 ++++++ frontend/src/Containers/ContainerDemo.tsx | 24 ++++ frontend/src/Pages/Home.tsx | 36 ++++++ frontend/src/Pages/Settings.tsx | 17 +++ frontend/src/index.css | 50 +++++++++ frontend/src/main.tsx | 20 +++- 21 files changed, 547 insertions(+), 93 deletions(-) create mode 100644 Makefile create mode 100644 backend/internal/database/migrations/0060_site_admin.sql delete mode 100644 frontend/src/App.css delete mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/Components/TaskList.tsx create mode 100644 frontend/src/Containers/ContainerDemo.tsx create mode 100644 frontend/src/Pages/Home.tsx create mode 100644 frontend/src/Pages/Settings.tsx diff --git a/Justfile b/Justfile index 0d99e3a..432fbd1 100644 --- a/Justfile +++ b/Justfile @@ -11,7 +11,7 @@ start-release: build-container-release remove-podman-containers # Removes and stops any containers related to the project [private] remove-podman-containers: - podman container rm -f ttime + podman container rm -fi ttime # Saves the release container to a tarball, pigz is just gzip but multithreaded save-release: build-container-release @@ -23,14 +23,14 @@ load-release file: # Tests every part of the project testall: - cd backend && make test - cd backend && make lint cd frontend && npm test cd frontend && npm run lint + cd backend && make test + cd backend && make lint # Cleans up everything related to the project clean: remove-podman-containers - podman image rm -f ttime-server + podman image rm -fi ttime-server rm -rf frontend/dist rm -rf frontend/node_modules rm -f ttime-server.tar.gz @@ -41,3 +41,6 @@ clean: remove-podman-containers [confirm] podman-clean: podman system reset --force + +install-linter: + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.56.2 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..668ccf1 --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +# Builds a release container +build-container-release: + podman build -t ttime-server -f container/Containerfile . + +# Builds a release container and runs it +start-release: build-container-release remove-podman-containers + podman run -d -e DATABASE_URL=sqlite:release.db -p 8080:8080 --name ttime ttime-server + @echo "Started production ttime-server on http://localhost:8080" + +# Removes and stops any containers related to the project +remove-podman-containers: + podman container rm -fi ttime + +# Tests every part of the project +testall: + cd frontend && npm test + cd frontend && npm run lint + cd backend && make test + cd backend && make lint + +# Cleans up everything related to the project +clean: remove-podman-containers + podman image rm -fi ttime-server + rm -rf frontend/dist + rm -rf frontend/node_modules + rm -f ttime-server.tar.gz + cd backend && make clean + @echo "Cleaned up!" + +# Cleans up everything related to podman, not just the project. Make sure you understand what this means. +podman-clean: + podman system reset --force + +# Installs the linter, which is not included in the ubuntu repo +install-linter: + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.56.2 + +# This installs just, a make alternative, which is slightly more ergonomic to use +install-just: + @echo "Installing just" + @curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin \ No newline at end of file diff --git a/README.md b/README.md index fc55126..d75861a 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ My recommendation would be to make WSL your primary development environment if y You should consult the [WSL documentation](https://docs.microsoft.com/en-us/windows/wsl/install), but for any recent version of windows, installation essentially boils down to running the following command in **PowerShell as an administrator**: ```powershell -wsl --install +wsl --install -d Ubuntu-22.04 # To get a somewhat recent version of Go ``` If you get any errors related to virtualization, you will need to enable virtualization in the BIOS. This is a common issue, and you can find a guide for your specific motherboard online. This is a one-time operation and will not affect your windows installation. This setting is usually called "VT-x" or "AMD-V" and is usually found in the CPU settings. If you can't find it, shoot me a message and I'll find it for you. diff --git a/backend/Makefile b/backend/Makefile index 23eefa0..dcc79b4 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -69,3 +69,7 @@ lint: # Default target default: build + +install-just: + @echo "Installing just" + @curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin diff --git a/backend/cmd/main.go b/backend/cmd/main.go index 5762bab..bae7a83 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -9,6 +9,8 @@ import ( "github.com/gofiber/fiber/v2" _ "github.com/mattn/go-sqlite3" + + jwtware "github.com/gofiber/contrib/jwt" ) func main() { @@ -33,9 +35,23 @@ func main() { // This will likely be replaced by an embedded filesystem in the future server.Static("/", "./static") - // Register our handlers + // Register our unprotected routes server.Post("/api/register", gs.Register) + // Register handlers for example button count + server.Get("/api/button", gs.GetButtonCount) + server.Post("/api/button", gs.IncrementButtonCount) + + server.Post("/api/login", gs.Login) + + // Every route from here on will require a valid JWT + server.Use(jwtware.New(jwtware.Config{ + SigningKey: jwtware.SigningKey{Key: []byte("secret")}, + })) + + server.Post("/api/loginrenew", gs.LoginRenew) + server.Delete("/api/userdelete", gs.UserDelete) // Perhaps just use POST to avoid headaches + // Announce the port we are listening on and start the server err = server.Listen(fmt.Sprintf(":%d", conf.Port)) if err != nil { diff --git a/backend/go.mod b/backend/go.mod index fdbbaf7..94d5519 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -8,18 +8,24 @@ require ( github.com/mattn/go-sqlite3 v1.14.22 ) +require ( + github.com/MicahParks/keyfunc/v2 v2.1.0 // indirect + github.com/gofiber/contrib/jwt v1.0.8 + github.com/golang-jwt/jwt/v5 v5.2.1 +) + // These are all for fiber require ( - github.com/andybalholm/brotli v1.0.5 // indirect - github.com/gofiber/fiber/v2 v2.52.1 // indirect - github.com/google/uuid v1.5.0 // indirect - github.com/klauspost/compress v1.17.0 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/gofiber/fiber/v2 v2.52.2 + github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.17.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.2.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.51.0 // indirect + github.com/valyala/fasthttp v1.52.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.18.0 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index 9ddb540..42908f6 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -1,17 +1,31 @@ github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/MicahParks/keyfunc/v2 v2.1.0 h1:6ZXKb9Rp6qp1bDbJefnG7cTH8yMN1IC/4nf+GVjO99k= +github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/gofiber/contrib/jwt v1.0.8 h1:/GeOsm/Mr1OGr0GTy+RIVSz5VgNNyP3ZgK4wdqxF/WY= +github.com/gofiber/contrib/jwt v1.0.8/go.mod h1:gWWBtBiLmKXRN7xy6a96QO0KGvPEyxdh8x496Ujtg84= github.com/gofiber/fiber/v2 v2.52.1 h1:1RoU2NS+b98o1L77sdl5mboGPiW+0Ypsi5oLmcYlgHI= github.com/gofiber/fiber/v2 v2.52.1/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/gofiber/fiber/v2 v2.52.2 h1:b0rYH6b06Df+4NyrbdptQL8ifuxw/Tf2DgfkZkDaxEo= +github.com/gofiber/fiber/v2 v2.52.2/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= +github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -26,13 +40,19 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= +github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0= +github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 6e86641..84937f1 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -14,9 +14,13 @@ import ( type Database interface { 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 } // This struct is a wrapper type that holds the database connection @@ -30,6 +34,11 @@ var scripts embed.FS const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)" const projectInsert = "INSERT INTO projects (name, description, user_id) SELECT ?, ?, id FROM users WHERE username = ?" +const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?" + +// const addTimeReport = "" +// const addUserToProject = "" +// const changeUserRole = "" // DbConnect connects to the database func DbConnect(dbpath string) Database { @@ -48,6 +57,18 @@ func DbConnect(dbpath string) Database { return &Db{db} } +// func (d *Db) AddTimeReport(projectname string, start time.Time, end time.Time) error { + +// } + +// func (d *Db) AddUserToProject(username string, projectname string) error { + +// } + +// 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) @@ -60,6 +81,11 @@ func (d *Db) RemoveUser(username string) error { 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) diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index 6830668..96eb9b7 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -74,3 +74,32 @@ func TestDbRemoveUser(t *testing.T) { t.Error("RemoveUser failed:", err) } } + +func TestPromoteToAdmin(t *testing.T) { + db, err := setupState() + if err != nil { + t.Error("setupState failed:", err) + } + + err = db.AddUser("test", "password") + if err != nil { + t.Error("AddUser failed:", err) + } + + err = db.PromoteToAdmin("test") + if err != nil { + t.Error("PromoteToAdmin failed:", err) + } +} + +// func TestAddTimeReport(t *testing.T) { + +// } + +// func TestAddUserToProject(t *testing.T) { + +// } + +// func TestChangeUserRole(t *testing.T) { + +// } diff --git a/backend/internal/database/migrations/0060_site_admin.sql b/backend/internal/database/migrations/0060_site_admin.sql new file mode 100644 index 0000000..14de60d --- /dev/null +++ b/backend/internal/database/migrations/0060_site_admin.sql @@ -0,0 +1,4 @@ +CREATE TABLE IF NOT EXISTS site_admin ( + admin_id INTEGER PRIMARY KEY, + FOREIGN KEY (admin_id) REFERENCES users (id) ON DELETE CASCADE +) \ No newline at end of file diff --git a/backend/internal/handlers/global_state.go b/backend/internal/handlers/global_state.go index 4dae3d8..ffe2072 100644 --- a/backend/internal/handlers/global_state.go +++ b/backend/internal/handlers/global_state.go @@ -1,25 +1,49 @@ package handlers import ( + "time" "ttime/internal/database" "ttime/internal/types" "github.com/gofiber/fiber/v2" + "github.com/golang-jwt/jwt/v5" ) // The actual interface that we will use type GlobalState interface { - Register(c *fiber.Ctx) error + Register(c *fiber.Ctx) error // To register a new user + UserDelete(c *fiber.Ctx) error // To delete a user + Login(c *fiber.Ctx) error // To get the token + LoginRenew(c *fiber.Ctx) error // To renew the token + // CreateProject(c *fiber.Ctx) error // To create a new project + // GetProjects(c *fiber.Ctx) error // To get all projects + // GetProject(c *fiber.Ctx) error // To get a specific project + // UpdateProject(c *fiber.Ctx) error // To update a project + // DeleteProject(c *fiber.Ctx) error // To delete a project + // CreateTask(c *fiber.Ctx) error // To create a new task + // GetTasks(c *fiber.Ctx) error // To get all tasks + // GetTask(c *fiber.Ctx) error // To get a specific task + // UpdateTask(c *fiber.Ctx) error // To update a task + // DeleteTask(c *fiber.Ctx) error // To delete a task + // CreateCollection(c *fiber.Ctx) error // To create a new collection + // GetCollections(c *fiber.Ctx) error // To get all collections + // GetCollection(c *fiber.Ctx) error // To get a specific collection + // UpdateCollection(c *fiber.Ctx) error // To update a collection + // DeleteCollection(c *fiber.Ctx) error // To delete a collection + // SignCollection(c *fiber.Ctx) error // To sign a collection + GetButtonCount(c *fiber.Ctx) error // For demonstration purposes + IncrementButtonCount(c *fiber.Ctx) error // For demonstration purposes } // "Constructor" func NewGlobalState(db database.Database) GlobalState { - return &GState{Db: db} + return &GState{Db: db, ButtonCount: 0} } // The global state, which implements all the handlers type GState struct { - Db database.Database + Db database.Database + ButtonCount int } func (gs *GState) Register(c *fiber.Ctx) error { @@ -34,3 +58,76 @@ func (gs *GState) Register(c *fiber.Ctx) error { return c.Status(200).SendString("User added") } + +// This path should obviously be protected in the future +// UserDelete deletes a user from the database +func (gs *GState) UserDelete(c *fiber.Ctx) error { + u := new(types.User) + if err := c.BodyParser(u); err != nil { + return c.Status(400).SendString(err.Error()) + } + + if err := gs.Db.RemoveUser(u.Username); err != nil { + return c.Status(500).SendString(err.Error()) + } + + return c.Status(200).SendString("User deleted") +} + +func (gs *GState) GetButtonCount(c *fiber.Ctx) error { + return c.Status(200).JSON(fiber.Map{"pressCount": gs.ButtonCount}) +} + +func (gs *GState) IncrementButtonCount(c *fiber.Ctx) error { + gs.ButtonCount++ + return c.Status(200).JSON(fiber.Map{"pressCount": gs.ButtonCount}) +} + +// Login is a simple login handler that returns a JWT token +func (gs *GState) Login(c *fiber.Ctx) error { + // To test: curl --data "user=user&pass=pass" http://localhost:8080/api/login + user := c.FormValue("user") + pass := c.FormValue("pass") + + // Throws Unauthorized error + if user != "user" || pass != "pass" { + return c.SendStatus(fiber.StatusUnauthorized) + } + + // Create the Claims + claims := jwt.MapClaims{ + "name": user, + "admin": false, + "exp": time.Now().Add(time.Hour * 72).Unix(), + } + + // Create token + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + // Generate encoded token and send it as response. + t, err := token.SignedString([]byte("secret")) + if err != nil { + return c.SendStatus(fiber.StatusInternalServerError) + } + + return c.JSON(fiber.Map{"token": t}) +} + +// LoginRenew is a simple handler that renews the token +func (gs *GState) LoginRenew(c *fiber.Ctx) error { + // For testing: curl localhost:3000/restricted -H "Authorization: Bearer " + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + claims["exp"] = time.Now().Add(time.Hour * 72).Unix() + renewed := jwt.MapClaims{ + "name": claims["name"], + "admin": claims["admin"], + "exp": claims["exp"], + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, renewed) + t, err := token.SignedString([]byte("secret")) + if err != nil { + return c.SendStatus(fiber.StatusInternalServerError) + } + return c.JSON(fiber.Map{"token": t}) +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5131784..eecde70 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,8 +8,12 @@ "name": "tmp", "version": "0.0.0", "dependencies": { + "localforage": "^1.10.0", + "match-sorter": "^6.3.4", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^6.22.2", + "sort-by": "^0.0.2" }, "devDependencies": { "@swc/core": "^1.4.2", @@ -670,6 +674,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", + "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.24.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", @@ -1782,6 +1797,14 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@remix-run/router": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.2.tgz", + "integrity": "sha512-+Rnav+CaoTE5QJc4Jcwh5toUpnVLKYbpU6Ys0zqbakqbaLQHeglLVHPfxOiQqdNmUy5C2lXz5dwC6tQNX2JW2Q==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.10.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.10.0.tgz", @@ -4644,6 +4667,11 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -5898,6 +5926,14 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -5913,6 +5949,14 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "dependencies": { + "lie": "3.1.1" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -5981,6 +6025,15 @@ "tmpl": "1.0.5" } }, + "node_modules/match-sorter": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.4.tgz", + "integrity": "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==", + "dependencies": { + "@babel/runtime": "^7.23.8", + "remove-accents": "0.5.0" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -6849,6 +6902,36 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "node_modules/react-router": { + "version": "6.22.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.2.tgz", + "integrity": "sha512-YD3Dzprzpcq+tBMHBS822tCjnWD3iIZbTeSXMY9LPSG541EfoBGyZ3bS25KEnaZjLcmQpw2AVLkFyfgXY8uvcw==", + "dependencies": { + "@remix-run/router": "1.15.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.22.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.2.tgz", + "integrity": "sha512-WgqxD2qySEIBPZ3w0sHH+PUAiamDeszls9tzqMPBDA1YYVucTBXLU7+gtRfcSnhe92A3glPnvSxK2dhNoAVOIQ==", + "dependencies": { + "@remix-run/router": "1.15.2", + "react-router": "6.22.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-shallow-renderer": { "version": "16.15.0", "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", @@ -6924,6 +7007,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -6942,6 +7030,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7242,6 +7335,11 @@ "node": ">=8" } }, + "node_modules/sort-by": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/sort-by/-/sort-by-0.0.2.tgz", + "integrity": "sha512-iOX5oHA4a0eqTMFiWrHYqv924UeRKFBLhym7iwSVG37Egg2wApgZKAjyzM9WZjMwKv6+8Zi+nIaJ7FYsO9EkoA==" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 9dce126..e8d78c1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,8 +12,12 @@ "test": "jest" }, "dependencies": { + "localforage": "^1.10.0", + "match-sorter": "^6.3.4", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^6.22.2", + "sort-by": "^0.0.2" }, "devDependencies": { "@swc/core": "^1.4.2", diff --git a/frontend/src/App.css b/frontend/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/frontend/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx deleted file mode 100644 index 20e5f1f..0000000 --- a/frontend/src/App.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import reactLogo from "./assets/react.svg"; -import viteLogo from "/vite.svg"; -import "./App.css"; -import { CountButton } from "./Components/CountButton"; - -function App(): JSX.Element { - return ( - <> - -

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

- - ); -} - -export default App; diff --git a/frontend/src/Components/TaskList.tsx b/frontend/src/Components/TaskList.tsx new file mode 100644 index 0000000..1e9cb76 --- /dev/null +++ b/frontend/src/Components/TaskList.tsx @@ -0,0 +1,36 @@ +/** + * The shape of a task + */ +interface Task { + id: number; + name: string; +} + +/** + * The props for the TaskList component + */ +interface TaskProps { + tasks: Task[]; +} + +/** + * A simple list of tasks + * @param props - The tasks to display + * @returns {JSX.Element} The task list + * @example + * const tasks = [{ id: 1, name: "Do the thing" }]; + * return ; + */ +export function TaskList(props: TaskProps): JSX.Element { + return ( +
+

Task List

+

Tasks go here

+
    + {props.tasks.map((task) => ( +
  • {task.name}
  • + ))} +
+
+ ); +} diff --git a/frontend/src/Containers/ContainerDemo.tsx b/frontend/src/Containers/ContainerDemo.tsx new file mode 100644 index 0000000..ac1cfc6 --- /dev/null +++ b/frontend/src/Containers/ContainerDemo.tsx @@ -0,0 +1,24 @@ +import { ReactNode } from "react"; + +/** The props for the container */ +interface ContainerProps { + children: ReactNode; +} + +/** + * Contains children + * @param props - The children to contain + * @returns {JSX.Element} The container + */ +export function Container(props: ContainerProps): JSX.Element { + return
{props.children}
; +} + +/** + * Contains even more children + * @param props + * @returns {JSX.Element} + */ +export function Container2(props: ContainerProps): JSX.Element { + return {props.children}; +} diff --git a/frontend/src/Pages/Home.tsx b/frontend/src/Pages/Home.tsx new file mode 100644 index 0000000..7ce73a6 --- /dev/null +++ b/frontend/src/Pages/Home.tsx @@ -0,0 +1,36 @@ +import reactLogo from "../assets/react.svg"; +import viteLogo from "/vite.svg"; +import "../index.css"; +import { CountButton } from "../Components/CountButton"; +import { Link } from "react-router-dom"; + +/** + * The home page of the application + * @returns {JSX.Element} The home page + */ +export default function HomePage(): JSX.Element { + return ( + <> + +

Vite + React

+
+ + To Settings +
+

+ Click on the Vite and React logos to learn more +

+ + ); +} diff --git a/frontend/src/Pages/Settings.tsx b/frontend/src/Pages/Settings.tsx new file mode 100644 index 0000000..b5bf81c --- /dev/null +++ b/frontend/src/Pages/Settings.tsx @@ -0,0 +1,17 @@ +import "../index.css"; +import { Link } from "react-router-dom"; + +/** + * The settings page of the application + * @returns {JSX.Element} The settings page + */ +export default function SettingsPage(): JSX.Element { + return ( + <> +

Very Fancy Settings Page

+
+ To Home +
+ + ); +} diff --git a/frontend/src/index.css b/frontend/src/index.css index e7d4bb2..94ce567 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -2,6 +2,11 @@ @tailwind components; @tailwind utilities; +/* + * We are using tailwind, so do not add any custom CSS here. + * Most of this is going to get cleaned up eventually. + */ + :root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; @@ -70,3 +75,48 @@ button:focus-visible { background-color: #f9f9f9; } } + +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +a { + display: inline-block; +} + +.logo { + transition: filter 0.25s; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 05dff10..cefddf1 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,12 +1,28 @@ import React from "react"; import ReactDOM from "react-dom/client"; -import App from "./App.tsx"; +import SettingsPage from "./Pages/Settings.tsx"; +import HomePage from "./Pages/Home.tsx"; import "./index.css"; +import { createBrowserRouter, RouterProvider } from "react-router-dom"; +// This is where the routes are mounted +const router = createBrowserRouter([ + { + path: "/", + element: , + }, + { + path: "/settings", + element: , + }, +]); + +// Semi-hacky way to get the root element const root = document.getElementById("root") ?? document.createElement("div"); +// Render the router at the root ReactDOM.createRoot(root).render( - + , ); From 5a85f2bf811d2319164c2a5540775840bf5b9217 Mon Sep 17 00:00:00 2001 From: borean Date: Thu, 7 Mar 2024 21:57:27 +0100 Subject: [PATCH 12/37] first draft of AddTimeReport in db.go --- backend/internal/database/db.go | 11 +++++++---- backend/internal/model/timereport | 19 ------------------- 2 files changed, 7 insertions(+), 23 deletions(-) delete mode 100644 backend/internal/model/timereport diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 84937f1..b000ae4 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -5,6 +5,7 @@ import ( "log" "os" "path/filepath" + "time" "github.com/jmoiron/sqlx" _ "github.com/mattn/go-sqlite3" @@ -36,7 +37,8 @@ const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)" const projectInsert = "INSERT INTO projects (name, description, user_id) SELECT ?, ?, id FROM users WHERE username = ?" const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?" -// const addTimeReport = "" +const addTimeReport = "INSERT INTO activity (report_id, activity_nbr, start_time, end_time, break, comment) VALUES (?, ?, ?, ?, ?, ?)" + // const addUserToProject = "" // const changeUserRole = "" @@ -57,9 +59,10 @@ func DbConnect(dbpath string) Database { return &Db{db} } -// func (d *Db) AddTimeReport(projectname string, start time.Time, end time.Time) error { - -// } +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) error { diff --git a/backend/internal/model/timereport b/backend/internal/model/timereport deleted file mode 100644 index d48c5fb..0000000 --- a/backend/internal/model/timereport +++ /dev/null @@ -1,19 +0,0 @@ -package model - -type TimeReport struct { - reportId string - projectName string - userName string - userRole string - reportDate string - timeWorked uint64 - isSigned bool - reportStatus string // Example "draft", "signed", "unsigned" -} - -type Project struct { - timeReports []TimeReport - projectName string - projectmembers map[string]User - projectRoles map[string]User -} From eec3a455094343874e1f4ae3794f3b6160ce5fb6 Mon Sep 17 00:00:00 2001 From: borean Date: Thu, 7 Mar 2024 23:24:54 +0100 Subject: [PATCH 13/37] Added GetProjectId and AddUserToProject, WIP --- backend/internal/database/db.go | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index b000ae4..503557a 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -36,10 +36,8 @@ var scripts embed.FS const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)" const projectInsert = "INSERT INTO projects (name, description, 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 (?, ?, ?, ?, ?, ?)" - -// const addUserToProject = "" +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 = "" // DbConnect connects to the database @@ -64,9 +62,22 @@ func (d *Db) AddTimeReport(projectname string, start time.Time, end time.Time, b return err } -// func (d *Db) AddUserToProject(username string, projectname string) error { +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 { @@ -91,7 +102,13 @@ func (d *Db) PromoteToAdmin(username string) error { func (d *Db) GetUserId(username string) (int, error) { var id int - err := d.Get(&id, "SELECT id FROM users WHERE username = ?", username) + 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) { // WIP, denna kan vara goof + var id int + err := d.Get(&id, "SELECT id FROM project WHERE project_name = ?", projectname) return id, err } From 553ba2c7c64fab798e6065695b9de67bb0528fb0 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Fri, 8 Mar 2024 07:33:31 +0100 Subject: [PATCH 14/37] New project_role table to represent possible roles --- .../database/migrations/0049_project_role.sql | 9 +++++++++ .../database/migrations/0050_user_roles.sql | 13 +++---------- 2 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 backend/internal/database/migrations/0049_project_role.sql diff --git a/backend/internal/database/migrations/0049_project_role.sql b/backend/internal/database/migrations/0049_project_role.sql new file mode 100644 index 0000000..8716800 --- /dev/null +++ b/backend/internal/database/migrations/0049_project_role.sql @@ -0,0 +1,9 @@ +-- This table represents the possible role a user can have in a project. +-- It has nothing to do with the site admin table. +CREATE TABLE IF NOT EXISTS project_role ( + p_role TEXT PRIMARY KEY +); + +-- Insert the possible roles a user can have in a project. +INSERT OR IGNORE INTO project_role (p_role) VALUES ('admin'); +INSERT OR IGNORE INTO project_role (p_role) VALUES ('member'); diff --git a/backend/internal/database/migrations/0050_user_roles.sql b/backend/internal/database/migrations/0050_user_roles.sql index 56e597b..aad25f7 100644 --- a/backend/internal/database/migrations/0050_user_roles.sql +++ b/backend/internal/database/migrations/0050_user_roles.sql @@ -1,16 +1,9 @@ CREATE TABLE IF NOT EXISTS user_roles ( user_id INTEGER NOT NULL, project_id INTEGER NOT NULL, - role STRING NOT NULL, -- 'admin' or 'member' + p_role TEXT NOT NULL, -- 'admin' or 'member' FOREIGN KEY (user_id) REFERENCES users (id) FOREIGN KEY (project_id) REFERENCES projects (id) + FOREIGN KEY (p_role) REFERENCES project_role (p_role) PRIMARY KEY (user_id, project_id) -); - --- Make sure that the role is either 'admin' or 'member' -CREATE TRIGGER IF NOT EXISTS user_role_admin_or_member - BEFORE INSERT ON user_roles - FOR EACH ROW - BEGIN - SELECT RAISE(ABORT, 'Invalid role') WHERE NEW.role NOT IN ('admin', 'member'); - END; \ No newline at end of file +); \ No newline at end of file From f5d5eee2670f13719d20496d888a93152a7a6868 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Fri, 8 Mar 2024 07:33:58 +0100 Subject: [PATCH 15/37] Breaking changes in projects table related to naming --- backend/internal/database/migrations/0020_projects.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/internal/database/migrations/0020_projects.sql b/backend/internal/database/migrations/0020_projects.sql index 8592e75..adfb818 100644 --- a/backend/internal/database/migrations/0020_projects.sql +++ b/backend/internal/database/migrations/0020_projects.sql @@ -3,9 +3,9 @@ CREATE TABLE IF NOT EXISTS projects ( projectId TEXT DEFAULT (HEX(RANDOMBLOB(4))) NOT NULL UNIQUE, name VARCHAR(255) NOT NULL UNIQUE, description TEXT NOT NULL, - user_id INTEGER NOT NULL, - FOREIGN KEY (user_id) REFERENCES users (id) + 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 (user_id); \ No newline at end of file +CREATE INDEX IF NOT EXISTS projects_user_id_index ON projects (owner_user_id); \ No newline at end of file From a77b388f8e7bbac457e48049402de3bc1c684c93 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Fri, 8 Mar 2024 07:34:26 +0100 Subject: [PATCH 16/37] Warning in make migrate target --- backend/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/Makefile b/backend/Makefile index dcc79b4..cb0bb64 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -43,6 +43,7 @@ update: # Migration target migrate: + @echo "If this ever fails, run make clean and try again" @echo "Migrating database $(DB_FILE) using SQL scripts in $(MIGRATIONS_DIR)" @for file in $(wildcard $(MIGRATIONS_DIR)/*.sql); do \ echo "Applying migration: $$file"; \ From 2351a0cb4a7ed54e3a9c831c70307968552ad9be Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Fri, 8 Mar 2024 07:42:11 +0100 Subject: [PATCH 17/37] Github actions CI for db migrations --- .github/workflows/sqlite3-migrations.yml | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/sqlite3-migrations.yml diff --git a/.github/workflows/sqlite3-migrations.yml b/.github/workflows/sqlite3-migrations.yml new file mode 100644 index 0000000..0e53b98 --- /dev/null +++ b/.github/workflows/sqlite3-migrations.yml @@ -0,0 +1,25 @@ +name: SQLite3 Migrations + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + + defaults: + run: + working-directory: ./backend + + steps: + - uses: actions/checkout@v3 + - name: Install SQLite3 + run: sudo apt-get install sqlite3 + - name: Install Make + run: sudo apt-get install make + - name: Run Migrations + run: make migrate From 12536d5e89ef66628867982fbc7fa1a65b18d80e Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Fri, 8 Mar 2024 07:47:32 +0100 Subject: [PATCH 18/37] Fixing inconsistent sql in db interface --- backend/internal/database/db.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 84937f1..3377837 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -33,7 +33,7 @@ type Db struct { var scripts embed.FS const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)" -const projectInsert = "INSERT INTO projects (name, description, user_id) SELECT ?, ?, id FROM users WHERE username = ?" +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 = "" From 944f9b6ccc68566c6d4b6869528d4a9420d4a3c3 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Fri, 8 Mar 2024 07:48:25 +0100 Subject: [PATCH 19/37] Removed overly verbose debug printing from db migration --- backend/internal/database/db.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 3377837..6bb113b 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -2,7 +2,6 @@ package database import ( "embed" - "log" "os" "path/filepath" @@ -127,7 +126,6 @@ func (d *Db) Migrate(dirname string) error { if err != nil { return err } - log.Println("Executed SQL file:", file.Name()) } if tr.Commit() != nil { From bb81a2973e16d2c039ce6a8cf67c5d9271e2d0e5 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Fri, 8 Mar 2024 09:10:20 +0100 Subject: [PATCH 20/37] Moving main out of cmd to accomodate swag cli tool --- backend/{cmd => }/main.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename backend/{cmd => }/main.go (100%) diff --git a/backend/cmd/main.go b/backend/main.go similarity index 100% rename from backend/cmd/main.go rename to backend/main.go From f75cb7e9fc11a538e4441bed1a4161553a1eb5b6 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Fri, 8 Mar 2024 10:15:05 +0100 Subject: [PATCH 21/37] Pulling in deps related to swagger --- backend/go.mod | 26 +++++++++++++++++++++++++ backend/go.sum | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/backend/go.mod b/backend/go.mod index 94d5519..c251e95 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -8,6 +8,32 @@ require ( github.com/mattn/go-sqlite3 v1.14.22 ) +require ( + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/PuerkitoBio/purell v1.2.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/go-openapi/jsonpointer v0.20.3 // indirect + github.com/go-openapi/jsonreference v0.20.5 // indirect + github.com/go-openapi/spec v0.20.15 // indirect + github.com/go-openapi/swag v0.22.10 // indirect + github.com/gofiber/swagger v1.0.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/swaggo/files/v2 v2.0.0 // indirect + github.com/swaggo/swag v1.16.3 // indirect + github.com/urfave/cli/v2 v2.27.1 // indirect + github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.19.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) + require ( github.com/MicahParks/keyfunc/v2 v2.1.0 // indirect github.com/gofiber/contrib/jwt v1.0.8 diff --git a/backend/go.sum b/backend/go.sum index 42908f6..a2fdf1e 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -1,11 +1,28 @@ github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/MicahParks/keyfunc/v2 v2.1.0 h1:6ZXKb9Rp6qp1bDbJefnG7cTH8yMN1IC/4nf+GVjO99k= github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k= +github.com/PuerkitoBio/purell v1.2.1 h1:QsZ4TjvwiMpat6gBCBxEQI0rcS9ehtkKtSpiUnd9N28= +github.com/PuerkitoBio/purell v1.2.1/go.mod h1:ZwHcC/82TOaovDi//J/804umJFFmbOHPngi8iYYv/Eo= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-openapi/jsonpointer v0.20.3 h1:jykzYWS/kyGtsHfRt6aV8JTB9pcQAXPIA7qlZ5aRlyk= +github.com/go-openapi/jsonpointer v0.20.3/go.mod h1:c7l0rjoouAuIxCm8v/JWKRgMjDG/+/7UBWsXMrv6PsM= +github.com/go-openapi/jsonreference v0.20.5 h1:hutI+cQI+HbSQaIGSfsBsYI0pHk+CATf8Fk5gCSj0yI= +github.com/go-openapi/jsonreference v0.20.5/go.mod h1:thAqAp31UABtI+FQGKAQfmv7DbFpKNUlva2UPCxKu2Y= +github.com/go-openapi/spec v0.20.15 h1:8bDcVxF607pTh9NpPwgsH4J5Uhh5mV5XoWnkurdiY+U= +github.com/go-openapi/spec v0.20.15/go.mod h1:o0upgqg5uYFG7O5mADrDVmSG3Wa6y6OLhwiCqQ+sTv4= +github.com/go-openapi/swag v0.22.10 h1:4y86NVn7Z2yYd6pfS4Z+Nyh3aAUL3Nul+LMbhFKy0gA= +github.com/go-openapi/swag v0.22.10/go.mod h1:Cnn8BYtRlx6BNE3DPN86f/xkapGIcLWzh3CLEb4C1jI= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/gofiber/contrib/jwt v1.0.8 h1:/GeOsm/Mr1OGr0GTy+RIVSz5VgNNyP3ZgK4wdqxF/WY= @@ -14,20 +31,28 @@ github.com/gofiber/fiber/v2 v2.52.1 h1:1RoU2NS+b98o1L77sdl5mboGPiW+0Ypsi5oLmcYlg github.com/gofiber/fiber/v2 v2.52.1/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/gofiber/fiber/v2 v2.52.2 h1:b0rYH6b06Df+4NyrbdptQL8ifuxw/Tf2DgfkZkDaxEo= github.com/gofiber/fiber/v2 v2.52.2/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/gofiber/swagger v1.0.0 h1:BzUzDS9ZT6fDUa692kxmfOjc1DZiloLiPK/W5z1H1tc= +github.com/gofiber/swagger v1.0.0/go.mod h1:QrYNF1Yrc7ggGK6ATsJ6yfH/8Zi5bu9lA7wB8TmCecg= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -38,10 +63,21 @@ github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= +github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= +github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= +github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= +github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= +github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= @@ -50,9 +86,24 @@ github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7g github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI= +github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= From 13c12b424a34fddd24184be47234ef0102b8703b Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Fri, 8 Mar 2024 10:16:56 +0100 Subject: [PATCH 22/37] Mounting handlers and basic swagger docs example --- backend/internal/handlers/global_state.go | 11 +++++++++++ backend/main.go | 17 +++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/backend/internal/handlers/global_state.go b/backend/internal/handlers/global_state.go index ffe2072..4c642ff 100644 --- a/backend/internal/handlers/global_state.go +++ b/backend/internal/handlers/global_state.go @@ -46,6 +46,17 @@ type GState struct { ButtonCount int } +// Register is a simple handler that registers a new user +// +// @Summary Register a new user +// @Description Register a new user +// @Tags User +// @Accept json +// @Produce json +// @Success 200 {string} string "User added" +// @Failure 400 {string} string "Bad request" +// @Failure 500 {string} string "Internal server error" +// @Router /api/register [post] func (gs *GState) Register(c *fiber.Ctx) error { u := new(types.User) if err := c.BodyParser(u); err != nil { diff --git a/backend/main.go b/backend/main.go index bae7a83..d7713c9 100644 --- a/backend/main.go +++ b/backend/main.go @@ -3,16 +3,31 @@ package main import ( "encoding/json" "fmt" + _ "ttime/docs" "ttime/internal/config" "ttime/internal/database" "ttime/internal/handlers" "github.com/gofiber/fiber/v2" + "github.com/gofiber/swagger" _ "github.com/mattn/go-sqlite3" jwtware "github.com/gofiber/contrib/jwt" ) +// @title TTime API +// @version 0.0.1 +// @description This is the API for TTime, a time tracking application. + +// @license.name AGPL +// @license.url https://www.gnu.org/licenses/agpl-3.0.html + +// @host localhost:8080 +// @BasePath /api + +// @externalDocs.description OpenAPI +// @externalDocs.url https://swagger.io/resources/open-api/ + func main() { conf, err := config.ReadConfigFromFile("config.toml") if err != nil { @@ -31,6 +46,8 @@ func main() { // Create the server server := fiber.New() + server.Get("/swagger/*", swagger.HandlerDefault) + // Mount our static files (Beware of the security implications of this!) // This will likely be replaced by an embedded filesystem in the future server.Static("/", "./static") From ae0b0895b2a74de98fa221d495530a03feb895c2 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Fri, 8 Mar 2024 10:17:27 +0100 Subject: [PATCH 23/37] Version controlling generated docs --- backend/docs/docs.go | 80 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 backend/docs/docs.go diff --git a/backend/docs/docs.go b/backend/docs/docs.go new file mode 100644 index 0000000..8026f58 --- /dev/null +++ b/backend/docs/docs.go @@ -0,0 +1,80 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "license": { + "name": "AGPL", + "url": "https://www.gnu.org/licenses/agpl-3.0.html" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/api/register": { + "post": { + "description": "Register a new user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Register a new user", + "responses": { + "200": { + "description": "User added", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "type": "string" + } + } + } + } + } + }, + "externalDocs": { + "description": "OpenAPI", + "url": "https://swagger.io/resources/open-api/" + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "0.0.1", + Host: "localhost:8080", + BasePath: "/api", + Schemes: []string{}, + Title: "TTime API", + Description: "This is the API for TTime, a time tracking application.", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} From d4a2a00f69bce1623faaf9aa17b5c2909d0b59a7 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Fri, 8 Mar 2024 10:17:51 +0100 Subject: [PATCH 24/37] Makefile update with doc generation and convenience installers --- backend/Makefile | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/backend/Makefile b/backend/Makefile index cb0bb64..bd906e4 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -13,7 +13,7 @@ MIGRATIONS_DIR = internal/database/migrations # Build target build: - $(GOBUILD) -o bin/server cmd/*.go + $(GOBUILD) -o bin/server main.go # Run target run: build @@ -71,6 +71,21 @@ lint: # Default target default: build +# Generate swagger docs +.PHONY: docs +docs: + swag init -outputTypes go + +install-swag: + @echo "Installing swag" + @go get -u github.com/swaggo/swag/cmd/swag + +# Convenience target to install golangci-lint +install-lint: + @echo "Installing golangci-lint" + @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.42.1 + +# Convenience target to install just (requires sudo privileges) install-just: @echo "Installing just" @curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin From b52f13ee8b93ae13098b77c6e143dff414589bde Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Fri, 8 Mar 2024 10:25:22 +0100 Subject: [PATCH 25/37] Better developer feedback --- backend/main.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/backend/main.go b/backend/main.go index d7713c9..523a6a0 100644 --- a/backend/main.go +++ b/backend/main.go @@ -1,13 +1,14 @@ package main import ( - "encoding/json" "fmt" + "os" _ "ttime/docs" "ttime/internal/config" "ttime/internal/database" "ttime/internal/handlers" + "github.com/BurntSushi/toml" "github.com/gofiber/fiber/v2" "github.com/gofiber/swagger" _ "github.com/mattn/go-sqlite3" @@ -35,9 +36,11 @@ func main() { _ = conf.WriteConfigToFile("config.toml") } - // Pretty print the current config - str, _ := json.MarshalIndent(conf, "", " ") - fmt.Println(string(str)) + // Pretty print the current config with toml + toml.NewEncoder(os.Stdout).Encode(conf) + + fmt.Printf("Starting server on http://localhost:%d\n", conf.Port) + fmt.Printf("For documentation, go to http://localhost:%d/swagger/index.html\n", conf.Port) // Connect to the database db := database.DbConnect(conf.DbPath) From efc95e7feadbf6898765bcef4e9e9eed55c5606e Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Fri, 8 Mar 2024 10:30:17 +0100 Subject: [PATCH 26/37] Add target for formatting swag docs & formatting swag docs --- backend/Makefile | 4 ++++ backend/internal/handlers/global_state.go | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/backend/Makefile b/backend/Makefile index bd906e4..2200639 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -76,6 +76,10 @@ default: build docs: swag init -outputTypes go +.PHONY: docfmt +docfmt: + swag fmt + install-swag: @echo "Installing swag" @go get -u github.com/swaggo/swag/cmd/swag diff --git a/backend/internal/handlers/global_state.go b/backend/internal/handlers/global_state.go index 4c642ff..689759b 100644 --- a/backend/internal/handlers/global_state.go +++ b/backend/internal/handlers/global_state.go @@ -53,9 +53,9 @@ type GState struct { // @Tags User // @Accept json // @Produce json -// @Success 200 {string} string "User added" -// @Failure 400 {string} string "Bad request" -// @Failure 500 {string} string "Internal server error" +// @Success 200 {string} string "User added" +// @Failure 400 {string} string "Bad request" +// @Failure 500 {string} string "Internal server error" // @Router /api/register [post] func (gs *GState) Register(c *fiber.Ctx) error { u := new(types.User) From a7ebfe7110e69fe7d36ea060d0a44d49f3370efc Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Fri, 8 Mar 2024 10:32:02 +0100 Subject: [PATCH 27/37] Ignore formatter output in main --- backend/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/main.go b/backend/main.go index 523a6a0..1aaca45 100644 --- a/backend/main.go +++ b/backend/main.go @@ -37,7 +37,7 @@ func main() { } // Pretty print the current config with toml - toml.NewEncoder(os.Stdout).Encode(conf) + _ = toml.NewEncoder(os.Stdout).Encode(conf) fmt.Printf("Starting server on http://localhost:%d\n", conf.Port) fmt.Printf("For documentation, go to http://localhost:%d/swagger/index.html\n", conf.Port) From 02c9c502b828ffcedc236443785d83492ba57906 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Fri, 8 Mar 2024 11:22:09 +0100 Subject: [PATCH 28/37] Eralchemy2 target for generating er diagrams --- .gitignore | 1 + backend/Makefile | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index 9ad11ec..2d89407 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ bin db.sqlite3 +*.png # Test binary, built with `go test -c` *.test diff --git a/backend/Makefile b/backend/Makefile index 2200639..ea1e292 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -80,6 +80,12 @@ docs: docfmt: swag fmt +# Generate ERD +# Requires eralchemy2 +.PHONY: eralchemy +erd: + eralchemy2 -i sqlite:///db.sqlite3 -o erd.png + install-swag: @echo "Installing swag" @go get -u github.com/swaggo/swag/cmd/swag From 394b2837129a5d0929bc95228c225ed1815d51ce Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Fri, 8 Mar 2024 11:25:31 +0100 Subject: [PATCH 29/37] Typo --- backend/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/Makefile b/backend/Makefile index ea1e292..d005846 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -82,7 +82,7 @@ docfmt: # Generate ERD # Requires eralchemy2 -.PHONY: eralchemy +.PHONY: erd erd: eralchemy2 -i sqlite:///db.sqlite3 -o erd.png From ac6638b344b9d39a508c7fea77234e62b8919c35 Mon Sep 17 00:00:00 2001 From: Mattias Date: Fri, 8 Mar 2024 11:36:36 +0100 Subject: [PATCH 30/37] Removed example pages Home and Settings aswell as the react-logo --- frontend/src/Pages/Home.tsx | 36 --------------------------------- frontend/src/Pages/Settings.tsx | 17 ---------------- frontend/src/assets/react.svg | 1 - 3 files changed, 54 deletions(-) delete mode 100644 frontend/src/Pages/Home.tsx delete mode 100644 frontend/src/Pages/Settings.tsx delete mode 100644 frontend/src/assets/react.svg diff --git a/frontend/src/Pages/Home.tsx b/frontend/src/Pages/Home.tsx deleted file mode 100644 index 7ce73a6..0000000 --- a/frontend/src/Pages/Home.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import reactLogo from "../assets/react.svg"; -import viteLogo from "/vite.svg"; -import "../index.css"; -import { CountButton } from "../Components/CountButton"; -import { Link } from "react-router-dom"; - -/** - * The home page of the application - * @returns {JSX.Element} The home page - */ -export default function HomePage(): JSX.Element { - return ( - <> - -

Vite + React

-
- - To Settings -
-

- Click on the Vite and React logos to learn more -

- - ); -} diff --git a/frontend/src/Pages/Settings.tsx b/frontend/src/Pages/Settings.tsx deleted file mode 100644 index b5bf81c..0000000 --- a/frontend/src/Pages/Settings.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import "../index.css"; -import { Link } from "react-router-dom"; - -/** - * The settings page of the application - * @returns {JSX.Element} The settings page - */ -export default function SettingsPage(): JSX.Element { - return ( - <> -

Very Fancy Settings Page

-
- To Home -
- - ); -} diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/frontend/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 6acfdd36b20baae09110b1c5535d0da6c32a71ad Mon Sep 17 00:00:00 2001 From: borean Date: Fri, 8 Mar 2024 14:56:24 +0100 Subject: [PATCH 31/37] cleanup --- backend/internal/database/db.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 2524f47..38a158a 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -16,7 +16,6 @@ type Database interface { AddUser(username string, password string) error RemoveUser(username string) error PromoteToAdmin(username string) error - PromoteToAdmin(username string) error GetUserId(username string) (int, error) AddProject(name string, description string, username string) error Migrate(dirname string) error @@ -40,11 +39,6 @@ var scripts embed.FS const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)" const projectInsert = "INSERT INTO projects (name, description, user_id) SELECT ?, ?, id FROM users WHERE username = ?" const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?" - -// const addTimeReport = "" -// const addUserToProject = "" -// const changeUserRole = "" -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 = "" @@ -66,14 +60,6 @@ func DbConnect(dbpath string) Database { return &Db{db} } -// func (d *Db) AddTimeReport(projectname string, start time.Time, end time.Time) error { - -// } - -// func (d *Db) AddUserToProject(username string, projectname string) error { - -// } - // func (d *Db) ChangeUserRole(username string, projectname string, role string) error { // } @@ -121,11 +107,6 @@ func (d *Db) PromoteToAdmin(username string) error { 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 From d06122864ea5e96deb2d005b54e09a3ef442bb87 Mon Sep 17 00:00:00 2001 From: borean Date: Fri, 8 Mar 2024 15:06:32 +0100 Subject: [PATCH 32/37] pls no error --- go.work.sum | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/go.work.sum b/go.work.sum index f0a8e9c..a58340b 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,5 +1,15 @@ +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From ad4d43988719c7157f3b729ddd22601fafd72e0d Mon Sep 17 00:00:00 2001 From: borean Date: Fri, 8 Mar 2024 15:07:25 +0100 Subject: [PATCH 33/37] cleanup --- backend/internal/database/db.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 7647186..2b3cced 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -59,10 +59,6 @@ func DbConnect(dbpath string) Database { return &Db{db} } -// func (d *Db) ChangeUserRole(username string, projectname string, role string) error { - -// } - 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 From 9b67a580dad87ea2e0007ac24d7ecc00d852f123 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Tue, 12 Mar 2024 20:44:40 +0100 Subject: [PATCH 34/37] Gluecode for database/handlers --- backend/internal/database/db.go | 2 ++ backend/internal/handlers/global_state.go | 33 ++++++++++++++++++----- backend/internal/types/project.go | 21 +++++++++++++++ backend/internal/types/users.go | 5 ++++ 4 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 backend/internal/types/project.go diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 2b3cced..5a88873 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -12,7 +12,9 @@ import ( // 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) diff --git a/backend/internal/handlers/global_state.go b/backend/internal/handlers/global_state.go index 689759b..9c42133 100644 --- a/backend/internal/handlers/global_state.go +++ b/backend/internal/handlers/global_state.go @@ -11,11 +11,11 @@ import ( // The actual interface that we will use type GlobalState interface { - Register(c *fiber.Ctx) error // To register a new user - UserDelete(c *fiber.Ctx) error // To delete a user - Login(c *fiber.Ctx) error // To get the token - LoginRenew(c *fiber.Ctx) error // To renew the token - // CreateProject(c *fiber.Ctx) error // To create a new project + Register(c *fiber.Ctx) error // To register a new user + UserDelete(c *fiber.Ctx) error // To delete a user + Login(c *fiber.Ctx) error // To get the token + LoginRenew(c *fiber.Ctx) error // To renew the token + CreateProject(c *fiber.Ctx) error // To create a new project // GetProjects(c *fiber.Ctx) error // To get all projects // GetProject(c *fiber.Ctx) error // To get a specific project // UpdateProject(c *fiber.Ctx) error // To update a project @@ -58,7 +58,7 @@ type GState struct { // @Failure 500 {string} string "Internal server error" // @Router /api/register [post] func (gs *GState) Register(c *fiber.Ctx) error { - u := new(types.User) + u := new(types.NewUser) if err := c.BodyParser(u); err != nil { return c.Status(400).SendString(err.Error()) } @@ -142,3 +142,24 @@ func (gs *GState) LoginRenew(c *fiber.Ctx) error { } return c.JSON(fiber.Map{"token": t}) } + +// CreateProject is a simple handler that creates a new project +func (gs *GState) CreateProject(c *fiber.Ctx) error { + user := c.Locals("user").(*jwt.Token) + + p := new(types.NewProject) + if err := c.BodyParser(p); err != nil { + return c.Status(400).SendString(err.Error()) + } + + // Get the username from the token and set it as the owner of the project + // This is ugly but + claims := user.Claims.(jwt.MapClaims) + p.Owner = claims["name"].(string) + + if err := gs.Db.AddProject(p.Name, p.Description, p.Owner); err != nil { + return c.Status(500).SendString(err.Error()) + } + + return c.Status(200).SendString("Project added") +} diff --git a/backend/internal/types/project.go b/backend/internal/types/project.go new file mode 100644 index 0000000..cabf6c6 --- /dev/null +++ b/backend/internal/types/project.go @@ -0,0 +1,21 @@ +package types + +import ( + "time" +) + +// Project is a struct that holds the information about a project +type Project struct { + ID int `json:"id" db:"id"` + Name string `json:"name" db:"name"` + Description string `json:"description" db:"description"` + Owner string `json:"owner" db:"owner"` + Created time.Time `json:"created" db:"created"` +} + +// As it arrives from the client +type NewProject struct { + Name string `json:"name"` + Description string `json:"description"` + Owner string `json:"owner"` +} diff --git a/backend/internal/types/users.go b/backend/internal/types/users.go index fa735d7..233ec71 100644 --- a/backend/internal/types/users.go +++ b/backend/internal/types/users.go @@ -16,6 +16,11 @@ func (u *User) ToPublicUser() (*PublicUser, error) { }, nil } +type NewUser struct { + Username string `json:"username"` + Password string `json:"password"` +} + // PublicUser represents a user that is safe to send over the API (no password) type PublicUser struct { UserId string `json:"userId"` From 736cebe036dfadd9a90dd093b8ed25291ea901fa Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Tue, 12 Mar 2024 20:44:54 +0100 Subject: [PATCH 35/37] Sql comments and salts table --- .../internal/database/migrations/0010_users.sql | 5 +++++ .../internal/database/migrations/0070_salts.sql | 15 +++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 backend/internal/database/migrations/0070_salts.sql diff --git a/backend/internal/database/migrations/0010_users.sql b/backend/internal/database/migrations/0010_users.sql index 7cb23fd..5c9d329 100644 --- a/backend/internal/database/migrations/0010_users.sql +++ b/backend/internal/database/migrations/0010_users.sql @@ -1,3 +1,7 @@ +-- Id is a surrogate key for in ternal use +-- userId is what is used for external id +-- username is what is used for login +-- password is the hashed password CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY, userId TEXT DEFAULT (HEX(RANDOMBLOB(4))) NOT NULL UNIQUE, @@ -5,5 +9,6 @@ CREATE TABLE IF NOT EXISTS users ( password VARCHAR(255) NOT NULL ); +-- Users are commonly searched by username and userId CREATE INDEX IF NOT EXISTS users_username_index ON users (username); CREATE INDEX IF NOT EXISTS users_userId_index ON users (userId); \ No newline at end of file diff --git a/backend/internal/database/migrations/0070_salts.sql b/backend/internal/database/migrations/0070_salts.sql new file mode 100644 index 0000000..9fb0588 --- /dev/null +++ b/backend/internal/database/migrations/0070_salts.sql @@ -0,0 +1,15 @@ +-- It is unclear weather this table will be used + +-- Create the table to store hash salts +CREATE TABLE salts ( + id INTEGER PRIMARY KEY, + salt TEXT NOT NULL +); + +-- Create a trigger to automatically generate a salt when inserting a new user record +CREATE TRIGGER generate_salt_trigger +AFTER INSERT ON users +BEGIN + INSERT INTO salts (salt) VALUES (randomblob(16)); + UPDATE users SET salt_id = (SELECT last_insert_rowid()) WHERE id = new.id; +END; From c4632104a8f9c84c71dba8019fdbeec8c1a3e30b Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 13 Mar 2024 11:46:17 +0100 Subject: [PATCH 36/37] Commenting out trigger related to salts --- backend/internal/database/migrations/0070_salts.sql | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/backend/internal/database/migrations/0070_salts.sql b/backend/internal/database/migrations/0070_salts.sql index 9fb0588..b84dfac 100644 --- a/backend/internal/database/migrations/0070_salts.sql +++ b/backend/internal/database/migrations/0070_salts.sql @@ -6,10 +6,11 @@ CREATE TABLE salts ( salt TEXT NOT NULL ); +-- Commented out for now, no time for good practices, which is atrocious -- Create a trigger to automatically generate a salt when inserting a new user record -CREATE TRIGGER generate_salt_trigger -AFTER INSERT ON users -BEGIN - INSERT INTO salts (salt) VALUES (randomblob(16)); - UPDATE users SET salt_id = (SELECT last_insert_rowid()) WHERE id = new.id; -END; +-- CREATE TRIGGER generate_salt_trigger +-- AFTER INSERT ON users +-- BEGIN +-- INSERT INTO salts (salt) VALUES (randomblob(16)); +-- UPDATE users SET salt_id = (SELECT last_insert_rowid()) WHERE id = new.id; +-- END; From 25fdf3bb9b105a343d50c3cef62139e3d2496cdc Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 13 Mar 2024 12:29:15 +0100 Subject: [PATCH 37/37] Nuke dead code --- backend/internal/model/timereport.go | 19 ---- backend/internal/model/user.go | 139 --------------------------- 2 files changed, 158 deletions(-) delete mode 100644 backend/internal/model/timereport.go delete mode 100644 backend/internal/model/user.go diff --git a/backend/internal/model/timereport.go b/backend/internal/model/timereport.go deleted file mode 100644 index d48c5fb..0000000 --- a/backend/internal/model/timereport.go +++ /dev/null @@ -1,19 +0,0 @@ -package model - -type TimeReport struct { - reportId string - projectName string - userName string - userRole string - reportDate string - timeWorked uint64 - isSigned bool - reportStatus string // Example "draft", "signed", "unsigned" -} - -type Project struct { - timeReports []TimeReport - projectName string - projectmembers map[string]User - projectRoles map[string]User -} diff --git a/backend/internal/model/user.go b/backend/internal/model/user.go deleted file mode 100644 index c125a60..0000000 --- a/backend/internal/model/user.go +++ /dev/null @@ -1,139 +0,0 @@ -package model - -import ( - "errors" -) - -type Account struct { - fullName string - userName string -} - -type Administrator struct { - projects map[string]Project - Account - ProjectMember // comp -} - -// Administrator reciever functions -func (administrator Administrator) addUser(project *Project, user *User) error { - // WIP - return errors.New("WIP") -} - -func (administrator Administrator) removerUser(project *Project, user *User) error { - // WIP - return errors.New("WIP") -} - -func (administrator Administrator) deleteProject(project *Project) error { - // WIP - return errors.New("WIP") -} - -func (administrator Administrator) changeUserRole(project *Project, user *User) error { - // WIP - return errors.New("WIP") -} - -func (administrator *Administrator) login() error { - // WIP - return errors.New("WIP") -} - -func (administrator *Administrator) logout() error { - // WIP - return errors.New("WIP") -} - -type ProjectManager struct { - managedProjects map[string]Project // projekt som förvaltas av projektledaren - totalTime uint64 // total totalt tid arbetat av projektledaren - Account - ProjectMember // comp -} - -// ProjectManager reciever functions -func (projectManager ProjectManager) signReport(timeReport *TimeReport, user User) error { - // WIP - return errors.New("WIP") -} - -func (projectManager ProjectManager) unsignReport(timeReport *TimeReport, user User) error { - // WIP - return errors.New("WIP") -} - -func (projectManager ProjectManager) getAllReports(project *Project) ([]TimeReport, error) { - // WIP - return project.timeReports, errors.New("WIP") -} - -func (projectManager ProjectManager) assignRole(user *User, project *Project, newRole string) error { - // WIP - return errors.New("WIP") -} - -func (projectManager ProjectManager) removeMember(project *Project, user *User) error { - // WIP - return errors.New("WIP") -} - -func (projectManager ProjectManager) getTotalTime(project *Project) (uint64, error) { - // WIP - return 0, errors.New("WIP") -} - -func (projectManager *ProjectManager) login() error { - // WIP - return errors.New("WIP") -} - -func (projectManager *ProjectManager) logout() error { - // WIP - return errors.New("WIP") -} - -type ProjectMember struct { - timereports []TimeReport - role string // ????? - Account // comp -} - -// User reciever functions - -// function used to create a time report, returning a *TimeReport is questionable -func (ProjectMember *ProjectMember) createTimeReport(Project *Project) (*TimeReport, error) { - // WIP - return &TimeReport{}, errors.New("WIP") -} - -func (ProjectMember ProjectMember) getTimeReport(timereports *[]TimeReport) (*TimeReport, error) { - // WIP - return &TimeReport{}, errors.New("WIP") -} - -func (ProjectMember *ProjectMember) editTimeReport(timereport *TimeReport) { - // timereport.editReport() - // WIP -} - -func (projectUser ProjectMember) deleteTimeReport(timeReport *TimeReport) error { // Ska bara project manager kunna göra detta? fråga ledarna! - // WIP - return errors.New("WIP") -} - -func (projectUser *ProjectMember) login() error { - // WIP - return errors.New("WIP") -} - -func (projectUser *ProjectMember) logout() error { - // WIP - return errors.New("WIP") -} - -type User interface { - login() - logout() -}