Before experimenting with the GUI, I'm writing tests to ensure that my data layer functions correctly.
This commit is contained in:
parent
a88d85d3bc
commit
7c9a29b16c
17
README.md
Normal file
17
README.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# TimeChain
|
||||||
|
|
||||||
|
A straightforward program for pasting text, HTML, photos, or anything else. The FILO interface is straightforward: tag it, timestamp it, then view it.
|
||||||
|
|
||||||
|
# Build
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
|
||||||
|
Type the following commands to run tests:
|
||||||
|
|
||||||
|
```
|
||||||
|
npx jest
|
||||||
|
```
|
||||||
5035
package-lock.json
generated
5035
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron .",
|
"start": "electron .",
|
||||||
"compile": "parcel build --dist-dir ./src/dist --target \"jason\" start.js"
|
"build": "parcel build --dist-dir ./src/dist --target \"jason\" start.js"
|
||||||
},
|
},
|
||||||
"browserslist": "> 0.5%, last 2 versions, not dead",
|
"browserslist": "> 0.5%, last 2 versions, not dead",
|
||||||
"targets": {
|
"targets": {
|
||||||
@ -25,12 +25,14 @@
|
|||||||
"@riotjs/compiler": "^6.1.3",
|
"@riotjs/compiler": "^6.1.3",
|
||||||
"@riotjs/parcel-transformer-riot": "^7.0.3",
|
"@riotjs/parcel-transformer-riot": "^7.0.3",
|
||||||
"electron": "^16.0.7",
|
"electron": "^16.0.7",
|
||||||
|
"jest": "^27.4.7",
|
||||||
"parcel": "^2.2.1"
|
"parcel": "^2.2.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"better-sqlite3": "^7.5.0",
|
"better-sqlite3": "^7.5.0",
|
||||||
"dayjs": "^1.10.7",
|
"dayjs": "^1.10.7",
|
||||||
"es6-interface": "^3.2.1",
|
"es6-interface": "^3.2.1",
|
||||||
|
"nanoid": "^3.2.0",
|
||||||
"pubsub-js": "^1.9.4",
|
"pubsub-js": "^1.9.4",
|
||||||
"riot": "^6.1.2"
|
"riot": "^6.1.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
const InterfaceRecord = {
|
const InterfaceRecord = {
|
||||||
find(search,limit,offset){},
|
find(search,sort,limit,offset){},
|
||||||
get(uuid){},
|
get(uuid){},
|
||||||
add(uuid,timestamp,content,mime,hash){},
|
add(uuid,timestamp,content,mime,hash){},
|
||||||
update(uuid,timestamp,content,mime,hash){},
|
update(uuid,content,mime,hash){},
|
||||||
delete(uuid){},
|
delete(uuid){},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,14 +16,14 @@ const InterfaceFile = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const InterfaceTag = {
|
const InterfaceTag = {
|
||||||
add(uuid,word){},
|
add(tag){},
|
||||||
delete(uuid){}
|
delete(tag){}
|
||||||
}
|
}
|
||||||
|
|
||||||
const InterfaceTagLink = {
|
const InterfaceTagLink = {
|
||||||
add(uuid_record,uuid_tag){},
|
add(uuid,tag){},
|
||||||
delete(uuid_record,uuid_tag){},
|
delete(uuid,tag){},
|
||||||
deleteTag(uuid){},
|
deleteTag(tag){},
|
||||||
deleteRecord(uuid){}
|
deleteRecord(uuid){}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,22 +3,61 @@ const {InterfaceRecord,InterfaceFile,InterfaceTag,InterfaceTagLink} = require(".
|
|||||||
|
|
||||||
let db = null;
|
let db = null;
|
||||||
|
|
||||||
const connectToDatabase = (path)=>{
|
const ConnectToDatabase = (path)=>{
|
||||||
db = require('better-sqlite3')(path, {});
|
db = require('better-sqlite3')(path, {});
|
||||||
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TimeChainDataSqliteTag extends Interface(InterfaceTag) {
|
class TimeChainDataSqliteTag extends Interface(InterfaceTag) {
|
||||||
|
|
||||||
constructor(){
|
constructor(){
|
||||||
|
super();
|
||||||
|
const table = `CREATE TABLE IF NOT EXISTS "tags" (
|
||||||
|
"tag" TEXT UNIQUE,
|
||||||
|
"created_at" INTEGER NOT NULL,
|
||||||
|
"updated_at" INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY("tag")
|
||||||
|
);`;
|
||||||
|
|
||||||
|
const table_idx = ``;
|
||||||
|
|
||||||
|
if(!db){
|
||||||
|
throw new Error("Database is not connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
db.exec(table + "\n" + table_idx);
|
||||||
|
this.db = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
add(uuid,word){
|
get table_add(){
|
||||||
|
if(!this._table_add){
|
||||||
|
this._table_add = db.prepare("INSERT INTO tags (tag,created_at,updated_at) VALUES (?,?,?)");
|
||||||
|
}
|
||||||
|
return this.table_add;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(uuid){
|
get table_delete(){
|
||||||
|
if(!this._table_delete){
|
||||||
|
this._table_delete = db.prepare("DELETE FROM tags WHERE tag = ?");
|
||||||
|
}
|
||||||
|
return this._table_delete;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(tag){
|
||||||
|
return new Promise(resolve=>{
|
||||||
|
const dt = Math.floor(Date.now());
|
||||||
|
const res = this.table_add.run(tag,dt,dt);
|
||||||
|
return resolve(res?.changes);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(tag){
|
||||||
|
return new Promise(resolve=>{
|
||||||
|
const dt = Math.floor(Date.now());
|
||||||
|
const res = this.table_delete.run(tag);
|
||||||
|
return resolve(res?.changes);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -26,14 +65,91 @@ class TimeChainDataSqliteTag extends Interface(InterfaceTag) {
|
|||||||
class TimeChainDataSqliteTagLink extends Interface(InterfaceTagLink) {
|
class TimeChainDataSqliteTagLink extends Interface(InterfaceTagLink) {
|
||||||
|
|
||||||
constructor(){
|
constructor(){
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.table = `CREATE TABLE IF NOT EXISTS "taglink" (
|
||||||
|
"uuid" TEXT NOT NULL,
|
||||||
|
"tag" TEXT NOT NULL,
|
||||||
|
"created_at" INTEGER NOT NULL
|
||||||
|
);`;
|
||||||
|
this.table_idx = `
|
||||||
|
CREATE INDEX IF NOT EXISTS "uuid_idx" ON "taglink" (
|
||||||
|
"uuid"
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS "tag_idx" ON "taglink" (
|
||||||
|
"tag"
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
db.exec(this.table + "\n" + this.table_idx);
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
get table_add(){
|
||||||
|
if(!_table_add){
|
||||||
|
this._table_add = db.prepare('INSERT INTO taglink (uuid,tag,created_at) VALUES (?,?,?)');
|
||||||
|
}
|
||||||
|
return this._table_add;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get table_delete(){
|
||||||
|
if(!this._table_delete){
|
||||||
|
this._table_delete = db.prepare('DELETE FROM taglink WHERE uuid=? AND tag=?');
|
||||||
|
}
|
||||||
|
return this._table_delete;
|
||||||
|
}
|
||||||
|
|
||||||
|
get table_delete_tag(){
|
||||||
|
if(!this._table_delete_tag){
|
||||||
|
this._table_delete_tag = db.prepare('DELETE FROM taglink WHERE tag=?');
|
||||||
|
}
|
||||||
|
return this._table_delete_tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
get table_delete_record(){
|
||||||
|
if(!this._table_delete_record){
|
||||||
|
this._table_delete_record = db.prepare('DELETE FROM taglink WHERE uuid=?');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add(uuid,tag){
|
||||||
|
return new Promise(resolve=>{
|
||||||
|
const prepare = this.table_add;
|
||||||
|
const dt = Math.floor(Date.now());
|
||||||
|
const res = prepare.run(uuid,tag,dt);
|
||||||
|
return resolve(res?.changes);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(uuid,tag){
|
||||||
|
return new Promise(resolve=>{
|
||||||
|
const prepare = this.table_delete;
|
||||||
|
const res = prepare.run(uuid,tag);
|
||||||
|
return resolve(res?.changes);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteTag(tag){
|
||||||
|
return new Promise(resolve=>{
|
||||||
|
const prepare = this.table_delete_tag;
|
||||||
|
const res = prepare.run(tag);
|
||||||
|
return resolve(res?.changes);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRecord(uuid){
|
||||||
|
return new Promise(resolve=>{
|
||||||
|
const prepare = this.table_delete_record;
|
||||||
|
const res = prepare.run(uuid);
|
||||||
|
return resolve(res?.changes);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TimeChainDataSqliteFile extends Interface(InterfaceFile) {
|
class TimeChainDataSqliteFile extends Interface(InterfaceFile) {
|
||||||
|
|
||||||
constructor(){
|
constructor(){
|
||||||
|
super();
|
||||||
const table = `CREATE TABLE IF NOT EXISTS "files" (
|
const table = `CREATE TABLE IF NOT EXISTS "files" (
|
||||||
"uuid" TEXT UNIQUE,
|
"uuid" TEXT UNIQUE,
|
||||||
"uuid_record" TEXT NOT NULL,
|
"uuid_record" TEXT NOT NULL,
|
||||||
@ -108,7 +224,7 @@ class TimeChainDataSqliteFile extends Interface(InterfaceFile) {
|
|||||||
add(uuid_record,uuid,timestamp,content,mime,hash){
|
add(uuid_record,uuid,timestamp,content,mime,hash){
|
||||||
return new Promise(resolve=>{
|
return new Promise(resolve=>{
|
||||||
const dt = Math.floor(Date.now());
|
const dt = Math.floor(Date.now());
|
||||||
const res = this.table_insert.exec(uuid,uuid_record,timestamp,content,mime,hash,dt,dt);
|
const res = this.table_insert.run(uuid,uuid_record,timestamp,content,mime,hash,dt,dt);
|
||||||
return resolve(res?.changes);
|
return resolve(res?.changes);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -129,21 +245,22 @@ class TimeChainDataSqliteFile extends Interface(InterfaceFile) {
|
|||||||
|
|
||||||
delete(uuid){
|
delete(uuid){
|
||||||
return new Promise(resolve=>{
|
return new Promise(resolve=>{
|
||||||
const res = this.table_delete.exec(uuid);
|
const prepare = this.table_delete;
|
||||||
|
const res = prepare.exec(uuid);
|
||||||
return resolve(res?.changes);
|
return resolve(res?.changes);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteRecord(){
|
deleteRecord(uuid_record){
|
||||||
return new Promise(resolve=>{
|
return new Promise(resolve=>{
|
||||||
const res = this.table_delete_record.exec(uuid_record);
|
const res = this.table_delete_record.run(uuid_record);
|
||||||
return resolve(res?.changes);
|
return resolve(res?.changes);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
update(uuid,timestamp,content,mime,hash){
|
update(uuid,timestamp,content,mime,hash){
|
||||||
return new Promise(resolve=>{
|
return new Promise(resolve=>{
|
||||||
const res = this.table_update.exec(timestamp,content,mime,hash,uuid);
|
const res = this.table_update.run(timestamp,content,mime,hash,uuid);
|
||||||
return resolve(res?.changes);
|
return resolve(res?.changes);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -152,6 +269,7 @@ class TimeChainDataSqliteFile extends Interface(InterfaceFile) {
|
|||||||
class TimeChainDataSqliteRecord extends Interface(InterfaceRecord) {
|
class TimeChainDataSqliteRecord extends Interface(InterfaceRecord) {
|
||||||
|
|
||||||
constructor(){
|
constructor(){
|
||||||
|
super();
|
||||||
//TODO: Create Tables Here if not exist
|
//TODO: Create Tables Here if not exist
|
||||||
const table = `CREATE TABLE IF NOT EXISTS "records" (
|
const table = `CREATE TABLE IF NOT EXISTS "records" (
|
||||||
"uuid" TEXT NOT NULL UNIQUE,
|
"uuid" TEXT NOT NULL UNIQUE,
|
||||||
@ -241,7 +359,8 @@ class TimeChainDataSqliteRecord extends Interface(InterfaceRecord) {
|
|||||||
|
|
||||||
delete(uuid){
|
delete(uuid){
|
||||||
return new Promise(resolve=>{
|
return new Promise(resolve=>{
|
||||||
const res = this.table_delete.exec(uuid);
|
const prepare = this.table_delete;
|
||||||
|
const res = prepare.run(uuid);
|
||||||
return resolve(res?.changes);
|
return resolve(res?.changes);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -249,7 +368,7 @@ class TimeChainDataSqliteRecord extends Interface(InterfaceRecord) {
|
|||||||
update(uuid,content,mime,hash){
|
update(uuid,content,mime,hash){
|
||||||
return new Promise(resolve=>{
|
return new Promise(resolve=>{
|
||||||
const dt = Math.floor(Date.now());
|
const dt = Math.floor(Date.now());
|
||||||
const res = this.table_update.exec(content,mime,hash,dt,uuid);
|
const res = this.table_update.run(content,mime,hash,dt,uuid);
|
||||||
return resolve(res?.changes);
|
return resolve(res?.changes);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -260,5 +379,14 @@ class TimeChainDataSqlite {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
ConnectToDatabase,
|
||||||
|
TimeChainDataSqlite,
|
||||||
|
TimeChainDataSqliteRecord,
|
||||||
|
TimeChainDataSqliteFile,
|
||||||
|
TimeChainDataSqliteTagLink,
|
||||||
|
TimeChainDataSqliteTag
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
<app>
|
<app>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</app>
|
</app>
|
||||||
77
test/sqlite.test.js
Normal file
77
test/sqlite.test.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
const {TimeChainDataSqliteRecord,ConnectToDatabase, TimeChainDataSqliteFile, TimeChainDataSqliteTag, TimeChainDataSqliteTagLink} = require('../src/data/sqlite');
|
||||||
|
const { nanoid } = require('nanoid');
|
||||||
|
const { unsubscribe } = require('pubsub-js');
|
||||||
|
|
||||||
|
let db = null;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
db = ConnectToDatabase(__dirname + "/test.db");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(()=>{
|
||||||
|
db.close();
|
||||||
|
})
|
||||||
|
|
||||||
|
test("Should create a records table", ()=>{
|
||||||
|
const rec = new TimeChainDataSqliteRecord();
|
||||||
|
|
||||||
|
const st = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?");
|
||||||
|
const res = st.get('records');
|
||||||
|
|
||||||
|
expect(res).not.toBeNull();
|
||||||
|
expect(res.name).toEqual('records');
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Should create a files table", ()=>{
|
||||||
|
const file = new TimeChainDataSqliteFile();
|
||||||
|
|
||||||
|
const st = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?");
|
||||||
|
const res = st.get('files');
|
||||||
|
|
||||||
|
expect(res).not.toBeNull();
|
||||||
|
expect(res.name).toEqual('files');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Should create a tags table", ()=>{
|
||||||
|
const tags = new TimeChainDataSqliteTag();
|
||||||
|
|
||||||
|
const st = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?");
|
||||||
|
const res = st.get('tags');
|
||||||
|
|
||||||
|
expect(res).not.toBeNull();
|
||||||
|
expect(res.name).toEqual('tags');
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Should create a tags link table", ()=>{
|
||||||
|
const links = new TimeChainDataSqliteTagLink();
|
||||||
|
|
||||||
|
const st = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?");
|
||||||
|
const res = st.get('taglink');
|
||||||
|
|
||||||
|
expect(res).not.toBeNull();
|
||||||
|
expect(res.name).toEqual('taglink');
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Should create a record in the database and then remove it",async ()=>{
|
||||||
|
const rec = new TimeChainDataSqliteRecord();
|
||||||
|
const id = nanoid();
|
||||||
|
const hash = "fakehash";
|
||||||
|
const content = "This is a test";
|
||||||
|
const mime = "text/plain";
|
||||||
|
const ts = Math.floor(Date.now());
|
||||||
|
|
||||||
|
return rec.add(id,ts,content,mime,hash).then(res=>{
|
||||||
|
expect(res).toEqual(1);
|
||||||
|
return rec.get(id).then(res=>{
|
||||||
|
expect(res.uuid).toEqual(id);
|
||||||
|
expect(res.timestamp).toEqual(ts);
|
||||||
|
expect(res.mime).toEqual(mime);
|
||||||
|
expect(res.content).toEqual(content);
|
||||||
|
expect(res.hash).toEqual(hash);
|
||||||
|
return rec.delete(id);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
BIN
test/test.db
Normal file
BIN
test/test.db
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user