Skip to content

Commit 3694908

Browse files
committed
add two libs to allow for seamless migration of all users
1 parent 202ad9d commit 3694908

2 files changed

Lines changed: 290 additions & 1 deletion

File tree

src/lib/migrateuser.ts

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
import bcrypt from 'bcrypt'
2+
import { prisma } from '@/lib/prisma'
3+
import { logger } from '@/lib/logger'
4+
import { z } from 'zod'
5+
6+
export const migrateUserSchema = z.object({
7+
id: z.string().optional(),
8+
email: z.string().email('Invalid email address'),
9+
password: z.string().min(1, 'Password is required'),
10+
name: z.string().optional(),
11+
avatar: z.string().optional(),
12+
isPremium: z.boolean().optional().default(false),
13+
isPremiumDrop: z.boolean().optional().default(false),
14+
isPremiumMails: z.boolean().optional().default(false),
15+
isPremiumVault: z.boolean().optional().default(false),
16+
isPremiumDB: z.boolean().optional().default(false),
17+
premiumTierDrop: z.string().optional().default('free'),
18+
premiumTierMails: z.string().optional().default('free'),
19+
premiumTierVault: z.string().optional().default('free'),
20+
premiumTierDB: z.string().optional().default('free'),
21+
twoFactorEnabled: z.boolean().optional().default(false),
22+
twoFactorSecret: z.string().optional(),
23+
customStorageLimit: z.number().optional(),
24+
customApiKeyLimit: z.number().optional(),
25+
isNullDropTeam: z.boolean().optional().default(false),
26+
accessFilesPreview: z.boolean().optional().default(false),
27+
accessFilesDownload: z.boolean().optional().default(false),
28+
nullDropTeamRole: z.string().optional().default('member'),
29+
customDomain: z.string().optional(),
30+
customDomainVerified: z.boolean().optional().default(false),
31+
polarCustomerId: z.string().optional(),
32+
polarSubscriptionId: z.string().optional(),
33+
polarSubscriptionStatus: z.string().optional(),
34+
createdAt: z.string().datetime().optional(),
35+
})
36+
37+
export type MigrateUserData = z.infer<typeof migrateUserSchema>
38+
39+
export async function migrateUser(data: MigrateUserData): Promise<{ success: boolean; userId: string; email: string; message: string }> {
40+
logger.ups('Migration attempt for user:', data.email)
41+
42+
let existingUser = null
43+
if (data.id) {
44+
existingUser = await prisma.user.findUnique({
45+
where: { id: data.id },
46+
})
47+
}
48+
49+
if (!existingUser) {
50+
existingUser = await prisma.user.findUnique({
51+
where: { email: data.email },
52+
})
53+
}
54+
55+
if (existingUser) {
56+
if (existingUser.migraited) {
57+
logger.warn('User already migrated:', data.email)
58+
return {
59+
success: false,
60+
userId: existingUser.id,
61+
email: existingUser.email,
62+
message: 'User already migrated',
63+
}
64+
}
65+
logger.info('Updating existing user during migration:', data.email)
66+
67+
let passwordHash: string
68+
if (data.password.startsWith('$2b$')) {
69+
passwordHash = data.password
70+
logger.info('Using existing hashed password for user:', data.email)
71+
} else {
72+
logger.info('Hashing new password for user:', data.email)
73+
passwordHash = await bcrypt.hash(data.password, 10)
74+
logger.info('Password hashed successfully for user:', data.email)
75+
}
76+
77+
logger.info('Attempting to update user in database:', existingUser.id)
78+
const user = await prisma.user.update({
79+
where: { id: existingUser.id },
80+
data: {
81+
passwordHash,
82+
displayName: data.name,
83+
avatar: data.avatar,
84+
twoFactorEnabled: data.twoFactorEnabled,
85+
twoFactorSecret: data.twoFactorSecret,
86+
migraited: true,
87+
createdAt: data.createdAt ? new Date(data.createdAt) : undefined,
88+
},
89+
})
90+
logger.info('User updated successfully in database:', user.id, user.email)
91+
92+
logger.info('Calling migrateServiceEntitlements for updated user:', user.id)
93+
await migrateServiceEntitlements(user.id, data)
94+
logger.info('migrateServiceEntitlements completed for updated user:', user.id)
95+
96+
logger.info('User migrated (updated):', user.id, user.email)
97+
98+
return {
99+
success: true,
100+
userId: user.id,
101+
email: user.email,
102+
message: 'User migrated successfully',
103+
}
104+
}
105+
106+
let passwordHash: string
107+
if (data.password.startsWith('$2b$')) {
108+
passwordHash = data.password
109+
logger.info('Using existing hashed password for new user:', data.email)
110+
} else {
111+
logger.info('Hashing new password for new user:', data.email)
112+
passwordHash = await bcrypt.hash(data.password, 10)
113+
logger.info('Password hashed successfully for new user:', data.email)
114+
}
115+
116+
logger.info('Attempting to create new user in database:', data.email)
117+
const user = await prisma.user.create({
118+
data: {
119+
id: data.id,
120+
email: data.email,
121+
passwordHash,
122+
displayName: data.name,
123+
avatar: data.avatar,
124+
twoFactorEnabled: data.twoFactorEnabled,
125+
twoFactorSecret: data.twoFactorSecret,
126+
migraited: true,
127+
createdAt: data.createdAt ? new Date(data.createdAt) : undefined,
128+
},
129+
})
130+
logger.info('New user created successfully in database:', user.id, user.email)
131+
132+
logger.info('Calling migrateServiceEntitlements for new user:', user.id)
133+
await migrateServiceEntitlements(user.id, data)
134+
logger.info('migrateServiceEntitlements completed for new user:', user.id)
135+
136+
logger.info('User migrated (created):', user.id, user.email)
137+
138+
return {
139+
success: true,
140+
userId: user.id,
141+
email: user.email,
142+
message: 'User migrated successfully',
143+
}
144+
}
145+
146+
async function migrateServiceEntitlements(
147+
userId: string,
148+
data: MigrateUserData
149+
) {
150+
const dropAccessFlags: Record<string, any> = {}
151+
if (data.isNullDropTeam) {
152+
dropAccessFlags.isNullDropTeam = true
153+
dropAccessFlags.nullDropTeamRole = data.nullDropTeamRole
154+
}
155+
if (data.accessFilesPreview) {
156+
dropAccessFlags.accessFilesPreview = true
157+
}
158+
if (data.accessFilesDownload) {
159+
dropAccessFlags.accessFilesDownload = true
160+
}
161+
162+
const dropMetadata: Record<string, any> = {}
163+
if (data.customDomain) {
164+
dropMetadata.customDomain = data.customDomain
165+
dropMetadata.customDomainVerified = data.customDomainVerified
166+
}
167+
168+
logger.info('Upserting DROP service entitlement for userId:', userId)
169+
await prisma.userServiceEntitlement.upsert({
170+
where: {
171+
userId_service: {
172+
userId,
173+
service: 'DROP',
174+
},
175+
},
176+
create: {
177+
userId,
178+
service: 'DROP',
179+
tier: data.premiumTierDrop || 'free',
180+
isPremium: data.isPremiumDrop || data.isPremium || false,
181+
accessFlags: Object.keys(dropAccessFlags).length > 0 ? dropAccessFlags : undefined,
182+
metadata: Object.keys(dropMetadata).length > 0 ? dropMetadata : undefined,
183+
customStorageLimit: data.customStorageLimit,
184+
customApiKeyLimit: data.customApiKeyLimit,
185+
polarCustomerId: data.polarCustomerId,
186+
polarSubscriptionId: data.polarSubscriptionId,
187+
polarSubscriptionStatus: data.polarSubscriptionStatus,
188+
},
189+
update: {
190+
tier: data.premiumTierDrop || 'free',
191+
isPremium: data.isPremiumDrop || data.isPremium || false,
192+
accessFlags: Object.keys(dropAccessFlags).length > 0 ? dropAccessFlags : undefined,
193+
metadata: Object.keys(dropMetadata).length > 0 ? dropMetadata : undefined,
194+
customStorageLimit: data.customStorageLimit,
195+
customApiKeyLimit: data.customApiKeyLimit,
196+
polarCustomerId: data.polarCustomerId,
197+
polarSubscriptionId: data.polarSubscriptionId,
198+
polarSubscriptionStatus: data.polarSubscriptionStatus,
199+
},
200+
})
201+
logger.info('DROP service entitlement upserted for userId:', userId)
202+
203+
if (data.isPremiumMails || data.isPremium) {
204+
logger.info('Upserting MAILS service entitlement for userId:', userId)
205+
await prisma.userServiceEntitlement.upsert({
206+
where: {
207+
userId_service: {
208+
userId,
209+
service: 'MAILS',
210+
},
211+
},
212+
create: {
213+
userId,
214+
service: 'MAILS',
215+
tier: data.premiumTierMails || 'free',
216+
isPremium: data.isPremiumMails || data.isPremium || false,
217+
},
218+
update: {
219+
tier: data.premiumTierMails || 'free',
220+
isPremium: data.isPremiumMails || data.isPremium || false,
221+
},
222+
})
223+
logger.info('MAILS service entitlement upserted for userId:', userId)
224+
}
225+
226+
if (data.isPremiumVault || data.isPremium) {
227+
logger.info('Upserting VAULT service entitlement for userId:', userId)
228+
await prisma.userServiceEntitlement.upsert({
229+
where: {
230+
userId_service: {
231+
userId,
232+
service: 'VAULT',
233+
},
234+
},
235+
create: {
236+
userId,
237+
service: 'VAULT',
238+
tier: data.premiumTierVault || 'free',
239+
isPremium: data.isPremiumVault || data.isPremium || false,
240+
},
241+
update: {
242+
tier: data.premiumTierVault || 'free',
243+
isPremium: data.isPremiumVault || data.isPremium || false,
244+
},
245+
})
246+
logger.info('VAULT service entitlement upserted for userId:', userId)
247+
}
248+
249+
if (data.isPremiumDB || data.isPremium) {
250+
logger.info('Upserting DB service entitlement for userId:', userId)
251+
await prisma.userServiceEntitlement.upsert({
252+
where: {
253+
userId_service: {
254+
userId,
255+
service: 'DB',
256+
},
257+
},
258+
create: {
259+
userId,
260+
service: 'DB',
261+
tier: data.premiumTierDB || 'free',
262+
isPremium: data.isPremiumDB || data.isPremium || false
263+
},
264+
update: {
265+
tier: data.premiumTierDB || 'free',
266+
isPremium: data.isPremiumDB || data.isPremium || false
267+
},
268+
})
269+
logger.info('DB service entitlement upserted for userId:', userId)
270+
}
271+
}

src/lib/prisma.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,27 @@
11
import { PrismaClient } from '@prisma/client'
2+
import { Pool } from 'pg'
23

34
const globalForPrisma = globalThis as unknown as {
45
prisma: PrismaClient | undefined
56
}
67

78
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
89

9-
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
10+
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
11+
12+
let dropDbPool: Pool | null = null
13+
14+
export function getDropDbPool(): Pool {
15+
if (!process.env.DROP_DATABASE_URL) {
16+
throw new Error('DROP_DATABASE_URL is not set')
17+
}
18+
19+
if (!dropDbPool) {
20+
dropDbPool = new Pool({
21+
connectionString: process.env.DROP_DATABASE_URL,
22+
ssl: { rejectUnauthorized: false },
23+
})
24+
}
25+
26+
return dropDbPool
27+
}

0 commit comments

Comments
 (0)