add redis, graphql
This commit is contained in:
parent
c5936cf05e
commit
b1a7097d02
@ -1,2 +0,0 @@
|
|||||||
node_modules
|
|
||||||
dist
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
node_modules
|
|
||||||
dist
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"trailingComma": "all",
|
|
||||||
"tabWidth": 2,
|
|
||||||
"useTabs": true,
|
|
||||||
"semi": true,
|
|
||||||
"singleQuote": true,
|
|
||||||
"jsxSingleQuote": false,
|
|
||||||
"arrowParens": "avoid",
|
|
||||||
"importOrderSeparation": true,
|
|
||||||
"importOrderSortSpecifiers": true,
|
|
||||||
"importOrderCaseInsensitive": true,
|
|
||||||
"importOrderParserPlugin": [
|
|
||||||
"classProperties",
|
|
||||||
"decorators-legacy",
|
|
||||||
"typescript"
|
|
||||||
],
|
|
||||||
"importOrder": ["<THIRD_PARTY_MODULES>", "^@/(.*)$", "^../(.*)", "^./(.*)"],
|
|
||||||
"plugins": ["@trivago/prettier-plugin-sort-imports"]
|
|
||||||
}
|
|
||||||
@ -13,9 +13,22 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- teastream-backend
|
- teastream-backend
|
||||||
|
|
||||||
|
redis:
|
||||||
|
container_name: teastream-redis
|
||||||
|
image: redis:5.0
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
networks:
|
||||||
|
- teastream-backend
|
||||||
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
|
redis_data:
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
teastream-backend:
|
teastream-backend:
|
||||||
|
|||||||
@ -1,16 +1,19 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
import eslint from '@eslint/js';
|
import eslint from '@eslint/js';
|
||||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
|
||||||
import globals from 'globals';
|
import globals from 'globals';
|
||||||
import tseslint from 'typescript-eslint';
|
import tseslint from 'typescript-eslint';
|
||||||
|
import stylistic from '@stylistic/eslint-plugin'
|
||||||
|
|
||||||
export default tseslint.config(
|
export default tseslint.config(
|
||||||
{
|
{
|
||||||
ignores: ['eslint.config.mjs'],
|
ignores: [
|
||||||
|
'node_modules',
|
||||||
|
'dist'
|
||||||
|
],
|
||||||
},
|
},
|
||||||
eslint.configs.recommended,
|
eslint.configs.recommended,
|
||||||
...tseslint.configs.recommendedTypeChecked,
|
...tseslint.configs.recommendedTypeChecked,
|
||||||
eslintPluginPrettierRecommended,
|
stylistic.configs.recommended,
|
||||||
{
|
{
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: {
|
globals: {
|
||||||
@ -23,6 +26,9 @@ export default tseslint.config(
|
|||||||
tsconfigRootDir: import.meta.dirname,
|
tsconfigRootDir: import.meta.dirname,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
plugins: {
|
||||||
|
'@stylistic': stylistic,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rules: {
|
rules: {
|
||||||
|
|||||||
@ -7,7 +7,6 @@
|
|||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nest build",
|
"build": "nest build",
|
||||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
||||||
"start": "nest start",
|
"start": "nest start",
|
||||||
"start:dev": "nest start --watch",
|
"start:dev": "nest start --watch",
|
||||||
"start:debug": "nest start --debug --watch",
|
"start:debug": "nest start --debug --watch",
|
||||||
@ -23,11 +22,22 @@
|
|||||||
"db:generate": "prisma generate"
|
"db:generate": "prisma generate"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@apollo/server": "^4.12.2",
|
||||||
|
"@nestjs/apollo": "^13.1.0",
|
||||||
"@nestjs/common": "^11.0.1",
|
"@nestjs/common": "^11.0.1",
|
||||||
|
"@nestjs/config": "^4.0.2",
|
||||||
"@nestjs/core": "^11.0.1",
|
"@nestjs/core": "^11.0.1",
|
||||||
|
"@nestjs/graphql": "^13.1.0",
|
||||||
"@nestjs/mapped-types": "*",
|
"@nestjs/mapped-types": "*",
|
||||||
"@nestjs/platform-express": "^11.0.1",
|
"@nestjs/platform-express": "^11.0.1",
|
||||||
"@prisma/client": "^6.10.1",
|
"@prisma/client": "^6.10.1",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.14.2",
|
||||||
|
"connect-redis": "^9.0.0",
|
||||||
|
"cookie-parser": "^1.4.7",
|
||||||
|
"express-session": "^1.18.1",
|
||||||
|
"graphql": "^16.11.0",
|
||||||
|
"ioredis": "^5.6.1",
|
||||||
"prisma": "^6.10.1",
|
"prisma": "^6.10.1",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1"
|
||||||
@ -38,19 +48,18 @@
|
|||||||
"@nestjs/cli": "^11.0.0",
|
"@nestjs/cli": "^11.0.0",
|
||||||
"@nestjs/schematics": "^11.0.0",
|
"@nestjs/schematics": "^11.0.0",
|
||||||
"@nestjs/testing": "^11.0.1",
|
"@nestjs/testing": "^11.0.1",
|
||||||
|
"@stylistic/eslint-plugin": "^5.0.0",
|
||||||
"@swc/cli": "^0.6.0",
|
"@swc/cli": "^0.6.0",
|
||||||
"@swc/core": "^1.10.7",
|
"@swc/core": "^1.10.7",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
"@types/cookie-parser": "^1.4.9",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
|
"@types/express-session": "^1.18.2",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^9.18.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
|
||||||
"eslint-plugin-prettier": "^5.2.2",
|
|
||||||
"globals": "^16.0.0",
|
"globals": "^16.0.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"prettier": "^3.4.2",
|
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"supertest": "^7.0.0",
|
"supertest": "^7.0.0",
|
||||||
"ts-jest": "^29.2.5",
|
"ts-jest": "^29.2.5",
|
||||||
|
|||||||
20
backend/src/core/config/graphql.config.ts
Normal file
20
backend/src/core/config/graphql.config.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { isDev } from '@/src/shared/util/is-dev.util'
|
||||||
|
import { ApolloDriverConfig } from '@nestjs/apollo'
|
||||||
|
import { ConfigService } from '@nestjs/config'
|
||||||
|
import { Request, Response } from 'express'
|
||||||
|
import * as path from 'node:path'
|
||||||
|
|
||||||
|
type ContextType = {
|
||||||
|
req: Request
|
||||||
|
res: Response
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getGraphQLConfig(configService: ConfigService): ApolloDriverConfig {
|
||||||
|
return {
|
||||||
|
playground: isDev(configService),
|
||||||
|
path: configService.getOrThrow('GRAPHQL_PREFIX'),
|
||||||
|
autoSchemaFile: path.join(process.cwd(), 'src', 'core', 'graphql', 'schema.gql'),
|
||||||
|
sortSchema: true,
|
||||||
|
context: ({ req, res }: ContextType) => ({ req, res }),
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,28 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { getGraphQLConfig } from '@/src/core/config/graphql.config'
|
||||||
import { PrismaModule } from './prisma/prisma.module';
|
import { AccountModule } from '@/src/module/auth/account/account.module'
|
||||||
|
import { IS_DEV } from '@/src/shared/util/is-dev.util'
|
||||||
|
import { ApolloDriver } from '@nestjs/apollo'
|
||||||
|
import { Module } from '@nestjs/common'
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config'
|
||||||
|
import { GraphQLModule } from '@nestjs/graphql'
|
||||||
|
import { PrismaModule } from './prisma/prisma.module'
|
||||||
|
import { RedisModule } from './redis/redis.module'
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [PrismaModule],
|
imports: [
|
||||||
|
ConfigModule.forRoot({
|
||||||
|
ignoreEnvFile: !IS_DEV,
|
||||||
|
isGlobal: true,
|
||||||
|
}),
|
||||||
|
GraphQLModule.forRootAsync({
|
||||||
|
driver: ApolloDriver,
|
||||||
|
imports: [ConfigModule],
|
||||||
|
useFactory: getGraphQLConfig,
|
||||||
|
inject: [ConfigService],
|
||||||
|
}),
|
||||||
|
PrismaModule,
|
||||||
|
RedisModule,
|
||||||
|
AccountModule,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class CoreModule {}
|
export class CoreModule {}
|
||||||
|
|||||||
24
backend/src/core/graphql/schema.gql
Normal file
24
backend/src/core/graphql/schema.gql
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# ------------------------------------------------------
|
||||||
|
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
|
||||||
|
# ------------------------------------------------------
|
||||||
|
|
||||||
|
"""
|
||||||
|
A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date-time format.
|
||||||
|
"""
|
||||||
|
scalar DateTime
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
findAllUsers: [UserModel!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserModel {
|
||||||
|
avatar: String
|
||||||
|
bio: String
|
||||||
|
createdAt: DateTime!
|
||||||
|
displayName: String!
|
||||||
|
email: String!
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
password: String!
|
||||||
|
updatedAt: DateTime!
|
||||||
|
}
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import { Global, Module } from '@nestjs/common';
|
import { Global, Module } from '@nestjs/common'
|
||||||
import { PrismaService } from './prisma.service';
|
import { PrismaService } from './prisma.service'
|
||||||
|
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
providers: [PrismaService],
|
providers: [PrismaService],
|
||||||
exports: [PrismaService]
|
exports: [PrismaService],
|
||||||
})
|
})
|
||||||
export class PrismaModule {}
|
export class PrismaModule {}
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { PrismaClient } from '@/prisma/generated';
|
import { PrismaClient } from '@/prisma/generated'
|
||||||
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
||||||
async onModuleInit() {
|
async onModuleInit() {
|
||||||
await this.$connect();
|
await this.$connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
async onModuleDestroy() {
|
async onModuleDestroy() {
|
||||||
await this.$disconnect();
|
await this.$disconnect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
backend/src/core/redis/redis.module.ts
Normal file
9
backend/src/core/redis/redis.module.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Global, Module } from '@nestjs/common'
|
||||||
|
import { RedisService } from './redis.service'
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
providers: [RedisService],
|
||||||
|
exports: [RedisService],
|
||||||
|
})
|
||||||
|
export class RedisModule {}
|
||||||
12
backend/src/core/redis/redis.service.ts
Normal file
12
backend/src/core/redis/redis.service.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Injectable } from '@nestjs/common'
|
||||||
|
import { ConfigService } from '@nestjs/config'
|
||||||
|
import Redis from 'ioredis'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RedisService extends Redis {
|
||||||
|
constructor(
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
) {
|
||||||
|
super(configService.getOrThrow('REDIS_URI'))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,50 @@
|
|||||||
import { CoreModule } from '@/src/core/core.module';
|
import { RedisService } from '@/src/core/redis/redis.service'
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { ms, StringValue } from '@/src/shared/util/ms.util'
|
||||||
|
import { parseBoolean } from '@/src/shared/util/parse-boolean.util'
|
||||||
|
import { ValidationPipe } from '@nestjs/common'
|
||||||
|
import { ConfigService } from '@nestjs/config'
|
||||||
|
import { NestFactory } from '@nestjs/core'
|
||||||
|
import { RedisStore } from 'connect-redis'
|
||||||
|
import * as cookieParser from 'cookie-parser'
|
||||||
|
import { CoreModule } from '@/src/core/core.module'
|
||||||
|
import * as session from 'express-session'
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(CoreModule);
|
const app = await NestFactory.create(CoreModule)
|
||||||
await app.listen(process.env.PORT ?? 3000);
|
|
||||||
|
const config = app.get(ConfigService)
|
||||||
|
const redis = app.get(RedisService)
|
||||||
|
|
||||||
|
app.use(cookieParser(config.getOrThrow<string>('COOKIE_SECRET')))
|
||||||
|
|
||||||
|
app.useGlobalPipes(new ValidationPipe({
|
||||||
|
transform: true,
|
||||||
|
}))
|
||||||
|
|
||||||
|
app.use(session({
|
||||||
|
secret: config.getOrThrow<string>('SESSION_SECRET'),
|
||||||
|
name: config.getOrThrow<string>('SESSION_NAME'),
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: false,
|
||||||
|
cookie: {
|
||||||
|
domain: config.getOrThrow<string>('SESSION_DOMAIN'),
|
||||||
|
maxAge: ms(config.getOrThrow<StringValue>('SESSION_MAX_AGE')),
|
||||||
|
httpOnly: parseBoolean(config.getOrThrow('SESSION_HTTP_ONLY')),
|
||||||
|
secure: parseBoolean(config.getOrThrow('SESSION_SECURE')),
|
||||||
|
sameSite: 'lax',
|
||||||
|
},
|
||||||
|
store: new RedisStore({
|
||||||
|
client: redis,
|
||||||
|
prefix: config.getOrThrow('SESSION_FOLDER'),
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
|
||||||
|
app.enableCors({
|
||||||
|
origin: config.getOrThrow<string>('ALLOWED_ORIGIN'),
|
||||||
|
credential: true,
|
||||||
|
exposedHeaders: ['set-cookie'],
|
||||||
|
})
|
||||||
|
|
||||||
|
await app.listen(config.getOrThrow('APPLICATION_PORT'))
|
||||||
}
|
}
|
||||||
bootstrap();
|
void bootstrap()
|
||||||
|
|||||||
8
backend/src/module/auth/account/account.module.ts
Normal file
8
backend/src/module/auth/account/account.module.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Module } from '@nestjs/common'
|
||||||
|
import { AccountService } from './account.service'
|
||||||
|
import { AccountResolver } from './account.resolver'
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [AccountResolver, AccountService],
|
||||||
|
})
|
||||||
|
export class AccountModule {}
|
||||||
13
backend/src/module/auth/account/account.resolver.ts
Normal file
13
backend/src/module/auth/account/account.resolver.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Query, Resolver } from '@nestjs/graphql'
|
||||||
|
import { AccountService } from './account.service'
|
||||||
|
import { UserModel } from './models/user.model'
|
||||||
|
|
||||||
|
@Resolver('Account')
|
||||||
|
export class AccountResolver {
|
||||||
|
constructor(private readonly accountService: AccountService) {}
|
||||||
|
|
||||||
|
@Query(() => [UserModel], { name: 'findAllUsers' })
|
||||||
|
public async findAll() {
|
||||||
|
return await this.accountService.findAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
15
backend/src/module/auth/account/account.service.ts
Normal file
15
backend/src/module/auth/account/account.service.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { PrismaService } from '@/src/core/prisma/prisma.service'
|
||||||
|
import { Injectable } from '@nestjs/common'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AccountService {
|
||||||
|
constructor(
|
||||||
|
private readonly prismaService: PrismaService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findAll() {
|
||||||
|
const users = await this.prismaService.user.findMany({})
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
}
|
||||||
31
backend/src/module/auth/account/models/user.model.ts
Normal file
31
backend/src/module/auth/account/models/user.model.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Field, ID, ObjectType } from '@nestjs/graphql'
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class UserModel {
|
||||||
|
@Field(() => ID)
|
||||||
|
id: string
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
email: string
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
password: string
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
name: string
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
displayName: string
|
||||||
|
|
||||||
|
@Field(() => String, { nullable: true })
|
||||||
|
avatar: string
|
||||||
|
|
||||||
|
@Field(() => String, { nullable: true })
|
||||||
|
bio: string
|
||||||
|
|
||||||
|
@Field(() => Date)
|
||||||
|
createdAt: Date
|
||||||
|
|
||||||
|
@Field(() => Date)
|
||||||
|
updatedAt: Date
|
||||||
|
}
|
||||||
11
backend/src/shared/util/is-dev.util.ts
Normal file
11
backend/src/shared/util/is-dev.util.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { ConfigService } from '@nestjs/config'
|
||||||
|
import * as dotenv from 'dotenv'
|
||||||
|
import * as process from 'node:process'
|
||||||
|
|
||||||
|
dotenv.config()
|
||||||
|
|
||||||
|
export function isDev(configService: ConfigService) {
|
||||||
|
return configService.getOrThrow('NODE_ENV') === 'development'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IS_DEV = process.env.NODE_ENV === 'development'
|
||||||
112
backend/src/shared/util/ms.util.ts
Normal file
112
backend/src/shared/util/ms.util.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
const s = 1000
|
||||||
|
const m = s * 60
|
||||||
|
const h = m * 60
|
||||||
|
const d = h * 24
|
||||||
|
const w = d * 7
|
||||||
|
const y = d * 365.25
|
||||||
|
|
||||||
|
type Unit
|
||||||
|
= | 'Years'
|
||||||
|
| 'Year'
|
||||||
|
| 'Yrs'
|
||||||
|
| 'Yr'
|
||||||
|
| 'Y'
|
||||||
|
| 'Weeks'
|
||||||
|
| 'Week'
|
||||||
|
| 'W'
|
||||||
|
| 'Days'
|
||||||
|
| 'Day'
|
||||||
|
| 'D'
|
||||||
|
| 'Hours'
|
||||||
|
| 'Hour'
|
||||||
|
| 'Hrs'
|
||||||
|
| 'Hr'
|
||||||
|
| 'H'
|
||||||
|
| 'Minutes'
|
||||||
|
| 'Minute'
|
||||||
|
| 'Mins'
|
||||||
|
| 'Min'
|
||||||
|
| 'M'
|
||||||
|
| 'Seconds'
|
||||||
|
| 'Second'
|
||||||
|
| 'Secs'
|
||||||
|
| 'Sec'
|
||||||
|
| 's'
|
||||||
|
| 'Milliseconds'
|
||||||
|
| 'Millisecond'
|
||||||
|
| 'Msecs'
|
||||||
|
| 'Msec'
|
||||||
|
| 'Ms'
|
||||||
|
|
||||||
|
type UnitAnyCase = Unit | Uppercase<Unit> | Lowercase<Unit>
|
||||||
|
|
||||||
|
export type StringValue
|
||||||
|
= | `${number}`
|
||||||
|
| `${number}${UnitAnyCase}`
|
||||||
|
| `${number} ${UnitAnyCase}`
|
||||||
|
|
||||||
|
export function ms(str: StringValue): number {
|
||||||
|
if (typeof str !== 'string' || str.length === 0 || str.length > 100) {
|
||||||
|
throw new Error(
|
||||||
|
'Value provided to ms() must be a string with length between 1 and 99.',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const match
|
||||||
|
= /^(?<value>-?(?:\d+)?\.?\d+) *(?<type>milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(
|
||||||
|
str,
|
||||||
|
)
|
||||||
|
|
||||||
|
const groups = match?.groups as { value: string, type?: string } | undefined
|
||||||
|
if (!groups) {
|
||||||
|
return NaN
|
||||||
|
}
|
||||||
|
const n = parseFloat(groups.value)
|
||||||
|
const type = (groups.type || 'ms').toLowerCase() as Lowercase<Unit>
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'years':
|
||||||
|
case 'year':
|
||||||
|
case 'yrs':
|
||||||
|
case 'yr':
|
||||||
|
case 'y':
|
||||||
|
return n * y
|
||||||
|
case 'weeks':
|
||||||
|
case 'week':
|
||||||
|
case 'w':
|
||||||
|
return n * w
|
||||||
|
case 'days':
|
||||||
|
case 'day':
|
||||||
|
case 'd':
|
||||||
|
return n * d
|
||||||
|
case 'hours':
|
||||||
|
case 'hour':
|
||||||
|
case 'hrs':
|
||||||
|
case 'hr':
|
||||||
|
case 'h':
|
||||||
|
return n * h
|
||||||
|
case 'minutes':
|
||||||
|
case 'minute':
|
||||||
|
case 'mins':
|
||||||
|
case 'min':
|
||||||
|
case 'm':
|
||||||
|
return n * m
|
||||||
|
case 'seconds':
|
||||||
|
case 'second':
|
||||||
|
case 'secs':
|
||||||
|
case 'sec':
|
||||||
|
case 's':
|
||||||
|
return n * s
|
||||||
|
case 'milliseconds':
|
||||||
|
case 'millisecond':
|
||||||
|
case 'msecs':
|
||||||
|
case 'msec':
|
||||||
|
case 'ms':
|
||||||
|
return n
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
|
`Ошибка: единица времени ${type} была распознана, но не существует соответствующего случая. Пожалуйста, проверьте введенные данные.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
19
backend/src/shared/util/parse-boolean.util.ts
Normal file
19
backend/src/shared/util/parse-boolean.util.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export function parseBoolean(value: string): boolean {
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const lowerValue = value.trim().toLowerCase()
|
||||||
|
if (lowerValue === 'true') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (lowerValue === 'false') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`Не удалось преобразовать значение "${value}" в логическое значение.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
1028
backend/yarn.lock
1028
backend/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user