First commit
This commit is contained in:
commit
831aeab919
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules
|
||||||
|
package-lock.json
|
||||||
|
data/*
|
|
@ -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);
|
|
@ -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"
|
||||||
|
})
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
|
@ -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";
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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>
|
|
@ -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
|
|
@ -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();
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
Loading…
Reference in New Issue