feat: add bot and api services with config, logger and database packages

- Add bot service with telegram integration and webhook/polling modes
- Add api service with hono, swagger and zod-openapi
- Create config package for environment variable management
- Create logger package for colored console logging
- Create database package with prisma integration
- Remove next.js web and docs apps
- Update turbo.json for new services
- Add dockerfiles and gitea workflows for deployment
This commit is contained in:
Yuu Ottosoka
2025-07-08 19:46:08 +03:00
parent 393c175460
commit 71365836d3
85 changed files with 5635 additions and 4333 deletions

View File

@@ -0,0 +1,23 @@
{
"name": "@repo/config",
"version": "0.0.0",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"dev": "tsc --watch",
"build": "tsc",
"clean": "rm -rf dist",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"znv": "^0.5.0",
"zod": "^3.25.76"
},
"devDependencies": {
"@repo/typescript-config": "*",
"typescript": "^5.0.0"
}
}

View File

@@ -0,0 +1,43 @@
import { parseEnv } from 'znv';
import { z } from 'zod';
import type { ConfigSchema, ConfigOptions, InferConfig } from './types';
export const createConfig = <T extends ConfigSchema>(
options: ConfigOptions<T>,
): InferConfig<T> => {
const { schema, env = process.env, prefix = '' } = options;
// Add prefix to schema keys if provided
const prefixedSchema = prefix
? Object.entries(schema).reduce(
(acc, [key, zodSchema]) => {
acc[`${prefix}${key}`] = zodSchema;
return acc;
},
{} as Record<string, z.ZodTypeAny>
)
: schema;
try {
const parsed = parseEnv(env, prefixedSchema);
// Remove prefix from result keys if it was added
if (prefix) {
const result = {} as Record<string, unknown>;
Object.entries(parsed).forEach(([key, value]) => {
const originalKey = key.startsWith(prefix)
? key.slice(prefix.length)
: key;
result[originalKey] = value;
});
return result as InferConfig<T>;
}
return parsed as InferConfig<T>;
} catch (error) {
if (error instanceof Error) {
throw new Error(`Configuration validation failed: ${error.message}`);
}
throw error;
}
};

View File

@@ -0,0 +1,2 @@
export * from './create-config';
export * from './types';

View File

@@ -0,0 +1,18 @@
import { z } from 'zod';
export type ConfigSchema = Record<string, z.ZodTypeAny>;
export interface ConfigOptions<T extends ConfigSchema> {
schema: T;
env?: Record<string, string | undefined>;
prefix?: string;
}
export type InferConfig<T extends ConfigSchema> = {
readonly [K in keyof T]: z.infer<T[K]>;
};
export interface ConfigResult<T extends ConfigSchema> {
config: InferConfig<T>;
errors: string[];
}

View File

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

View File

@@ -0,0 +1 @@
DATABASE_URL="your-database-url"

5
packages/database/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
node_modules
# Keep environment variables out of version control
.env
/generated/prisma

View File

@@ -0,0 +1,19 @@
{
"name": "@repo/db",
"version": "0.0.0",
"scripts": {
"db:generate": "prisma generate",
"db:migrate": "prisma migrate dev --skip-generate",
"db:deploy": "prisma migrate deploy"
},
"dependencies": {
"@prisma/client": "^6.11.1"
},
"devDependencies": {
"@types/node": "^24.0.10",
"prisma": "^6.11.1"
},
"exports": {
".": "./src/index.ts"
}
}

View File

@@ -0,0 +1,30 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
output = "../generated/prisma"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model TelegramSession {
id String @id
data Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model TelegramUser {
id String @id
username String?
isBlocked Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

View File

@@ -0,0 +1,8 @@
import { PrismaClient } from "../generated/prisma";
const globalForPrisma = global as unknown as { prisma: PrismaClient };
export const prisma =
globalForPrisma.prisma || new PrismaClient()
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;

View File

@@ -0,0 +1,2 @@
export { prisma } from "./client"; // exports instance of prisma
export * from "../generated/prisma"; // exports generated types from prisma

View File

@@ -1,49 +0,0 @@
import js from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier";
import tseslint from "typescript-eslint";
import pluginReactHooks from "eslint-plugin-react-hooks";
import pluginReact from "eslint-plugin-react";
import globals from "globals";
import pluginNext from "@next/eslint-plugin-next";
import { config as baseConfig } from "./base.js";
/**
* A custom ESLint configuration for libraries that use Next.js.
*
* @type {import("eslint").Linter.Config[]}
* */
export const nextJsConfig = [
...baseConfig,
js.configs.recommended,
eslintConfigPrettier,
...tseslint.configs.recommended,
{
...pluginReact.configs.flat.recommended,
languageOptions: {
...pluginReact.configs.flat.recommended.languageOptions,
globals: {
...globals.serviceworker,
},
},
},
{
plugins: {
"@next/next": pluginNext,
},
rules: {
...pluginNext.configs.recommended.rules,
...pluginNext.configs["core-web-vitals"].rules,
},
},
{
plugins: {
"react-hooks": pluginReactHooks,
},
settings: { react: { version: "detect" } },
rules: {
...pluginReactHooks.configs.recommended.rules,
// React scope no longer necessary with new JSX transform.
"react/react-in-jsx-scope": "off",
},
},
];

View File

@@ -0,0 +1,14 @@
{
"name": "@repo/logger",
"scripts": {
"dev": "tsc --watch",
"build": "tsc"
},
"exports": {
".": "./src/index.ts"
},
"devDependencies": {
"@repo/typescript-config": "*",
"typescript": "latest"
}
}

View File

@@ -0,0 +1 @@
export * from './logger'

View File

@@ -0,0 +1,39 @@
export const logger = {
info: (msg: string, data?: any) => {
console.log(
"\x1b[44m\x1b[37m\x1b[1m[INFO]\x1b[0m",
"\x1b[94m" + msg + "\x1b[0m",
);
if (data) {
console.log("\x1b[94m" + JSON.stringify(data, null, 2) + "\x1b[0m");
}
},
error: (msg: string, err?: unknown) => {
console.error(
"\x1b[41m\x1b[37m\x1b[1m[ERROR]\x1b[0m",
"\x1b[91m" + msg + "\x1b[0m",
);
if (err) {
console.error(
"\x1b[91m" +
(err instanceof Error ? err.stack : JSON.stringify(err, null, 2)) +
"\x1b[0m",
);
}
},
success: (msg: string) => {
console.log(
"\x1b[42m\x1b[37m\x1b[1m[SUCCESS]\x1b[0m",
"\x1b[92m" + msg + "\x1b[0m",
);
},
warn: (msg: string, data?: any) => {
console.warn(
"\x1b[43m\x1b[30m\x1b[1m[WARN]\x1b[0m",
"\x1b[93m" + msg + "\x1b[0m",
);
if (data) {
console.warn("\x1b[93m" + JSON.stringify(data, null, 2) + "\x1b[0m");
}
},
};

View File

@@ -0,0 +1,9 @@
{
"extends": "@repo/typescript-config/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,12 @@
{
"extends": [
"//"
],
"tasks": {
"build": {
"outputs": [
"dist/**"
]
}
}
}

View File

@@ -1,12 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./base.json",
"compilerOptions": {
"plugins": [{ "name": "next" }],
"module": "ESNext",
"moduleResolution": "Bundler",
"allowJs": true,
"jsx": "preserve",
"noEmit": true
}
}

View File

@@ -1,7 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./base.json",
"compilerOptions": {
"jsx": "react-jsx"
}
}

View File

@@ -1,4 +0,0 @@
import { config } from "@repo/eslint-config/react-internal";
/** @type {import("eslint").Linter.Config} */
export default config;

View File

@@ -1,26 +0,0 @@
{
"name": "@repo/ui",
"version": "0.0.0",
"private": true,
"exports": {
"./*": "./src/*.tsx"
},
"scripts": {
"lint": "eslint . --max-warnings 0",
"generate:component": "turbo gen react-component",
"check-types": "tsc --noEmit"
},
"devDependencies": {
"@repo/eslint-config": "workspace:*",
"@repo/typescript-config": "workspace:*",
"@types/node": "^22.15.3",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.1",
"eslint": "^9.30.0",
"typescript": "5.8.2"
},
"dependencies": {
"react": "^19.1.0",
"react-dom": "^19.1.0"
}
}

View File

@@ -1,20 +0,0 @@
"use client";
import { ReactNode } from "react";
interface ButtonProps {
children: ReactNode;
className?: string;
appName: string;
}
export const Button = ({ children, className, appName }: ButtonProps) => {
return (
<button
className={className}
onClick={() => alert(`Hello from your ${appName} app!`)}
>
{children}
</button>
);
};

View File

@@ -1,27 +0,0 @@
import { type JSX } from "react";
export function Card({
className,
title,
children,
href,
}: {
className?: string;
title: string;
children: React.ReactNode;
href: string;
}): JSX.Element {
return (
<a
className={className}
href={`${href}?utm_source=create-turbo&utm_medium=basic&utm_campaign=create-turbo"`}
rel="noopener noreferrer"
target="_blank"
>
<h2>
{title} <span>-&gt;</span>
</h2>
<p>{children}</p>
</a>
);
}

View File

@@ -1,11 +0,0 @@
import { type JSX } from "react";
export function Code({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}): JSX.Element {
return <code className={className}>{children}</code>;
}

View File

@@ -1,8 +0,0 @@
{
"extends": "@repo/typescript-config/react-library.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}