feat: add file storage service and update configurations

refactor: migrate to tsup for builds and update tsconfigs
fix: update error handling and validation in payment service
chore: update package scripts and dependencies
docs: update README and env examples
This commit is contained in:
Yuu Ottosoka
2025-07-12 16:01:57 +03:00
parent c0c04bbdba
commit f9ecac94f2
67 changed files with 3308 additions and 695 deletions

View File

@@ -1,2 +1,3 @@
PORT=3001
NODE_ENV=development
NODE_ENV=development
DATABASE_URL=copy from packages/database/.env

View File

@@ -1,2 +1,3 @@
# `api`
Апи клиент
Апи клиент

View File

@@ -2,10 +2,13 @@
"name": "api",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "NODE_PATH=./src tsnd --respawn --transpile-only --exit-child src/index.ts",
"build": "tsc",
"start": "NODE_PATH=./dist node ./dist/index.js"
"dev": "tsx watch src/index.ts",
"build": "tsup",
"start": "node dist/index.js",
"check-types": "tsc --noEmit",
"clean": "rm -rf dist"
},
"dependencies": {
"@hono/node-server": "^1.15.0",
@@ -22,7 +25,9 @@
"zod-openapi": "^4.2.4"
},
"devDependencies": {
"ts-node-dev": "^2.0.0",
"typescript": "latest"
"@types/node": "^22.10.2",
"tsup": "^8.3.5",
"tsx": "^4.19.2",
"typescript": "^5.8.3"
}
}

View File

@@ -1,10 +1,12 @@
import { createConfig } from '@repo/config';
import z from 'zod';
import 'dotenv/config';
import { createConfig } from "@repo/config";
import z from "zod";
import "dotenv/config";
export const config = createConfig({
schema: {
PORT: z.string().default('3001'),
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
PORT: z.string().default("3001"),
NODE_ENV: z
.enum(["development", "production", "test"])
.default("development"),
},
});
});

View File

@@ -1,27 +1,27 @@
import { serve } from '@hono/node-server';
import { OpenAPIHono } from '@hono/zod-openapi';
import { swaggerUI } from '@hono/swagger-ui';
import { config } from './config';
import { logger } from '@repo/logger';
import { serve } from "@hono/node-server";
import { OpenAPIHono } from "@hono/zod-openapi";
import { swaggerUI } from "@hono/swagger-ui";
import { config } from "./config";
import { logger } from "@repo/logger";
const app = new OpenAPIHono();
// OpenAPI documentation
app.doc('/doc', {
openapi: '3.0.0',
app.doc("/doc", {
openapi: "3.0.0",
info: {
version: '1.0.0',
title: 'API Documentation',
description: 'API built with Hono and Zod OpenAPI',
version: "1.0.0",
title: "API Documentation",
description: "API built with Hono and Zod OpenAPI",
},
});
// Swagger UI
app.get('/ui', swaggerUI({ url: '/doc' }));
app.get("/ui", swaggerUI({ url: "/doc" }));
// Health check
app.get('/health', (c) => {
return c.json({ status: 'ok', timestamp: new Date().toISOString() });
app.get("/health", (c) => {
return c.json({ status: "ok", timestamp: new Date().toISOString() });
});
const port = parseInt(config.PORT);
@@ -29,7 +29,24 @@ const port = parseInt(config.PORT);
logger.info(`Starting API server on port ${port}`);
logger.info(`Swagger UI available at http://localhost:${port}/ui`);
serve({
const server = serve({
fetch: app.fetch,
port,
});
const tearDown = () => {
logger.info("Server shutting down...");
if (server) {
server.close(() => {
logger.info("Server closed");
process.exit(0);
});
} else {
process.exit(0);
}
};
process.on("SIGINT", tearDown);
process.on("SIGTERM", tearDown);
process.on("SIGUSR1", tearDown);
process.on("SIGUSR2", tearDown);

View File

@@ -1,9 +1,15 @@
{
"extends": "@repo/typescript-config/base.json",
"extends": "@repo/typescript-config/node.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
"rootDir": ".",
"skipLibCheck": true
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
"include": ["src/**/*"],
"exclude": [
"node_modules",
"dist",
"**/*.test.*",
"**/*.spec.*"
]
}

File diff suppressed because one or more lines are too long

15
apps/api/tsup.config.ts Normal file
View File

@@ -0,0 +1,15 @@
import { defineConfig } from "tsup";
export default defineConfig({
entry: ["src/index.ts"],
format: ["esm"],
dts: false,
splitting: false,
sourcemap: true,
clean: true,
external: ["@repo/config", "@repo/logger", "@repo/typescript-config"],
treeshake: true,
minify: false,
target: "node18",
platform: "node",
});

View File

@@ -1,4 +1,5 @@
BOT_TOKEN="your-bot-token"
WEBHOOK_URL="your-webhook-url"
PORT=3000
MODE=polling or webhook
MODE=polling or webhook
DATABASE_URL=copy from packages/database/.env

View File

@@ -2,10 +2,13 @@
"name": "bot",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "NODE_PATH=./src tsnd --respawn --transpile-only --exit-child src/index.ts",
"start": "NODE_PATH=./dist node ./dist/index.js",
"build": "tsc"
"dev": "tsx watch src/index.ts",
"start": "node dist/index.js",
"build": "tsup",
"check-types": "tsc --noEmit",
"clean": "rm -rf dist"
},
"dependencies": {
"@hono/node-server": "^1.15.0",
@@ -19,7 +22,9 @@
"zod": "^3.25.76"
},
"devDependencies": {
"ts-node-dev": "^2.0.0",
"@types/node": "^22.10.2",
"tsup": "^8.3.5",
"tsx": "^4.19.2",
"typescript": "^5.8.3"
}
}

View File

@@ -1,12 +1,12 @@
import { createConfig } from '@repo/config';
import z from 'zod';
import 'dotenv/config';
import { createConfig } from "@repo/config";
import z from "zod";
import "dotenv/config";
export const config = createConfig({
schema: {
BOT_TOKEN: z.string(),
WEBHOOK_URI: z.string(),
PORT: z.string().default('3000'),
MODE: z.enum(['polling', 'webhook']).default('polling'),
PORT: z.string().default("3000"),
MODE: z.enum(["polling", "webhook"]).default("polling"),
},
});

View File

@@ -37,9 +37,15 @@ if (config.MODE === 'webhook') {
);
} else {
logger.info('Starting in polling mode...');
bot.start({
onStart: () => {
logger.info('Bot started polling...');
},
});
const startBot = async () => {
bot.start({
drop_pending_updates: true,
onStart: () => {
logger.info('Bot started polling...');
},
});
};
startBot();
}

View File

@@ -7,7 +7,8 @@ const errorReply =
const replyErrorLog = 'Не удалось отправить сообщение об ошибке пользователю';
export const onError: MiddlewareFn<BotContext> = async (ctx, next) => {
return await next().catch(async () => {
return await next().catch(async (e) => {
logger.error('Ошибка в onError', e);
await ctx.reply(errorReply).catch((err) => {
logger.error(replyErrorLog, err);
});

View File

@@ -1,6 +1,6 @@
import { MiddlewareFn } from 'grammy';
import { BotContext } from '../types';
import { Prisma, prisma } from '@repo/db';
import { MiddlewareFn } from "grammy";
import { BotContext } from "../types";
import { Prisma, prisma } from "@repo/db";
export const upsertUser: MiddlewareFn<BotContext> = async (ctx, next) => {
const telegramId = ctx.from?.id.toString();

View File

@@ -1,5 +1,5 @@
import { prisma } from '@repo/db';
import { StorageAdapter } from 'grammy';
import { prisma } from "@repo/db";
import { StorageAdapter } from "grammy";
export class PrismaStorageAdapter<T extends object>
implements StorageAdapter<T>
@@ -14,7 +14,7 @@ export class PrismaStorageAdapter<T extends object>
.then((session) => (session?.data as T) || undefined);
}
async write(key: string, data: T): Promise<void> {
console.log('write', data);
console.log("write", data);
await prisma.telegramSession.upsert({
where: {
id: key,

View File

@@ -1,5 +1,5 @@
import { Context, LazySessionFlavor } from 'grammy';
import { Context, LazySessionFlavor } from "grammy";
// config your session data
export type SessionData = { };
export type SessionData = {};
export type BotContext = Context & LazySessionFlavor<SessionData>;

View File

@@ -1,9 +1,15 @@
{
"extends": "@repo/typescript-config/base.json",
"extends": "@repo/typescript-config/node.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
"rootDir": ".",
"skipLibCheck": true
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
"include": ["src/**/*"],
"exclude": [
"node_modules",
"dist",
"**/*.test.*",
"**/*.spec.*"
]
}

20
apps/bot/tsup.config.ts Normal file
View File

@@ -0,0 +1,20 @@
import { defineConfig } from "tsup";
export default defineConfig({
entry: ["src/index.ts"],
format: ["esm"],
dts: false,
splitting: false,
sourcemap: true,
clean: true,
external: [
"@repo/config",
"@repo/db",
"@repo/logger",
"@repo/typescript-config",
],
treeshake: true,
minify: false,
target: "node18",
platform: "node",
});