Compare commits
5 commits
f437b25da5
...
472940cedc
Author | SHA1 | Date | |
---|---|---|---|
![]() |
472940cedc | ||
![]() |
f5a914330f | ||
![]() |
c31f145c35 | ||
![]() |
47b60038b4 | ||
![]() |
e0de61dd94 |
7 changed files with 68 additions and 6 deletions
|
@ -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
|
||||||
|
|
|
@ -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 = ?"
|
||||||
|
@ -378,3 +382,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
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
INSERT OR IGNORE INTO users (username, password) VALUES
|
||||||
|
('admin', 'password'),
|
||||||
|
('user', 'password');
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO projects (name, description, owner_user_id) VALUES
|
||||||
|
('Project 1', 'Description 1', 1),
|
||||||
|
('Project 2', 'Description 2', 2);
|
|
@ -48,11 +48,16 @@ func main() {
|
||||||
fmt.Println("Error migrating database: ", err)
|
fmt.Println("Error migrating database: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = db.MigrateSampleData(); err != nil {
|
||||||
|
fmt.Println("Error migrating sample data: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Get our global state
|
// Get our global state
|
||||||
gs := handlers.NewGlobalState(db)
|
gs := handlers.NewGlobalState(db)
|
||||||
// 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!)
|
||||||
|
|
|
@ -48,7 +48,7 @@ export default function Register(): JSX.Element {
|
||||||
<InputField
|
<InputField
|
||||||
label="Username"
|
label="Username"
|
||||||
type="text"
|
type="text"
|
||||||
value={username}
|
value={username ?? ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setUsername(e.target.value);
|
setUsername(e.target.value);
|
||||||
}}
|
}}
|
||||||
|
@ -56,7 +56,7 @@ export default function Register(): JSX.Element {
|
||||||
<InputField
|
<InputField
|
||||||
label="Password"
|
label="Password"
|
||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password ?? ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setPassword(e.target.value);
|
setPassword(e.target.value);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -53,7 +53,7 @@ function UserProjectPage(): JSX.Element {
|
||||||
|
|
||||||
const buttons = <></>;
|
const buttons = <></>;
|
||||||
|
|
||||||
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
|
return <BasicWindow content={content} buttons={buttons} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UserProjectPage;
|
export default UserProjectPage;
|
||||||
|
|
Loading…
Reference in a new issue