Add account-level crew pool with per-logbook and per-day selection.
Move skipper and crew master data to the user profile pool, replace the logbook crew tab with selection from that pool, inherit crew on new travel days, and sync via new PersonPayload and LogbookCrewSelection models. Includes migration from legacy crew records, tour/demo updates, and i18n. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -504,6 +504,75 @@ router.put('/appearance-prefs', requireUser, async (req: any, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
router.get('/person-pool', requireUser, async (req: any, res) => {
|
||||
try {
|
||||
const persons = await prisma.personPayload.findMany({
|
||||
where: { userId: req.userId }
|
||||
})
|
||||
return res.json({ persons })
|
||||
} catch (error: unknown) {
|
||||
return sendInternalError(res, error, 'auth/person-pool-get')
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/person-pool/push', requireUser, async (req: any, res) => {
|
||||
try {
|
||||
const { items } = req.body
|
||||
if (!items || !Array.isArray(items)) {
|
||||
return res.status(400).json({ error: 'items array is required' })
|
||||
}
|
||||
|
||||
const results: Array<{ payloadId: string; status: string; error?: string; reason?: string }> = []
|
||||
|
||||
for (const item of items) {
|
||||
const { action, payloadId, data, updatedAt } = item
|
||||
const itemUpdatedAt = new Date(updatedAt)
|
||||
|
||||
try {
|
||||
if (action === 'delete') {
|
||||
await prisma.personPayload.deleteMany({
|
||||
where: { userId: req.userId, payloadId }
|
||||
})
|
||||
results.push({ payloadId, status: 'success' })
|
||||
continue
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(data)
|
||||
const encryptedData = parsed.encryptedData || parsed.ciphertext
|
||||
const { iv, tag } = parsed
|
||||
|
||||
const existing = await prisma.personPayload.findUnique({
|
||||
where: { userId_payloadId: { userId: req.userId, payloadId } }
|
||||
})
|
||||
if (existing && new Date(existing.updatedAt) > itemUpdatedAt) {
|
||||
results.push({ payloadId, status: 'conflict', reason: 'Server version is newer' })
|
||||
continue
|
||||
}
|
||||
|
||||
await prisma.personPayload.upsert({
|
||||
where: { userId_payloadId: { userId: req.userId, payloadId } },
|
||||
create: {
|
||||
userId: req.userId,
|
||||
payloadId,
|
||||
encryptedData,
|
||||
iv,
|
||||
tag,
|
||||
updatedAt: itemUpdatedAt
|
||||
},
|
||||
update: { encryptedData, iv, tag, updatedAt: itemUpdatedAt }
|
||||
})
|
||||
results.push({ payloadId, status: 'success' })
|
||||
} catch (err: any) {
|
||||
results.push({ payloadId, status: 'error', error: err.message || 'Operation failed' })
|
||||
}
|
||||
}
|
||||
|
||||
return res.json({ results })
|
||||
} catch (error: unknown) {
|
||||
return sendInternalError(res, error, 'auth/person-pool-push')
|
||||
}
|
||||
})
|
||||
|
||||
router.get('/profile', requireUser, async (req: any, res) => {
|
||||
try {
|
||||
const user = await prisma.user.findUnique({
|
||||
|
||||
@@ -77,6 +77,9 @@ router.get('/share-pull', async (req: any, res) => {
|
||||
const yacht = await prisma.yachtPayload.findUnique({ where: { logbookId } })
|
||||
const deviation = await prisma.deviationPayload.findUnique({ where: { logbookId } })
|
||||
const crews = await prisma.crewPayload.findMany({ where: { logbookId } })
|
||||
const logbookCrewSelection = await prisma.logbookCrewSelectionPayload.findUnique({
|
||||
where: { logbookId }
|
||||
})
|
||||
const entries = await prisma.entryPayload.findMany({ where: { logbookId } })
|
||||
const photos = await prisma.photoPayload.findMany({ where: { logbookId } })
|
||||
const gpsTracks = await prisma.gpsTrackPayload.findMany({ where: { logbookId } })
|
||||
@@ -86,6 +89,7 @@ router.get('/share-pull', async (req: any, res) => {
|
||||
yacht,
|
||||
deviation,
|
||||
crews,
|
||||
logbookCrewSelection,
|
||||
entries,
|
||||
photos,
|
||||
gpsTracks
|
||||
|
||||
@@ -145,6 +145,8 @@ router.post('/push', async (req: any, res) => {
|
||||
await prisma.photoPayload.deleteMany({ where: { logbookId, payloadId } })
|
||||
} else if (type === 'gpsTrack') {
|
||||
await prisma.gpsTrackPayload.deleteMany({ where: { logbookId, entryId: payloadId } })
|
||||
} else if (type === 'logbookCrew') {
|
||||
await prisma.logbookCrewSelectionPayload.deleteMany({ where: { logbookId } })
|
||||
} else {
|
||||
results.push({ payloadId, status: 'error', error: `Unsupported delete type: ${type}` })
|
||||
continue
|
||||
@@ -245,6 +247,19 @@ router.post('/push', async (req: any, res) => {
|
||||
update: { encryptedData, iv, tag, updatedAt: itemUpdatedAt }
|
||||
})
|
||||
}
|
||||
} else if (type === 'logbookCrew') {
|
||||
{
|
||||
const existing = await prisma.logbookCrewSelectionPayload.findUnique({ where: { logbookId } })
|
||||
if (existing && new Date(existing.updatedAt) > itemUpdatedAt) {
|
||||
results.push({ payloadId, status: 'conflict', reason: 'Server version is newer' })
|
||||
continue
|
||||
}
|
||||
await prisma.logbookCrewSelectionPayload.upsert({
|
||||
where: { logbookId },
|
||||
create: { logbookId, encryptedData, iv, tag, updatedAt: itemUpdatedAt },
|
||||
update: { encryptedData, iv, tag, updatedAt: itemUpdatedAt }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
recordCollaboratorChange(
|
||||
@@ -310,11 +325,15 @@ router.get('/pull', async (req: any, res) => {
|
||||
const entries = await prisma.entryPayload.findMany({ where: { logbookId } })
|
||||
const photos = await prisma.photoPayload.findMany({ where: { logbookId } })
|
||||
const gpsTracks = await prisma.gpsTrackPayload.findMany({ where: { logbookId } })
|
||||
const logbookCrewSelection = await prisma.logbookCrewSelectionPayload.findUnique({
|
||||
where: { logbookId }
|
||||
})
|
||||
|
||||
return res.json({
|
||||
yacht,
|
||||
deviation,
|
||||
crews,
|
||||
logbookCrewSelection,
|
||||
entries,
|
||||
photos,
|
||||
gpsTracks
|
||||
|
||||
Reference in New Issue
Block a user