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:
23
packages/config/package.json
Normal file
23
packages/config/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
43
packages/config/src/create-config.ts
Normal file
43
packages/config/src/create-config.ts
Normal 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;
|
||||
}
|
||||
};
|
||||
2
packages/config/src/index.ts
Normal file
2
packages/config/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './create-config';
|
||||
export * from './types';
|
||||
18
packages/config/src/types.ts
Normal file
18
packages/config/src/types.ts
Normal 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[];
|
||||
}
|
||||
9
packages/config/tsconfig.json
Normal file
9
packages/config/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "@repo/typescript-config/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
1
packages/database/.env.example
Normal file
1
packages/database/.env.example
Normal file
@@ -0,0 +1 @@
|
||||
DATABASE_URL="your-database-url"
|
||||
5
packages/database/.gitignore
vendored
Normal file
5
packages/database/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
# Keep environment variables out of version control
|
||||
.env
|
||||
|
||||
/generated/prisma
|
||||
19
packages/database/package.json
Normal file
19
packages/database/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
30
packages/database/prisma/schema.prisma
Normal file
30
packages/database/prisma/schema.prisma
Normal 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
|
||||
}
|
||||
8
packages/database/src/client.ts
Normal file
8
packages/database/src/client.ts
Normal 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;
|
||||
2
packages/database/src/index.ts
Normal file
2
packages/database/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { prisma } from "./client"; // exports instance of prisma
|
||||
export * from "../generated/prisma"; // exports generated types from prisma
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
];
|
||||
14
packages/logger/package.json
Normal file
14
packages/logger/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "@repo/logger",
|
||||
"scripts": {
|
||||
"dev": "tsc --watch",
|
||||
"build": "tsc"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/typescript-config": "*",
|
||||
"typescript": "latest"
|
||||
}
|
||||
}
|
||||
1
packages/logger/src/index.ts
Normal file
1
packages/logger/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './logger'
|
||||
39
packages/logger/src/logger.ts
Normal file
39
packages/logger/src/logger.ts
Normal 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");
|
||||
}
|
||||
},
|
||||
};
|
||||
9
packages/logger/tsconfig.json
Normal file
9
packages/logger/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "@repo/typescript-config/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
12
packages/logger/turbo.json
Normal file
12
packages/logger/turbo.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": [
|
||||
"//"
|
||||
],
|
||||
"tasks": {
|
||||
"build": {
|
||||
"outputs": [
|
||||
"dist/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import { config } from "@repo/eslint-config/react-internal";
|
||||
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
export default config;
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>-></span>
|
||||
</h2>
|
||||
<p>{children}</p>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "@repo/typescript-config/react-library.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user