Skip to content

Commit

Permalink
feat: db πŸ’Ύ migrations 🚚 + pretty logging πŸ’„ (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
gorillamoe authored May 19, 2024
1 parent c5b83c5 commit 88938ad
Show file tree
Hide file tree
Showing 8 changed files with 424 additions and 42 deletions.
14 changes: 14 additions & 0 deletions db-migrations/2024-05-19-001.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
ALTER TABLE tasks RENAME TO tasks_old;

CREATE TABLE tasks (
name TEXT NOT NULL DEFAULT 'default',
project_name TEXT NOT NULL,
description TEXT NULL DEFAULT NULL,
date DATE NOT NULL DEFAULT (DATE('now','localtime')),
seconds INTEGER NOT NULL DEFAULT 0
);
INSERT INTO tasks (name,project_name,description,date,seconds) SELECT name,project_name,description,date,seconds FROM tasks_old;

DROP TABLE tasks_old;

UPDATE db_version SET version = '2024-05-19-001' WHERE version = '';
5 changes: 5 additions & 0 deletions db-migrations/2024-05-19-002.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ALTER TABLE tasks ADD COLUMN status TEXT NOT NULL DEFAULT 'active';
ALTER TABLE projects ADD COLUMN status TEXT NOT NULL DEFAULT 'active';
ALTER TABLE task_definitions ADD COLUMN status TEXT NOT NULL DEFAULT 'active';

UPDATE db_version SET version = '2024-05-19-002' WHERE version = '2024-05-19-001';
370 changes: 336 additions & 34 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"i": "0.3.7",
"js-yaml": "4.1.0",
"moment": "2.30.1",
"node-color-log": "12.0.1",
"npm": "10.8.0",
"react": "18.3.1",
"react-dom": "18.3.1",
Expand Down
57 changes: 54 additions & 3 deletions src/database.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,76 @@
import { readFile } from 'node:fs/promises'
import { readFile, readdir } from 'node:fs/promises'
import logger from 'node-color-log'
import path from 'node:path'
import { Database as Sqlite3Database } from 'sqlite3'
import { open, Database } from 'sqlite'
import { getUserDataPath, getUserConfig } from './lib/ConfigFile'

const dbMigrate = async (db: Database, version: string | null) => {
// read directory and get all migration files in order start from 'version' (excluded)
// and run all migration files in order
const migrationFiles = path.join(__dirname, 'db-migrations')
const files = await readdir(migrationFiles)
const migrationFilesToRun = files
.filter(f => {
const file = f.replace('.sql', '')
if (version === null || version === '') {
return true
}
return file > version
})
.sort()
if (migrationFilesToRun.length === 0) {
logger.info('πŸ—ƒοΈ No migrations to run')
return
}
migrationFilesToRun.forEach(async file => {
logger.warn('πŸ—ƒοΈ Running migration', file)
const sql = await readFile(path.join(migrationFiles, file), 'utf8')
await db.exec(sql)
await db.run('UPDATE db_version SET version = ?', file.replace('.sql', ''))
})
}

const initDBMigration = async (db: Database) => {
const versionTableExits = await db.all(
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
'db_version',
)
if (versionTableExits.length === 0) {
await db.exec('CREATE TABLE db_version (version TEXT)')
await db.run('INSERT INTO db_version (version) VALUES ("")')
await dbMigrate(db, null)
} else {
const version = await db.get('SELECT version FROM db_version')
await dbMigrate(db, version.version)
}
}

const initDB = async (): Promise<Database> => {
const userConfig = await getUserConfig()
const userDataPath = await getUserDataPath()
let sqlFilePath = path.join(userDataPath, 'timetrack.db')
if (userConfig && userConfig.database_file_path) {
sqlFilePath = userConfig.database_file_path
console.warn('Using custom database file path πŸ—ƒοΈ', sqlFilePath)
console.warn('πŸ—ƒοΈ Using custom database file path', sqlFilePath)
}
let db: Database
try {
db = await open({ filename: sqlFilePath, driver: Sqlite3Database })
} catch (err) {
logger.error('πŸ“’ Error opening database file:', sqlFilePath)
electron.app.quit()
}
const db = await open({ filename: sqlFilePath, driver: Sqlite3Database })
const atable = await db.all(
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
'projects',
)
if (atable.length === 0) {
const sql = await readFile(path.join(__dirname, 'db.sql'), 'utf8')
await db.exec(sql)
} else {
// files exists, and projects table exist check if migration is needed
await initDBMigration(db)
}
return db
}
Expand Down
4 changes: 4 additions & 0 deletions src/db.sql
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
CREATE TABLE IF NOT EXISTS projects (
name TEXT NOT NULL UNIQUE
);

CREATE TABLE IF NOT EXISTS tasks (
name TEXT NOT NULL DEFAULT 'default',
project_name TEXT NOT NULL,
description TEXT NULL DEFAULT NULL,
date DATE NOT NULL DEFAULT (DATE('now','localtime')),
seconds INTEGER NOT NULL DEFAULT 0
);

CREATE UNIQUE INDEX tasks_name_date_pjn_IDX ON tasks (name,date,project_name);

CREATE TABLE IF NOT EXISTS task_definitions (
name TEXT NOT NULL,
project_name TEXT NOT NULL
);

CREATE UNIQUE INDEX tasks_defs_name_pjn_IDX ON task_definitions (name,project_name);
11 changes: 6 additions & 5 deletions src/lib/ConfigFile.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { app } from 'electron'
import logger from 'node-color-log'
import path from 'node:path'
import { access, mkdir, readFile } from 'node:fs/promises'
import yaml from 'js-yaml'
Expand All @@ -14,11 +15,11 @@ export const getUserDataPath = async (): Promise<string | null> => {
return userDataPath
} catch (err) {
if (err.code === 'ENOENT') {
console.error('Creating user data path:', userDataPath)
logger.error('πŸ“’ Creating user data path:', userDataPath)
await mkdir(userDataPath, { recursive: true })
return userDataPath
} else {
console.error('Error accessing user data path:', userDataPath)
logger.error('πŸ“’ Error accessing user data path:', userDataPath)
return null
}
}
Expand All @@ -31,12 +32,12 @@ export const getUserConfig = async (): Promise<UserConfigFile> => {
await access(configFilePath)
const content = await readFile(configFilePath, 'utf8')
const config = yaml.load(content) as UserConfigFile
console.warn('User config loaded ✨', config)
logger.warn('User config loaded ✨', config)
return config
} catch (err) {
if (err.code === 'ENOENT') {
console.warn(
'No user file found, nothing special, no worries 🀷. Fallback to defaults πŸ˜‡',
logger.info(
'πŸ“’ No user file found, nothing special, no worries 🀷. Fallback to defaults πŸ˜‡',
configFilePath,
)
}
Expand Down
4 changes: 4 additions & 0 deletions vite.main.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export default defineConfig((env) => {
{
src: normalizePath('src/db.sql'),
dest: normalizePath('.')
},
{
src: normalizePath('db-migrations'),
dest: normalizePath('.')
}
]
})
Expand Down

0 comments on commit 88938ad

Please sign in to comment.