Rend les liens entre notes visibles et persistants (sync NoteLink au save, auto-save, graphe réseau rafraîchi), ajoute living blocks, Memory Echo, recherche globale, consentement IA explicite et consolide les prototypes design en architectural-grid. Co-authored-by: Cursor <cursoragent@cursor.com>
280 lines
8.9 KiB
TypeScript
280 lines
8.9 KiB
TypeScript
'use server'
|
|
|
|
import { revalidatePath } from 'next/cache'
|
|
import prisma from '@/lib/prisma'
|
|
import { auth } from '@/auth'
|
|
|
|
// Add a collaborator to a note (delegates to the share request system)
|
|
export async function addCollaborator(noteId: string, userEmail: string) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) throw new Error('Unauthorized')
|
|
|
|
try {
|
|
const result = await createShareRequest(noteId, userEmail, 'view')
|
|
|
|
const targetUser = await prisma.user.findUnique({
|
|
where: { email: userEmail },
|
|
select: { id: true, name: true, email: true, image: true }
|
|
})
|
|
|
|
if (!targetUser) throw new Error('User not found')
|
|
|
|
return { success: true, user: targetUser }
|
|
} catch (error: unknown) {
|
|
console.error('Error adding collaborator:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
export async function removeCollaborator(noteId: string, userId: string) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) throw new Error('Unauthorized')
|
|
|
|
try {
|
|
const note = await prisma.note.findUnique({ where: { id: noteId } })
|
|
if (!note) throw new Error('Note not found')
|
|
if (note.userId !== session.user.id) throw new Error('You can only manage collaborators on your own notes')
|
|
|
|
await prisma.noteShare.deleteMany({ where: { noteId, userId } })
|
|
|
|
return { success: true }
|
|
} catch (error: unknown) {
|
|
console.error('Error removing collaborator:', error)
|
|
throw new Error(error instanceof Error ? error.message : 'Failed to remove collaborator')
|
|
}
|
|
}
|
|
|
|
export async function getNoteCollaborators(noteId: string) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) throw new Error('Unauthorized')
|
|
|
|
try {
|
|
const note = await prisma.note.findUnique({
|
|
where: { id: noteId },
|
|
select: { userId: true }
|
|
})
|
|
if (!note) throw new Error('Note not found')
|
|
|
|
if (note.userId !== session.user.id) {
|
|
const share = await prisma.noteShare.findUnique({
|
|
where: { noteId_userId: { noteId, userId: session.user.id } }
|
|
})
|
|
if (!share || share.status !== 'accepted') throw new Error('You do not have access to this note')
|
|
}
|
|
|
|
const shares = await prisma.noteShare.findMany({
|
|
where: { noteId },
|
|
include: { user: { select: { id: true, name: true, email: true, image: true } } }
|
|
})
|
|
|
|
return shares.map(share => share.user)
|
|
} catch (error: unknown) {
|
|
console.error('Error fetching collaborators:', error)
|
|
throw new Error(error instanceof Error ? error.message : 'Failed to fetch collaborators')
|
|
}
|
|
}
|
|
|
|
export async function getNoteAllUsers(noteId: string) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) throw new Error('Unauthorized')
|
|
|
|
try {
|
|
const note = await prisma.note.findUnique({
|
|
where: { id: noteId },
|
|
select: { userId: true }
|
|
})
|
|
if (!note) throw new Error('Note not found')
|
|
|
|
const share = await prisma.noteShare.findUnique({
|
|
where: { noteId_userId: { noteId, userId: session.user.id } }
|
|
})
|
|
|
|
const hasAccess = note.userId === session.user.id || (share && share.status === 'accepted')
|
|
if (!hasAccess) throw new Error('You do not have access to this note')
|
|
|
|
const owner = await prisma.user.findUnique({
|
|
where: { id: note.userId! },
|
|
select: { id: true, name: true, email: true, image: true }
|
|
})
|
|
if (!owner) throw new Error('Owner not found')
|
|
|
|
const shares = await prisma.noteShare.findMany({
|
|
where: { noteId, status: 'accepted' },
|
|
include: { user: { select: { id: true, name: true, email: true, image: true } } }
|
|
})
|
|
|
|
return [owner, ...shares.map(s => s.user)]
|
|
} catch (error: unknown) {
|
|
console.error('Error fetching note users:', error)
|
|
throw new Error(error instanceof Error ? error.message : 'Failed to fetch note users')
|
|
}
|
|
}
|
|
|
|
export async function createShareRequest(
|
|
noteId: string,
|
|
recipientEmail: string,
|
|
permission: 'view' | 'comment' | 'edit' = 'view'
|
|
) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) throw new Error('Unauthorized')
|
|
|
|
try {
|
|
const note = await prisma.note.findUnique({ where: { id: noteId } })
|
|
if (!note) throw new Error('Note not found')
|
|
if (note.userId !== session.user.id) throw new Error('Only the owner can share notes')
|
|
|
|
const recipient = await prisma.user.findUnique({ where: { email: recipientEmail } })
|
|
if (!recipient) throw new Error('User not found')
|
|
if (recipient.id === session.user.id) throw new Error('You cannot share with yourself')
|
|
|
|
const existingShare = await prisma.noteShare.findUnique({
|
|
where: { noteId_userId: { noteId, userId: recipient.id } }
|
|
})
|
|
|
|
if (existingShare) {
|
|
if (existingShare.status === 'declined' || existingShare.status === 'removed') {
|
|
await prisma.noteShare.update({
|
|
where: { id: existingShare.id },
|
|
data: { status: 'pending', notifiedAt: new Date(), permission }
|
|
})
|
|
} else {
|
|
throw new Error('Note already shared with this user')
|
|
}
|
|
} else {
|
|
await prisma.noteShare.create({
|
|
data: {
|
|
noteId,
|
|
userId: recipient.id,
|
|
sharedBy: session.user.id,
|
|
status: 'pending',
|
|
permission,
|
|
notifiedAt: new Date()
|
|
}
|
|
})
|
|
}
|
|
|
|
return { success: true }
|
|
} catch (error: unknown) {
|
|
console.error('Error creating share request:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
export async function getPendingShareRequests() {
|
|
const session = await auth()
|
|
if (!session?.user?.id) throw new Error('Unauthorized')
|
|
|
|
try {
|
|
return await prisma.noteShare.findMany({
|
|
where: { userId: session.user.id, status: 'pending' },
|
|
include: {
|
|
note: { select: { id: true, title: true, content: true, color: true, createdAt: true } },
|
|
sharer: { select: { id: true, name: true, email: true, image: true } }
|
|
},
|
|
orderBy: { createdAt: 'desc' }
|
|
})
|
|
} catch (error: unknown) {
|
|
console.error('Error fetching pending share requests:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
export async function respondToShareRequest(shareId: string, action: 'accept' | 'decline') {
|
|
const session = await auth()
|
|
if (!session?.user?.id) throw new Error('Unauthorized')
|
|
|
|
try {
|
|
const share = await prisma.noteShare.findUnique({
|
|
where: { id: shareId },
|
|
include: { note: true, sharer: true }
|
|
})
|
|
if (!share) throw new Error('Share request not found')
|
|
if (share.userId !== session.user.id) throw new Error('Unauthorized')
|
|
|
|
const updatedShare = await prisma.noteShare.update({
|
|
where: { id: shareId },
|
|
data: { status: action === 'accept' ? 'accepted' : 'declined', respondedAt: new Date() },
|
|
include: { note: { select: { title: true } } }
|
|
})
|
|
|
|
revalidatePath('/home')
|
|
return { success: true, share: updatedShare }
|
|
} catch (error: unknown) {
|
|
console.error('Error responding to share request:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
export async function getAcceptedSharedNotes() {
|
|
const session = await auth()
|
|
if (!session?.user?.id) throw new Error('Unauthorized')
|
|
|
|
try {
|
|
const acceptedShares = await prisma.noteShare.findMany({
|
|
where: { userId: session.user.id, status: 'accepted' },
|
|
include: { note: true },
|
|
orderBy: { createdAt: 'desc' }
|
|
})
|
|
return acceptedShares.map(share => share.note)
|
|
} catch (error: unknown) {
|
|
console.error('Error fetching accepted shared notes:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
export async function removeSharedNoteFromView(shareId: string) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) throw new Error('Unauthorized')
|
|
|
|
try {
|
|
const share = await prisma.noteShare.findUnique({ where: { id: shareId } })
|
|
if (!share) throw new Error('Share not found')
|
|
if (share.userId !== session.user.id) throw new Error('Unauthorized')
|
|
|
|
await prisma.noteShare.update({ where: { id: shareId }, data: { status: 'removed' } })
|
|
|
|
revalidatePath('/home')
|
|
return { success: true }
|
|
} catch (error: unknown) {
|
|
console.error('Error removing shared note from view:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
export async function getPendingShareCount() {
|
|
const session = await auth()
|
|
if (!session?.user?.id) return 0
|
|
|
|
try {
|
|
return await prisma.noteShare.count({
|
|
where: { userId: session.user.id, status: 'pending' }
|
|
})
|
|
} catch (error) {
|
|
console.error('Error getting pending share count:', error)
|
|
return 0
|
|
}
|
|
}
|
|
|
|
export async function leaveSharedNote(noteId: string, options?: { skipRevalidation?: boolean }) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) throw new Error('Unauthorized')
|
|
|
|
try {
|
|
const share = await prisma.noteShare.findUnique({
|
|
where: { noteId_userId: { noteId, userId: session.user.id } }
|
|
})
|
|
if (!share) throw new Error('Share not found')
|
|
if (share.userId !== session.user.id) throw new Error('Unauthorized')
|
|
|
|
await prisma.noteShare.update({ where: { id: share.id }, data: { status: 'removed' } })
|
|
|
|
if (!options?.skipRevalidation) {
|
|
revalidatePath('/home')
|
|
}
|
|
return { success: true }
|
|
} catch (error: unknown) {
|
|
console.error('Error leaving shared note:', error)
|
|
throw error
|
|
}
|
|
}
|