feat: 支持一键发布服务端

This commit is contained in:
richarjiang
2026-04-04 15:39:26 +08:00
parent 2b3b636c54
commit 982e569fa3
45 changed files with 852 additions and 34 deletions

View File

@@ -1,6 +1,16 @@
import type { ApiResponse, PaginatedData } from '@mp-pilates/shared'
const BASE_URL = 'http://localhost:3000/api'
const BASE_URL = (() => {
try {
const { miniProgram } = uni.getAccountInfoSync()
if (miniProgram.envVersion !== 'develop') {
return 'https://focus.richarjiang.com/api'
}
} catch {
// 非小程序环境,使用开发地址
}
return 'http://localhost:3000/api'
})()
interface RequestOptions {
readonly url: string

View File

@@ -1,19 +0,0 @@
# Database
DATABASE_URL=postgresql://user:pass@localhost:5432/mp_pilates
# JWT
JWT_SECRET=change-me-to-a-secure-random-string
# WeChat Mini Program
WX_APPID=your-appid
WX_SECRET=your-secret
# WeChat Pay
WX_MCH_ID=your-mch-id
WX_MCH_KEY=your-mch-key
WX_MCH_SERIAL_NO=your-serial-no
WX_MCH_CERT_PATH=./certs/apiclient_cert.pem
WX_MCH_KEY_PATH=./certs/apiclient_key.pem
# Server
PORT=3000

View File

@@ -3,6 +3,7 @@
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
"deleteOutDir": true,
"tsConfigPath": "tsconfig.build.json"
}
}

View File

@@ -27,10 +27,13 @@
"@prisma/client": "^5.19.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"nest-winston": "^1.10.2",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
"rxjs": "^7.8.1",
"winston": "^3.19.0",
"winston-daily-rotate-file": "^5.0.0"
},
"devDependencies": {
"@nestjs/cli": "^10.4.0",
@@ -48,13 +51,21 @@
"typescript": "^5.4.0"
},
"jest": {
"moduleFileExtensions": ["js", "json", "ts"],
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": ["**/*.(t|j)s", "!**/*.module.ts", "!main.ts"],
"collectCoverageFrom": [
"**/*.(t|j)s",
"!**/*.module.ts",
"!main.ts"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node",
"moduleNameMapper": {

View File

@@ -3,7 +3,7 @@ generator client {
}
datasource db {
provider = "postgresql"
provider = "mysql"
url = env("DATABASE_URL")
}

View File

@@ -36,7 +36,7 @@ export class AuthController {
@Body() bindPhoneDto: BindPhoneDto,
): Promise<User> {
return this.authService.bindPhone(
req.user.userId,
req.user.sub,
bindPhoneDto.encryptedData,
bindPhoneDto.iv,
)

View File

@@ -6,7 +6,7 @@ import { UserRole } from '@mp-pilates/shared'
import { JwtPayload } from './auth.service'
export interface AuthenticatedUser {
userId: string
sub: string
role: UserRole
}
@@ -20,9 +20,9 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
})
}
validate(payload: JwtPayload): AuthenticatedUser {
validate(payload: JwtPayload): { sub: string; role: UserRole } {
return {
userId: payload.sub,
sub: payload.sub,
role: payload.role,
}
}

View File

@@ -0,0 +1,68 @@
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
Logger,
} from '@nestjs/common'
import type { Request, Response } from 'express'
import type { ApiResponse } from '@mp-pilates/shared'
@Catch()
export class ApiExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(ApiExceptionFilter.name)
catch(exception: unknown, host: ArgumentsHost): void {
const ctx = host.switchToHttp()
const request = ctx.getRequest<Request>()
const response = ctx.getResponse<Response>()
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR
const message =
exception instanceof HttpException
? this.extractMessage(exception)
: '服务器内部错误'
// Log all server errors (5xx) with full stack; log 4xx at warn level
if (status >= 500) {
this.logger.error(
`${request.method} ${request.originalUrl}${String(status)} ${message}`,
exception instanceof Error ? exception.stack : undefined,
)
} else if (status >= 400) {
this.logger.warn(
`${request.method} ${request.originalUrl}${String(status)} ${message}`,
)
}
const body: ApiResponse<null> = {
success: false,
data: null,
message,
}
response.status(status).json(body)
}
private extractMessage(exception: HttpException): string {
const response = exception.getResponse()
if (typeof response === 'string') {
return response
}
if (typeof response === 'object' && response !== null) {
const res = response as Record<string, unknown>
if (typeof res.message === 'string') {
return res.message
}
if (Array.isArray(res.message)) {
return res.message.join('; ')
}
}
return exception.message
}
}

View File

@@ -0,0 +1,26 @@
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common'
import { Observable, map } from 'rxjs'
import type { ApiResponse } from '@mp-pilates/shared'
@Injectable()
export class ApiResponseInterceptor<T>
implements NestInterceptor<T, ApiResponse<T>>
{
intercept(
_context: ExecutionContext,
next: CallHandler<T>,
): Observable<ApiResponse<T>> {
return next.handle().pipe(
map((data) => ({
success: true,
data: data ?? null,
message: null,
})),
)
}
}

View File

@@ -0,0 +1,76 @@
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
Logger,
} from '@nestjs/common'
import { Observable, tap } from 'rxjs'
import type { Request, Response } from 'express'
/** Fields stripped from logged request bodies to avoid leaking secrets. */
const SENSITIVE_FIELDS: ReadonlySet<string> = new Set([
'password',
'token',
'secret',
'code',
'sessionKey',
'encryptedData',
'iv',
])
function sanitizeBody(
body: Record<string, unknown> | undefined,
): Record<string, unknown> | undefined {
if (!body || typeof body !== 'object') return undefined
return Object.fromEntries(
Object.entries(body).map(([key, value]) =>
SENSITIVE_FIELDS.has(key) ? [key, '***'] : [key, value],
),
)
}
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger('HTTP')
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
const req = context.switchToHttp().getRequest<Request>()
const { method, originalUrl } = req
const start = Date.now()
return next.handle().pipe(
tap({
next: () => {
const res = context.switchToHttp().getResponse<Response>()
const duration = Date.now() - start
const bodyLog = this.formatBody(method, req.body as Record<string, unknown>)
this.logger.log(
`${method} ${originalUrl}${String(res.statusCode)} (${String(duration)}ms)${bodyLog}`,
)
},
error: (err: unknown) => {
const duration = Date.now() - start
const status =
err instanceof Object && 'getStatus' in err
? String((err as { getStatus: () => number }).getStatus())
: '500'
const bodyLog = this.formatBody(method, req.body as Record<string, unknown>)
this.logger.error(
`${method} ${originalUrl}${status} (${String(duration)}ms)${bodyLog}`,
)
},
}),
)
}
private formatBody(
method: string,
body: Record<string, unknown> | undefined,
): string {
if (!['POST', 'PUT', 'PATCH'].includes(method)) return ''
const sanitized = sanitizeBody(body)
if (!sanitized || Object.keys(sanitized).length === 0) return ''
return ` body=${JSON.stringify(sanitized)}`
}
}

View File

@@ -0,0 +1,70 @@
import * as winston from 'winston'
import 'winston-daily-rotate-file'
import type { WinstonModuleOptions } from 'nest-winston'
const { combine, timestamp, printf, colorize, errors } = winston.format
/** Shared log line format: `[timestamp] [LEVEL] [context] message` */
const logPrint = printf(({ timestamp, level, context, message, stack }) => {
const ctx = context ? `[${context}] ` : ''
const msg = stack ?? message
return `${timestamp as string} [${level}] ${ctx}${msg as string}`
})
function buildTransports(): winston.transport[] {
const transports: winston.transport[] = []
const isProduction = process.env.NODE_ENV === 'production'
// Console — always enabled; colorized in dev, plain in prod
transports.push(
new winston.transports.Console({
format: combine(
...(isProduction ? [] : [colorize({ all: true })]),
timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
errors({ stack: true }),
logPrint,
),
}),
)
// File transports — always enabled so logs persist even in dev
// App log: all levels, 14-day retention, 20 MB max per file
transports.push(
new winston.transports.DailyRotateFile({
dirname: 'logs',
filename: 'app-%DATE%.log',
datePattern: 'YYYY-MM-DD',
maxSize: '20m',
maxFiles: '14d',
format: combine(
timestamp({ format: 'YYYY-MM-DDTHH:mm:ss.SSSZ' }),
errors({ stack: true }),
logPrint,
),
}),
)
// Error log: error-level only, 30-day retention
transports.push(
new winston.transports.DailyRotateFile({
dirname: 'logs',
filename: 'error-%DATE%.log',
datePattern: 'YYYY-MM-DD',
level: 'error',
maxSize: '20m',
maxFiles: '30d',
format: combine(
timestamp({ format: 'YYYY-MM-DDTHH:mm:ss.SSSZ' }),
errors({ stack: true }),
logPrint,
),
}),
)
return transports
}
export const loggerConfig: WinstonModuleOptions = {
transports: buildTransports(),
}

View File

@@ -1,10 +1,17 @@
import { NestFactory } from '@nestjs/core'
import { ValidationPipe } from '@nestjs/common'
import { Logger, ValidationPipe } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { WinstonModule } from 'nest-winston'
import { AppModule } from './app.module'
import { loggerConfig } from './common/logger/logger.config'
import { ApiResponseInterceptor } from './common/interceptors/api-response.interceptor'
import { LoggingInterceptor } from './common/interceptors/logging.interceptor'
import { ApiExceptionFilter } from './common/filters/api-exception.filter'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
const logger = WinstonModule.createLogger(loggerConfig)
const app = await NestFactory.create(AppModule, { logger })
const configService = app.get(ConfigService)
app.setGlobalPrefix('api')
@@ -15,10 +22,15 @@ async function bootstrap() {
transform: true,
}),
)
app.useGlobalInterceptors(
new LoggingInterceptor(),
new ApiResponseInterceptor(),
)
app.useGlobalFilters(new ApiExceptionFilter())
app.enableCors()
const port = configService.get<number>('PORT', 3000)
await app.listen(port)
console.log(`Server running on http://localhost:${port}`)
new Logger('Bootstrap').log(`Server running on http://localhost:${port}`)
}
bootstrap()

View File

@@ -1,7 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "dist"
"outDir": "dist",
"rootDir": "src",
"paths": {},
"incremental": false
},
"exclude": ["node_modules", "dist", "test", "**/*.spec.ts"]
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,20 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WEEKDAY_LABELS = exports.DATE_SELECTOR_DAYS = exports.TIME_PERIODS = exports.SLOT_GENERATION_DAYS = exports.DEFAULT_SLOT_CAPACITY = exports.DEFAULT_CANCEL_HOURS_LIMIT = void 0;
/** 默认免费取消截止小时数 */
exports.DEFAULT_CANCEL_HOURS_LIMIT = 2;
/** 默认时段容量(私教 = 1 */
exports.DEFAULT_SLOT_CAPACITY = 1;
/** 自动生成时段的天数范围 */
exports.SLOT_GENERATION_DAYS = 14;
/** 时段筛选区间 */
exports.TIME_PERIODS = {
MORNING: { label: '上午', start: '06:00', end: '12:00' },
AFTERNOON: { label: '下午', start: '12:00', end: '18:00' },
EVENING: { label: '晚上', start: '18:00', end: '22:00' },
};
/** 日期选择器展示天数 */
exports.DATE_SELECTOR_DAYS = 7;
/** 星期映射 */
exports.WEEKDAY_LABELS = ['', '周一', '周二', '周三', '周四', '周五', '周六', '周日'];
//# sourceMappingURL=constants.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"constants.js","sourceRoot":"","sources":["constants.ts"],"names":[],"mappings":";;;AAAA,kBAAkB;AACL,QAAA,0BAA0B,GAAG,CAAC,CAAA;AAE3C,qBAAqB;AACR,QAAA,qBAAqB,GAAG,CAAC,CAAA;AAEtC,kBAAkB;AACL,QAAA,oBAAoB,GAAG,EAAE,CAAA;AAEtC,aAAa;AACA,QAAA,YAAY,GAAG;IAC1B,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE;IACtD,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE;IACxD,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE;CAC9C,CAAA;AAEV,gBAAgB;AACH,QAAA,kBAAkB,GAAG,CAAC,CAAA;AAEnC,WAAW;AACE,QAAA,cAAc,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAA"}

View File

@@ -0,0 +1,51 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OrderStatus = exports.BookingStatus = exports.TimeSlotSource = exports.TimeSlotStatus = exports.MembershipStatus = exports.CardTypeCategory = exports.UserRole = void 0;
// ===== User =====
var UserRole;
(function (UserRole) {
UserRole["MEMBER"] = "MEMBER";
UserRole["ADMIN"] = "ADMIN";
})(UserRole || (exports.UserRole = UserRole = {}));
// ===== CardType =====
var CardTypeCategory;
(function (CardTypeCategory) {
CardTypeCategory["TIMES"] = "TIMES";
CardTypeCategory["DURATION"] = "DURATION";
CardTypeCategory["TRIAL"] = "TRIAL";
})(CardTypeCategory || (exports.CardTypeCategory = CardTypeCategory = {}));
// ===== Membership =====
var MembershipStatus;
(function (MembershipStatus) {
MembershipStatus["ACTIVE"] = "ACTIVE";
MembershipStatus["EXPIRED"] = "EXPIRED";
MembershipStatus["USED_UP"] = "USED_UP";
})(MembershipStatus || (exports.MembershipStatus = MembershipStatus = {}));
// ===== TimeSlot =====
var TimeSlotStatus;
(function (TimeSlotStatus) {
TimeSlotStatus["OPEN"] = "OPEN";
TimeSlotStatus["FULL"] = "FULL";
TimeSlotStatus["CLOSED"] = "CLOSED";
})(TimeSlotStatus || (exports.TimeSlotStatus = TimeSlotStatus = {}));
var TimeSlotSource;
(function (TimeSlotSource) {
TimeSlotSource["TEMPLATE"] = "TEMPLATE";
TimeSlotSource["MANUAL"] = "MANUAL";
})(TimeSlotSource || (exports.TimeSlotSource = TimeSlotSource = {}));
// ===== Booking =====
var BookingStatus;
(function (BookingStatus) {
BookingStatus["CONFIRMED"] = "CONFIRMED";
BookingStatus["CANCELLED"] = "CANCELLED";
BookingStatus["COMPLETED"] = "COMPLETED";
BookingStatus["NO_SHOW"] = "NO_SHOW";
})(BookingStatus || (exports.BookingStatus = BookingStatus = {}));
// ===== Order =====
var OrderStatus;
(function (OrderStatus) {
OrderStatus["PENDING"] = "PENDING";
OrderStatus["PAID"] = "PAID";
OrderStatus["REFUNDED"] = "REFUNDED";
})(OrderStatus || (exports.OrderStatus = OrderStatus = {}));
//# sourceMappingURL=enums.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"enums.js","sourceRoot":"","sources":["enums.ts"],"names":[],"mappings":";;;AAAA,mBAAmB;AACnB,IAAY,QAGX;AAHD,WAAY,QAAQ;IAClB,6BAAiB,CAAA;IACjB,2BAAe,CAAA;AACjB,CAAC,EAHW,QAAQ,wBAAR,QAAQ,QAGnB;AAED,uBAAuB;AACvB,IAAY,gBAIX;AAJD,WAAY,gBAAgB;IAC1B,mCAAe,CAAA;IACf,yCAAqB,CAAA;IACrB,mCAAe,CAAA;AACjB,CAAC,EAJW,gBAAgB,gCAAhB,gBAAgB,QAI3B;AAED,yBAAyB;AACzB,IAAY,gBAIX;AAJD,WAAY,gBAAgB;IAC1B,qCAAiB,CAAA;IACjB,uCAAmB,CAAA;IACnB,uCAAmB,CAAA;AACrB,CAAC,EAJW,gBAAgB,gCAAhB,gBAAgB,QAI3B;AAED,uBAAuB;AACvB,IAAY,cAIX;AAJD,WAAY,cAAc;IACxB,+BAAa,CAAA;IACb,+BAAa,CAAA;IACb,mCAAiB,CAAA;AACnB,CAAC,EAJW,cAAc,8BAAd,cAAc,QAIzB;AAED,IAAY,cAGX;AAHD,WAAY,cAAc;IACxB,uCAAqB,CAAA;IACrB,mCAAiB,CAAA;AACnB,CAAC,EAHW,cAAc,8BAAd,cAAc,QAGzB;AAED,sBAAsB;AACtB,IAAY,aAKX;AALD,WAAY,aAAa;IACvB,wCAAuB,CAAA;IACvB,wCAAuB,CAAA;IACvB,wCAAuB,CAAA;IACvB,oCAAmB,CAAA;AACrB,CAAC,EALW,aAAa,6BAAb,aAAa,QAKxB;AAED,oBAAoB;AACpB,IAAY,WAIX;AAJD,WAAY,WAAW;IACrB,kCAAmB,CAAA;IACnB,4BAAa,CAAA;IACb,oCAAqB,CAAA;AACvB,CAAC,EAJW,WAAW,2BAAX,WAAW,QAItB"}

View File

@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WEEKDAY_LABELS = exports.DATE_SELECTOR_DAYS = exports.TIME_PERIODS = exports.SLOT_GENERATION_DAYS = exports.DEFAULT_SLOT_CAPACITY = exports.DEFAULT_CANCEL_HOURS_LIMIT = exports.OrderStatus = exports.BookingStatus = exports.TimeSlotSource = exports.TimeSlotStatus = exports.MembershipStatus = exports.CardTypeCategory = exports.UserRole = void 0;
// Enums
var enums_1 = require("./enums");
Object.defineProperty(exports, "UserRole", { enumerable: true, get: function () { return enums_1.UserRole; } });
Object.defineProperty(exports, "CardTypeCategory", { enumerable: true, get: function () { return enums_1.CardTypeCategory; } });
Object.defineProperty(exports, "MembershipStatus", { enumerable: true, get: function () { return enums_1.MembershipStatus; } });
Object.defineProperty(exports, "TimeSlotStatus", { enumerable: true, get: function () { return enums_1.TimeSlotStatus; } });
Object.defineProperty(exports, "TimeSlotSource", { enumerable: true, get: function () { return enums_1.TimeSlotSource; } });
Object.defineProperty(exports, "BookingStatus", { enumerable: true, get: function () { return enums_1.BookingStatus; } });
Object.defineProperty(exports, "OrderStatus", { enumerable: true, get: function () { return enums_1.OrderStatus; } });
// Constants
var constants_1 = require("./constants");
Object.defineProperty(exports, "DEFAULT_CANCEL_HOURS_LIMIT", { enumerable: true, get: function () { return constants_1.DEFAULT_CANCEL_HOURS_LIMIT; } });
Object.defineProperty(exports, "DEFAULT_SLOT_CAPACITY", { enumerable: true, get: function () { return constants_1.DEFAULT_SLOT_CAPACITY; } });
Object.defineProperty(exports, "SLOT_GENERATION_DAYS", { enumerable: true, get: function () { return constants_1.SLOT_GENERATION_DAYS; } });
Object.defineProperty(exports, "TIME_PERIODS", { enumerable: true, get: function () { return constants_1.TIME_PERIODS; } });
Object.defineProperty(exports, "DATE_SELECTOR_DAYS", { enumerable: true, get: function () { return constants_1.DATE_SELECTOR_DAYS; } });
Object.defineProperty(exports, "WEEKDAY_LABELS", { enumerable: true, get: function () { return constants_1.WEEKDAY_LABELS; } });
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;AAAA,QAAQ;AACR,iCAQgB;AAPd,iGAAA,QAAQ,OAAA;AACR,yGAAA,gBAAgB,OAAA;AAChB,yGAAA,gBAAgB,OAAA;AAChB,uGAAA,cAAc,OAAA;AACd,uGAAA,cAAc,OAAA;AACd,sGAAA,aAAa,OAAA;AACb,oGAAA,WAAW,OAAA;AAGb,YAAY;AACZ,yCAOoB;AANlB,uHAAA,0BAA0B,OAAA;AAC1B,kHAAA,qBAAqB,OAAA;AACrB,iHAAA,oBAAoB,OAAA;AACpB,yGAAA,YAAY,OAAA;AACZ,+GAAA,kBAAkB,OAAA;AAClB,2GAAA,cAAc,OAAA"}

View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=api.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"api.js","sourceRoot":"","sources":["api.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=booking.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"booking.js","sourceRoot":"","sources":["booking.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=card-type.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"card-type.js","sourceRoot":"","sources":["card-type.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=membership.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"membership.js","sourceRoot":"","sources":["membership.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=order.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"order.js","sourceRoot":"","sources":["order.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=studio.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"studio.js","sourceRoot":"","sources":["studio.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=time-slot.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"time-slot.js","sourceRoot":"","sources":["time-slot.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=user.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"user.js","sourceRoot":"","sources":["user.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=week-template.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"week-template.js","sourceRoot":"","sources":["week-template.ts"],"names":[],"mappings":""}

View File

@@ -3,6 +3,8 @@
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"module": "CommonJS",
"moduleResolution": "node",
"declaration": true,
"declarationMap": true
},