Redesign login/register with Mento branding and email/mobile OTP.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "mobile" TEXT;
|
||||
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "emailVerifiedAt" TIMESTAMP(3);
|
||||
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "mobileVerifiedAt" TIMESTAMP(3);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "User_mobile_key" ON "User"("mobile");
|
||||
CREATE INDEX IF NOT EXISTS "User_email_idx" ON "User"("email");
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE IF NOT EXISTS "AuthChallenge" (
|
||||
"id" TEXT NOT NULL,
|
||||
"kind" TEXT NOT NULL,
|
||||
"channel" TEXT,
|
||||
"email" TEXT,
|
||||
"mobile" TEXT,
|
||||
"passwordHash" TEXT,
|
||||
"payload" JSONB NOT NULL DEFAULT '{}',
|
||||
"emailCode" TEXT,
|
||||
"mobileCode" TEXT,
|
||||
"emailVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"mobileVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"consumedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "AuthChallenge_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "AuthChallenge_kind_createdAt_idx" ON "AuthChallenge"("kind", "createdAt");
|
||||
CREATE INDEX IF NOT EXISTS "AuthChallenge_expiresAt_idx" ON "AuthChallenge"("expiresAt");
|
||||
CREATE INDEX IF NOT EXISTS "AuthChallenge_email_idx" ON "AuthChallenge"("email");
|
||||
CREATE INDEX IF NOT EXISTS "AuthChallenge_mobile_idx" ON "AuthChallenge"("mobile");
|
||||
@@ -172,9 +172,13 @@ model Organization {
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
email String
|
||||
/// موبایل نرمالشده (مثلاً 9121234567) — برای ورود با موبایل
|
||||
mobile String?
|
||||
passwordHash String
|
||||
fullName String
|
||||
role UserRole @default(sales)
|
||||
emailVerifiedAt DateTime?
|
||||
mobileVerifiedAt DateTime?
|
||||
organizationId String
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
ownedDeals Deal[] @relation("DealOwner")
|
||||
@@ -197,7 +201,9 @@ model User {
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([organizationId, email])
|
||||
@@unique([mobile])
|
||||
@@index([organizationId])
|
||||
@@index([email])
|
||||
}
|
||||
|
||||
model Company {
|
||||
@@ -934,6 +940,32 @@ model AutomationRule {
|
||||
@@index([organizationId, isActive, trigger])
|
||||
}
|
||||
|
||||
/// چالش OTP برای ثبتنام / ورود پنل
|
||||
model AuthChallenge {
|
||||
id String @id @default(cuid())
|
||||
/// register | login
|
||||
kind String
|
||||
/// برای login: email | mobile
|
||||
channel String?
|
||||
email String?
|
||||
mobile String?
|
||||
passwordHash String?
|
||||
/// دادهٔ موقت ثبتنام (سازمان، نام، زمینه، کد معرف، …)
|
||||
payload Json @default("{}")
|
||||
emailCode String?
|
||||
mobileCode String?
|
||||
emailVerified Boolean @default(false)
|
||||
mobileVerified Boolean @default(false)
|
||||
expiresAt DateTime
|
||||
consumedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([kind, createdAt])
|
||||
@@index([expiresAt])
|
||||
@@index([email])
|
||||
@@index([mobile])
|
||||
}
|
||||
|
||||
/// اتصال به نرمافزار حسابداری خارجی (تهحساب) — چند اتصال/DB بهازای هر سازمان
|
||||
model AccountingConnection {
|
||||
id String @id @default(cuid())
|
||||
|
||||
@@ -38,9 +38,12 @@ async function main() {
|
||||
users: {
|
||||
create: {
|
||||
email,
|
||||
mobile: '09120000001',
|
||||
fullName: 'مدیر نمونه',
|
||||
passwordHash,
|
||||
role: 'owner',
|
||||
emailVerifiedAt: new Date(),
|
||||
mobileVerifiedAt: new Date(),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -54,6 +57,17 @@ async function main() {
|
||||
const owner = await prisma.user.findFirst({
|
||||
where: { organizationId: org.id, email },
|
||||
});
|
||||
if (owner && !owner.mobile) {
|
||||
await prisma.user.update({
|
||||
where: { id: owner.id },
|
||||
data: {
|
||||
mobile: '09120000001',
|
||||
emailVerifiedAt: owner.emailVerifiedAt ?? new Date(),
|
||||
mobileVerifiedAt: owner.mobileVerifiedAt ?? new Date(),
|
||||
},
|
||||
});
|
||||
console.log('Updated demo owner mobile: 09120000001');
|
||||
}
|
||||
|
||||
// چندارزی نمونه
|
||||
await prisma.organization.update({
|
||||
@@ -93,9 +107,12 @@ async function main() {
|
||||
data: {
|
||||
organizationId: org.id,
|
||||
email: salesEmail,
|
||||
mobile: '09120000002',
|
||||
fullName: 'کارشناس فروش نمونه',
|
||||
passwordHash: await bcrypt.hash('demo1234', 10),
|
||||
role: 'sales',
|
||||
emailVerifiedAt: new Date(),
|
||||
mobileVerifiedAt: new Date(),
|
||||
},
|
||||
});
|
||||
console.log('Seeded sales user:', salesEmail, '/ demo1234');
|
||||
@@ -110,9 +127,12 @@ async function main() {
|
||||
data: {
|
||||
organizationId: org.id,
|
||||
email: adminEmail,
|
||||
mobile: '09120000003',
|
||||
fullName: 'مدیر سازمانی نمونه',
|
||||
passwordHash: await bcrypt.hash('demo1234', 10),
|
||||
role: 'admin',
|
||||
emailVerifiedAt: new Date(),
|
||||
mobileVerifiedAt: new Date(),
|
||||
},
|
||||
});
|
||||
console.log('Seeded admin user:', adminEmail, '/ demo1234');
|
||||
|
||||
Reference in New Issue
Block a user