First commit

This commit is contained in:
Abdussamed 2024-10-24 10:46:40 +03:00
commit 831aeab919
11 changed files with 359 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
package-lock.json
data/*

67
core/database.js Normal file
View File

@ -0,0 +1,67 @@
import knex from "knex";
import {resolve} from "node:path";
let EventDB = knex({
client: "sqlite3",
connection:{
filename: resolve(import.meta.dirname,"../data/events.db")
},
useNullAsDefault: true
});
let CollectionDB = knex({
client: "sqlite3",
connection:{
filename: resolve(import.meta.dirname,"../data/collection.db")
},
useNullAsDefault: true
});
export async function migrate()
{
if(await EventDB.schema.hasTable("events") == false)
{
await EventDB.schema.createTable("events",function(table){
table.bigIncrements("id",{primaryKey: true}).unsigned();
table.json("payload").notNullable();
table.string("domain",255).index().notNullable();
table.string("type",100).index().notNullable();
table.datetime("time").notNullable();
table.datetime("expire").notNullable();
})
};
if(await CollectionDB.schema.hasTable("collection") == false)
{
await CollectionDB.schema.createTable("collection",function(table){
table.bigIncrements("id",{primaryKey: true}).unsigned();
table.json("data").notNullable();
table.string("catalog",255).index().notNullable();
table.string("domain",255).index().notNullable();
table.datetime("time").notNullable();
table.datetime("expire").notNullable();
})
}
}
/**
* @returns {import("knex").Knex}
*/
export function Event()
{
return EventDB.table("events");
}
/**
* @returns {import("knex").Knex}
*/
export function Collection()
{
return CollectionDB.table("collection");
}
process.nextTick(migrate);

33
core/http.js Normal file
View File

@ -0,0 +1,33 @@
import express from "express";
let app = express();
app.listen(6886,'0.0.0.0');
export const Application = app;
/**
* @param {import("express").Request} request
* @param {import("express").Response} response
*/
export function cors()
{
return (request, response, next) => {
response.setHeader("Access-Control-Allow-Origin","*");
response.setHeader("Access-Control-Allow-Methods","GET, POST");
response.setHeader("Access-Control-Allow-Credentials","false");
response.setHeader("x-powered-by","Qulak/1.1");
next();
}
}
/**
* @param {import("express").Request} request
* @param {import("express").Response} response
*/
export function jsonrequest()
{
return express.json({
limit: "5mb"
})
}

13
core/publicfolder.js Normal file
View File

@ -0,0 +1,13 @@
import {Application} from "./http.js";
import express from "express";
import {resolve} from "node:path";
Application.use(
express.static(
resolve(import.meta.dirname,"../public"),
{
acceptRanges: true,
index: "main.html"
}
)
)

5
init.js Normal file
View File

@ -0,0 +1,5 @@
import "./core/database.js";
import "./core/http.js";
import "./core/publicfolder.js";
import "./src/collection.add.js";
import "./src/collection.get.js";

19
package.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "collectioner",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"express": "^4.21.1",
"joi": "^17.13.3",
"knex": "^3.1.0",
"moment": "^2.30.1",
"sqlite3": "^5.1.7"
}
}

78
public/colletioner.js Normal file
View File

@ -0,0 +1,78 @@
async function AddCollection(catalog, data, expire = "1 year")
{
if(typeof data != "object") throw new Error("data must be object");
if(typeof catalog != "string") throw new Error("catalog must be string");
let pack = {
data: data,
domain: window.location.host,
expire: expire
};
let serializedData = JSON.stringify(pack);
let url = new URL(`/collection/${catalog}/add`,window.location);
let request = await fetch(url,{
method: "post",
headers: {
'accept': "text/json, application/json",
'content-type': 'application/json'
},
body: serializedData,
cache: "no-cache",
priority: "low",
referrerPolicy: "strict-origin",
redirect: "error"
});
if(request.ok == false)
{
throw await request.json()
}
let response = await request.json();
if(response.status != "success")
{
throw new Error("Bir sorun oluştu ve veri eklenemedi")
}
}
async function ReadCollection(catalog, order = "asc", length = 100, page = 1)
{
if(typeof catalog != "string") throw new Error("catalog must be object");
if(typeof order != "string") throw new Error("order must be string");
if(typeof length != "number") throw new Error("length must be object");
if(typeof page != "number") throw new Error("page must be object");
let serializedData = JSON.stringify({
"order": order,
"length": length,
"page": page
});
let url = new URL(`/collection/${catalog}/data.json`,window.location);
let request = await fetch(url,{
method: "post",
headers: {
'accept': "text/json, application/json",
'content-type': 'application/json'
},
body: serializedData,
cache: "no-cache",
priority: "low",
referrerPolicy: "strict-origin",
redirect: "error"
});
if(request.ok == false || request.status != 200)
{
throw await request.json()
}
let response = await request.json();
return response;
}

11
public/main.html Normal file
View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="colletioner.js"></script>
</body>
</html>

31
readme.md Normal file
View File

@ -0,0 +1,31 @@
## Collection
Verileri depolayıp TAMAMINI herkese dağıtıp bir süre sonra geçersiz olacak veriler için kullanılır. Genel kullanım amacı cihazları birbirleri arasında eşitlemek için kullanılabiliir.
- pagination özelliği vardır
- eventsource tüm verileri hızlıca aktarır
- Geçerlilik tarihine göre collection'un tamamı silinir
- Tek yönlüdür okuyan cihazlar feedback dönemezler
## Events
Tüm verileri kanallar üzerinden hızlıca herkese aktarır. Bu özellike bir bildirimi belli kanallar üzerinden tüm cihazları haberdar etmek için kullanılabilir.
- Tarihli sistemdir belli bir tarihin sonrasında gerçekleşen olayları bildirir
- Bağlantıyı sağlayanlar hangi olayları dinleyeceklerini belirtirler ve yanlızca o olayları haberlerini alırlar
- Eventlerden sunucuya aksine bir rapor toplanabilir
- Temelde servis tarafında throttle uygulanır ve tüm olaylar dinleyicilere belirli periotlar ile dağıtılır
- Eventler tüm cihaza ulaştığı an veya geçerlilik süresi bittiğinde eventler silinir.
- Çift yönlüdür okuyan cihazlar veri hakkında feedback dönebilirler, feedbackler yavaşça sunucuya iletilir
## Notification
Temel amacı çok fazla veriyi yavaşça en güncelden başlayarak en eskiye doğru tüm cihazlara dağıtmaktır. Temel olarak eventler ile arasındaki bir gruba değil nokta atışı bir veya aynı kullanıcı tarafından kullanılan birden fazla cihaza iletmektir.
Sunucu tarafından etiketlenen tüm kullanıcılara veriyi iletir
Mesaj tüm kullanıcılara iletildiğinde veriyi siler, iletilmediğinde geçerlilik tarihi bitene kadar depolar
- Bağlanan cihazlar kendileri hakkında ayrıntılı bilgi verirler
- Notificationu gönderen sunucu hangi kesime veya kişiye göndereceğini bildirir ancak depolanırken tek tek depolanır
- Veri iletildiği an silinir veya iletilmediğinde bir süre sonra silinir
- Çift yönlüdür okuyan cihazlar veri hakkında feedback dönebilirler ve ayrıca sistem bildirimin iletildiğinide ayrıca tutabilir. Cihazların gönderdiği veriler sunucuya otomatik iletilmez, bunun yerine sunucu gelip alması gerekir

58
src/collection.add.js Normal file
View File

@ -0,0 +1,58 @@
import Joi from "joi";
import {Collection} from "../core/database.js";
import {Application,cors, jsonrequest} from "../core/http.js";
import moment from "moment";
Application.post("/collection/:catalog/add", jsonrequest(), cors(), AddCollection);
/**
* @param {import("express").Request} request
* @param {import("express").Response} response
*/
async function AddCollection(request, response)
{
let catalog = request.params.catalog;
let expression = Joi.object({
data: Joi.object().required().max(873741824),
domain: Joi.string().max(200).min(5).required(),
expire: Joi.string().pattern(/^(\d{1,3})\s+(minute|hour|day|month|year)+$/).required()
});
let validate = expression.validate(request.body);
if(validate.error)
{
return response.json({
status: "fail",
message: "format not valid"
}).status(400);
}
const {
data,
domain,
expire
} = request.body;
let realExpire = parseRelativeTime(expire);
await Collection().insert({
data: data,
domain: domain,
time: moment().toISOString(),
expire: realExpire,
catalog: catalog
});
return response.status(200).json({
status: "success",
});
}
function parseRelativeTime(relativeTime) {
const executedReg = /^(\d+)\s+(minute|hour|day|month|year)+$/g.exec(relativeTime);
let time = moment().add(executedReg[1], executedReg[2]);
return time.toISOString();
}

41
src/collection.get.js Normal file
View File

@ -0,0 +1,41 @@
import moment from "moment";
import {Collection} from "../core/database.js";
import {Application,cors} from "../core/http.js";
Application.post("/collection/:catalog/data.json", cors(), ReadCollection);
/**
* @param {import("express").Request} request
* @param {import("express").Response} response
*/
async function ReadCollection(request, response)
{
let catalog = request.params.catalog;
let order = (request.body?.order == "asc" ? "asc" : "desc") || "asc";
let length = typeof request.body?.length == "number" ? Math.max(1, Math.min(request.query.length, 250)) : 100;
let page = typeof request.body?.page == "number" ? Math.max(1, request.query.length) : 1;
let fromtime = typeof request.body?.fromtime == "string" ? parseRelativeTime(request.body.fromtime) : null;
let build = Collection()
.where("catalog", catalog)
.where("expire", ">", moment().toISOString())
.orderBy("time", order)
.offset((page - 1) * length)
.limit(length);
if(fromtime) build.where("time",">", fromtime);
let result = await build;
let responseData = result.map(e => JSON.parse(e.data));
return response.status(200).json(responseData);
}
function parseRelativeTime(relativeTime) {
const executedReg = /^(\d+)\s+(minute|hour|day|month|year)+$/g.exec(relativeTime);
let time = moment().add(executedReg[1], executedReg[2]);
return time.toISOString();
}