Keep issues when assignee or reporter is deleted
Switch issue.assigneeId/reporterId FKs from CASCADE to SET NULL and make reporter nullable through the entity, DTOs and GraphQL types. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4b8c855855
commit
2696f33a7e
8 files changed
+31
-21
No files matched your search
@@ -0,0 +1,14 @@
|
|||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "issue" DROP CONSTRAINT "issue_assigneeId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "issue" DROP CONSTRAINT "issue_reporterId_fkey";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "issue" ALTER COLUMN "reporterId" DROP NOT NULL;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "issue" ADD CONSTRAINT "issue_assigneeId_fkey" FOREIGN KEY ("assigneeId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "issue" ADD CONSTRAINT "issue_reporterId_fkey" FOREIGN KEY ("reporterId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
@@ -86,9 +86,9 @@ model Issue {
|
|||||||
status String
|
status String
|
||||||
priority Int @default(0)
|
priority Int @default(0)
|
||||||
assigneeId String?
|
assigneeId String?
|
||||||
assignee User? @relation(name: "assigneeIssues", fields: [assigneeId], references: [id], onDelete: Cascade)
|
assignee User? @relation(name: "assigneeIssues", fields: [assigneeId], references: [id], onDelete: SetNull)
|
||||||
reporterId String
|
reporterId String?
|
||||||
reporter User @relation(name: "reporterIssues", fields: [reporterId], references: [id], onDelete: Cascade)
|
reporter User? @relation(name: "reporterIssues", fields: [reporterId], references: [id], onDelete: SetNull)
|
||||||
projectId String
|
projectId String
|
||||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||||
dueDate DateTime? @db.Timestamptz(3)
|
dueDate DateTime? @db.Timestamptz(3)
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ export class IssueWithUsersDto {
|
|||||||
status: string;
|
status: string;
|
||||||
priority: number;
|
priority: number;
|
||||||
assigneeId: string | null;
|
assigneeId: string | null;
|
||||||
reporterId: string;
|
reporterId: string | null;
|
||||||
assignee: UserDto | null;
|
assignee: UserDto | null;
|
||||||
reporter: UserDto;
|
reporter: UserDto | null;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
dueDate: Date | null;
|
dueDate: Date | null;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export class IssueDto {
|
|||||||
status: string;
|
status: string;
|
||||||
priority: number;
|
priority: number;
|
||||||
assigneeId: string | null;
|
assigneeId: string | null;
|
||||||
reporterId: string;
|
reporterId: string | null;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
dueDate: Date | null;
|
dueDate: Date | null;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|||||||
@@ -23,11 +23,6 @@ export class IssueApplicationMapper {
|
|||||||
|
|
||||||
/** Раскрывает assignee/reporter в полноценных пользователей (см. GetIssueUseCase) */
|
/** Раскрывает assignee/reporter в полноценных пользователей (см. GetIssueUseCase) */
|
||||||
static toDtoWithUsers(issue: Issue, usersById: ReadonlyMap<string, UserDto>): IssueWithUsersDto {
|
static toDtoWithUsers(issue: Issue, usersById: ReadonlyMap<string, UserDto>): IssueWithUsersDto {
|
||||||
const reporter = usersById.get(issue.reporter);
|
|
||||||
if (!reporter) {
|
|
||||||
throw new Error(`Reporter user ${issue.reporter} not found for issue ${issue.key}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: issue.id as number,
|
id: issue.id as number,
|
||||||
key: issue.key,
|
key: issue.key,
|
||||||
@@ -38,7 +33,7 @@ export class IssueApplicationMapper {
|
|||||||
assigneeId: issue.assignee,
|
assigneeId: issue.assignee,
|
||||||
reporterId: issue.reporter,
|
reporterId: issue.reporter,
|
||||||
assignee: issue.assignee ? (usersById.get(issue.assignee) ?? null) : null,
|
assignee: issue.assignee ? (usersById.get(issue.assignee) ?? null) : null,
|
||||||
reporter,
|
reporter: issue.reporter ? (usersById.get(issue.reporter) ?? null) : null,
|
||||||
projectId: issue.projectId,
|
projectId: issue.projectId,
|
||||||
dueDate: issue.dueDate,
|
dueDate: issue.dueDate,
|
||||||
createdAt: issue.createdAt,
|
createdAt: issue.createdAt,
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ interface IssueProps {
|
|||||||
status: IssueStatus;
|
status: IssueStatus;
|
||||||
priority: IssuePriority;
|
priority: IssuePriority;
|
||||||
assignee: string | null;
|
assignee: string | null;
|
||||||
reporter: string;
|
/** null — автор задачи удалил аккаунт */
|
||||||
|
reporter: string | null;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
dueDate: Date | null;
|
dueDate: Date | null;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
@@ -72,7 +73,7 @@ export class Issue {
|
|||||||
status: string;
|
status: string;
|
||||||
priority: number;
|
priority: number;
|
||||||
assignee: string | null;
|
assignee: string | null;
|
||||||
reporter: string;
|
reporter: string | null;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
dueDate: Date | null;
|
dueDate: Date | null;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
@@ -122,7 +123,7 @@ export class Issue {
|
|||||||
return this.props.assignee;
|
return this.props.assignee;
|
||||||
}
|
}
|
||||||
|
|
||||||
get reporter(): string {
|
get reporter(): string | null {
|
||||||
return this.props.reporter;
|
return this.props.reporter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,14 +24,14 @@ export class IssueDetailType {
|
|||||||
@Field(() => String, { nullable: true })
|
@Field(() => String, { nullable: true })
|
||||||
assigneeId: string | null;
|
assigneeId: string | null;
|
||||||
|
|
||||||
@Field()
|
@Field(() => String, { nullable: true })
|
||||||
reporterId: string;
|
reporterId: string | null;
|
||||||
|
|
||||||
@Field(() => UserType, { nullable: true })
|
@Field(() => UserType, { nullable: true })
|
||||||
assignee: UserType | null;
|
assignee: UserType | null;
|
||||||
|
|
||||||
@Field(() => UserType)
|
@Field(() => UserType, { nullable: true })
|
||||||
reporter: UserType;
|
reporter: UserType | null;
|
||||||
|
|
||||||
@Field()
|
@Field()
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ export class IssueType {
|
|||||||
@Field(() => String, { nullable: true })
|
@Field(() => String, { nullable: true })
|
||||||
assigneeId: string | null;
|
assigneeId: string | null;
|
||||||
|
|
||||||
@Field()
|
@Field(() => String, { nullable: true })
|
||||||
reporterId: string;
|
reporterId: string | null;
|
||||||
|
|
||||||
@Field()
|
@Field()
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
|||||||
Reference in new issue
Block a user