From 9afe6d756f2104fb62a657cf6ca05f4f6d7f582e Mon Sep 17 00:00:00 2001 From: "HeXiaoLong:Suanier" Date: Fri, 30 May 2025 18:37:21 +0800 Subject: [PATCH] feat(redis mysql middleware): --- star-tune/.env | 33 +- star-tune/README.md | 105 +- star-tune/dbDesign/init.sql | 67 + star-tune/dbDesign/user.01.sql | 67 + star-tune/drizzle.config.ts | 75 + star-tune/package-lock.json | 1227 ++++++++++++++++- star-tune/package.json | 13 +- star-tune/src/app.controller.ts | 4 +- star-tune/src/app.module.ts | 40 +- star-tune/src/app.service.ts | 7 +- .../src/common/database/database.module.ts | 19 + .../src/common/database/database.service.ts | 75 + star-tune/src/common/init/init.module.ts | 17 + star-tune/src/common/init/init.service.ts | 85 ++ .../src/{ => common}/logger/logger.module.ts | 42 +- .../logger/logger.service.ts} | 0 .../middleware/request-logger.middleware.ts | 47 + star-tune/src/common/redis/redis.module.ts | 11 + star-tune/src/common/redis/redis.service.ts | 84 ++ star-tune/src/common/utils/utils.module.ts | 9 + star-tune/src/common/utils/utils.server.ts | 57 + star-tune/src/config/config.module.ts | 2 +- star-tune/src/config/configuration.ts | 21 +- star-tune/src/drizzle/0000_tearful_menace.sql | 62 + .../drizzle/0001_silent_natasha_romanoff.sql | 8 + star-tune/src/main.ts | 10 +- star-tune/tsconfig.json | 3 + 27 files changed, 2068 insertions(+), 122 deletions(-) create mode 100644 star-tune/dbDesign/init.sql create mode 100644 star-tune/dbDesign/user.01.sql create mode 100644 star-tune/drizzle.config.ts create mode 100644 star-tune/src/common/database/database.module.ts create mode 100644 star-tune/src/common/database/database.service.ts create mode 100644 star-tune/src/common/init/init.module.ts create mode 100644 star-tune/src/common/init/init.service.ts rename star-tune/src/{ => common}/logger/logger.module.ts (80%) rename star-tune/src/{logger/custom.logger.ts => common/logger/logger.service.ts} (100%) create mode 100644 star-tune/src/common/middleware/request-logger.middleware.ts create mode 100644 star-tune/src/common/redis/redis.module.ts create mode 100644 star-tune/src/common/redis/redis.service.ts create mode 100644 star-tune/src/common/utils/utils.module.ts create mode 100644 star-tune/src/common/utils/utils.server.ts create mode 100644 star-tune/src/drizzle/0000_tearful_menace.sql create mode 100644 star-tune/src/drizzle/0001_silent_natasha_romanoff.sql diff --git a/star-tune/.env b/star-tune/.env index 70964a8..b5b3c79 100644 --- a/star-tune/.env +++ b/star-tune/.env @@ -1,6 +1,37 @@ +# 系统信息 +HOST=0.0.0.0 +PORT=3000 + # 日志配置 LOG_LEVEL=debug # 日志级别:debug, verbose, info, warn, error LOG_MAX_FILES=30d # 保留日志文件的时间 LOG_MAX_SIZE=20m # 单个日志文件的最大大小 LOG_DIRECTORY=logs # 日志文件存储目录 -LOG_CONSOLE=true # 是否在控制台输出日志 \ No newline at end of file +LOG_CONSOLE=true # 是否在控制台输出日志 + +# 数据库MYSQL配置 +DB_HOST=172.16.1.3 +DB_PORT=3306 +DB_USER=root +DB_NAME= +DB_PASSWORD=docker + +# redis配置 +REDIS_HOST=172.16.1.3 +REDIS_PORT=6379 +REDIS_USERNAME=default +REDIS_PASSWORD=docker +REDIS_DATABASE=9 +REDIS_CONNECT_NAME=star-tune +REDIS_TTL=3600 + +# 初始密码 +USER_DEFAULT_PASSWORD=startune +# 加密盐值 +PASSWORD_SALT=StarTune123 +# 迭代次数 +PASSWORD_ITERATIONS=15000 +# 密钥长度 +PASSWORD_KEYLEN=64 +哈希算法 +PASSWORD_DIGEST=sha512 \ No newline at end of file diff --git a/star-tune/README.md b/star-tune/README.md index 8f0f65f..b86035c 100644 --- a/star-tune/README.md +++ b/star-tune/README.md @@ -1,98 +1,27 @@ -

- Nest Logo -

+# star-tune -[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 -[circleci-url]: https://circleci.com/gh/nestjs/nest - -

A progressive Node.js framework for building efficient and scalable server-side applications.

-

-NPM Version -Package License -NPM Downloads -CircleCI -Discord -Backers on Open Collective -Sponsors on Open Collective - Donate us - Support us - Follow us on Twitter -

- - -## Description - -[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. - -## Project setup +> nestjs fastify drizzle mysql2 redis ```bash -$ npm install +npm run dev +npm run start:dev +npm run start:debug ``` -## Compile and run the project - ```bash -# development -$ npm run start - -# watch mode -$ npm run start:dev - -# production mode -$ npm run start:prod +npm start +npm run start +npm run start:prod ``` -## Run tests - -```bash -# unit tests -$ npm run test - -# e2e tests -$ npm run test:e2e - -# test coverage -$ npm run test:cov +### DB +``` +makeSQL +makeEntity +syncDB +sqlV ``` -## Deployment - -When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information. - -If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps: - -```bash -$ npm install -g @nestjs/mau -$ mau deploy -``` - -With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure. - -## Resources - -Check out a few resources that may come in handy when working with NestJS: - -- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework. -- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy). -- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/). -- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks. -- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com). -- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com). -- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs). -- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com). - -## Support - -Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). - -## Stay in touch - -- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec) -- Website - [https://nestjs.com](https://nestjs.com/) -- Twitter - [@nestframework](https://twitter.com/nestframework) - -## License - -Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE). +1. npm i -d +2. 将init.sql同步到数据库 +3. npm start \ No newline at end of file diff --git a/star-tune/dbDesign/init.sql b/star-tune/dbDesign/init.sql new file mode 100644 index 0000000..e636f93 --- /dev/null +++ b/star-tune/dbDesign/init.sql @@ -0,0 +1,67 @@ +-- 用户主表 (移除外键约束) +CREATE TABLE `user` ( + `user_id` BIGINT UNSIGNED NOT NULL PRIMARY KEY COMMENT '用户ID(雪花ID)', + `status` TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '状态:0=正常 1=禁用', + `user_type` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户类型:0=一般用户 1=root', + `user_role` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户角色:0默认角色', + `username` VARCHAR(50) NOT NULL COMMENT '唯一用户名', + `email` VARCHAR(320) NOT NULL COMMENT '邮箱', + `nickname` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '昵称', + `gender` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '性别:0=未知 1=男 2=女', + `birthdate` DATE NULL DEFAULT NULL COMMENT '出生年月', + `address` VARCHAR(255) NULL DEFAULT '' COMMENT '地址', + `avatar` VARCHAR(255) NULL DEFAULT '' COMMENT '头像URL', + `background_image` VARCHAR(255) NULL DEFAULT '' COMMENT '背景图URL', + `created_by` ENUM('SELF','ADMIN') NOT NULL COMMENT '创建方式:SELF=自注册 ADMIN=管理员添加', + `created_at` DATETIME(3) NOT NULL DEFAULT (CURRENT_TIMESTAMP) COMMENT '创建时间(毫秒精度)', + `updated_at` DATETIME(3) NOT NULL DEFAULT (CURRENT_TIMESTAMP) COMMENT '最后更新时间', + `username_updated_at` DATETIME(3) NULL DEFAULT NULL COMMENT '用户名修改时间', + `is_deleted` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '软删除标记:0=正常 1=已删除', + `deleted_at` DATETIME(3) NULL DEFAULT NULL COMMENT '删除时间', + + -- 唯一索引确保数据完整性 + UNIQUE INDEX `idx_unique_username` (`username`), + -- 复合唯一索引实现删除后邮箱可重用 + UNIQUE INDEX `idx_unique_active_email` (`email`, `is_deleted`), + -- 时间索引优化查询 + INDEX `idx_created_at` (`created_at`), + INDEX `idx_email` (`email`), + INDEX `idx_status` (`status`), + INDEX `idx_user_type` (`user_type`), + INDEX `idx_username_updated` (`username_updated_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户主表'; + +-- 个人简介表 (移除外键) +CREATE TABLE `user_profile` ( + `user_id` BIGINT UNSIGNED NOT NULL PRIMARY KEY COMMENT '用户ID', + `profile` MEDIUMTEXT NOT NULL COMMENT '简介内容', + `created_at` DATETIME(3) NOT NULL DEFAULT (CURRENT_TIMESTAMP) COMMENT '创建时间', + `updated_at` DATETIME(3) NOT NULL DEFAULT (CURRENT_TIMESTAMP) COMMENT '最后更新时间', + + -- 添加索引优化关联查询 + INDEX `idx_user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户简介表'; + +-- 密码表 (移除外键) +CREATE TABLE `user_password` ( + `user_id` BIGINT UNSIGNED NOT NULL PRIMARY KEY COMMENT '用户ID', + `password_hash` VARCHAR(128) NOT NULL COMMENT '加密密码(argon2格式)', + `previous_password_hash` VARCHAR(128) NULL DEFAULT NULL COMMENT '前次密码', + `last_updated_at` DATETIME(3) NOT NULL DEFAULT (CURRENT_TIMESTAMP) COMMENT '最后修改时间', + + -- 添加索引 + INDEX `idx_user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户密码表'; + +-- 个性签名历史表 (移除外键) +CREATE TABLE `user_signature_history` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '记录ID', + `user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID', + `signature` VARCHAR(100) NOT NULL COMMENT '签名内容', + `signature_tag` VARCHAR(100) NOT NULL COMMENT '签名标签', + `created_at` DATETIME(3) NOT NULL DEFAULT (CURRENT_TIMESTAMP) COMMENT '创建时间', + + -- 优化用户签名查询 + INDEX `idx_user_created` (`user_id`, `created_at` DESC), + INDEX `idx_user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='签名历史表'; \ No newline at end of file diff --git a/star-tune/dbDesign/user.01.sql b/star-tune/dbDesign/user.01.sql new file mode 100644 index 0000000..e636f93 --- /dev/null +++ b/star-tune/dbDesign/user.01.sql @@ -0,0 +1,67 @@ +-- 用户主表 (移除外键约束) +CREATE TABLE `user` ( + `user_id` BIGINT UNSIGNED NOT NULL PRIMARY KEY COMMENT '用户ID(雪花ID)', + `status` TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '状态:0=正常 1=禁用', + `user_type` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户类型:0=一般用户 1=root', + `user_role` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户角色:0默认角色', + `username` VARCHAR(50) NOT NULL COMMENT '唯一用户名', + `email` VARCHAR(320) NOT NULL COMMENT '邮箱', + `nickname` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '昵称', + `gender` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '性别:0=未知 1=男 2=女', + `birthdate` DATE NULL DEFAULT NULL COMMENT '出生年月', + `address` VARCHAR(255) NULL DEFAULT '' COMMENT '地址', + `avatar` VARCHAR(255) NULL DEFAULT '' COMMENT '头像URL', + `background_image` VARCHAR(255) NULL DEFAULT '' COMMENT '背景图URL', + `created_by` ENUM('SELF','ADMIN') NOT NULL COMMENT '创建方式:SELF=自注册 ADMIN=管理员添加', + `created_at` DATETIME(3) NOT NULL DEFAULT (CURRENT_TIMESTAMP) COMMENT '创建时间(毫秒精度)', + `updated_at` DATETIME(3) NOT NULL DEFAULT (CURRENT_TIMESTAMP) COMMENT '最后更新时间', + `username_updated_at` DATETIME(3) NULL DEFAULT NULL COMMENT '用户名修改时间', + `is_deleted` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '软删除标记:0=正常 1=已删除', + `deleted_at` DATETIME(3) NULL DEFAULT NULL COMMENT '删除时间', + + -- 唯一索引确保数据完整性 + UNIQUE INDEX `idx_unique_username` (`username`), + -- 复合唯一索引实现删除后邮箱可重用 + UNIQUE INDEX `idx_unique_active_email` (`email`, `is_deleted`), + -- 时间索引优化查询 + INDEX `idx_created_at` (`created_at`), + INDEX `idx_email` (`email`), + INDEX `idx_status` (`status`), + INDEX `idx_user_type` (`user_type`), + INDEX `idx_username_updated` (`username_updated_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户主表'; + +-- 个人简介表 (移除外键) +CREATE TABLE `user_profile` ( + `user_id` BIGINT UNSIGNED NOT NULL PRIMARY KEY COMMENT '用户ID', + `profile` MEDIUMTEXT NOT NULL COMMENT '简介内容', + `created_at` DATETIME(3) NOT NULL DEFAULT (CURRENT_TIMESTAMP) COMMENT '创建时间', + `updated_at` DATETIME(3) NOT NULL DEFAULT (CURRENT_TIMESTAMP) COMMENT '最后更新时间', + + -- 添加索引优化关联查询 + INDEX `idx_user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户简介表'; + +-- 密码表 (移除外键) +CREATE TABLE `user_password` ( + `user_id` BIGINT UNSIGNED NOT NULL PRIMARY KEY COMMENT '用户ID', + `password_hash` VARCHAR(128) NOT NULL COMMENT '加密密码(argon2格式)', + `previous_password_hash` VARCHAR(128) NULL DEFAULT NULL COMMENT '前次密码', + `last_updated_at` DATETIME(3) NOT NULL DEFAULT (CURRENT_TIMESTAMP) COMMENT '最后修改时间', + + -- 添加索引 + INDEX `idx_user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户密码表'; + +-- 个性签名历史表 (移除外键) +CREATE TABLE `user_signature_history` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '记录ID', + `user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID', + `signature` VARCHAR(100) NOT NULL COMMENT '签名内容', + `signature_tag` VARCHAR(100) NOT NULL COMMENT '签名标签', + `created_at` DATETIME(3) NOT NULL DEFAULT (CURRENT_TIMESTAMP) COMMENT '创建时间', + + -- 优化用户签名查询 + INDEX `idx_user_created` (`user_id`, `created_at` DESC), + INDEX `idx_user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='签名历史表'; \ No newline at end of file diff --git a/star-tune/drizzle.config.ts b/star-tune/drizzle.config.ts new file mode 100644 index 0000000..700011b --- /dev/null +++ b/star-tune/drizzle.config.ts @@ -0,0 +1,75 @@ +/** + * Drizzle ORM 配置文件 + * + * 用于配置数据库连接、迁移和 schema 生成 + * 支持从环境变量读取配置 + */ +import type { Config } from 'drizzle-kit'; +import * as dotenv from 'dotenv'; + +// 加载环境变量 +dotenv.config(); + +// 1. `makeSQL` (drizzle-kit generate): +// - 作用:根据 `schema.ts` 文件中的定义生成 SQL 迁移文件 +// - 具体功能: +// - 比较当前数据库状态和 schema 定义的差异 +// - 在 `src/drizzle` 目录下生成新的迁移文件(如 `0000_green_doorman.sql`) +// - 同时更新 `meta/_journal.json` 记录迁移历史 +// - 使用场景:当你修改了 `schema.ts` 中的表结构定义后,需要生成对应的 SQL 迁移文件 +// - 执行命令:`npm run makeSQL` + +// 2. `makeEntity` (drizzle-kit introspect): +// - 作用:从现有数据库反向生成 TypeScript 实体定义 +// - 具体功能: +// - 扫描数据库中的表结构 +// - 自动生成对应的 TypeScript 类型定义 +// - 在 `src/drizzle/schema.ts` 中生成表结构代码 +// - 使用场景: +// - 当你有一个现成的数据库,想要快速生成对应的 TypeScript 类型定义 +// - 数据库表结构发生变化,需要更新 TypeScript 定义 +// - 执行命令:`npm run makeEntity` + +// 3. `syncDB` (drizzle-kit migrate): +// - 作用:将生成的 SQL 迁移文件同步到数据库 +// - 具体功能: +// - 读取 `src/drizzle` 目录下的迁移文件 +// - 按照迁移历史记录的顺序执行 SQL 语句 +// - 将数据库结构更新到最新状态 +// - 使用场景: +// - 当你生成了新的迁移文件后,需要将这些更改应用到数据库 +// - 团队协作时,需要同步其他人的数据库更改 +// - 执行命令:`npm run syncDB` + +// 典型的工作流程是: +// 1. 修改 `schema.ts` 中的表结构定义 +// 2. 运行 `makeSQL` 生成迁移文件 +// 3. 检查生成的 SQL 文件是否正确 +// 4. 运行 `syncDB` 将更改同步到数据库 + +// 如果你是从现有数据库开始,可以: +// 1. 运行 `makeEntity` 生成 TypeScript 定义 +// 2. 根据需要修改生成的代码 +// 3. 运行 `makeSQL` 和 `syncDB` 同步更改 + +// 这些命令都是通过 `drizzle-kit` 工具提供的,它提供了完整的数据库迁移和类型生成功能。 + + +export default { + schema: './src/drizzle/schema.ts', + out: './src/drizzle', + dialect: 'mysql', + dbCredentials: { + host: process.env.DB_HOST || 'localhost', + port: Number(process.env.DB_PORT) || 3306, + user: process.env.DB_USER || 'root', + password: process.env.DB_PASSWORD || 'root', + database: process.env.DB_NAME || 'star_tune', + }, + verbose: true, + strict: true, + introspect: { + // 启用驼峰命名 + casing: 'camel', + }, +} satisfies Config; \ No newline at end of file diff --git a/star-tune/package-lock.json b/star-tune/package-lock.json index 9740f0d..0078013 100644 --- a/star-tune/package-lock.json +++ b/star-tune/package-lock.json @@ -1,11 +1,11 @@ { - "name": "star-cyan", + "name": "star-tune", "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "star-cyan", + "name": "star-tune", "version": "0.0.1", "license": "UNLICENSED", "dependencies": { @@ -14,7 +14,10 @@ "@nestjs/core": "^11.0.1", "@nestjs/platform-fastify": "^11.1.2", "cross-env": "^7.0.3", + "drizzle-orm": "^0.44.0", + "mysql2": "^3.14.1", "nest-winston": "^1.10.2", + "redis": "^5.1.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", "winston": "^3.17.0", @@ -31,6 +34,7 @@ "@types/jest": "^29.5.14", "@types/node": "^22.10.7", "@types/supertest": "^6.0.2", + "drizzle-kit": "^0.31.1", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-prettier": "^5.2.2", @@ -45,6 +49,9 @@ "tsconfig-paths": "^4.2.0", "typescript": "^5.7.3", "typescript-eslint": "^8.20.0" + }, + "engines": { + "node": ">=22.0.0" } }, "node_modules/@ampproject/remapping": { @@ -767,6 +774,823 @@ "kuler": "^2.0.0" } }, + "node_modules/@drizzle-team/brocli": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", + "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==", + "dev": true + }, + "node_modules/@esbuild-kit/core-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", + "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", + "deprecated": "Merged into tsx: https://tsx.is", + "dev": true, + "dependencies": { + "esbuild": "~0.18.20", + "source-map-support": "^0.5.21" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/@esbuild-kit/esm-loader": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", + "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", + "deprecated": "Merged into tsx: https://tsx.is", + "dev": true, + "dependencies": { + "@esbuild-kit/core-utils": "^3.3.2", + "get-tsconfig": "^4.7.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", @@ -2993,6 +3817,66 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@redis/bloom": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.1.1.tgz", + "integrity": "sha512-PnMcvpL7O2DHtnSL5JtyNmraNrdHuJXi3u2isGTUuPgkbAuWQKfZdknq471ySILL+qKtLfVJqzgDFMjYmZzK6Q==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.1.1" + } + }, + "node_modules/@redis/client": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.1.1.tgz", + "integrity": "sha512-vojbBqUdbkD+ylCy3+ZDXLzSmgiYH9pLrv87kF+nDgsRaHKrVVxPV9B4u6EfWRx7XGvQGZqsXVkKFhsEOsG3LA==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@redis/json": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.1.1.tgz", + "integrity": "sha512-A5M0dcgxGKq+oE6spIPBcGLDBiwoSPTs2wesVb4x30rXfG6rPtqt1Z7fCMtvTL2kHUNRKgZ78zhD+0+MENZt7g==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.1.1" + } + }, + "node_modules/@redis/search": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.1.1.tgz", + "integrity": "sha512-bChudQmcqfYUxEGMeXMkljXtwse4hzqcqRwbZDwRyYe+EEeW/lXVl3w/mS2tHnAb2yqGnfDghid8iHEtVNqjww==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.1.1" + } + }, + "node_modules/@redis/time-series": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.1.1.tgz", + "integrity": "sha512-HPjZLfcZxh5mBLqRgx7KCZG6JXxGnb7yJqo9qZ/KMTWK/k3SWyH47DHJbYbRNzKOEkbK/l/5kikDTm79uJuCbg==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.1.1" + } + }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "resolved": "https://registry.npmmirror.com/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", @@ -4993,6 +5877,14 @@ "fastq": "^1.17.1" } }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/b4a": { "version": "1.6.7", "resolved": "https://registry.npmmirror.com/b4a/-/b4a-1.6.7.tgz", @@ -5694,6 +6586,15 @@ "node": ">=0.8" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmmirror.com/co/-/co-4.6.0.tgz", @@ -6140,6 +7041,14 @@ "node": ">=0.4.0" } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", @@ -6228,6 +7137,145 @@ "url": "https://dotenvx.com" } }, + "node_modules/drizzle-kit": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.1.tgz", + "integrity": "sha512-PUjYKWtzOzPtdtQlTHQG3qfv4Y0XT8+Eas6UbxCmxTj7qgMf+39dDujf1BP1I+qqZtw9uzwTh8jYtkMuCq+B0Q==", + "dev": true, + "dependencies": { + "@drizzle-team/brocli": "^0.10.2", + "@esbuild-kit/esm-loader": "^2.5.5", + "esbuild": "^0.25.2", + "esbuild-register": "^3.5.0" + }, + "bin": { + "drizzle-kit": "bin.cjs" + } + }, + "node_modules/drizzle-orm": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.44.0.tgz", + "integrity": "sha512-/8wYepe887Gp1eCVILKitNegclGLPUMdK4oDDyDftKJOeHoCylY+XM++chMJloy9oKTceWOALiQMl2aAuFuPTw==", + "peerDependencies": { + "@aws-sdk/client-rds-data": ">=3", + "@cloudflare/workers-types": ">=4", + "@electric-sql/pglite": ">=0.2.0", + "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", + "@neondatabase/serverless": ">=0.10.0", + "@op-engineering/op-sqlite": ">=2", + "@opentelemetry/api": "^1.4.1", + "@planetscale/database": ">=1.13", + "@prisma/client": "*", + "@tidbcloud/serverless": "*", + "@types/better-sqlite3": "*", + "@types/pg": "*", + "@types/sql.js": "*", + "@upstash/redis": ">=1.34.7", + "@vercel/postgres": ">=0.8.0", + "@xata.io/client": "*", + "better-sqlite3": ">=7", + "bun-types": "*", + "expo-sqlite": ">=14.0.0", + "gel": ">=2", + "knex": "*", + "kysely": "*", + "mysql2": ">=2", + "pg": ">=8", + "postgres": ">=3", + "sql.js": ">=1", + "sqlite3": ">=5" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-rds-data": { + "optional": true + }, + "@cloudflare/workers-types": { + "optional": true + }, + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@libsql/client-wasm": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@op-engineering/op-sqlite": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@tidbcloud/serverless": { + "optional": true + }, + "@types/better-sqlite3": { + "optional": true + }, + "@types/pg": { + "optional": true + }, + "@types/sql.js": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "bun-types": { + "optional": true + }, + "expo-sqlite": { + "optional": true + }, + "gel": { + "optional": true + }, + "knex": { + "optional": true + }, + "kysely": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "postgres": { + "optional": true + }, + "prisma": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + } + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -6398,6 +7446,58 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "node_modules/esbuild-register": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", + "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "esbuild": ">=0.12 <1" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", @@ -7458,6 +8558,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dependencies": { + "is-property": "^1.0.2" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -7540,6 +8648,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "11.0.1", "resolved": "https://registry.npmmirror.com/glob/-/glob-11.0.1.tgz", @@ -7799,8 +8919,6 @@ "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", - "optional": true, - "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -8028,6 +9146,11 @@ "optional": true, "peer": true }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz", @@ -9187,6 +10310,11 @@ "node": ">=0.1.90" } }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, "node_modules/lowercase-keys": { "version": "3.0.0", "resolved": "https://registry.npmmirror.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz", @@ -9210,6 +10338,20 @@ "yallist": "^3.0.2" } }, + "node_modules/lru.min": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz", + "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz", @@ -9558,6 +10700,44 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/mysql2": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.1.tgz", + "integrity": "sha512-7ytuPQJjQB8TNAYX/H2yhL+iQOnIBjAMam361R7UAL0lOVXWjtdrmoL9HYKqKoLp/8UUTRcvo1QPvK9KL7wA8w==", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz", @@ -10485,6 +11665,22 @@ "node": ">= 12.13.0" } }, + "node_modules/redis": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-5.1.1.tgz", + "integrity": "sha512-4t6n2Q9aFqpQnqBR/g84zsXW+U0hdSzYymqoGGZk44p+kuzzHbgukjOAca+PlQ563TbXcgv1njerllYWjAWw4g==", + "license": "MIT", + "dependencies": { + "@redis/bloom": "5.1.1", + "@redis/client": "5.1.1", + "@redis/json": "5.1.1", + "@redis/search": "5.1.1", + "@redis/time-series": "5.1.1" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/reflect-metadata": { "version": "0.2.2", "resolved": "https://registry.npmmirror.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz", @@ -10581,6 +11777,15 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve.exports": { "version": "2.0.3", "resolved": "https://registry.npmmirror.com/resolve.exports/-/resolve.exports-2.0.3.tgz", @@ -10757,7 +11962,6 @@ "version": "2.1.2", "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "devOptional": true, "license": "MIT" }, "node_modules/schema-utils": { @@ -10884,6 +12088,11 @@ "node": ">= 18" } }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -11149,6 +12358,14 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmmirror.com/stack-trace/-/stack-trace-0.0.10.tgz", diff --git a/star-tune/package.json b/star-tune/package.json index 9d2349b..14b6829 100644 --- a/star-tune/package.json +++ b/star-tune/package.json @@ -5,6 +5,9 @@ "author": "", "private": true, "license": "UNLICENSED", + "engines": { + "node": ">=22.0.0" + }, "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", @@ -18,7 +21,11 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json" + "test:e2e": "jest --config ./test/jest-e2e.json", + "makeSQL": "drizzle-kit generate", + "makeEntity": "drizzle-kit introspect", + "syncDB": "drizzle-kit migrate", + "sqlV": "drizzle-kit studio" }, "dependencies": { "@nestjs/common": "^11.0.1", @@ -26,7 +33,10 @@ "@nestjs/core": "^11.0.1", "@nestjs/platform-fastify": "^11.1.2", "cross-env": "^7.0.3", + "drizzle-orm": "^0.44.0", + "mysql2": "^3.14.1", "nest-winston": "^1.10.2", + "redis": "^5.1.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", "winston": "^3.17.0", @@ -43,6 +53,7 @@ "@types/jest": "^29.5.14", "@types/node": "^22.10.7", "@types/supertest": "^6.0.2", + "drizzle-kit": "^0.31.1", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-prettier": "^5.2.2", diff --git a/star-tune/src/app.controller.ts b/star-tune/src/app.controller.ts index a325e8b..b8d8b75 100644 --- a/star-tune/src/app.controller.ts +++ b/star-tune/src/app.controller.ts @@ -6,7 +6,7 @@ export class AppController { constructor(private readonly appService: AppService) {} @Get() - getHello(): string { - return this.appService.getHello(); + async getHello(): Promise { + return await this.appService.getHello(); } } diff --git a/star-tune/src/app.module.ts b/star-tune/src/app.module.ts index 8d176c2..2d26ef3 100644 --- a/star-tune/src/app.module.ts +++ b/star-tune/src/app.module.ts @@ -1,12 +1,38 @@ -import { Module } from '@nestjs/common'; -import { AppController } from './app.controller'; -import { AppService } from './app.service'; -import { AppConfigModule } from './config/config.module'; -import { LoggerModule } from './logger/logger.module'; +import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { InitModule } from '@/common/init/init.module'; +import { DatabaseModule } from '@/common/database/database.module'; +import { LoggerModule } from '@/common/logger/logger.module'; +import { UtilsModule } from '@/common/utils/utils.module'; +import { RedisModule } from '@/common/redis/redis.module'; +import configuration from '@/config/configuration'; +import { AppController } from '@/app.controller'; +import { AppService } from '@/app.service'; +import { RequestLoggerMiddleware } from '@/common/middleware/request-logger.middleware'; @Module({ - imports: [AppConfigModule, LoggerModule], + imports: [ + // 配置模块 + ConfigModule.forRoot({ + isGlobal: true, + load: [configuration], + }), + // 日志模块 + LoggerModule, + // 数据库模块 + DatabaseModule, + // Redis 模块 + RedisModule, + // 初始化模块 + InitModule, + // 工具模块 + UtilsModule, + ], controllers: [AppController], providers: [AppService], }) -export class AppModule {} +export class AppModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + consumer.apply(RequestLoggerMiddleware).forRoutes('*'); + } +} diff --git a/star-tune/src/app.service.ts b/star-tune/src/app.service.ts index 61b7a5b..8a88b94 100644 --- a/star-tune/src/app.service.ts +++ b/star-tune/src/app.service.ts @@ -2,7 +2,10 @@ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { - getHello(): string { - return 'Hello World!'; + async getHello(): Promise { + await new Promise(resolve => setTimeout(resolve, 1000)); + let str = 'Hello World!'; + str = str.repeat(1000); + return {str}; } } diff --git a/star-tune/src/common/database/database.module.ts b/star-tune/src/common/database/database.module.ts new file mode 100644 index 0000000..72774a4 --- /dev/null +++ b/star-tune/src/common/database/database.module.ts @@ -0,0 +1,19 @@ +/** + * 数据库模块 + * + * 这是一个全局模块,使用 @Global() 装饰器使其在整个应用中可用 + * 不需要在每个使用数据库的模块中重复导入 + */ +import { Global, Module } from '@nestjs/common'; +import { LoggerModule } from '@/common/logger/logger.module'; +import { DatabaseService } from '@/common/database/database.service'; + +@Global() +@Module({ + imports: [LoggerModule], + // 提供数据库服务 + providers: [DatabaseService], + // 导出数据库服务,使其可以被其他模块注入使用 + exports: [DatabaseService], +}) +export class DatabaseModule {} \ No newline at end of file diff --git a/star-tune/src/common/database/database.service.ts b/star-tune/src/common/database/database.service.ts new file mode 100644 index 0000000..09b4169 --- /dev/null +++ b/star-tune/src/common/database/database.service.ts @@ -0,0 +1,75 @@ +/** + * 数据库服务 + * + * 负责管理数据库连接和提供数据库操作接口 + * 实现了 OnModuleInit 接口,在模块初始化时自动建立数据库连接 + */ +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { drizzle, type MySql2Database } from 'drizzle-orm/mysql2'; +import { createPool, Pool} from 'mysql2/promise'; +import * as schema from '@/drizzle/schema'; +import { CustomLogger } from '@/common/logger/logger.service'; +import { type ResultSetHeader } from 'mysql2'; + +@Injectable() +export class DatabaseService implements OnModuleInit { + // 数据库实例,使用 drizzle ORM + private db!: MySql2Database; + // MySQL 连接池实例 + private pool!: Pool; + + constructor(private configService: ConfigService, private logger: CustomLogger) {} + + /** + * 模块初始化时自动执行 + * 建立数据库连接并初始化 drizzle ORM + */ + async onModuleInit() { + // 从配置服务获取数据库配置 + const dbConfig = this.configService.get('database'); + + // 先创建临时连接(不指定数据库名) + const tempPool = createPool({ + host: dbConfig.host, + port: dbConfig.port, + user: dbConfig.username, + password: dbConfig.password, + }); + + // 创建数据库(如果不存在) + await tempPool.query(`CREATE DATABASE IF NOT EXISTS \`${dbConfig.database}\``) as [ResultSetHeader, any]; + + this.logger.log(`数据库 ${dbConfig.database} 初始化成功`, 'InitMySQL'); + await tempPool.end(); + + // 创建正式的数据库连接池 + this.pool = createPool({ + host: dbConfig.host, + port: dbConfig.port, + user: dbConfig.username, + password: dbConfig.password, + database: dbConfig.database, + }); + + // 初始化 drizzle ORM,使用默认模式 + this.db = drizzle(this.pool, { schema, mode: 'default' }); + } + + /** + * 模块销毁时自动执行 + * 关闭数据库连接池 + */ + async onModuleDestroy() { + await this.pool.end(); + } + + /** + * 获取数据库实例 + * @returns drizzle ORM 实例 + */ + getDb() { + return this.db; + } + +} \ No newline at end of file diff --git a/star-tune/src/common/init/init.module.ts b/star-tune/src/common/init/init.module.ts new file mode 100644 index 0000000..e804aa6 --- /dev/null +++ b/star-tune/src/common/init/init.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { InitService } from './init.service'; +import { DatabaseModule } from '../database/database.module'; +import { LoggerModule } from '../logger/logger.module'; +import { UtilsModule } from '../utils/utils.module'; +import { ConfigModule } from '@nestjs/config'; +@Module({ + imports: [ + DatabaseModule, + LoggerModule, + UtilsModule, + ConfigModule, + ], + providers: [InitService], + exports: [InitService], +}) +export class InitModule {} \ No newline at end of file diff --git a/star-tune/src/common/init/init.service.ts b/star-tune/src/common/init/init.service.ts new file mode 100644 index 0000000..2ac42ab --- /dev/null +++ b/star-tune/src/common/init/init.service.ts @@ -0,0 +1,85 @@ +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { DatabaseService } from '@/common/database/database.service'; +import { CustomLogger } from '@/common/logger/logger.service'; +import { eq } from 'drizzle-orm'; +import { user, userPassword, userProfile } from '@/drizzle/schema'; +import { UtilsServer } from '@/common/utils/utils.server'; +import { ConfigService } from '@nestjs/config'; +@Injectable() +export class InitService implements OnModuleInit { + constructor( + private readonly dbService: DatabaseService, + private readonly logger: CustomLogger, + private readonly utilsServer: UtilsServer, + private readonly configService: ConfigService, + ) {} + + async onModuleInit() { + await this.initRootUser(); + } + + /** + * 初始化 root 用户 + * 如果不存在则创建 + */ + private async initRootUser() { + const db = this.dbService.getDb(); + + try { + // 检查 root 用户是否存在 + const rootUser = await db.query.user.findFirst({ + where: eq(user.userId, 0), + }); + + if (!rootUser) { + this.logger.log('正在创建 root 用户...', 'InitService'); + + // 生成密码哈希 + const rootPassword = this.configService.get('password.userDefaultPassword'); // 默认密码 + const passwordHash = await this.utilsServer.hashPassword(rootPassword as string); + const now = new Date().toISOString().slice(0, 23).replace('T', ' '); + + // 创建 root 用户 + await db.transaction(async (tx) => { + // 插入用户基本信息 + await tx.insert(user).values({ + userId: 0, + status: 0, + userType: 1, // root 用户 + userRole: 0, + username: 'root', + email: 'root@star-tune.com', + nickname: '系统管理员', + gender: 0, + createdBy: 'ADMIN', + createdAt: now, + updatedAt: now, + isDeleted: 0, + }); + + // 插入用户密码 + await tx.insert(userPassword).values({ + userId: 0, + passwordHash, + lastUpdatedAt: now, + }); + + // 插入用户简介 + await tx.insert(userProfile).values({ + userId: 0, + profile: '系统管理员账号', + createdAt: now, + updatedAt: now, + }); + }); + + this.logger.log('root 用户创建成功', 'InitService'); + } else { + this.logger.log('root 用户已存在', 'InitService'); + } + } catch (error) { + this.logger.error('初始化 root 用户失败', error, 'InitService'); + throw error; + } + } +} \ No newline at end of file diff --git a/star-tune/src/logger/logger.module.ts b/star-tune/src/common/logger/logger.module.ts similarity index 80% rename from star-tune/src/logger/logger.module.ts rename to star-tune/src/common/logger/logger.module.ts index 90dc701..5e0fd02 100644 --- a/star-tune/src/logger/logger.module.ts +++ b/star-tune/src/common/logger/logger.module.ts @@ -4,7 +4,7 @@ import { ConfigService } from '@nestjs/config'; import * as winston from 'winston'; import * as DailyRotateFile from 'winston-daily-rotate-file'; import { join } from 'path'; -import { CustomLogger } from './custom.logger'; +import { CustomLogger } from '@/common/logger/logger.service'; // 定义配置接口 interface LogConfig { @@ -44,21 +44,35 @@ const getPaddedPid = () => process.pid.toString().padStart(5, '0'); // 创建自定义的 winston 格式化器 const createCustomFormat = (environment: Environment) => { return winston.format.printf( - ({ - timestamp, - level, - message, - context, - }: { + (info: { timestamp: string; level: string; - message: string; + message: string | Error; context?: string; }) => { + const { timestamp, level, message, context} = info; + + // 处理错误信息 + let errorMessage = message; + let errorStack: string | undefined; + + // 如果消息是错误对象或包含堆栈信息 + if (message instanceof Error) { + errorMessage = message.message; + errorStack = message.stack; + } else if (typeof message === 'string' && message.includes('at ')) { + // 如果消息字符串中包含堆栈信息 + const parts = message.split('\n'); + errorMessage = parts[0]; + errorStack = parts.slice(1).join('\n'); + } + // 获取环境颜色,默认白色 const envColor = envColors[environment] || '\x1b[37m'; // 获取日志级别颜色,默认白色 const levelColor = levelColors[level] || '\x1b[37m'; + // 粗体 + const bold = '\x1b[1m'; // 重置颜色的代码 const reset = '\x1b[0m'; @@ -82,10 +96,18 @@ const createCustomFormat = (environment: Environment) => { contextContent.length > FORMAT_CONFIG.CONTEXT_LENGTH ? contextContent.slice(0, FORMAT_CONFIG.CONTEXT_LENGTH) : contextContent.padEnd(FORMAT_CONFIG.CONTEXT_LENGTH, ' '); - const paddedContext = `[${formattedContext}]`; + + // 处理错误堆栈信息 + const stackInfo = errorStack ? `\n${levelColor}${errorStack}${reset}` : ''; // 返回格式化的日志字符串 - return `${envColor}[${envTag}] ${getPaddedPid()}${reset} - ${timestamp} ${levelColor}${upperLevel} ${reset}${paddedContext} ${levelColor}${message}${reset}`; + let paddedContext = formattedContext; + if(formattedContext.trim() === 'RequestLogger') { + paddedContext = `[${levelColors['debug']}${bold}${formattedContext}${reset}]`; + }else{ + paddedContext = `[${formattedContext}]`; + } + return `${envColor}[${envTag}] ${getPaddedPid()}${reset} - ${timestamp} ${levelColor}${upperLevel} ${reset}${paddedContext} ${levelColor}${errorMessage}${reset}${stackInfo}`; }, ); }; diff --git a/star-tune/src/logger/custom.logger.ts b/star-tune/src/common/logger/logger.service.ts similarity index 100% rename from star-tune/src/logger/custom.logger.ts rename to star-tune/src/common/logger/logger.service.ts diff --git a/star-tune/src/common/middleware/request-logger.middleware.ts b/star-tune/src/common/middleware/request-logger.middleware.ts new file mode 100644 index 0000000..15e60c7 --- /dev/null +++ b/star-tune/src/common/middleware/request-logger.middleware.ts @@ -0,0 +1,47 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import { FastifyRequest, FastifyReply } from 'fastify'; +import { CustomLogger } from '@/common/logger/logger.service'; +import { ConfigService } from '@nestjs/config'; +import { UtilsServer } from '@/common/utils/utils.server'; + +interface ResponseWithHeader { + _header?: string; +} + +@Injectable() +export class RequestLoggerMiddleware implements NestMiddleware { + constructor( + private readonly logger: CustomLogger, + private readonly configService: ConfigService, + private readonly utilsServer: UtilsServer, + ) { + const environment = this.configService.get('environment'); + // 只在开发环境下记录请求日志 + if (environment === 'development') { + this.use = this.useBack; + } + } + + use(req: FastifyRequest, res: FastifyReply, next: () => void) { + } + + useBack(req: FastifyRequest & FastifyRequest['raw'], res: FastifyReply & FastifyReply['raw'], next: () => void) { + const requestStart = process.hrtime(); + const ip = req.id?.slice(4).padEnd(6).toUpperCase(); + this.logger.debug(`<=== ${ip} | ${req.method} ${req.url} ${req.ip}`, 'RequestLogger'); + res.on('finish', () => { + const [seconds, nanoseconds] = process.hrtime(requestStart); + const duration = seconds * 1000 + nanoseconds / 1000000; + const header = (res as unknown as ResponseWithHeader)._header; + const contentLength = header?.match(/content-length: (\d+)/i)?.[1]; + const size = contentLength ? this.utilsServer.formatFileSize(parseInt(contentLength, 10)) : '0 B'; + const contentType = header?.match(/content-type: (\w+\/\w+)/i)?.[1]; + + this.logger.debug( + `===> ${ip} | ${req.method} ${req.url} ${res.statusCode} ${duration.toFixed(2)}ms [${size}] [${contentType}]`, + 'RequestLogger' + ); + }) + next() + } +} \ No newline at end of file diff --git a/star-tune/src/common/redis/redis.module.ts b/star-tune/src/common/redis/redis.module.ts new file mode 100644 index 0000000..c081e4d --- /dev/null +++ b/star-tune/src/common/redis/redis.module.ts @@ -0,0 +1,11 @@ +import { Global, Module } from '@nestjs/common'; +import { RedisService } from './redis.service'; +import { LoggerModule } from '../logger/logger.module'; + +@Global() +@Module({ + imports: [LoggerModule], + providers: [RedisService], + exports: [RedisService], +}) +export class RedisModule {} \ No newline at end of file diff --git a/star-tune/src/common/redis/redis.service.ts b/star-tune/src/common/redis/redis.service.ts new file mode 100644 index 0000000..94dcd29 --- /dev/null +++ b/star-tune/src/common/redis/redis.service.ts @@ -0,0 +1,84 @@ +import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { createClient, RedisClientType } from 'redis'; +import { CustomLogger } from '../logger/logger.service'; + +@Injectable() +export class RedisService implements OnModuleInit, OnModuleDestroy { + public client: RedisClientType; + + constructor( + private configService: ConfigService, + private logger: CustomLogger, + ) { + const config = this.configService.get('redis'); + this.client = createClient({ + name: config.connectName, + username: config.username, + password: config.password, + database: config.database, + url: `redis://${config.username}:${config.password}@${config.host}:${config.port}/${config.database}`, + }); + this.client.on('connect', async () => { + this.logger.log(await this.client.set('SI HI', this.configService.get('redis.connectName') as string) || '', 'InitRedis'); + }); + + this.client.on('error', (err) => { + this.logger.error(err.message, err.stack, 'InitRedis'); + }); + } + + async onModuleInit() { + await this.client.connect(); + } + + async onModuleDestroy() { + await this.client.quit(); + } + + /** + * 设置键值对 + * @param key 键 + * @param value 值 + * @param ttl 过期时间(秒) + */ + async set(key: string, value: string, ttl?: number): Promise { + const expireTime = ttl || this.configService.get('redis.ttl'); + await this.client.set(key, value, { EX: expireTime }); + } + + /** + * 获取键值 + * @param key 键 + * @returns 值 + */ + async get(key: string): Promise { + return await this.client.get(key); + } + + /** + * 删除键 + * @param key 键 + */ + async del(key: string): Promise { + await this.client.del(key); + } + + /** + * 检查键是否存在 + * @param key 键 + * @returns 是否存在 + */ + async exists(key: string): Promise { + return (await this.client.exists(key)) === 1; + } + + /** + * 设置键的过期时间 + * @param key 键 + * @param ttl 过期时间(秒) + */ + async expire(key: string, ttl: number): Promise { + await this.client.expire(key, ttl); + } +} \ No newline at end of file diff --git a/star-tune/src/common/utils/utils.module.ts b/star-tune/src/common/utils/utils.module.ts new file mode 100644 index 0000000..d2470e0 --- /dev/null +++ b/star-tune/src/common/utils/utils.module.ts @@ -0,0 +1,9 @@ +import { Module, Global } from '@nestjs/common'; +import { UtilsServer } from './utils.server'; + +@Global() +@Module({ + providers: [UtilsServer], + exports: [UtilsServer], +}) +export class UtilsModule {} \ No newline at end of file diff --git a/star-tune/src/common/utils/utils.server.ts b/star-tune/src/common/utils/utils.server.ts new file mode 100644 index 0000000..dc3a312 --- /dev/null +++ b/star-tune/src/common/utils/utils.server.ts @@ -0,0 +1,57 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import * as crypto from 'crypto'; + +@Injectable() +export class UtilsServer { + constructor(private readonly configService: ConfigService) {} + + /** + * 加密密码 + * @param password 原始密码 + * @returns 加密后的密码 + */ + async hashPassword(password: string): Promise { + const salt = this.configService.get('password.salt'); + const iterations = this.configService.get('password.iterations'); + // 我们设置了 keylen: 128,这表示 128 字节 + // 每个字节转换为 2 个十六进制字符 + // 所以最终输出长度是 128 * 2 = 256 个字符 + const keylen = this.configService.get('password.keylen'); + const digest = this.configService.get('password.digest'); + + return new Promise((resolve, reject) => { + crypto.pbkdf2(password, salt as string, iterations as number, keylen as number, digest as string, (err, derivedKey) => { + if (err) reject(err); + resolve(derivedKey.toString('hex')); + }); + }); + } + + /** + * 验证密码 + * @param password 原始密码 + * @param hashedPassword 加密后的密码 + * @returns 是否匹配 + */ + async verifyPassword(password: string, hashedPassword: string): Promise { + const hashedInput = await this.hashPassword(password); + return crypto.timingSafeEqual( + Buffer.from(hashedInput, 'hex'), + Buffer.from(hashedPassword, 'hex') + ); + } + + formatFileSize(bytes: number): string { + const units = ['B', 'KB', 'MB', 'GB', 'TB']; + let size = bytes; + let unitIndex = 0; + + while (size >= 1024 && unitIndex < units.length - 1) { + size /= 1024; + unitIndex++; + } + + return `${size.toFixed(2)} ${units[unitIndex]}`; + } +} \ No newline at end of file diff --git a/star-tune/src/config/config.module.ts b/star-tune/src/config/config.module.ts index a8adfa5..dd33fdd 100644 --- a/star-tune/src/config/config.module.ts +++ b/star-tune/src/config/config.module.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; -import configuration from './configuration'; +import configuration from '@/config/configuration'; @Module({ imports: [ diff --git a/star-tune/src/config/configuration.ts b/star-tune/src/config/configuration.ts index d96fb21..5bb4aa1 100644 --- a/star-tune/src/config/configuration.ts +++ b/star-tune/src/config/configuration.ts @@ -4,13 +4,25 @@ export default () => { server: { port: parseInt(process.env.PORT || '3000', 10), host: process.env.HOST || 'localhost', + serviceName: 'StarTune', + description: 'A music platform', + version: '1.0.0', }, database: { host: process.env.DB_HOST || 'localhost', port: parseInt(process.env.DB_PORT || '3306', 10), username: process.env.DB_USERNAME || 'root', password: process.env.DB_PASSWORD || 'root', - database: process.env.DB_DATABASE || 'star_cyan', + database: process.env.DB_NAME || 'star_tune', + }, + redis: { + host: process.env.REDIS_HOST || 'localhost', + port: parseInt(process.env.REDIS_PORT || '6379', 10), + username: process.env.REDIS_USERNAME || 'default', + password: process.env.REDIS_PASSWORD || '', + database: parseInt(process.env.REDIS_DATABASE || '0', 10), + connectName: process.env.REDIS_CONNECT_NAME || 'star-tune', + ttl: parseInt(process.env.REDIS_TTL || '3600', 10), }, jwt: { secret: process.env.JWT_SECRET || 'your-secret-key', @@ -23,5 +35,12 @@ export default () => { directory: process.env.LOG_DIRECTORY || 'logs', console: (process.env.LOG_CONSOLE || 'true') === 'true', }, + password: { + userDefaultPassword: (process.env.USER_DEFAULT_PASSWORD || 'StarTune123') as string, + salt: (process.env.PASSWORD_SALT || 'star-tune-salt') as string, + iterations: parseInt(process.env.PASSWORD_ITERATIONS || '10000', 10), + keylen: parseInt(process.env.PASSWORD_KEYLEN || '64', 10), + digest: process.env.PASSWORD_DIGEST || 'sha512', + }, }; }; diff --git a/star-tune/src/drizzle/0000_tearful_menace.sql b/star-tune/src/drizzle/0000_tearful_menace.sql new file mode 100644 index 0000000..6b98068 --- /dev/null +++ b/star-tune/src/drizzle/0000_tearful_menace.sql @@ -0,0 +1,62 @@ +-- Current sql file was generated after introspecting the database +-- If you want to run this migration please uncomment this code before executing migrations +/* +CREATE TABLE `user` ( + `user_id` bigint unsigned NOT NULL, + `status` tinyint unsigned NOT NULL DEFAULT 0, + `user_type` tinyint unsigned NOT NULL DEFAULT 0, + `user_role` int unsigned NOT NULL DEFAULT 0, + `username` varchar(50) NOT NULL, + `email` varchar(320) NOT NULL, + `nickname` varchar(50) NOT NULL DEFAULT '', + `gender` tinyint unsigned NOT NULL DEFAULT 0, + `birthdate` date, + `address` varchar(255) DEFAULT '', + `avatar` varchar(255) DEFAULT '', + `background_image` varchar(255) DEFAULT '', + `created_by` enum('SELF','ADMIN') NOT NULL, + `created_at` datetime(3) NOT NULL, + `updated_at` datetime(3) NOT NULL, + `username_updated_at` datetime(3), + `is_deleted` tinyint unsigned NOT NULL DEFAULT 0, + `deleted_at` datetime(3), + CONSTRAINT `user_user_id` PRIMARY KEY(`user_id`), + CONSTRAINT `idx_unique_username` UNIQUE(`username`), + CONSTRAINT `idx_unique_active_email` UNIQUE(`email`,`is_deleted`) +); +--> statement-breakpoint +CREATE TABLE `user_password` ( + `user_id` bigint unsigned NOT NULL, + `password_hash` char(97) NOT NULL, + `previous_password_hash` char(97), + `last_updated_at` datetime(3) NOT NULL, + CONSTRAINT `user_password_user_id` PRIMARY KEY(`user_id`) +); +--> statement-breakpoint +CREATE TABLE `user_profile` ( + `user_id` bigint unsigned NOT NULL, + `profile` mediumtext NOT NULL, + `created_at` datetime(3) NOT NULL, + `updated_at` datetime(3) NOT NULL, + CONSTRAINT `user_profile_user_id` PRIMARY KEY(`user_id`) +); +--> statement-breakpoint +CREATE TABLE `user_signature_history` ( + `id` bigint unsigned AUTO_INCREMENT NOT NULL, + `user_id` bigint unsigned NOT NULL, + `signature` varchar(100) NOT NULL, + `signature_tag` varchar(100) NOT NULL, + `created_at` datetime(3) NOT NULL, + CONSTRAINT `user_signature_history_id` PRIMARY KEY(`id`) +); +--> statement-breakpoint +CREATE INDEX `idx_created_at` ON `user` (`created_at`);--> statement-breakpoint +CREATE INDEX `idx_email` ON `user` (`email`);--> statement-breakpoint +CREATE INDEX `idx_status` ON `user` (`status`);--> statement-breakpoint +CREATE INDEX `idx_user_type` ON `user` (`user_type`);--> statement-breakpoint +CREATE INDEX `idx_username_updated` ON `user` (`username_updated_at`);--> statement-breakpoint +CREATE INDEX `idx_user_id` ON `user_password` (`user_id`);--> statement-breakpoint +CREATE INDEX `idx_user_id` ON `user_profile` (`user_id`);--> statement-breakpoint +CREATE INDEX `idx_user_created` ON `user_signature_history` (`user_id`,`created_at`);--> statement-breakpoint +CREATE INDEX `idx_user_id` ON `user_signature_history` (`user_id`); +*/ \ No newline at end of file diff --git a/star-tune/src/drizzle/0001_silent_natasha_romanoff.sql b/star-tune/src/drizzle/0001_silent_natasha_romanoff.sql new file mode 100644 index 0000000..29505b5 --- /dev/null +++ b/star-tune/src/drizzle/0001_silent_natasha_romanoff.sql @@ -0,0 +1,8 @@ +ALTER TABLE `user` MODIFY COLUMN `created_at` datetime(3) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint +ALTER TABLE `user` MODIFY COLUMN `updated_at` datetime(3) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint +ALTER TABLE `user_password` MODIFY COLUMN `password_hash` varchar(128) NOT NULL;--> statement-breakpoint +ALTER TABLE `user_password` MODIFY COLUMN `previous_password_hash` varchar(128);--> statement-breakpoint +ALTER TABLE `user_password` MODIFY COLUMN `last_updated_at` datetime(3) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint +ALTER TABLE `user_profile` MODIFY COLUMN `created_at` datetime(3) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint +ALTER TABLE `user_profile` MODIFY COLUMN `updated_at` datetime(3) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint +ALTER TABLE `user_signature_history` MODIFY COLUMN `created_at` datetime(3) NOT NULL DEFAULT (CURRENT_TIMESTAMP); \ No newline at end of file diff --git a/star-tune/src/main.ts b/star-tune/src/main.ts index 6d416c5..8eb63f7 100644 --- a/star-tune/src/main.ts +++ b/star-tune/src/main.ts @@ -4,8 +4,8 @@ import { NestFastifyApplication, } from '@nestjs/platform-fastify'; import { ConfigService } from '@nestjs/config'; -import { AppModule } from './app.module'; -import { CustomLogger } from './logger/custom.logger'; +import { AppModule } from '@/app.module'; +import { CustomLogger } from '@/common/logger/logger.service'; interface ServerConfig { port: number; @@ -15,7 +15,9 @@ interface ServerConfig { async function bootstrap() { const app = await NestFactory.create( AppModule, - new FastifyAdapter(), + new FastifyAdapter({ + logger: false + }), ); const configService = app.get(ConfigService); @@ -41,7 +43,7 @@ async function bootstrap() { await app.listen(port, host); const environment = configService.get('environment'); logger.info( - `应用程序正在运行: http://${host}:${port}, 环境: ${environment}`, + `应用程序正在运行: http://${host==='0.0.0.0'?'127.0.0.1':host}:${port}, 环境: ${environment}`, 'Bootstrap', ); } diff --git a/star-tune/tsconfig.json b/star-tune/tsconfig.json index e4dbf2e..bbca6ae 100644 --- a/star-tune/tsconfig.json +++ b/star-tune/tsconfig.json @@ -10,6 +10,9 @@ "sourceMap": true, "outDir": "./dist", "baseUrl": "./", + "paths": { + "@/*": ["src/*"] + }, "incremental": true, "skipLibCheck": true, "strictNullChecks": true,