8
8
findFullSocialIdBySocialKey ,
9
9
findPersonBySocialKey ,
10
10
mergeSpecifiedPersons ,
11
- mergeSpecifiedAccounts
11
+ mergeSpecifiedAccounts ,
12
+ createAccount
12
13
} from '@hcengineering/account'
13
14
import { getFirstName , getLastName } from '@hcengineering/contact'
14
15
import {
@@ -23,7 +24,11 @@ import {
23
24
type AccountUuid ,
24
25
parseSocialIdString ,
25
26
DOMAIN_SPACE ,
26
- AccountRole
27
+ AccountRole ,
28
+ generateId ,
29
+ type WorkspaceDataId ,
30
+ type WorkspaceUuid ,
31
+ generateUuid
27
32
} from '@hcengineering/core'
28
33
import { getMongoClient , getWorkspaceMongoDB } from '@hcengineering/mongo'
29
34
import {
@@ -39,6 +44,11 @@ import { type DBDoc } from '@hcengineering/postgres/types/utils'
39
44
import { getTransactorEndpoint } from '@hcengineering/server-client'
40
45
import { generateToken } from '@hcengineering/server-token'
41
46
import { connect } from '@hcengineering/server-tool'
47
+ import {
48
+ type MongoAccountDB as v6MongoAccountDB ,
49
+ type Account as OldAccount ,
50
+ type Workspace as OldWorkspace
51
+ } from '@hcengineering/account-service'
42
52
import { type MongoClient } from 'mongodb'
43
53
import type postgres from 'postgres'
44
54
import { type Row } from 'postgres'
@@ -1138,3 +1148,331 @@ export async function ensureGlobalPersonsForLocalAccounts (
1138
1148
pg . close ( )
1139
1149
}
1140
1150
}
1151
+
1152
+ export async function migrateTrustedV6Accounts (
1153
+ ctx : MeasureMetricsContext ,
1154
+ accountDB : AccountDB ,
1155
+ mongoDb : v6MongoAccountDB ,
1156
+ dryRun : boolean ,
1157
+ skipWorkspaces : Set < string >
1158
+ ) : Promise < void > {
1159
+ // Mapping between <ObjectId, UUID>
1160
+ const accountsIdToUuid : Record < string , AccountUuid > = { }
1161
+ // Mapping between <email, UUID>
1162
+ const accountsEmailToUuid : Record < string , AccountUuid > = { }
1163
+ // Mapping between <OldId, UUID>
1164
+ const workspacesIdToUuid : Record < WorkspaceDataId , WorkspaceUuid > = { }
1165
+
1166
+ console . log ( 'Migrating accounts database...' )
1167
+ let accountsProcessed = 0
1168
+ const accountsCursor = mongoDb . account . findCursor ( { } )
1169
+ try {
1170
+ while ( await accountsCursor . hasNext ( ) ) {
1171
+ const account = await accountsCursor . next ( )
1172
+ if ( account == null ) {
1173
+ break
1174
+ }
1175
+
1176
+ try {
1177
+ const accountUuid = await migrateAccount ( account , accountDB , dryRun )
1178
+ if ( accountUuid == null ) {
1179
+ console . log ( 'Account not migrated' , account )
1180
+ continue
1181
+ }
1182
+ accountsIdToUuid [ account . _id . toString ( ) ] = accountUuid
1183
+ accountsEmailToUuid [ account . email ] = accountUuid
1184
+
1185
+ accountsProcessed ++
1186
+ if ( accountsProcessed % 100 === 0 ) {
1187
+ console . log ( 'Processed accounts:' , accountsProcessed )
1188
+ }
1189
+ } catch ( err : any ) {
1190
+ console . log ( 'Failed to migrate account' , account . _id , account . email , err )
1191
+ }
1192
+ }
1193
+ } catch ( err : any ) {
1194
+ console . log ( 'Failed to migrate accounts' , err )
1195
+ } finally {
1196
+ await accountsCursor . close ( )
1197
+ }
1198
+
1199
+ console . log ( 'Total accounts processed:' , accountsProcessed )
1200
+
1201
+ let processedWorkspaces = 0
1202
+ const workspacesCursor = mongoDb . workspace . findCursor ( { } )
1203
+ try {
1204
+ while ( await workspacesCursor . hasNext ( ) ) {
1205
+ const workspace = await workspacesCursor . next ( )
1206
+ if ( workspace == null ) {
1207
+ break
1208
+ }
1209
+
1210
+ if (
1211
+ skipWorkspaces . has ( workspace . workspace ) ||
1212
+ ( workspace . workspaceUrl != null && skipWorkspaces . has ( workspace . workspaceUrl ) )
1213
+ ) {
1214
+ console . log ( 'Skipping workspace' , workspace . workspace , workspace . workspaceUrl )
1215
+ continue
1216
+ }
1217
+
1218
+ try {
1219
+ const workspaceUuid = await migrateWorkspace (
1220
+ workspace ,
1221
+ accountDB ,
1222
+ accountsIdToUuid ,
1223
+ accountsEmailToUuid ,
1224
+ dryRun
1225
+ )
1226
+
1227
+ if ( workspaceUuid !== undefined ) {
1228
+ workspacesIdToUuid [ workspace . workspace ] = workspaceUuid
1229
+ }
1230
+ processedWorkspaces ++
1231
+ if ( processedWorkspaces % 100 === 0 ) {
1232
+ console . log ( 'Processed workspaces:' , processedWorkspaces )
1233
+ }
1234
+ } catch ( err : any ) {
1235
+ console . log ( 'Failed to migrate workspace' , workspace . workspaceUrl , workspace . workspace , err )
1236
+ }
1237
+ }
1238
+ } catch ( err : any ) {
1239
+ console . log ( 'Failed to migrate workspaces' , err )
1240
+ } finally {
1241
+ await workspacesCursor . close ( )
1242
+ }
1243
+
1244
+ console . log ( 'Total workspaces processed:' , processedWorkspaces )
1245
+ console . log ( 'Total workspaces created/ensured:' , Object . values ( workspacesIdToUuid ) . length )
1246
+
1247
+ let invitesProcessed = 0
1248
+ const invitesCursor = mongoDb . invite . findCursor ( { } )
1249
+ try {
1250
+ while ( await invitesCursor . hasNext ( ) ) {
1251
+ const invite = await invitesCursor . next ( )
1252
+ if ( invite == null ) {
1253
+ break
1254
+ }
1255
+
1256
+ try {
1257
+ const workspaceUuid = workspacesIdToUuid [ invite . workspace . name ]
1258
+ if ( workspaceUuid === undefined ) {
1259
+ console . log ( 'No workspace with id' , invite . workspace . name , 'found for invite' , invite . _id )
1260
+ continue
1261
+ }
1262
+
1263
+ const existing = await accountDB . invite . findOne ( { migratedFrom : invite . _id . toString ( ) } )
1264
+ if ( existing != null ) {
1265
+ continue
1266
+ }
1267
+
1268
+ const inviteRecord = {
1269
+ migratedFrom : invite . _id . toString ( ) ,
1270
+ workspaceUuid,
1271
+ expiresOn : invite . exp ,
1272
+ emailPattern : invite . emailMask ,
1273
+ remainingUses : invite . limit ,
1274
+ role : invite . role ?? AccountRole . User
1275
+ }
1276
+
1277
+ if ( ! dryRun ) {
1278
+ await accountDB . invite . insertOne ( inviteRecord )
1279
+ } else {
1280
+ console . log ( 'Creating invite record' , inviteRecord )
1281
+ }
1282
+
1283
+ invitesProcessed ++
1284
+ if ( invitesProcessed % 100 === 0 ) {
1285
+ console . log ( 'Processed invites:' , invitesProcessed )
1286
+ }
1287
+ } catch ( err : any ) {
1288
+ console . log ( 'Failed to migrate invite' , invite . _id , err )
1289
+ }
1290
+ }
1291
+ } catch ( err : any ) {
1292
+ console . log ( 'Failed to migrate invites' , err )
1293
+ } finally {
1294
+ await invitesCursor . close ( )
1295
+ }
1296
+
1297
+ console . log ( 'Total invites processed:' , invitesProcessed )
1298
+ }
1299
+
1300
+ async function migrateAccount (
1301
+ account : OldAccount ,
1302
+ accountDB : AccountDB ,
1303
+ dryRun = true
1304
+ ) : Promise < AccountUuid | undefined > {
1305
+ const primaryKey : SocialKey = {
1306
+ type : SocialIdType . EMAIL ,
1307
+ value : account . email
1308
+ }
1309
+
1310
+ let personUuid : PersonUuid
1311
+ const verified = account . confirmed === true ? { verifiedOn : Date . now ( ) } : { }
1312
+
1313
+ const existing = await accountDB . socialId . findOne ( primaryKey )
1314
+ if ( existing == null ) {
1315
+ // Create new global person
1316
+ const personRecord = {
1317
+ firstName : account . first ,
1318
+ lastName : account . last
1319
+ }
1320
+
1321
+ if ( ! dryRun ) {
1322
+ personUuid = await accountDB . person . insertOne ( personRecord )
1323
+ } else {
1324
+ console . log ( 'Creating person record' , personRecord )
1325
+ personUuid = generateUuid ( ) as PersonUuid
1326
+ }
1327
+
1328
+ const socialIdRecord = {
1329
+ ...primaryKey ,
1330
+ personUuid,
1331
+ ...verified
1332
+ }
1333
+
1334
+ if ( ! dryRun ) {
1335
+ await accountDB . socialId . insertOne ( socialIdRecord )
1336
+ } else {
1337
+ console . log ( 'Creating social id record' , socialIdRecord )
1338
+ }
1339
+
1340
+ if ( ! dryRun ) {
1341
+ await createAccount ( accountDB , personUuid , account . confirmed , false , account . createdOn )
1342
+ } else {
1343
+ console . log ( 'Creating account record' , { personUuid, confirmed : account . confirmed } )
1344
+ }
1345
+
1346
+ if ( account . hash != null && account . salt != null ) {
1347
+ if ( ! dryRun ) {
1348
+ await accountDB . setPassword ( personUuid as AccountUuid , account . hash , account . salt )
1349
+ } else {
1350
+ console . log ( 'Updating account password' , { personUuid } )
1351
+ }
1352
+ }
1353
+ } else {
1354
+ personUuid = existing . personUuid
1355
+
1356
+ // if there's no existing account, create a new one
1357
+ const existingAcc = await accountDB . account . findOne ( { uuid : personUuid as AccountUuid } )
1358
+ if ( existingAcc == null ) {
1359
+ if ( ! dryRun ) {
1360
+ await createAccount ( accountDB , personUuid , account . confirmed , false , account . createdOn )
1361
+ } else {
1362
+ console . log ( 'Creating account record' , { personUuid, confirmed : account . confirmed } )
1363
+ }
1364
+
1365
+ if ( account . hash != null && account . salt != null ) {
1366
+ if ( ! dryRun ) {
1367
+ await accountDB . setPassword ( personUuid as AccountUuid , account . hash , account . salt )
1368
+ } else {
1369
+ console . log ( 'Updating account password' , { personUuid } )
1370
+ }
1371
+ }
1372
+ }
1373
+ }
1374
+
1375
+ return personUuid as AccountUuid
1376
+ }
1377
+
1378
+ async function migrateWorkspace (
1379
+ workspace : OldWorkspace ,
1380
+ accountDB : AccountDB ,
1381
+ accountsIdToUuid : Record < string , AccountUuid > ,
1382
+ accountsEmailToUuid : Record < string , AccountUuid > ,
1383
+ dryRun = true
1384
+ ) : Promise < WorkspaceUuid | undefined > {
1385
+ if ( workspace . workspaceUrl == null ) {
1386
+ console . log ( 'No workspace url, skipping' , workspace . workspace )
1387
+ return
1388
+ }
1389
+
1390
+ const createdBy = workspace . createdBy !== undefined ? accountsEmailToUuid [ workspace . createdBy ] : undefined
1391
+ if ( createdBy === undefined ) {
1392
+ console . log ( 'No account found for workspace' , workspace . workspace , 'created by' , workspace . createdBy )
1393
+ }
1394
+
1395
+ const existingByUrl = await accountDB . workspace . findOne ( { url : workspace . workspaceUrl } )
1396
+ const existingByUuid = await accountDB . workspace . findOne ( { uuid : workspace . uuid } )
1397
+
1398
+ let workspaceUuid : WorkspaceUuid
1399
+
1400
+ if ( existingByUuid == null ) {
1401
+ let url = workspace . workspaceUrl
1402
+ if ( existingByUrl != null ) {
1403
+ // generate new url
1404
+ url = `${ url } -${ generateId ( '-' ) } `
1405
+ console . log ( 'Generating new url' , url )
1406
+ }
1407
+
1408
+ const workspaceRecord = {
1409
+ uuid : workspace . uuid ,
1410
+ name : workspace . workspaceName ,
1411
+ url,
1412
+ dataId : workspace . workspace ,
1413
+ branding : workspace . branding ,
1414
+ region : workspace . region ,
1415
+ createdBy,
1416
+ billingAccount : createdBy ,
1417
+ createdOn : workspace . createdOn ?? Date . now ( )
1418
+ }
1419
+
1420
+ if ( ! dryRun ) {
1421
+ workspaceUuid = await accountDB . workspace . insertOne ( workspaceRecord )
1422
+ } else {
1423
+ console . log ( 'Creating workspace record' , workspaceRecord )
1424
+ workspaceUuid = generateUuid ( ) as WorkspaceUuid
1425
+ }
1426
+ } else {
1427
+ workspaceUuid = existingByUuid . uuid
1428
+ }
1429
+
1430
+ const existingStatus = await accountDB . workspaceStatus . findOne ( { workspaceUuid } )
1431
+
1432
+ if ( existingStatus == null ) {
1433
+ const statusRecord = {
1434
+ workspaceUuid,
1435
+ mode : workspace . mode ,
1436
+ processingProgress : workspace . progress !== undefined ? Math . floor ( workspace . progress ) : undefined ,
1437
+ versionMajor : workspace . version ?. major ,
1438
+ versionMinor : workspace . version ?. minor ,
1439
+ versionPatch : workspace . version ?. patch ,
1440
+ lastProcessingTime : workspace . lastProcessingTime ,
1441
+ lastVisit : workspace . lastVisit ,
1442
+ isDisabled : workspace . disabled ,
1443
+ processingAttempts : workspace . attempts ,
1444
+ processingMessage : workspace . message ,
1445
+ backupInfo : workspace . backupInfo
1446
+ }
1447
+
1448
+ if ( ! dryRun ) {
1449
+ await accountDB . workspaceStatus . insertOne ( statusRecord )
1450
+ } else {
1451
+ console . log ( 'Creating workspace status record' , statusRecord )
1452
+ }
1453
+ }
1454
+
1455
+ const uniqueAccounts = Array . from ( new Set ( ( workspace . accounts ?? [ ] ) . map ( ( it ) => it . toString ( ) ) ) )
1456
+ const existingMembers = new Set ( ( await accountDB . getWorkspaceMembers ( workspaceUuid ) ) . map ( ( mi ) => mi . person ) )
1457
+ for ( const member of uniqueAccounts ) {
1458
+ const accountUuid = accountsIdToUuid [ member ]
1459
+
1460
+ if ( accountUuid === undefined ) {
1461
+ console . log ( 'No account found for workspace' , workspace . workspace , 'member' , member )
1462
+ continue
1463
+ }
1464
+
1465
+ if ( existingMembers . has ( accountUuid ) ) {
1466
+ continue
1467
+ }
1468
+
1469
+ if ( ! dryRun ) {
1470
+ // Actual roles are being set in workspace migration
1471
+ await accountDB . assignWorkspace ( accountUuid , workspaceUuid , AccountRole . Guest )
1472
+ } else {
1473
+ console . log ( 'Assigning account' , member , accountUuid , 'to workspace' , workspaceUuid )
1474
+ }
1475
+ }
1476
+
1477
+ return workspaceUuid
1478
+ }
0 commit comments