feat(api-task): implement task crud with tag filters
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { ConfigModule } from "@nestjs/config";
|
||||
import { AuthModule } from "./auth/auth.module";
|
||||
import { PrismaModule } from "./prisma/prisma.module";
|
||||
import { TaskModule } from "./task/task.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -8,7 +10,9 @@ import { AuthModule } from "./auth/auth.module";
|
||||
isGlobal: true,
|
||||
envFilePath: ".env"
|
||||
}),
|
||||
AuthModule
|
||||
PrismaModule,
|
||||
AuthModule,
|
||||
TaskModule
|
||||
]
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Global, Module } from "@nestjs/common";
|
||||
import { PrismaService } from "./prisma.service";
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [PrismaService],
|
||||
exports: [PrismaService]
|
||||
})
|
||||
export class PrismaModule {}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Injectable, OnModuleDestroy, OnModuleInit } from "@nestjs/common";
|
||||
import { PrismaClient } from "../../generated/prisma/client";
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
||||
async onModuleInit(): Promise<void> {
|
||||
await this.$connect();
|
||||
}
|
||||
|
||||
async onModuleDestroy(): Promise<void> {
|
||||
await this.$disconnect();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Transform } from "class-transformer";
|
||||
import {
|
||||
IsArray,
|
||||
IsDateString,
|
||||
IsEnum,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MaxLength,
|
||||
MinLength
|
||||
} from "class-validator";
|
||||
import { TaskPriority, TaskStatus } from "../../../generated/prisma/client";
|
||||
|
||||
function normalizeString(value: unknown): unknown {
|
||||
if (typeof value !== "string") {
|
||||
return value;
|
||||
}
|
||||
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
export class CreateTaskDto {
|
||||
@Transform(({ value }) => normalizeString(value))
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@MaxLength(120)
|
||||
title!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
contentJson?: Record<string, unknown>;
|
||||
|
||||
@Transform(({ value }) => normalizeString(value))
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(20000)
|
||||
contentText?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(TaskPriority)
|
||||
priority?: TaskPriority;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(TaskStatus)
|
||||
status?: TaskStatus;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
ddl?: string;
|
||||
|
||||
@Transform(({ value }) => {
|
||||
if (!Array.isArray(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return value.map((item) => normalizeString(item));
|
||||
})
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
@MinLength(1, { each: true })
|
||||
@MaxLength(30, { each: true })
|
||||
tagNames?: string[];
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { Transform, Type } from "class-transformer";
|
||||
import { IsArray, IsEnum, IsInt, IsOptional, IsString, Max, MaxLength, Min } from "class-validator";
|
||||
import { TaskPriority, TaskStatus } from "../../../generated/prisma/client";
|
||||
|
||||
export enum TaskSortBy {
|
||||
CREATED_AT = "createdAt",
|
||||
UPDATED_AT = "updatedAt",
|
||||
DDL = "ddl"
|
||||
}
|
||||
|
||||
export enum TaskSortOrder {
|
||||
ASC = "asc",
|
||||
DESC = "desc"
|
||||
}
|
||||
|
||||
function normalizeString(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const normalized = value.trim();
|
||||
if (!normalized) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export class ListTasksQueryDto {
|
||||
@IsOptional()
|
||||
@IsEnum(TaskStatus)
|
||||
status?: TaskStatus;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(TaskPriority)
|
||||
priority?: TaskPriority;
|
||||
|
||||
@Transform(({ value }) => {
|
||||
if (value === undefined || value === null || value === "") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
const normalized = value
|
||||
.map((item) => normalizeString(item))
|
||||
.filter((item): item is string => item !== undefined);
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
const normalized = value
|
||||
.split(",")
|
||||
.map((item) => normalizeString(item))
|
||||
.filter((item): item is string => item !== undefined);
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
})
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
@MaxLength(30, { each: true })
|
||||
tags?: string[];
|
||||
|
||||
@Transform(({ value }) => normalizeString(value))
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
keyword?: string;
|
||||
|
||||
@Type(() => Number)
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
page?: number;
|
||||
|
||||
@Type(() => Number)
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(100)
|
||||
pageSize?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(TaskSortBy)
|
||||
sortBy?: TaskSortBy;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(TaskSortOrder)
|
||||
sortOrder?: TaskSortOrder;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Transform } from "class-transformer";
|
||||
import {
|
||||
IsArray,
|
||||
IsDateString,
|
||||
IsEnum,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MaxLength,
|
||||
MinLength
|
||||
} from "class-validator";
|
||||
import { TaskPriority, TaskStatus } from "../../../generated/prisma/client";
|
||||
|
||||
function normalizeString(value: unknown): unknown {
|
||||
if (typeof value !== "string") {
|
||||
return value;
|
||||
}
|
||||
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
export class UpdateTaskDto {
|
||||
@Transform(({ value }) => normalizeString(value))
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@MaxLength(120)
|
||||
title?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
contentJson?: Record<string, unknown>;
|
||||
|
||||
@Transform(({ value }) => normalizeString(value))
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(20000)
|
||||
contentText?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(TaskPriority)
|
||||
priority?: TaskPriority;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(TaskStatus)
|
||||
status?: TaskStatus;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
ddl?: string;
|
||||
|
||||
@Transform(({ value }) => {
|
||||
if (!Array.isArray(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return value.map((item) => normalizeString(item));
|
||||
})
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
@MinLength(1, { each: true })
|
||||
@MaxLength(30, { each: true })
|
||||
tagNames?: string[];
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Headers,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Query,
|
||||
UnauthorizedException
|
||||
} from "@nestjs/common";
|
||||
import { CreateTaskDto } from "./dto/create-task.dto";
|
||||
import { ListTasksQueryDto } from "./dto/list-tasks-query.dto";
|
||||
import { UpdateTaskDto } from "./dto/update-task.dto";
|
||||
import { ListTasksResponse, TaskResponse, TaskService } from "./task.service";
|
||||
|
||||
@Controller("tasks")
|
||||
export class TaskController {
|
||||
constructor(private readonly taskService: TaskService) {}
|
||||
|
||||
@Get()
|
||||
async listTasks(
|
||||
@Headers("x-user-id") userIdHeader: string | string[] | undefined,
|
||||
@Query() query: ListTasksQueryDto
|
||||
): Promise<ListTasksResponse> {
|
||||
return this.taskService.listTasks(this.resolveUserId(userIdHeader), query);
|
||||
}
|
||||
|
||||
@Get(":taskId")
|
||||
async getTaskById(
|
||||
@Headers("x-user-id") userIdHeader: string | string[] | undefined,
|
||||
@Param("taskId") taskId: string
|
||||
): Promise<TaskResponse> {
|
||||
return this.taskService.getTaskById(this.resolveUserId(userIdHeader), taskId);
|
||||
}
|
||||
|
||||
@Post()
|
||||
async createTask(
|
||||
@Headers("x-user-id") userIdHeader: string | string[] | undefined,
|
||||
@Body() body: CreateTaskDto
|
||||
): Promise<TaskResponse> {
|
||||
return this.taskService.createTask(this.resolveUserId(userIdHeader), body);
|
||||
}
|
||||
|
||||
@Patch(":taskId")
|
||||
async updateTask(
|
||||
@Headers("x-user-id") userIdHeader: string | string[] | undefined,
|
||||
@Param("taskId") taskId: string,
|
||||
@Body() body: UpdateTaskDto
|
||||
): Promise<TaskResponse> {
|
||||
return this.taskService.updateTask(this.resolveUserId(userIdHeader), taskId, body);
|
||||
}
|
||||
|
||||
@Delete(":taskId")
|
||||
async deleteTask(
|
||||
@Headers("x-user-id") userIdHeader: string | string[] | undefined,
|
||||
@Param("taskId") taskId: string
|
||||
): Promise<{ success: boolean }> {
|
||||
return this.taskService.deleteTask(this.resolveUserId(userIdHeader), taskId);
|
||||
}
|
||||
|
||||
private resolveUserId(userIdHeader: string | string[] | undefined): string {
|
||||
const userId = Array.isArray(userIdHeader) ? userIdHeader[0] : userIdHeader;
|
||||
if (!userId) {
|
||||
throw new UnauthorizedException("缺少用户上下文");
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { PrismaModule } from "../prisma/prisma.module";
|
||||
import { TaskController } from "./task.controller";
|
||||
import { TaskService } from "./task.service";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [TaskController],
|
||||
providers: [TaskService]
|
||||
})
|
||||
export class TaskModule {}
|
||||
@@ -0,0 +1,390 @@
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { Prisma, TaskPriority, TaskStatus } from "../../generated/prisma/client";
|
||||
import { PrismaService } from "../prisma/prisma.service";
|
||||
import { CreateTaskDto } from "./dto/create-task.dto";
|
||||
import { ListTasksQueryDto, TaskSortBy, TaskSortOrder } from "./dto/list-tasks-query.dto";
|
||||
import { UpdateTaskDto } from "./dto/update-task.dto";
|
||||
|
||||
type TaskEntity = Prisma.TaskGetPayload<{
|
||||
include: {
|
||||
taskTags: {
|
||||
include: {
|
||||
tag: {
|
||||
select: {
|
||||
name: true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}>;
|
||||
|
||||
export type TaskResponse = {
|
||||
id: string;
|
||||
title: string;
|
||||
contentJson: unknown | null;
|
||||
contentText: string | null;
|
||||
priority: TaskPriority;
|
||||
status: TaskStatus;
|
||||
ddl: string | null;
|
||||
completedAt: string | null;
|
||||
version: number;
|
||||
tags: string[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type ListTasksResponse = {
|
||||
items: TaskResponse[];
|
||||
page: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class TaskService {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
async listTasks(userId: string, query: ListTasksQueryDto): Promise<ListTasksResponse> {
|
||||
const page = query.page ?? 1;
|
||||
const pageSize = query.pageSize ?? 20;
|
||||
const skip = (page - 1) * pageSize;
|
||||
|
||||
const where = this.buildWhereInput(userId, query);
|
||||
const orderBy = this.buildOrderByInput(query);
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
this.prismaService.task.findMany({
|
||||
where,
|
||||
orderBy,
|
||||
skip,
|
||||
take: pageSize,
|
||||
include: {
|
||||
taskTags: {
|
||||
include: {
|
||||
tag: {
|
||||
select: {
|
||||
name: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
this.prismaService.task.count({ where })
|
||||
]);
|
||||
|
||||
return {
|
||||
items: items.map((item) => this.serializeTask(item)),
|
||||
page,
|
||||
pageSize,
|
||||
total
|
||||
};
|
||||
}
|
||||
|
||||
async getTaskById(userId: string, taskId: string): Promise<TaskResponse> {
|
||||
const task = await this.prismaService.task.findFirst({
|
||||
where: {
|
||||
id: taskId,
|
||||
userId
|
||||
},
|
||||
include: {
|
||||
taskTags: {
|
||||
include: {
|
||||
tag: {
|
||||
select: {
|
||||
name: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!task) {
|
||||
throw new NotFoundException("任务不存在");
|
||||
}
|
||||
|
||||
return this.serializeTask(task);
|
||||
}
|
||||
|
||||
async createTask(userId: string, body: CreateTaskDto): Promise<TaskResponse> {
|
||||
const tagNames = this.normalizeTagNames(body.tagNames);
|
||||
const nextStatus = body.status ?? TaskStatus.TODO;
|
||||
const contentJson =
|
||||
body.contentJson !== undefined ? (body.contentJson as Prisma.InputJsonValue) : undefined;
|
||||
|
||||
const task = await this.prismaService.$transaction(async (tx) => {
|
||||
const createdTask = await tx.task.create({
|
||||
data: {
|
||||
userId,
|
||||
title: body.title,
|
||||
contentJson,
|
||||
contentText: body.contentText ?? null,
|
||||
priority: body.priority ?? TaskPriority.MEDIUM,
|
||||
status: nextStatus,
|
||||
ddl: body.ddl ? new Date(body.ddl) : null,
|
||||
completedAt: nextStatus === TaskStatus.DONE ? new Date() : null
|
||||
}
|
||||
});
|
||||
|
||||
await this.replaceTaskTags(tx, userId, createdTask.id, tagNames);
|
||||
|
||||
return tx.task.findUniqueOrThrow({
|
||||
where: { id: createdTask.id },
|
||||
include: {
|
||||
taskTags: {
|
||||
include: {
|
||||
tag: {
|
||||
select: {
|
||||
name: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return this.serializeTask(task);
|
||||
}
|
||||
|
||||
async updateTask(userId: string, taskId: string, body: UpdateTaskDto): Promise<TaskResponse> {
|
||||
const currentTask = await this.prismaService.task.findFirst({
|
||||
where: {
|
||||
id: taskId,
|
||||
userId
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
status: true
|
||||
}
|
||||
});
|
||||
|
||||
if (!currentTask) {
|
||||
throw new NotFoundException("任务不存在");
|
||||
}
|
||||
|
||||
const data: Prisma.TaskUpdateInput = {
|
||||
version: {
|
||||
increment: 1
|
||||
}
|
||||
};
|
||||
|
||||
if (body.title !== undefined) {
|
||||
data.title = body.title;
|
||||
}
|
||||
if (body.contentJson !== undefined) {
|
||||
data.contentJson = body.contentJson as Prisma.InputJsonValue;
|
||||
}
|
||||
if (body.contentText !== undefined) {
|
||||
data.contentText = body.contentText;
|
||||
}
|
||||
if (body.priority !== undefined) {
|
||||
data.priority = body.priority;
|
||||
}
|
||||
if (body.status !== undefined) {
|
||||
data.status = body.status;
|
||||
if (body.status === TaskStatus.DONE && currentTask.status !== TaskStatus.DONE) {
|
||||
data.completedAt = new Date();
|
||||
} else if (body.status !== TaskStatus.DONE) {
|
||||
data.completedAt = null;
|
||||
}
|
||||
}
|
||||
if (body.ddl !== undefined) {
|
||||
data.ddl = body.ddl ? new Date(body.ddl) : null;
|
||||
}
|
||||
|
||||
const shouldReplaceTags = body.tagNames !== undefined;
|
||||
const nextTagNames = this.normalizeTagNames(body.tagNames);
|
||||
|
||||
const task = await this.prismaService.$transaction(async (tx) => {
|
||||
await tx.task.update({
|
||||
where: { id: taskId },
|
||||
data
|
||||
});
|
||||
|
||||
if (shouldReplaceTags) {
|
||||
await this.replaceTaskTags(tx, userId, taskId, nextTagNames);
|
||||
}
|
||||
|
||||
return tx.task.findUniqueOrThrow({
|
||||
where: { id: taskId },
|
||||
include: {
|
||||
taskTags: {
|
||||
include: {
|
||||
tag: {
|
||||
select: {
|
||||
name: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return this.serializeTask(task);
|
||||
}
|
||||
|
||||
async deleteTask(userId: string, taskId: string): Promise<{ success: boolean }> {
|
||||
const deleted = await this.prismaService.task.deleteMany({
|
||||
where: {
|
||||
id: taskId,
|
||||
userId
|
||||
}
|
||||
});
|
||||
|
||||
if (deleted.count === 0) {
|
||||
throw new NotFoundException("任务不存在");
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
private buildWhereInput(userId: string, query: ListTasksQueryDto): Prisma.TaskWhereInput {
|
||||
const where: Prisma.TaskWhereInput = {
|
||||
userId
|
||||
};
|
||||
|
||||
if (query.status !== undefined) {
|
||||
where.status = query.status;
|
||||
}
|
||||
|
||||
if (query.priority !== undefined) {
|
||||
where.priority = query.priority;
|
||||
}
|
||||
|
||||
if (query.tags !== undefined && query.tags.length > 0) {
|
||||
where.taskTags = {
|
||||
some: {
|
||||
tag: {
|
||||
name: {
|
||||
in: query.tags
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (query.keyword !== undefined && query.keyword.length > 0) {
|
||||
where.OR = [
|
||||
{
|
||||
title: {
|
||||
contains: query.keyword,
|
||||
mode: "insensitive"
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: {
|
||||
contains: query.keyword,
|
||||
mode: "insensitive"
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
return where;
|
||||
}
|
||||
|
||||
private buildOrderByInput(query: ListTasksQueryDto): Prisma.TaskOrderByWithRelationInput {
|
||||
const order: Prisma.SortOrder =
|
||||
query.sortOrder === TaskSortOrder.ASC ? Prisma.SortOrder.asc : Prisma.SortOrder.desc;
|
||||
|
||||
if (query.sortBy === TaskSortBy.CREATED_AT) {
|
||||
return { createdAt: order };
|
||||
}
|
||||
|
||||
if (query.sortBy === TaskSortBy.DDL) {
|
||||
return { ddl: order };
|
||||
}
|
||||
|
||||
return { updatedAt: order };
|
||||
}
|
||||
|
||||
private normalizeTagNames(tagNames: string[] | undefined): string[] {
|
||||
if (!tagNames) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result: string[] = [];
|
||||
const uniqueNames = new Set<string>();
|
||||
|
||||
for (const rawTagName of tagNames) {
|
||||
const normalized = rawTagName.trim();
|
||||
if (!normalized) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const uniqueKey = normalized.toLocaleLowerCase();
|
||||
if (uniqueNames.has(uniqueKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uniqueNames.add(uniqueKey);
|
||||
result.push(normalized);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async replaceTaskTags(
|
||||
tx: Prisma.TransactionClient,
|
||||
userId: string,
|
||||
taskId: string,
|
||||
tagNames: string[]
|
||||
): Promise<void> {
|
||||
await tx.taskTag.deleteMany({
|
||||
where: {
|
||||
taskId
|
||||
}
|
||||
});
|
||||
|
||||
if (tagNames.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tags = await Promise.all(
|
||||
tagNames.map((name) =>
|
||||
tx.tag.upsert({
|
||||
where: {
|
||||
userId_name: {
|
||||
userId,
|
||||
name
|
||||
}
|
||||
},
|
||||
update: {},
|
||||
create: {
|
||||
userId,
|
||||
name
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
await tx.taskTag.createMany({
|
||||
data: tags.map((tag) => ({
|
||||
taskId,
|
||||
tagId: tag.id
|
||||
})),
|
||||
skipDuplicates: true
|
||||
});
|
||||
}
|
||||
|
||||
private serializeTask(task: TaskEntity): TaskResponse {
|
||||
return {
|
||||
id: task.id,
|
||||
title: task.title,
|
||||
contentJson: task.contentJson,
|
||||
contentText: task.contentText,
|
||||
priority: task.priority,
|
||||
status: task.status,
|
||||
ddl: task.ddl?.toISOString() ?? null,
|
||||
completedAt: task.completedAt?.toISOString() ?? null,
|
||||
version: task.version,
|
||||
tags: task.taskTags.map((taskTag) => taskTag.tag.name),
|
||||
createdAt: task.createdAt.toISOString(),
|
||||
updatedAt: task.updatedAt.toISOString()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "../../packages/tsconfig/nest-app.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"rootDir": ".",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"include": ["src/**/*.ts", "generated/prisma/**/*.ts"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user