Merge branch 'frontend' into gruppDM

This commit is contained in:
Davenludd 2024-03-18 23:37:48 +01:00
commit 2eab030212
7 changed files with 121 additions and 38 deletions

View file

@ -10,6 +10,7 @@ DB_FILE = db.sqlite3
# Directory containing migration SQL scripts # Directory containing migration SQL scripts
MIGRATIONS_DIR = internal/database/migrations MIGRATIONS_DIR = internal/database/migrations
SAMPLE_DATA_DIR = internal/database/sample_data
# Build target # Build target
build: build:
@ -54,6 +55,14 @@ migrate:
sqlite3 $(DB_FILE) < $$file; \ sqlite3 $(DB_FILE) < $$file; \
done done
sampledata:
@echo "If this ever fails, run make clean and try again"
@echo "Migrating database $(DB_FILE) using SQL scripts in $(SAMPLE_DATA_DIR)"
@for file in $(wildcard $(SAMPLE_DATA_DIR)/*.sql); do \
echo "Applying migration: $$file"; \
sqlite3 $(DB_FILE) < $$file; \
done
# Target added primarily for CI/CD to ensure that the database is created before running tests # Target added primarily for CI/CD to ensure that the database is created before running tests
db.sqlite3: db.sqlite3:
make migrate make migrate

View file

@ -20,6 +20,7 @@ type Database interface {
GetUserId(username string) (int, error) GetUserId(username string) (int, error)
AddProject(name string, description string, username string) error AddProject(name string, description string, username string) error
Migrate() error Migrate() error
MigrateSampleData() error
GetProjectId(projectname string) (int, error) GetProjectId(projectname string) (int, error)
AddWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error AddWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error
AddUserToProject(username string, projectname string, role string) error AddUserToProject(username string, projectname string, role string) error
@ -49,6 +50,9 @@ type UserProjectMember struct {
//go:embed migrations //go:embed migrations
var scripts embed.FS var scripts embed.FS
//go:embed sample_data
var sampleData embed.FS
// TODO: Possibly break these out into separate files bundled with the embed package? // TODO: Possibly break these out into separate files bundled with the embed package?
const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)" const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)"
const projectInsert = "INSERT INTO projects (name, description, owner_user_id) SELECT ?, ?, id FROM users WHERE username = ?" const projectInsert = "INSERT INTO projects (name, description, owner_user_id) SELECT ?, ?, id FROM users WHERE username = ?"
@ -60,9 +64,10 @@ const addWeeklyReport = `WITH UserLookup AS (SELECT id FROM users WHERE username
const addUserToProject = "INSERT INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)" // WIP const addUserToProject = "INSERT INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)" // WIP
const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = ? AND project_id = ?" const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = ? AND project_id = ?"
const getProjectsForUser = `SELECT projects.id, projects.name, projects.description, projects.owner_user_id const getProjectsForUser = `SELECT p.id, p.name, p.description FROM projects p
FROM projects JOIN user_roles ON projects.id = user_roles.project_id JOIN user_roles ur ON p.id = ur.project_id
JOIN users ON user_roles.user_id = users.id WHERE users.username = ?;` JOIN users u ON ur.user_id = u.id
WHERE u.username = ?`
// DbConnect connects to the database // DbConnect connects to the database
func DbConnect(dbpath string) Database { func DbConnect(dbpath string) Database {
@ -378,3 +383,42 @@ func (d *Db) Migrate() error {
return nil return nil
} }
// MigrateSampleData applies sample data to the database.
func (d *Db) MigrateSampleData() error {
// Insert sample data
files, err := sampleData.ReadDir("sample_data")
if err != nil {
return err
}
if len(files) == 0 {
println("No sample data files found")
}
tr := d.MustBegin()
// Iterate over each SQL file and execute it
for _, file := range files {
if file.IsDir() || filepath.Ext(file.Name()) != ".sql" {
continue
}
// This is perhaps not the most elegant way to do this
sqlBytes, err := sampleData.ReadFile("sample_data/" + file.Name())
if err != nil {
return err
}
sqlQuery := string(sqlBytes)
_, err = tr.Exec(sqlQuery)
if err != nil {
return err
}
}
if tr.Commit() != nil {
return err
}
return nil
}

View file

@ -4,11 +4,9 @@
-- password is the hashed password -- password is the hashed password
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
userId TEXT DEFAULT (HEX(RANDOMBLOB(4))) NOT NULL UNIQUE,
username VARCHAR(255) NOT NULL UNIQUE, username VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL password VARCHAR(255) NOT NULL
); );
-- Users are commonly searched by username and userId -- 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_username_index ON users (username);
CREATE INDEX IF NOT EXISTS users_userId_index ON users (userId);

View file

@ -0,0 +1,35 @@
INSERT OR IGNORE INTO users(username, password)
VALUES ("admin", "123");
INSERT OR IGNORE INTO users(username, password)
VALUES ("user", "123");
INSERT OR IGNORE INTO users(username, password)
VALUES ("user2", "123");
INSERT OR IGNORE INTO projects(name,description,owner_user_id)
VALUES ("projecttest","test project", 1);
INSERT OR IGNORE INTO projects(name,description,owner_user_id)
VALUES ("projecttest2","test project2", 1);
INSERT OR IGNORE INTO projects(name,description,owner_user_id)
VALUES ("projecttest3","test project3", 1);
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (1,1,"project_manager");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (2,1,"member");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (3,1,"member");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (3,2,"member");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (3,3,"member");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (2,1,"project_manager");

View file

@ -34,29 +34,17 @@ type GlobalState interface {
// UpdateCollection(c *fiber.Ctx) error // To update a collection // UpdateCollection(c *fiber.Ctx) error // To update a collection
// DeleteCollection(c *fiber.Ctx) error // To delete a collection // DeleteCollection(c *fiber.Ctx) error // To delete a collection
// SignCollection(c *fiber.Ctx) error // To sign a collection // SignCollection(c *fiber.Ctx) error // To sign a collection
GetButtonCount(c *fiber.Ctx) error // For demonstration purposes ListAllUsers(c *fiber.Ctx) error // To get a list of all users in the application database
IncrementButtonCount(c *fiber.Ctx) error // For demonstration purposes ListAllUsersProject(c *fiber.Ctx) error // To get a list of all users for a specific project
ListAllUsers(c *fiber.Ctx) error // To get a list of all users in the application database ProjectRoleChange(c *fiber.Ctx) error // To change a users role in a project
ListAllUsersProject(c *fiber.Ctx) error // To get a list of all users for a specific project
ProjectRoleChange(c *fiber.Ctx) error // To change a users role in a project
} }
// "Constructor" // "Constructor"
func NewGlobalState(db database.Database) GlobalState { func NewGlobalState(db database.Database) GlobalState {
return &GState{Db: db, ButtonCount: 0} return &GState{Db: db}
} }
// The global state, which implements all the handlers // The global state, which implements all the handlers
type GState struct { type GState struct {
Db database.Database Db database.Database
ButtonCount int
}
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})
} }

View file

@ -46,6 +46,12 @@ func main() {
// Migrate the database // Migrate the database
if err = db.Migrate(); err != nil { if err = db.Migrate(); err != nil {
fmt.Println("Error migrating database: ", err) fmt.Println("Error migrating database: ", err)
os.Exit(1)
}
if err = db.MigrateSampleData(); err != nil {
fmt.Println("Error migrating sample data: ", err)
os.Exit(1)
} }
// Get our global state // Get our global state
@ -53,6 +59,7 @@ func main() {
// Create the server // Create the server
server := fiber.New() server := fiber.New()
// Mounts the swagger documentation, this is available at /swagger/index.html
server.Get("/swagger/*", swagger.HandlerDefault) server.Get("/swagger/*", swagger.HandlerDefault)
// Mount our static files (Beware of the security implications of this!) // Mount our static files (Beware of the security implications of this!)
@ -61,11 +68,6 @@ func main() {
// Register our unprotected routes // Register our unprotected routes
server.Post("/api/register", gs.Register) 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) server.Post("/api/login", gs.Login)
// Every route from here on will require a valid JWT // Every route from here on will require a valid JWT
@ -73,6 +75,7 @@ func main() {
SigningKey: jwtware.SigningKey{Key: []byte("secret")}, SigningKey: jwtware.SigningKey{Key: []byte("secret")},
})) }))
// Protected routes (require a valid JWT bearer token authentication header)
server.Post("/api/submitReport", gs.SubmitWeeklyReport) server.Post("/api/submitReport", gs.SubmitWeeklyReport)
server.Get("/api/getUserProjects", gs.GetUserProjects) server.Get("/api/getUserProjects", gs.GetUserProjects)
server.Post("/api/loginrenew", gs.LoginRenew) server.Post("/api/loginrenew", gs.LoginRenew)

View file

@ -28,6 +28,21 @@ addUserToProjectPath = base_url + "/api/addUserToProject"
promoteToAdminPath = base_url + "/api/promoteToAdmin" promoteToAdminPath = base_url + "/api/promoteToAdmin"
getUserProjectsPath = base_url + "/api/getUserProjects" getUserProjectsPath = base_url + "/api/getUserProjects"
def test_get_user_projects():
print("Testing get user projects")
loginResponse = login("user2", "123")
# Check if the user is added to the project
response = requests.get(
getUserProjectsPath,
json={"username": "user2"},
headers={"Authorization": "Bearer " + loginResponse.json()["token"]},
)
print(response.text)
print(response.json())
assert response.status_code == 200, "Get user projects failed"
print("got user projects successfully")
# Posts the username and password to the register endpoint # Posts the username and password to the register endpoint
def register(username: string, password: string): def register(username: string, password: string):
@ -148,16 +163,6 @@ def test_add_user_to_project():
assert response.status_code == 200, "Add user to project failed" assert response.status_code == 200, "Add user to project failed"
print("Add user to project successful") print("Add user to project successful")
# Check if the user is added to the project
response = requests.get(
getUserProjectsPath,
json={"username": new_user},
headers={"Authorization": "Bearer " + admin_token},
)
print(response.text)
assert response.status_code == 200, "Get user projects failed"
print("got user projects successfully")
# Test function to sign a report # Test function to sign a report
def test_sign_report(): def test_sign_report():
# Create a project manager user # Create a project manager user
@ -235,6 +240,7 @@ def test_sign_report():
if __name__ == "__main__": if __name__ == "__main__":
test_get_user_projects()
test_create_user() test_create_user()
test_login() test_login()
test_add_project() test_add_project()