Compare commits

..

2 Commits

17 changed files with 5763 additions and 515 deletions

80
.gitignore vendored
View File

@ -1,40 +1,40 @@
# Logs # Logs
logs logs
*.log *.log
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
lerna-debug.log* lerna-debug.log*
.pnpm-debug.log* .pnpm-debug.log*
.npm .npm
# yarn v2 # yarn v2
.yarn/cache .yarn/cache
.yarn/unplugged .yarn/unplugged
.yarn/build-state.yml .yarn/build-state.yml
.yarn/install-state.gz .yarn/install-state.gz
.pnp.* .pnp.*
# Dependency directories # Dependency directories
node_modules/ node_modules/
jspm_packages/ jspm_packages/
# dotenv environment variable files # dotenv environment variable files
.env .env
.env.development.local .env.development.local
.env.test.local .env.test.local
.env.production.local .env.production.local
.env.local .env.local
# IDEs # IDEs
.vscode .vscode
.idea .idea
# parcel-bundler cache (https://parceljs.org/) # parcel-bundler cache (https://parceljs.org/)
.cache .cache
.parcel-cache .parcel-cache
# build directory # build directory
dist/ dist/

View File

@ -1,6 +1,6 @@
{ {
"extends": "@parcel/config-default", "extends": "@parcel/config-default",
"transformers": { "transformers": {
"*.riot": ["@riotjs/parcel-transformer-riot"] "*.riot": ["@riotjs/parcel-transformer-riot"]
} }
} }

17
README.md Normal file
View 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
```

View File

@ -1,21 +1,21 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'"> <meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Time Chain</title> <title>Time Chain</title>
<link rel="stylesheet" href="src/css/main.css"> <link rel="stylesheet" href="src/css/main.css">
</head> </head>
<body> <body>
<div class="loading" > <div class="loading" >
<h1>Time Chain!</h1> <h1>Time Chain!</h1>
<p>Time traveling your data! Comming soon...</p> <p>Time traveling your data! Comming soon...</p>
<p id="timestamp"></p> <p id="timestamp"></p>
<img src="src/img/chain2.svg" class="rotate"> <img src="src/img/chain2.svg" class="rotate">
</div> </div>
<script type="module" src="src/dist/start.js"></script> <script type="module" src="src/dist/start.js"></script>
</body> </body>
</html> </html>

5035
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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"
} }

View File

@ -1,31 +1,31 @@
body { body {
background-color: #333; background-color: #333;
color: whitesmoke; color: whitesmoke;
} }
.loading { .loading {
text-align: center; text-align: center;
padding: 5em; padding: 5em;
} }
.loading img { .loading img {
width: 30%; width: 30%;
} }
.loading p { .loading p {
font-weight: 700; font-weight: 700;
font-size: 1.2em; font-size: 1.2em;
} }
.rotate { .rotate {
animation: rotation 2s infinite linear; animation: rotation 2s infinite linear;
} }
@keyframes rotation { @keyframes rotation {
from { from {
transform: rotate(0deg); transform: rotate(0deg);
} }
to { to {
transform: rotate(359deg); transform: rotate(359deg);
} }
} }

View File

@ -1,35 +1,35 @@
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){},
} }
const InterfaceFile = { const InterfaceFile = {
add(uuid_record,uuid,timestamp,content,mime,hash){}, add(uuid_record,uuid,timestamp,content,mime,hash){},
getByRecord(uuid_record){}, getByRecord(uuid_record){},
get(uuid){}, get(uuid){},
delete(uuid){}, delete(uuid){},
deleteRecord(uuid_record){}, deleteRecord(uuid_record){},
update(uuid,timestamp,content,mime,hash){} update(uuid,timestamp,content,mime,hash){}
} }
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){}
} }
module.exports = { module.exports = {
InterfaceTagLink, InterfaceTagLink,
InterfaceTag, InterfaceTag,
InterfaceFile, InterfaceFile,
InterfaceRecord InterfaceRecord
} }

View File

@ -1,76 +1,76 @@
const { ipcMain } = require('electron'); const { ipcMain } = require('electron');
const {app} = require('electron'); const {app} = require('electron');
const config = app.getPath('userData'); const config = app.getPath('userData');
const Conf = require('conf'); const Conf = require('conf');
const config = new Conf(); const config = new Conf();
// ** Extra Data // ** Extra Data
ipcMain.on('timechain-config-dir', (event,arg) => { ipcMain.on('timechain-config-dir', (event,arg) => {
const configDir = app.getPath('userData'); const configDir = app.getPath('userData');
event.reply('timechain-config-dir-reply', configDir); event.reply('timechain-config-dir-reply', configDir);
}); });
// ** RECORD ** // ** RECORD **
ipcMain.on('timechain-record-add', (event, arg) => { ipcMain.on('timechain-record-add', (event, arg) => {
event.reply('timechain-record-add-reply', 'pong') event.reply('timechain-record-add-reply', 'pong')
}); });
ipcMain.on('timechain-record-delete', (event, arg) => { ipcMain.on('timechain-record-delete', (event, arg) => {
event.reply('timechain-record-delete-reply', 'pong') event.reply('timechain-record-delete-reply', 'pong')
}); });
ipcMain.on('timechain-record-update', (event, arg) => { ipcMain.on('timechain-record-update', (event, arg) => {
event.reply('timechain-record-update-reply', 'pong') event.reply('timechain-record-update-reply', 'pong')
}); });
ipcMain.on('timechain-record-find', (event, arg) => { ipcMain.on('timechain-record-find', (event, arg) => {
event.reply('timechain-record-find-reply', 'pong') event.reply('timechain-record-find-reply', 'pong')
}); });
// ** FILE ** // ** FILE **
ipcMain.on('timechain-file-find', (event, arg) => { ipcMain.on('timechain-file-find', (event, arg) => {
event.reply('timechain-file-find-reply', 'pong') event.reply('timechain-file-find-reply', 'pong')
}); });
ipcMain.on('timechain-file-add', (event, arg) => { ipcMain.on('timechain-file-add', (event, arg) => {
event.reply('timechain-file-add-reply', 'pong') event.reply('timechain-file-add-reply', 'pong')
}); });
ipcMain.on('timechain-file-update', (event, arg) => { ipcMain.on('timechain-file-update', (event, arg) => {
event.reply('timechain-file-update-reply', 'pong') event.reply('timechain-file-update-reply', 'pong')
}); });
ipcMain.on('timechain-file-delete', (event, arg) => { ipcMain.on('timechain-file-delete', (event, arg) => {
event.reply('timechain-file-delete-reply', 'pong') event.reply('timechain-file-delete-reply', 'pong')
}); });
// ** TAG ** // ** TAG **
ipcMain.on('timechain-tag-add', (event, arg) => { ipcMain.on('timechain-tag-add', (event, arg) => {
event.reply('timechain-tag-add-reply', 'pong') event.reply('timechain-tag-add-reply', 'pong')
}); });
ipcMain.on('timechain-tag-delete', (event, arg) => { ipcMain.on('timechain-tag-delete', (event, arg) => {
event.reply('timechain-tag-delete-reply', 'pong') event.reply('timechain-tag-delete-reply', 'pong')
}); });
// ** TAG LINK ** // ** TAG LINK **
ipcMain.on('timechain-taglink-add', (event, arg) => { ipcMain.on('timechain-taglink-add', (event, arg) => {
event.reply('timechain-taglink-add-reply', 'pong') event.reply('timechain-taglink-add-reply', 'pong')
}); });
ipcMain.on('timechain-taglink-delete', (event, arg) => { ipcMain.on('timechain-taglink-delete', (event, arg) => {
event.reply('timechain-taglink-delete-reply', 'pong') event.reply('timechain-taglink-delete-reply', 'pong')
}); });
ipcMain.on('timechain-taglink-deleteTag', (event, arg) => { ipcMain.on('timechain-taglink-deleteTag', (event, arg) => {
event.reply('timechain-taglink-deleteTag-reply', 'pong') event.reply('timechain-taglink-deleteTag-reply', 'pong')
}); });
ipcMain.on('timechain-taglink-deleteRecord', (event, arg) => { ipcMain.on('timechain-taglink-deleteRecord', (event, arg) => {
event.reply('timechain-taglink-deleteRecord-reply', 'pong') event.reply('timechain-taglink-deleteRecord-reply', 'pong')
}); });

View File

@ -1,264 +1,392 @@
const Interface = require("es6-interface"); const Interface = require("es6-interface");
const {InterfaceRecord,InterfaceFile,InterfaceTag,InterfaceTagLink} = require("./interfaces"); const {InterfaceRecord,InterfaceFile,InterfaceTag,InterfaceTagLink} = require("./interfaces");
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" (
add(uuid,word){ "tag" TEXT UNIQUE,
"created_at" INTEGER NOT NULL,
} "updated_at" INTEGER NOT NULL,
PRIMARY KEY("tag")
delete(uuid){ );`;
} const table_idx = ``;
} if(!db){
throw new Error("Database is not connected");
class TimeChainDataSqliteTagLink extends Interface(InterfaceTagLink) { }
constructor(){
db.exec(table + "\n" + table_idx);
} this.db = db;
}
}
get table_add(){
class TimeChainDataSqliteFile extends Interface(InterfaceFile) { if(!this._table_add){
this._table_add = db.prepare("INSERT INTO tags (tag,created_at,updated_at) VALUES (?,?,?)");
constructor(){ }
const table = `CREATE TABLE IF NOT EXISTS "files" ( return this.table_add;
"uuid" TEXT UNIQUE, }
"uuid_record" TEXT NOT NULL,
"timestamp" INTEGER NOT NULL, get table_delete(){
"content" BLOB NOT NULL, if(!this._table_delete){
"mime" TEXT NOT NULL, this._table_delete = db.prepare("DELETE FROM tags WHERE tag = ?");
"hash" TEXT, }
"created_at" INTEGER NOT NULL, return this._table_delete;
"updated_at" INTEGER NOT NULL, }
PRIMARY KEY("uuid")
);`; add(tag){
const table_idx = ` return new Promise(resolve=>{
CREATE INDEX IF NOT EXISTS "files_idx_record" ON "files" ( const dt = Math.floor(Date.now());
"uuid_record" const res = this.table_add.run(tag,dt,dt);
); return resolve(res?.changes);
CREATE INDEX IF NOT EXISTS "files_idx_time" ON "files" ( });
"timestamp" DESC }
);`;
delete(tag){
if(!db){ return new Promise(resolve=>{
throw new Error("Database is not connected"); const dt = Math.floor(Date.now());
} const res = this.table_delete.run(tag);
return resolve(res?.changes);
db.exec(table + "\n" + table_idx); });
this.db = db; }
}
}
get table_insert() {
if(!this._table_insert){ class TimeChainDataSqliteTagLink extends Interface(InterfaceTagLink) {
this._table_insert = db.prepare(`INSERT INTO files (uuid,uuid_record,timestamp,content,mime,hash,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)`);
} constructor(){
return this._table_insert; super();
}
this.table = `CREATE TABLE IF NOT EXISTS "taglink" (
get table_update() { "uuid" TEXT NOT NULL,
if(!this._table_update){ "tag" TEXT NOT NULL,
this._table_update = db.prepare(`UPDATE files SET timestamp = ?, content = ?, mime = ? hash = ?, updated_at = ? WHERE uuid = ?`); "created_at" INTEGER NOT NULL
} );`;
return this._table_update; this.table_idx = `
} CREATE INDEX IF NOT EXISTS "uuid_idx" ON "taglink" (
"uuid"
get table_delete() { );
if(!this._table_delete){ CREATE INDEX IF NOT EXISTS "tag_idx" ON "taglink" (
this._table_delete = db.prepare(`DELETE FROM files WHERE uuid = ?`); "tag"
} );
return this._table_delete; `;
}
db.exec(this.table + "\n" + this.table_idx);
get table_delete_record() { this.db = db;
}
if(!this._table_delete_record){
this._table_delete_record = db.prepare(`DELETE FROM files WHERE uuid_record = ?`); get table_add(){
} if(!_table_add){
this._table_add = db.prepare('INSERT INTO taglink (uuid,tag,created_at) VALUES (?,?,?)');
return this._table_delete_record; }
} return this._table_add;
}
get table_find_one() {
if(!this._table_find_one){ get table_delete(){
this._table_find_one = db.prepare(`SELECT * FROM files WHERE uuid = ?`); if(!this._table_delete){
} this._table_delete = db.prepare('DELETE FROM taglink WHERE uuid=? AND tag=?');
return this._table_find_one; }
} return this._table_delete;
}
get table_find_record(){
if(!this._table_find_record){ get table_delete_tag(){
this._table_find_record = db.prepare(`SELECT * FROM files WHERE uuid_record = ?`); if(!this._table_delete_tag){
} this._table_delete_tag = db.prepare('DELETE FROM taglink WHERE tag=?');
return this._table_find_record; }
} return this._table_delete_tag;
}
add(uuid_record,uuid,timestamp,content,mime,hash){
return new Promise(resolve=>{ get table_delete_record(){
const dt = Math.floor(Date.now()); if(!this._table_delete_record){
const res = this.table_insert.exec(uuid,uuid_record,timestamp,content,mime,hash,dt,dt); this._table_delete_record = db.prepare('DELETE FROM taglink WHERE uuid=?');
return resolve(res?.changes); }
}); }
}
add(uuid,tag){
getByRecord(uuid_record){ return new Promise(resolve=>{
return new Promise(resolve => { const prepare = this.table_add;
const res = this.table_find_record.get(uuid_record); const dt = Math.floor(Date.now());
return resolve(res); const res = prepare.run(uuid,tag,dt);
}); return resolve(res?.changes);
} });
}
get(uuid){
return new Promise(resolve => { delete(uuid,tag){
const res = this.table_fine_one.get(uuid); return new Promise(resolve=>{
return resolve(res); const prepare = this.table_delete;
}); const res = prepare.run(uuid,tag);
} return resolve(res?.changes);
});
delete(uuid){ }
return new Promise(resolve=>{
const res = this.table_delete.exec(uuid); deleteTag(tag){
return resolve(res?.changes); return new Promise(resolve=>{
}) const prepare = this.table_delete_tag;
} const res = prepare.run(tag);
return resolve(res?.changes);
deleteRecord(){ });
return new Promise(resolve=>{ }
const res = this.table_delete_record.exec(uuid_record);
return resolve(res?.changes); deleteRecord(uuid){
}) return new Promise(resolve=>{
} const prepare = this.table_delete_record;
const res = prepare.run(uuid);
update(uuid,timestamp,content,mime,hash){ return resolve(res?.changes);
return new Promise(resolve=>{ });
const res = this.table_update.exec(timestamp,content,mime,hash,uuid); }
return resolve(res?.changes); }
})
} class TimeChainDataSqliteFile extends Interface(InterfaceFile) {
}
constructor(){
class TimeChainDataSqliteRecord extends Interface(InterfaceRecord) { super();
const table = `CREATE TABLE IF NOT EXISTS "files" (
constructor(){ "uuid" TEXT UNIQUE,
//TODO: Create Tables Here if not exist "uuid_record" TEXT NOT NULL,
const table = `CREATE TABLE IF NOT EXISTS "records" ( "timestamp" INTEGER NOT NULL,
"uuid" TEXT NOT NULL UNIQUE, "content" BLOB NOT NULL,
"timestamp" INTEGER NOT NULL, "mime" TEXT NOT NULL,
"content" TEXT NOT NULL, "hash" TEXT,
"mime" TEXT NOT NULL, "created_at" INTEGER NOT NULL,
"hash" TEXT, "updated_at" INTEGER NOT NULL,
"created_at" INTEGER NOT NULL, PRIMARY KEY("uuid")
"updated_at" INTEGER NOT NULL, );`;
PRIMARY KEY("uuid") const table_idx = `
);`; CREATE INDEX IF NOT EXISTS "files_idx_record" ON "files" (
const table_idx = `CREATE INDEX IF NOT EXISTS "records_idx_timestamp" ON "records" ( "uuid_record"
"timestamp" DESC );
);` CREATE INDEX IF NOT EXISTS "files_idx_time" ON "files" (
"timestamp" DESC
if(!db){ );`;
throw new Error("Database is not connected");
} if(!db){
throw new Error("Database is not connected");
db.exec(table + "\n" + table_idx); }
this.db = db;
} db.exec(table + "\n" + table_idx);
this.db = db;
get table_insert() { }
if(!this._table_insert){
this._table_insert = db.prepare(`INSERT INTO records (uuid,timestamp,content,mime,hash,created_at,updated_at) VALUES (?,?,?,?,?,?,?)`); get table_insert() {
} if(!this._table_insert){
return this._table_insert; this._table_insert = db.prepare(`INSERT INTO files (uuid,uuid_record,timestamp,content,mime,hash,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)`);
} }
return this._table_insert;
get table_update(){ }
if(!this._table_update){
this._table_update = db.prepare(`UPDATE records SET timestamp = ?, content = ?, mime = ?, hash = ? updated_at = ? WHERE uuid = ?`); get table_update() {
} if(!this._table_update){
return this._table_update; this._table_update = db.prepare(`UPDATE files SET timestamp = ?, content = ?, mime = ? hash = ?, updated_at = ? WHERE uuid = ?`);
} }
return this._table_update;
get table_delete(){ }
if(!this._table_delete){
this._table_delete = db.prepare(`DELETE FROM records WHERE uuid = ?`); get table_delete() {
} if(!this._table_delete){
return this._table_delete; this._table_delete = db.prepare(`DELETE FROM files WHERE uuid = ?`);
} }
return this._table_delete;
get table_fine_one(){ }
if(!this._table_fine_one){
this._table_fine_one = db.prepare(`SELECT * FROM records WHERE uuid = ?`); get table_delete_record() {
}
return this._table_fine_one; if(!this._table_delete_record){
} this._table_delete_record = db.prepare(`DELETE FROM files WHERE uuid_record = ?`);
}
add(uuid,timestamp,content,mime,hash){
return new Promise(resolve=>{ return this._table_delete_record;
const dt = Math.floor(Date.now()); }
const res = this.table_insert.run(uuid,timestamp,content,mime,hash,dt,dt);
return resolve(res?.changes); get table_find_one() {
}); if(!this._table_find_one){
} this._table_find_one = db.prepare(`SELECT * FROM files WHERE uuid = ?`);
}
get(uuid){ return this._table_find_one;
return new Promise(resolve=>{ }
const rec = this.table_fine_one.get(uuid);
return resolve(rec); get table_find_record(){
}); if(!this._table_find_record){
} this._table_find_record = db.prepare(`SELECT * FROM files WHERE uuid_record = ?`);
}
find(search,sort=null,limit=undefined,offset=0){ return this._table_find_record;
//TODO: add logic for seach and sort (Currently does nothing) }
return new Promise(resolve=>{
const rescount = this.db.prepare("SELECT count(*) as cnt FROM records").get(); add(uuid_record,uuid,timestamp,content,mime,hash){
return new Promise(resolve=>{
if(limit){ const dt = Math.floor(Date.now());
const res = this.db.prepare("SELECT * FROM records ORDER BY timestamp DESC LIMIT ? OFFSET ?").all(limit,offset); const res = this.table_insert.run(uuid,uuid_record,timestamp,content,mime,hash,dt,dt);
}else{ return resolve(res?.changes);
const res = this.db.prepare("SELECT * FROM records ORDER BY timestamp DESC").all(); });
} }
return resolve({ getByRecord(uuid_record){
data: res, return new Promise(resolve => {
count: rescount?.cnt, const res = this.table_find_record.get(uuid_record);
limit: limit, return resolve(res);
offset: offset });
}); }
}) get(uuid){
} return new Promise(resolve => {
const res = this.table_fine_one.get(uuid);
delete(uuid){ return resolve(res);
return new Promise(resolve=>{ });
const res = this.table_delete.exec(uuid); }
return resolve(res?.changes);
}); delete(uuid){
} return new Promise(resolve=>{
const prepare = this.table_delete;
update(uuid,content,mime,hash){ const res = prepare.exec(uuid);
return new Promise(resolve=>{ return resolve(res?.changes);
const dt = Math.floor(Date.now()); })
const res = this.table_update.exec(content,mime,hash,dt,uuid); }
return resolve(res?.changes);
}) deleteRecord(uuid_record){
} return new Promise(resolve=>{
const res = this.table_delete_record.run(uuid_record);
} return resolve(res?.changes);
})
class TimeChainDataSqlite { }
} update(uuid,timestamp,content,mime,hash){
return new Promise(resolve=>{
const res = this.table_update.run(timestamp,content,mime,hash,uuid);
return resolve(res?.changes);
})
}
}
class TimeChainDataSqliteRecord extends Interface(InterfaceRecord) {
constructor(){
super();
//TODO: Create Tables Here if not exist
const table = `CREATE TABLE IF NOT EXISTS "records" (
"uuid" TEXT NOT NULL UNIQUE,
"timestamp" INTEGER NOT NULL,
"content" TEXT NOT NULL,
"mime" TEXT NOT NULL,
"hash" TEXT,
"created_at" INTEGER NOT NULL,
"updated_at" INTEGER NOT NULL,
PRIMARY KEY("uuid")
);`;
const table_idx = `CREATE INDEX IF NOT EXISTS "records_idx_timestamp" ON "records" (
"timestamp" DESC
);`
if(!db){
throw new Error("Database is not connected");
}
db.exec(table + "\n" + table_idx);
this.db = db;
}
get table_insert() {
if(!this._table_insert){
this._table_insert = db.prepare(`INSERT INTO records (uuid,timestamp,content,mime,hash,created_at,updated_at) VALUES (?,?,?,?,?,?,?)`);
}
return this._table_insert;
}
get table_update(){
if(!this._table_update){
this._table_update = db.prepare(`UPDATE records SET timestamp = ?, content = ?, mime = ?, hash = ? updated_at = ? WHERE uuid = ?`);
}
return this._table_update;
}
get table_delete(){
if(!this._table_delete){
this._table_delete = db.prepare(`DELETE FROM records WHERE uuid = ?`);
}
return this._table_delete;
}
get table_fine_one(){
if(!this._table_fine_one){
this._table_fine_one = db.prepare(`SELECT * FROM records WHERE uuid = ?`);
}
return this._table_fine_one;
}
add(uuid,timestamp,content,mime,hash){
return new Promise(resolve=>{
const dt = Math.floor(Date.now());
const res = this.table_insert.run(uuid,timestamp,content,mime,hash,dt,dt);
return resolve(res?.changes);
});
}
get(uuid){
return new Promise(resolve=>{
const rec = this.table_fine_one.get(uuid);
return resolve(rec);
});
}
find(search,sort=null,limit=undefined,offset=0){
//TODO: add logic for seach and sort (Currently does nothing)
return new Promise(resolve=>{
const rescount = this.db.prepare("SELECT count(*) as cnt FROM records").get();
if(limit){
const res = this.db.prepare("SELECT * FROM records ORDER BY timestamp DESC LIMIT ? OFFSET ?").all(limit,offset);
}else{
const res = this.db.prepare("SELECT * FROM records ORDER BY timestamp DESC").all();
}
return resolve({
data: res,
count: rescount?.cnt,
limit: limit,
offset: offset
});
})
}
delete(uuid){
return new Promise(resolve=>{
const prepare = this.table_delete;
const res = prepare.run(uuid);
return resolve(res?.changes);
});
}
update(uuid,content,mime,hash){
return new Promise(resolve=>{
const dt = Math.floor(Date.now());
const res = this.table_update.run(content,mime,hash,dt,uuid);
return resolve(res?.changes);
})
}
}
class TimeChainDataSqlite {
}
module.exports = {
ConnectToDatabase,
TimeChainDataSqlite,
TimeChainDataSqliteRecord,
TimeChainDataSqliteFile,
TimeChainDataSqliteTagLink,
TimeChainDataSqliteTag
}

3
src/ui/app.riot Normal file
View File

@ -0,0 +1,3 @@
<app>
</app>

View File

0
src/ui/timer-ctrl.riot Normal file
View File

View File

@ -1,34 +1,34 @@
<timestamp> <timestamp>
<div class="timestamp"> <div class="timestamp">
<div class="timestamp-icon"> <div class="timestamp-icon">
<div class="timestamp-text">{ state.time_text }</div> <div class="timestamp-text">{ state.time_text }</div>
</div> </div>
<script> <script>
const pubsub = require('pubsub-js'); const pubsub = require('pubsub-js');
const dayjs = require('dayjs'); const dayjs = require('dayjs');
export default { export default {
state: { state: {
time_text: "", time_text: "",
format: "long" format: "long"
}, },
onMounted(){ onMounted(){
this.start(); this.start();
}, },
start(){ start(){
setTimeout(()=>{ setTimeout(()=>{
this.makeString(); this.makeString();
this.start(); this.start();
},200); },200);
}, },
makeString(){ makeString(){
if(this.state.format=="long"){ if(this.state.format=="long"){
this.update({time_text:dayjs().format("MMMM D, YYYY h:mm:ss A")}); this.update({time_text:dayjs().format("MMMM D, YYYY h:mm:ss A")});
} }
} }
}; };
</script> </script>
</timestamp> </timestamp>

View File

@ -1,7 +1,7 @@
import Timestamp from './timestamp.riot' import Timestamp from './src/ui/timestamp.riot'
import { component } from 'riot' import { component } from 'riot'
component(Timestamp)(document.getElementById('timestamp')) component(Timestamp)(document.getElementById('timestamp'))
console.log("yes!"); console.log("yes!");

77
test/sqlite.test.js Normal file
View 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

Binary file not shown.