-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
319 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
const SQLite = (window as any).sqlitePlugin; | ||
let db; | ||
|
||
export class Database { | ||
db; | ||
|
||
/** | ||
* Start the database and create an instance when necessary | ||
*/ | ||
static async getInstance(): Promise<Database> { | ||
if (db) return db | ||
db = new Database() | ||
await db.connect() | ||
return db | ||
} | ||
|
||
/** | ||
* Start database connection | ||
* Only used in getInstance() method | ||
*/ | ||
async connect() { | ||
if (this.db) return this.db; | ||
this.db = await new Promise((resolve, reject) => { | ||
const db = SQLite.openDatabase({ name: 'tasklist5.db' }, () => resolve(db), err => reject(err)) | ||
}) | ||
return this.db | ||
} | ||
|
||
/** | ||
* Executes a query | ||
* @param sql Query to be executed | ||
* @param params Params that substitutes '?' in the query | ||
*/ | ||
async query(sql: string, params: any[] = []): Promise<SQLResult> { | ||
params = this.treatParams(params) | ||
return new Promise<SQLResult>(resolve => { | ||
this.db.transaction(async tx => { | ||
const [newTx, results] = await (new Promise((resolve, reject) => { | ||
tx.executeSql(sql, params, (a, b) => resolve([a, b]), err => reject(err)) | ||
})) as any | ||
resolve(results) | ||
}) | ||
}) | ||
} | ||
|
||
/** | ||
* Execute a query and get returned rows as an array | ||
* Used in SELECT queries | ||
* @param sql Query to be executed | ||
* @param params Params that substitutes '?' in the query | ||
*/ | ||
async queryAndGetRows(sql: string, params: any[] = []): Promise<any[]> { | ||
const result = await this.query(sql, params) | ||
const data = [] | ||
|
||
for (let i = 0; i < result.rows.length; i++) { | ||
data.push(result.rows.item(i)) | ||
} | ||
|
||
return data; | ||
} | ||
|
||
/** | ||
* Executes a query and get the inserted id | ||
* Used in INSERT queries | ||
* @param sql Query to be executed | ||
* @param params Params that substitutes '?' in the query | ||
*/ | ||
async queryAndGetInsertId(sql: string, params: any[] = []): Promise<number> { | ||
const result = await this.query(sql, params) | ||
return result.insertId | ||
} | ||
|
||
/** | ||
* Insert data in the database | ||
* @param table Table to be inserted | ||
* @param keys Keys to be inserted | ||
* @param values Values to be inserted | ||
*/ | ||
async insert(table: string, keys: string[], values: any[]): Promise<number> { | ||
const gaps = keys.map(k => '?') | ||
const sql = `INSERT INTO ${table} (${keys.join(', ')}) VALUES (${gaps.join(', ')})` | ||
const insertId = this.queryAndGetInsertId(sql, values) | ||
return insertId | ||
} | ||
|
||
/** | ||
* Update data in the database | ||
* @param table Table to be inserted | ||
* @param keys Keys to be inserted | ||
* @param values Values to be inserted | ||
* @param where Where statement to know which lines will be updated | ||
*/ | ||
async update(table: string, keys: string[], values: any[], where?: { [key: string]: any }) { | ||
const keysString = keys.map(k => k + ' = ?').join(', ') | ||
const { sql, params } = this.buildWhere(where) | ||
const query = `UPDATE ${table} SET ${keysString} ` + sql | ||
return this.query(query, values.concat(params)) | ||
} | ||
|
||
/** | ||
* Delete one or more rows in a table | ||
* @param table Table name | ||
* @param statement Where statement to know which lines will be deleted | ||
*/ | ||
async delete<T>(table: string, statement?: WhereStatement<T>): Promise<any> { | ||
const { sql, params } = this.buildWhere(statement) | ||
const query = `DELETE ${table} ` + sql | ||
return this.query(query, params) | ||
} | ||
|
||
/** | ||
* Treats an where object and returns the query with its parameters | ||
* @param where Where statement | ||
*/ | ||
private buildWhere<T>(where: WhereStatement<T>): WhereStatementResult { | ||
const values = [] | ||
let sql = '' | ||
if (where) { | ||
Object.keys(where).forEach(k => values.push(where[k])) | ||
sql = 'WHERE ' + Object.keys(where).map(k => k + ' = ?').join(' AND ') | ||
} | ||
return { | ||
params: values, | ||
sql: sql, | ||
} | ||
} | ||
|
||
/** | ||
* Treat all parameters to be used in queries | ||
* @param params Parameters | ||
*/ | ||
private treatParams(params: any[]): any[] { | ||
const newParams = [] | ||
params.forEach(p => { | ||
switch (typeof p) { | ||
case 'string': | ||
newParams.push(p.trim()); break; | ||
case 'boolean': | ||
newParams.push(p ? 1 : 0); break; | ||
case 'object': | ||
if (p instanceof Date) { | ||
newParams.push(p.toISOString()) | ||
} else if (p !== null) { | ||
try { newParams.push(JSON.stringify(p)) } | ||
catch (e) { newParams.push(p) } | ||
} else { | ||
newParams.push(p) | ||
} | ||
break; | ||
default: | ||
newParams.push(p) | ||
} | ||
}) | ||
return newParams | ||
} | ||
} | ||
|
||
type WhereStatement<T> = { | ||
[K in keyof T]?: T[K] | ||
} | ||
|
||
type WhereStatementResult = { | ||
sql: string | ||
params: any[] | ||
} | ||
|
||
type SQLResult = { | ||
rows: { | ||
length: number | ||
item: (index: number) => any | ||
} | ||
insertId: number | undefined | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import { Database } from "../helpers/Database"; | ||
import * as moment from 'moment' | ||
|
||
export class Task { | ||
|
||
static TABLE_NAME = 'task' | ||
static KEYS: ObjectKeyDefinition<Task>[] = [ | ||
{ name: 'id', type: Number, primary: true }, | ||
{ name: 'title', type: String }, | ||
{ name: 'description', type: String }, | ||
{ name: 'done', type: Boolean }, | ||
{ name: 'create_date', type: Date }, | ||
] | ||
|
||
id: number | ||
title: string | ||
description: string | ||
done: boolean | ||
create_date: Date | ||
|
||
/** | ||
* Convert an object to a Task instance | ||
* @param data Task params | ||
*/ | ||
static parse(data?: ObjectKeys<Task>): Task; | ||
static parse(data?: ObjectKeys<Task>[]): Task[]; | ||
static parse(data?: (ObjectKeys<Task> | ObjectKeys<Task>[])): (Task | Task[]) { | ||
data = data || {} | ||
if (Array.isArray(data)) { | ||
return data.map(i => Task.parse(i)) | ||
} | ||
|
||
const task = new Task() | ||
Task.KEYS.forEach(key => { | ||
if (typeof data[key.name] == 'undefined') return; | ||
if (key.type == Date && data[key.name]) { | ||
try { | ||
task[key.name] = moment(data[key.name] as any).toDate() | ||
} catch (e) { console.warn('Not possible to convert date type') } | ||
} else if (data[key.name] === null) { | ||
task[key.name] = null | ||
} else { | ||
task[key.name] = key.type(data[key.name]) as any | ||
} | ||
}) | ||
|
||
return task | ||
} | ||
|
||
/** | ||
* Prepare Task table in the database | ||
*/ | ||
static async prepare(): Promise<any> { | ||
const db = await Database.getInstance() | ||
|
||
const keys = Task.KEYS.map(k => { | ||
const name = k.name | ||
const primary = k.primary ? 'PRIMARY KEY AUTOINCREMENT' : '' | ||
let type = 'TEXT' | ||
switch (k.type) { | ||
case Number: | ||
type = 'INTEGER'; break; | ||
case String: | ||
type = 'VARCHAR(255)'; break; | ||
case Date: | ||
type = 'TEXT'; break; | ||
case Boolean: | ||
type = 'INTEGER'; break; | ||
} | ||
return [name, type, primary].join(' ') | ||
}) | ||
|
||
const sql = ` | ||
CREATE TABLE IF NOT EXISTS ${Task.TABLE_NAME} ( | ||
${keys.join(',\n')} | ||
) | ||
`.trim() | ||
|
||
return db.query(sql) | ||
} | ||
|
||
/** | ||
* Find all stored tasks | ||
*/ | ||
static async findAll(): Promise<Task[]> { | ||
const db = await Database.getInstance() | ||
const rows = await db.queryAndGetRows(`SELECT * FROM ${Task.TABLE_NAME}`) | ||
return Task.parse(rows) | ||
} | ||
|
||
/** | ||
* Find a task | ||
* @param id Task id | ||
*/ | ||
static async findById(id: number): Promise<Task> { | ||
const db = await Database.getInstance() | ||
const primaryKey = Task.KEYS.find(k => k.primary).name | ||
const rows = await db.queryAndGetRows(`SELECT * FROM ${Task.TABLE_NAME} WHERE ${primaryKey} = ?`, [id]) | ||
if (!rows.length) return null | ||
return Task.parse(rows[0]) | ||
} | ||
|
||
/** | ||
* Save (insert or update) a task in the database | ||
*/ | ||
async save(): Promise<Task> { | ||
const db = await Database.getInstance() | ||
const primaryKey = Task.KEYS.find(k => k.primary).name | ||
const update = !!(this[primaryKey] && (await Task.findById(this[primaryKey] as any))) | ||
const keys = Task.KEYS.filter(k => !k.primary).map(k => k.name) | ||
const values = keys.map(k => this[k]) | ||
|
||
const updateWhere = {} | ||
updateWhere[primaryKey] = this[primaryKey] | ||
|
||
const insertId = !update ? | ||
await db.insert(Task.TABLE_NAME, keys, values) : | ||
await db.update(Task.TABLE_NAME, keys, values, updateWhere) | ||
|
||
if (typeof insertId == 'number') this[primaryKey] = insertId | ||
return this | ||
} | ||
|
||
/** | ||
* Delete a task in the database | ||
*/ | ||
async delete(): Promise<Task> { | ||
const db = await Database.getInstance() | ||
const primaryKey = Task.KEYS.find(k => k.primary).name | ||
const value = this[primaryKey] | ||
const where = {} | ||
where[primaryKey] = value | ||
await db.delete(Task.TABLE_NAME, where) | ||
this[primaryKey] = null | ||
return this | ||
} | ||
} | ||
|
||
type Creator<T> = (v) => T | ||
type ObjectKeys<T> = {[K in keyof T]?: T[K]} | ||
type ObjectKeyDefinition<T> = { | ||
name: keyof T | ||
type: Creator<Number | String | Boolean | Date> | ||
primary?: boolean | ||
} |