create qr-codes by tg bot
Deploy app frontend / app frontend (push) Failing after 48s
Deploy db-migrations / db-migrations (push) Failing after 35s
Deploy service message-delivery-method-web-push / service message-delivery-method-web-push (push) Failing after 53s

This commit is contained in:
2024-12-10 13:21:38 +02:00
parent e9b3df38fe
commit 1810c550ba
91 changed files with 3317 additions and 1909 deletions
+1
View File
@@ -0,0 +1 @@
dist
+26
View File
@@ -0,0 +1,26 @@
{
"name": "@hereconnect/constants",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": ["dist/**/*"],
"private": true,
"scripts": {
"lint": "tsc --noEmit src/index.ts",
"build": "tsc",
"prepare": "npm run build"
},
"devDependencies": {
"@hereconnect/types": "workspace:^"
},
"peerDependencies": {
"typescript": "~5.6.3"
}
}
+1
View File
@@ -0,0 +1 @@
export const EPOCH = 1731661630981;
+2
View File
@@ -0,0 +1,2 @@
export * from "./qrCodePlacements";
export * from "./EPOCH";
@@ -0,0 +1,12 @@
import { type QRCodeDocument } from '@hereconnect/types'
export type QrCodePlacement = QRCodeDocument['placement']
export const qrCodePlacements = [
{ type: 'car', label: 'Автомобиль' },
{ type: 'pet', label: 'Питомец' },
{ type: 'door', label: 'Входная дверь дома или квартиры' },
{ type: 'store', label: 'Магазин' },
{ type: 'restaurant_table', label: 'Стол в кафе или ресторане' },
{ type: 'other', label: 'Другое' },
] as const satisfies { type: QrCodePlacement; label: string }[]
+14
View File
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"declaration": true,
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"outDir": "./dist",
"strict": true,
"types": [],
"skipLibCheck": true
},
"include": ["src/index.ts"],
"exclude": ["node_modules", "dist"]
}
+2 -6
View File
@@ -42,11 +42,7 @@
"eslint": "^9.16.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"fast-check": "^3.23.1",
"jest": "^29.7.0",
"prettier": "^3.4.2",
"ts-jest": "^29.2.5",
"typescript": "^5.7.2",
"vitest": "^2.1.8"
"prettier": "^3.4.1",
"typescript": "^5.7.2"
}
}
@@ -0,0 +1 @@
dist
@@ -0,0 +1,21 @@
{
"name": "@hereconnect/lib-service-health-checkserver",
"version": "1.0.0",
"main": "src/health-server.ts",
"exports": {
".": {
"import": "./src/health-server.ts",
"default": "./src/health-server.ts"
}
},
"files": ["src/**/*"],
"private": true,
"scripts": {
"lint": "tsc --noEmit src/health-server.ts",
"build": "pnpm run lint"
},
"devDependencies": {
"@types/node": "^20.17.6",
"typescript": "~5.6.3"
}
}
@@ -0,0 +1,64 @@
import http from "http";
/**
* Запускает HTTP-сервер для проверки здоровья.
* @param controller - AbortController для управления состоянием здоровья.
* @param defaultPort - Порт для сервера по умолчанию (если не указан в ENV HEALTH_PORT).
* @param defaultHostName - Имя хоста для сервера по умолчанию (если не указан в ENV HEALTH_HOST).
*/
export function startHealthServer(
controller: AbortController,
defaultPort: number = 8000,
defaultHostName: string = "127.0.0.1",
) {
const port = parseInt(process.env.HEALTH_PORT || `${defaultPort}`, 10);
const hostname = process.env.HEALTH_HOST || defaultHostName;
const server = http.createServer((req, res) => {
if (req.url === "/health") {
const signal = controller.signal;
// Если сигнал отмены активирован, сервер помечается как "unhealthy"
if (signal.aborted) {
res.writeHead(500, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
status: "unhealthy",
reason: "Service aborted",
}),
);
return;
}
// Возвращаем стандартный статус "ok", если всё нормально
res.writeHead(200, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
status: "ok",
}),
);
} else {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Not Found");
}
});
const { promise, resolve, reject } = Promise.withResolvers<void>();
server.once("error", reject);
server.listen(port, hostname, resolve);
promise.then(() => {
console.log(
`Healthcheck server running on http://${hostname}:${port}/health`,
);
});
// Закрываем сервер при активации AbortController
controller.signal.addEventListener("abort", () => {
console.log("Healthcheck server shutting down due to abort signal.");
reject();
server.close();
});
return promise;
}
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "./dist",
"strict": true,
"types": ["node"]
},
"include": ["index.ts"]
}
@@ -0,0 +1 @@
dist
@@ -0,0 +1,22 @@
{
"name": "@hereconnect/lib-service-seq-tracker",
"version": "1.0.0",
"main": "src/serviceSeqTracker.ts",
"exports": {
".": {
"import": "./src/serviceSeqTracker.ts",
"default": "./src/serviceSeqTracker.ts"
}
},
"files": ["src/**/*"],
"private": true,
"scripts": {
"lint": "tsc --noEmit src/serviceSeqTracker.ts",
"build": "pnpm run lint"
},
"devDependencies": {
"typescript": "~5.6.3",
"@types/node": "^20.17.6",
"@types/pouchdb-core": "^7.0.15"
}
}
@@ -0,0 +1,57 @@
import { type defaults as PouchDB } from "pouchdb-core";
export interface ServiceSeqTrackerDocument {
lastSeq: string | number;
}
export type ServiceSeqTrackerDatabase =
PouchDB.Database<ServiceSeqTrackerDocument>;
export class ServiceSeqTracker {
/**
* @param _id
* @param serviceSeqTrackerDb
* @constructor
*/
static async Init(
_id: string,
serviceSeqTrackerDb: ServiceSeqTrackerDatabase,
) {
const doc = await serviceSeqTrackerDb.get(_id).catch((error) => {
if (error.name === "not_found") {
return {
_id,
lastSeq: "0",
};
}
throw error; // Re-throw other errors
});
return new ServiceSeqTracker(doc, serviceSeqTrackerDb);
}
private constructor(
private doc: ServiceSeqTrackerDocument,
private serviceSeqTrackerDb: ServiceSeqTrackerDatabase,
) {}
get lastSeq() {
return this.doc.lastSeq;
}
checkNextSeq(seq: number | string) {
if (!this.doc) {
}
const next = parseInt(seq as string, 10);
const current = parseInt(this.doc.lastSeq as string, 10);
if (next < current) {
throw new Error(
`Sequence number is too old: ${seq} (${next} < ${current})`,
);
}
}
saveSeq(seq: number | string) {
this.doc.lastSeq = seq;
return this.serviceSeqTrackerDb.put(this.doc);
}
}
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "./dist",
"strict": true,
"types": ["node"]
},
"include": ["index.ts"]
}
+1
View File
@@ -0,0 +1 @@
export * from "./updates/saveUpdate";
+18
View File
@@ -0,0 +1,18 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./updates/saveUpdate"), exports);
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,uDAAqC"}
+16
View File
@@ -0,0 +1,16 @@
import type { BaseDocument } from "@hereconnect/types";
export interface TelegramWebhookDesignDocument extends BaseDocument {
_id: "_design/webhook";
type: "_design/webhook";
updated_at: BaseDocument["created_at"];
updates: {
saveUpdate: string;
};
constants: {
telegram_secret_token: string;
telegram_webhook_url?: string;
};
}
export interface TgUpdateDocument extends BaseDocument {
data: unknown;
}
+3
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=types.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,22 @@
import { TelegramWebhookDesignDocument, TgUpdateDocument } from "../types";
export type { TelegramWebhookDesignDocument };
type CouchRequest = {
body: string;
headers: Record<string, string>;
method: string;
query: Record<string, string>;
userCtx: {
name: string | null;
roles: string[];
};
};
type CouchResponse = [
TgUpdateDocument | null,
{
code: number;
body: string;
} | string
];
type UpdateFunction = (doc: TgUpdateDocument | null, req: CouchRequest) => CouchResponse;
export declare const saveUpdate: UpdateFunction;
export declare const saveUpdateString: string;
+23
View File
@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.saveUpdateString = exports.saveUpdate = void 0;
var saveUpdate = function (doc, req) {
var token = req.headers["X-Telegram-Bot-Api-Secret-Token"];
if (token !== this.constants.telegram_secret_token) {
return [null, { code: 403, body: "Forbidden: Invalid token" }];
}
var data = JSON.parse(req.body);
if (doc) {
return [null, { code: 304, body: "Update already exists" }];
}
doc = {
_id: "tg_update:" + data.update_id.toString(),
type: "tg_update",
data: data,
created_at: new Date().toISOString(),
};
return [doc, "Message saved"];
};
exports.saveUpdate = saveUpdate;
exports.saveUpdateString = exports.saveUpdate.toString();
//# sourceMappingURL=saveUpdate.js.map
@@ -0,0 +1 @@
{"version":3,"file":"saveUpdate.js","sourceRoot":"","sources":["../../src/updates/saveUpdate.ts"],"names":[],"mappings":";;;AAyBO,IAAM,UAAU,GAAmB,UAExC,GAA4B,EAC5B,GAAiB;IAEjB,IAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAC;IAC7D,IAAI,KAAK,KAAK,IAAI,CAAC,SAAS,CAAC,qBAAqB,EAAE,CAAC;QACnD,OAAO,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,0BAA0B,EAAE,CAAC,CAAC;IACjE,CAAC;IAED,IAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAElC,IAAI,GAAG,EAAE,CAAC;QACR,OAAO,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,uBAAuB,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,GAAG,GAAG;QACJ,GAAG,EAAE,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE;QAC7C,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,IAAI;QACV,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACrC,CAAC;IAEF,OAAO,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;AAChC,CAAC,CAAC;AAxBW,QAAA,UAAU,cAwBrB;AAEW,QAAA,gBAAgB,GAAG,kBAAU,CAAC,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,25 @@
// eslint.config.mjs
import typescriptPlugin from "@typescript-eslint/eslint-plugin";
import parser from "@typescript-eslint/parser";
export default [
{
ignores: ["dist", "node_modules"], // Исключаем папки из проверки
},
{
files: ["src/**/*.ts"], // Проверяем только TypeScript файлы
languageOptions: {
parser, // Используем парсер TypeScript
ecmaVersion: "latest", // Последняя версия ECMAScript
sourceType: "module", // Модули ES
},
plugins: {
"@typescript-eslint": typescriptPlugin, // Используем плагин TypeScript
},
rules: {
"@typescript-eslint/no-unused-vars": ["error"], // Ошибка для неиспользуемых переменных
semi: ["error", "always"], // Всегда требовать точку с запятой
quotes: ["error", "double"], // Использовать двойные кавычки
},
},
];
+36
View File
@@ -0,0 +1,36 @@
{
"name": "@hereconnect/tg-bot-webhook-ddoc",
"version": "1.0.1",
"license": "@hereconnect/license",
"private": true,
"engines": {
"node": ">=v20.10.0",
"pnpm": ">=8.15.0"
},
"contributors": [],
"scripts": {
"build": "tsc",
"type-check": "tsc --noEmit",
"clean": "rm -rf dist",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"validate": "npm run type-check && npm run lint"
},
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts",
"require": "./dist/index.js"
}
},
"files": ["dist/**/*"],
"devDependencies": {
"@hereconnect/types": "workspace:^",
"@typescript-eslint/eslint-plugin": "^8.18.0",
"@typescript-eslint/parser": "^8.18.0",
"eslint": "^9.16.0"
}
}
@@ -0,0 +1 @@
export * from "./updates/saveUpdate";
+19
View File
@@ -0,0 +1,19 @@
import type { BaseDocument } from "@hereconnect/types";
export interface TelegramWebhookDesignDocument extends BaseDocument {
_id: "_design/webhook";
type: "_design/webhook";
updated_at: BaseDocument["created_at"];
updates: {
saveUpdate: string;
};
constants: {
telegram_secret_token: string;
telegram_webhook_url?: string;
};
}
export interface TgUpdateDocument extends BaseDocument {
data: unknown;
}
@@ -0,0 +1,52 @@
import { TelegramWebhookDesignDocument, TgUpdateDocument } from "../types";
export type { TelegramWebhookDesignDocument };
type CouchRequest = {
body: string; // JSON-строка
headers: Record<string, string>; // Заголовки запроса
method: string; // HTTP-метод (обычно POST для update handlers)
query: Record<string, string>; // Параметры запроса (например, ?key=value)
userCtx: {
name: string | null; // Имя пользователя, если используется аутентификация
roles: string[]; // Роли пользователя
};
};
type CouchResponse = [
TgUpdateDocument | null,
{ code: number; body: string } | string,
];
type UpdateFunction = (
doc: TgUpdateDocument | null,
req: CouchRequest,
) => CouchResponse;
export const saveUpdate: UpdateFunction = function (
this: TelegramWebhookDesignDocument,
doc: TgUpdateDocument | null,
req: CouchRequest,
): CouchResponse {
const token = req.headers["X-Telegram-Bot-Api-Secret-Token"];
if (token !== this.constants.telegram_secret_token) {
return [null, { code: 403, body: "Forbidden: Invalid token" }];
}
const data = JSON.parse(req.body);
if (doc) {
return [null, { code: 304, body: "Update already exists" }];
}
doc = {
_id: "tg_update:" + data.update_id.toString(),
type: "tg_update",
data: data,
created_at: new Date().toISOString(),
};
return [doc, "Message saved"];
};
export const saveUpdateString = saveUpdate.toString();
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES5",
"module": "CommonJS",
"outDir": "dist",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true,
"declaration": true
},
"files": ["src/index.ts"],
}
+8 -6
View File
@@ -1,19 +1,21 @@
{
"name": "@hereconnect/types",
"version": "1.0.0",
"main": "src/index.ts",
"types": "src/index.ts",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./src/index.ts",
"default": "./src/index.ts"
"import": "./dist/index.d.js",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": ["src/**/*"],
"files": ["dist/**/*"],
"private": true,
"scripts": {
"lint": "tsc --noEmit src/index.ts",
"build": "pnpm run lint"
"build": "tsc",
"prepare": "npm run build"
},
"peerDependencies": {
"typescript": "~5.6.3",
@@ -27,7 +27,10 @@ export interface QRCodeDocument extends BaseDocument {
user_uuid: string;
/** Идентификатор браузера, через который был создан QR-код */
browser_uuid: string;
browser_uuid?: string;
/** Идентификатор телеграм-аккаунта, через который был создан QR-код */
telegram_id?: string;
/** Название объекта, для которого создан QR‑код, например "Мой автомобиль" */
name: string;
+10 -7
View File
@@ -1,4 +1,4 @@
import type { BaseDocument } from './BaseDocument'
import type { BaseDocument } from "./BaseDocument";
/**
* Интерфейс для документа пользователя.
@@ -6,20 +6,23 @@ import type { BaseDocument } from './BaseDocument'
*/
export interface UserDocument extends BaseDocument {
/** Тип документа, всегда 'user' */
type: 'user'
type: "user";
/** Telegram ID пользователя */
telegram_id?: number;
/** Имя пользователя */
name: string
name: string;
/** Уникальный UUID пользователя, не связанный с _id */
user_uuid: string
user_uuid: string;
/** Email пользователя */
email: string
email?: string;
/** Роли пользователя (например, ['admin', 'user']) */
roles: string[]
roles: string[];
/** Пароль пользователя (должен храниться в зашифрованном виде) */
password: string
password: string;
}
+8 -3
View File
@@ -1,9 +1,14 @@
{
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "./dist",
"strict": true
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"strict": true,
"skipLibCheck": true,
"types": ["web-push"],
},
"include": ["index.ts"]
"include": ["src/index.ts"],
"exclude": ["node_modules", "dist"],
}