Compare commits

...

5 commits

7 changed files with 68 additions and 6 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 = ?"
@ -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
}

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,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);

View file

@ -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!)

View file

@ -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);
}} }}

View file

@ -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;