Phase 2a: Prisma schema + shared schemas for v1.0 features
- New models: Attachment, Webhook, Notification, SavedView - New fields: User.notificationPrefs (Json), indexes on Ticket - post-push.sql manages the tsvector columns + GIN indexes + triggers for FTS on Ticket (title/overview/displayId) and Comment (body); Prisma can't express these - package.json scripts: db:push and start:prod now chain `prisma db execute` against post-push.sql after `prisma db push` - db:migrate script removed — project uses push workflow, not migrations - Shared Zod schemas: attachment (25MB limit + mimetype allowlist), savedView, notification (prefs, mark-read, webhook CRUD) - Shared type additions: Attachment, Notification, SavedView, Webhook, PaginatedResponse<T> - Test fixtures updated for the new User.notificationPrefs column Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Generated
+133
@@ -16,7 +16,9 @@
|
||||
"express-async-errors": "^3.1.0",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"multer": "^2.1.1",
|
||||
"node-cron": "^3.0.3",
|
||||
"nodemailer": "^8.0.5",
|
||||
"pino": "^9.5.0",
|
||||
"pino-http": "^10.3.0",
|
||||
"pino-pretty": "^13.0.0",
|
||||
@@ -27,8 +29,10 @@
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jsonwebtoken": "^9.0.7",
|
||||
"@types/multer": "^2.1.0",
|
||||
"@types/node": "^22.10.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@types/nodemailer": "^8.0.0",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"prisma": "^5.22.0",
|
||||
"supertest": "^7.0.0",
|
||||
@@ -1090,6 +1094,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/multer": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.1.0.tgz",
|
||||
"integrity": "sha512-zYZb0+nJhOHtPpGDb3vqPjwpdeGlGC157VpkqNQL+UU2qwoacoQ7MpsAmUptI/0Oa127X32JzWDqQVEXp2RcIA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/express": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.19.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz",
|
||||
@@ -1107,6 +1121,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/nodemailer": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-8.0.0.tgz",
|
||||
"integrity": "sha512-fyf8jWULsCo0d0BuoQ75i6IeoHs47qcqxWc7yUdUcV0pOZGjUTTOvwdG1PRXUDqN/8A64yQdQdnA2pZgcdi+cA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz",
|
||||
@@ -1304,6 +1328,12 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/append-field": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
|
||||
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
@@ -1379,6 +1409,23 @@
|
||||
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
||||
"dependencies": {
|
||||
"streamsearch": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
@@ -1483,6 +1530,21 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
|
||||
"engines": [
|
||||
"node >= 6.0"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.0.2",
|
||||
"typedarray": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
@@ -2342,6 +2404,25 @@
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/multer": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/multer/-/multer-2.1.1.tgz",
|
||||
"integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"append-field": "^1.0.0",
|
||||
"busboy": "^1.6.0",
|
||||
"concat-stream": "^2.0.0",
|
||||
"type-is": "^1.6.18"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
@@ -2382,6 +2463,15 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemailer": {
|
||||
"version": "8.0.5",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.5.tgz",
|
||||
"integrity": "sha512-0PF8Yb1yZuQfQbq+5/pZJrtF6WQcjTd5/S4JOHs9PGFxuTqoB/icwuB44pOdURHJbRKX1PPoJZtY7R4VUoCC8w==",
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -2699,6 +2789,20 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/real-require": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
|
||||
@@ -3007,6 +3111,23 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/streamsearch": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz",
|
||||
@@ -3201,6 +3322,12 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/typedarray": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
@@ -3231,6 +3358,12 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
|
||||
+6
-3
@@ -5,9 +5,8 @@
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"build": "tsc",
|
||||
"start": "node dist/server/src/index.js",
|
||||
"start:prod": "prisma db push && node dist/server/src/index.js",
|
||||
"db:migrate": "prisma migrate dev",
|
||||
"db:push": "prisma db push",
|
||||
"start:prod": "prisma db push && prisma db execute --file prisma/post-push.sql --schema prisma/schema.prisma && node dist/server/src/index.js",
|
||||
"db:push": "prisma db push && prisma db execute --file prisma/post-push.sql --schema prisma/schema.prisma",
|
||||
"db:generate": "prisma generate",
|
||||
"db:seed": "tsx prisma/seed.ts",
|
||||
"typecheck": "tsc --noEmit",
|
||||
@@ -23,7 +22,9 @@
|
||||
"express-async-errors": "^3.1.0",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"multer": "^2.1.1",
|
||||
"node-cron": "^3.0.3",
|
||||
"nodemailer": "^8.0.5",
|
||||
"pino": "^9.5.0",
|
||||
"pino-http": "^10.3.0",
|
||||
"pino-pretty": "^13.0.0",
|
||||
@@ -34,8 +35,10 @@
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jsonwebtoken": "^9.0.7",
|
||||
"@types/multer": "^2.1.0",
|
||||
"@types/node": "^22.10.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@types/nodemailer": "^8.0.0",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"prisma": "^5.22.0",
|
||||
"supertest": "^7.0.0",
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
-- Idempotent SQL applied after `prisma db push`.
|
||||
-- Adds Postgres full-text-search columns + triggers + GIN indexes for Ticket and Comment.
|
||||
-- Prisma can't express tsvector/triggers, so we manage them here.
|
||||
|
||||
-- Ticket.searchVector
|
||||
ALTER TABLE "Ticket" ADD COLUMN IF NOT EXISTS "searchVector" tsvector;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ticket_search_idx ON "Ticket" USING GIN ("searchVector");
|
||||
|
||||
CREATE OR REPLACE FUNCTION ticket_search_trigger() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
NEW."searchVector" :=
|
||||
setweight(to_tsvector('english', coalesce(NEW."displayId", '')), 'A') ||
|
||||
setweight(to_tsvector('english', coalesce(NEW."title", '')), 'A') ||
|
||||
setweight(to_tsvector('english', coalesce(NEW."overview", '')), 'B');
|
||||
RETURN NEW;
|
||||
END
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS ticket_search_update ON "Ticket";
|
||||
CREATE TRIGGER ticket_search_update
|
||||
BEFORE INSERT OR UPDATE OF "title", "overview", "displayId" ON "Ticket"
|
||||
FOR EACH ROW EXECUTE FUNCTION ticket_search_trigger();
|
||||
|
||||
-- Backfill any rows missing the vector (first run or new columns)
|
||||
UPDATE "Ticket"
|
||||
SET "searchVector" =
|
||||
setweight(to_tsvector('english', coalesce("displayId", '')), 'A') ||
|
||||
setweight(to_tsvector('english', coalesce("title", '')), 'A') ||
|
||||
setweight(to_tsvector('english', coalesce("overview", '')), 'B')
|
||||
WHERE "searchVector" IS NULL;
|
||||
|
||||
-- Comment.searchVector
|
||||
ALTER TABLE "Comment" ADD COLUMN IF NOT EXISTS "searchVector" tsvector;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS comment_search_idx ON "Comment" USING GIN ("searchVector");
|
||||
|
||||
CREATE OR REPLACE FUNCTION comment_search_trigger() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
NEW."searchVector" := to_tsvector('english', coalesce(NEW."body", ''));
|
||||
RETURN NEW;
|
||||
END
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS comment_search_update ON "Comment";
|
||||
CREATE TRIGGER comment_search_update
|
||||
BEFORE INSERT OR UPDATE OF "body" ON "Comment"
|
||||
FOR EACH ROW EXECUTE FUNCTION comment_search_trigger();
|
||||
|
||||
UPDATE "Comment"
|
||||
SET "searchVector" = to_tsvector('english', coalesce("body", ''))
|
||||
WHERE "searchVector" IS NULL;
|
||||
+97
-20
@@ -22,20 +22,24 @@ enum TicketStatus {
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
username String @unique
|
||||
email String @unique
|
||||
passwordHash String
|
||||
displayName String
|
||||
role Role @default(AGENT)
|
||||
apiKey String? @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
id String @id @default(cuid())
|
||||
username String @unique
|
||||
email String @unique
|
||||
passwordHash String
|
||||
displayName String
|
||||
role Role @default(AGENT)
|
||||
apiKey String? @unique
|
||||
notificationPrefs Json?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
assignedTickets Ticket[] @relation("AssignedTickets")
|
||||
createdTickets Ticket[] @relation("CreatedTickets")
|
||||
assignedTickets Ticket[] @relation("AssignedTickets")
|
||||
createdTickets Ticket[] @relation("CreatedTickets")
|
||||
comments Comment[]
|
||||
auditLogs AuditLog[]
|
||||
attachments Attachment[]
|
||||
notifications Notification[]
|
||||
savedViews SavedView[]
|
||||
}
|
||||
|
||||
model Category {
|
||||
@@ -83,13 +87,21 @@ model Ticket {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
category Category @relation(fields: [categoryId], references: [id])
|
||||
type Type @relation(fields: [typeId], references: [id])
|
||||
item Item @relation(fields: [itemId], references: [id])
|
||||
assignee User? @relation("AssignedTickets", fields: [assigneeId], references: [id])
|
||||
createdBy User @relation("CreatedTickets", fields: [createdById], references: [id])
|
||||
comments Comment[]
|
||||
auditLogs AuditLog[]
|
||||
category Category @relation(fields: [categoryId], references: [id])
|
||||
type Type @relation(fields: [typeId], references: [id])
|
||||
item Item @relation(fields: [itemId], references: [id])
|
||||
assignee User? @relation("AssignedTickets", fields: [assigneeId], references: [id])
|
||||
createdBy User @relation("CreatedTickets", fields: [createdById], references: [id])
|
||||
comments Comment[]
|
||||
auditLogs AuditLog[]
|
||||
attachments Attachment[]
|
||||
notifications Notification[]
|
||||
|
||||
@@index([status])
|
||||
@@index([severity])
|
||||
@@index([assigneeId])
|
||||
@@index([createdById])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
model Comment {
|
||||
@@ -99,8 +111,73 @@ model Comment {
|
||||
authorId String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade)
|
||||
author User @relation(fields: [authorId], references: [id])
|
||||
ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade)
|
||||
author User @relation(fields: [authorId], references: [id])
|
||||
attachments Attachment[]
|
||||
notifications Notification[]
|
||||
|
||||
@@index([ticketId])
|
||||
}
|
||||
|
||||
model Attachment {
|
||||
id String @id @default(cuid())
|
||||
filename String
|
||||
mimetype String
|
||||
size Int
|
||||
storagePath String
|
||||
ticketId String?
|
||||
commentId String?
|
||||
uploadedById String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
ticket Ticket? @relation(fields: [ticketId], references: [id], onDelete: Cascade)
|
||||
comment Comment? @relation(fields: [commentId], references: [id], onDelete: Cascade)
|
||||
uploadedBy User @relation(fields: [uploadedById], references: [id])
|
||||
|
||||
@@index([ticketId])
|
||||
@@index([commentId])
|
||||
}
|
||||
|
||||
model Webhook {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
url String
|
||||
events String[]
|
||||
secret String
|
||||
active Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Notification {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
kind String
|
||||
ticketId String?
|
||||
commentId String?
|
||||
data Json?
|
||||
readAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
ticket Ticket? @relation(fields: [ticketId], references: [id], onDelete: Cascade)
|
||||
comment Comment? @relation(fields: [commentId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId, readAt])
|
||||
@@index([userId, createdAt])
|
||||
}
|
||||
|
||||
model SavedView {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
name String
|
||||
filters Json
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([userId, name])
|
||||
}
|
||||
|
||||
model AuditLog {
|
||||
|
||||
@@ -16,6 +16,7 @@ describe('authService.login', () => {
|
||||
passwordHash: await bcrypt.hash(password, 4),
|
||||
role: 'AGENT',
|
||||
apiKey: null,
|
||||
notificationPrefs: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
@@ -35,6 +36,7 @@ describe('authService.login', () => {
|
||||
passwordHash: await bcrypt.hash('correct', 4),
|
||||
role: 'AGENT',
|
||||
apiKey: null,
|
||||
notificationPrefs: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
@@ -59,6 +61,7 @@ describe('authService.login', () => {
|
||||
passwordHash: await bcrypt.hash(password, 4),
|
||||
role: 'SERVICE',
|
||||
apiKey: 'sk_xyz',
|
||||
notificationPrefs: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ const stubUser = {
|
||||
role: 'AGENT' as const,
|
||||
passwordHash: '',
|
||||
apiKey: null,
|
||||
notificationPrefs: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user