diff --git a/src/app/api/websites/[websiteId]/route.ts b/src/app/api/websites/[websiteId]/route.ts index 59f314d3..1443a541 100644 --- a/src/app/api/websites/[websiteId]/route.ts +++ b/src/app/api/websites/[websiteId]/route.ts @@ -1,8 +1,17 @@ import { z } from 'zod'; +import { ENTITY_TYPE } from '@/lib/constants'; +import { uuid } from '@/lib/crypto'; import { parseRequest } from '@/lib/request'; -import { json, ok, unauthorized } from '@/lib/response'; +import { badRequest, json, ok, serverError, unauthorized } from '@/lib/response'; import { canDeleteWebsite, canUpdateWebsite, canViewWebsite } from '@/permissions'; -import { deleteWebsite, getWebsite, updateWebsite } from '@/queries/prisma'; +import { + createShare, + deleteSharesByEntityId, + deleteWebsite, + getShareByEntityId, + getWebsite, + updateWebsite, +} from '@/queries/prisma'; export async function GET( request: Request, @@ -32,6 +41,7 @@ export async function POST( const schema = z.object({ name: z.string().optional(), domain: z.string().optional(), + shareId: z.string().max(50).nullable().optional(), }); const { auth, body, error } = await parseRequest(request, schema); @@ -41,15 +51,41 @@ export async function POST( } const { websiteId } = await params; - const { name, domain } = body; + const { name, domain, shareId } = body; if (!(await canUpdateWebsite(auth, websiteId))) { return unauthorized(); } - const website = await updateWebsite(websiteId, { name, domain }); + try { + const website = await updateWebsite(websiteId, { name, domain }); - return Response.json(website); + if (shareId === null) { + await deleteSharesByEntityId(website.id); + } + + const share = shareId + ? await createShare({ + id: uuid(), + entityId: websiteId, + shareType: ENTITY_TYPE.website, + name: website.name, + slug: shareId, + parameters: { overview: true, events: true }, + }) + : await getShareByEntityId(websiteId); + + return json({ + ...website, + shareId: share?.slug ?? null, + }); + } catch (e: any) { + if (e.message.toLowerCase().includes('unique constraint')) { + return badRequest({ message: 'That share ID is already taken.' }); + } + + return serverError(e); + } } export async function DELETE( diff --git a/src/app/api/websites/route.ts b/src/app/api/websites/route.ts index 29e79965..d54aeac6 100644 --- a/src/app/api/websites/route.ts +++ b/src/app/api/websites/route.ts @@ -1,11 +1,12 @@ import { z } from 'zod'; +import { ENTITY_TYPE } from '@/lib/constants'; import { uuid } from '@/lib/crypto'; import { fetchAccount } from '@/lib/load'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { pagingParams, searchParams } from '@/lib/schema'; import { canCreateTeamWebsite, canCreateWebsite } from '@/permissions'; -import { createWebsite, getWebsiteCount } from '@/queries/prisma'; +import { createShare, createWebsite, getWebsiteCount } from '@/queries/prisma'; import { getAllUserWebsitesIncludingTeamOwner, getUserWebsites } from '@/queries/prisma/website'; const CLOUD_WEBSITE_LIMIT = 3; @@ -38,6 +39,7 @@ export async function POST(request: Request) { const schema = z.object({ name: z.string().max(100), domain: z.string().max(500), + shareId: z.string().max(50).nullable().optional(), teamId: z.uuid().nullable().optional(), id: z.uuid().nullable().optional(), }); @@ -48,7 +50,7 @@ export async function POST(request: Request) { return error(); } - const { id, name, domain, teamId } = body; + const { id, name, domain, shareId, teamId } = body; if (process.env.CLOUD_MODE && !teamId) { const account = await fetchAccount(auth.user.id); @@ -80,5 +82,19 @@ export async function POST(request: Request) { const website = await createWebsite(data); - return json(website); + const share = shareId + ? await createShare({ + id: uuid(), + entityId: website.id, + shareType: ENTITY_TYPE.website, + name: website.name, + slug: shareId, + parameters: { overview: true, events: true }, + }) + : null; + + return json({ + ...website, + shareId: share?.slug ?? null, + }); } diff --git a/src/queries/prisma/share.ts b/src/queries/prisma/share.ts index 53246ffb..146f11ee 100644 --- a/src/queries/prisma/share.ts +++ b/src/queries/prisma/share.ts @@ -22,6 +22,17 @@ export async function getShareByCode(slug: string) { }); } +export async function getShareByEntityId(entityId: string) { + return prisma.client.share.findFirst({ + where: { + entityId, + }, + orderBy: { + createdAt: 'desc', + }, + }); +} + export async function getSharesByEntityId(entityId: string, filters?: QueryFilters) { const { pagedQuery } = prisma; @@ -62,3 +73,11 @@ export async function updateShare( export async function deleteShare(shareId: string) { return prisma.client.share.delete({ where: { id: shareId } }); } + +export async function deleteSharesByEntityId(entityId: string) { + return prisma.client.share.deleteMany({ + where: { + entityId, + }, + }); +} diff --git a/src/queries/prisma/website.ts b/src/queries/prisma/website.ts index fe57589c..2099ff08 100644 --- a/src/queries/prisma/website.ts +++ b/src/queries/prisma/website.ts @@ -1,4 +1,4 @@ -import type { Prisma } from '@/generated/prisma/client'; +import type { Prisma, Website } from '@/generated/prisma/client'; import { ROLES } from '@/lib/constants'; import prisma from '@/lib/prisma'; import redis from '@/lib/redis'; @@ -9,11 +9,13 @@ export async function findWebsite(criteria: Prisma.WebsiteFindUniqueArgs) { } export async function getWebsite(websiteId: string) { - return findWebsite({ + const website = await findWebsite({ where: { id: websiteId, }, }); + + return attachShareIdToWebsite(website); } export async function getWebsites(criteria: Prisma.WebsiteFindManyArgs, filters: QueryFilters) { @@ -31,7 +33,9 @@ export async function getWebsites(criteria: Prisma.WebsiteFindManyArgs, filters: deletedAt: null, }; - return pagedQuery('website', { ...criteria, where }, filters); + const websites = await pagedQuery('website', { ...criteria, where }, filters); + + return attachShareIdToWebsites(websites); } export async function getAllUserWebsitesIncludingTeamOwner(userId: string, filters?: QueryFilters) { @@ -203,6 +207,10 @@ export async function deleteWebsite(websiteId: string) { where: { websiteId }, }); + await tx.share.deleteMany({ + where: { entityId: websiteId }, + }); + const website = cloudMode ? await tx.website.update({ data: { @@ -236,3 +244,60 @@ export async function getWebsiteCount(userId: string) { }, }); } + +export async function attachShareIdToWebsite(website: Website) { + const share = await prisma.client.share.findFirst({ + where: { + entityId: website.id, + }, + orderBy: { + createdAt: 'desc', + }, + select: { + slug: true, + }, + }); + + return { + ...website, + shareId: share?.slug ?? null, + }; +} + +export async function attachShareIdToWebsites(websites: { + data: any; + count: any; + page: number; + pageSize: number; + orderBy: string; + search: string; +}) { + const websiteIds = websites.data.map(website => website.id); + + if (websiteIds.length === 0) { + return { + ...websites, + data: websites.data.map(website => ({ ...website, shareId: null })), + }; + } + + const shares = await prisma.client.share.findMany({ + where: { + entityId: { in: websiteIds }, + }, + distinct: ['entityId'], + orderBy: { + createdAt: 'desc', + }, + }); + + const shareByWebsiteId = new Map(shares.map(share => [share.entityId, share.slug])); + + return { + ...websites, + data: websites.data.map(website => ({ + ...website, + shareId: shareByWebsiteId.get(website.id) ?? null, + })), + }; +}