Add board share management UI

This commit is contained in:
Mike Cao committed 2026-03-08 22:24:24 -07:00
1 parent b429e7b24d
commit c4226bc63d
30 files changed
+565 -56

No files matched your search

+5 -3
View File
@@ -1,5 +1,5 @@
import { z } from 'zod';
import { BOARD_TYPES } from '@/lib/boards';
import { BOARD_TYPES, normalizeBoardType } from '@/lib/boards';
import { parseRequest } from '@/lib/request';
import { badRequest, json, ok, serverError, unauthorized } from '@/lib/response';
import { canDeleteBoard, canUpdateBoard, canViewBoard } from '@/permissions';
@@ -28,11 +28,12 @@ export async function POST(request: Request, { params }: { params: Promise<{ boa
type: z
.enum([
BOARD_TYPES.dashboard,
BOARD_TYPES.open,
BOARD_TYPES.mixed,
BOARD_TYPES.website,
BOARD_TYPES.pixel,
BOARD_TYPES.link,
])
.or(z.literal('open'))
.optional(),
name: z.string().optional(),
description: z.string().optional(),
@@ -46,7 +47,8 @@ export async function POST(request: Request, { params }: { params: Promise<{ boa
}
const { boardId } = await params;
const { type, name, description, parameters } = body;
const { name, description, parameters } = body;
const type = normalizeBoardType(body.type);
if (!(await canUpdateBoard(auth, boardId))) {
return unauthorized();
@@ -0,0 +1,73 @@
import { z } from 'zod';
import { ENTITY_TYPE } from '@/lib/constants';
import { uuid } from '@/lib/crypto';
import { getRandomChars } from '@/lib/generate';
import { parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams, pagingParams } from '@/lib/schema';
import { canUpdateBoard, canViewBoard } from '@/permissions';
import { createShare, getSharesByEntityId } from '@/queries/prisma';
export async function GET(
request: Request,
{ params }: { params: Promise<{ boardId: string }> },
) {
const schema = z.object({
...filterParams,
...pagingParams,
});
const { auth, query, error } = await parseRequest(request, schema);
if (error) {
return error();
}
const { boardId } = await params;
const { page, pageSize, search } = query;
if (!(await canViewBoard(auth, boardId))) {
return unauthorized();
}
const data = await getSharesByEntityId(boardId, {
page,
pageSize,
search,
});
return json(data);
}
export async function POST(
request: Request,
{ params }: { params: Promise<{ boardId: string }> },
) {
const schema = z.object({
name: z.string().max(200),
});
const { auth, body, error } = await parseRequest(request, schema);
if (error) {
return error();
}
const { boardId } = await params;
const { name } = body;
if (!(await canUpdateBoard(auth, boardId))) {
return unauthorized();
}
const share = await createShare({
id: uuid(),
entityId: boardId,
shareType: ENTITY_TYPE.board,
name,
slug: getRandomChars(16),
parameters: {},
});
return json(share);
}
+5 -2
View File
@@ -1,5 +1,5 @@
import { z } from 'zod';
import { BOARD_TYPES } from '@/lib/boards';
import { BOARD_TYPES, normalizeBoardType } from '@/lib/boards';
import { uuid } from '@/lib/crypto';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
@@ -28,7 +28,9 @@ export async function GET(request: Request) {
export async function POST(request: Request) {
const schema = z.object({
type: z.enum([BOARD_TYPES.open, BOARD_TYPES.website, BOARD_TYPES.pixel, BOARD_TYPES.link]),
type: z
.enum([BOARD_TYPES.mixed, BOARD_TYPES.website, BOARD_TYPES.pixel, BOARD_TYPES.link])
.or(z.literal('open')),
name: z.string().max(100),
description: z.string().max(500).optional(),
slug: z.string().max(100),
@@ -58,6 +60,7 @@ export async function POST(request: Request) {
const data = {
...body,
type: normalizeBoardType(body.type),
id: uuid(),
parameters: body.parameters ?? {},
slug: uuid(),
+28 -2
View File
@@ -1,11 +1,12 @@
import { ROLES } from '@/lib/constants';
import { ENTITY_TYPE, ROLES } from '@/lib/constants';
import { secret } from '@/lib/crypto';
import { createToken } from '@/lib/jwt';
import { getBoardWebsiteIds } from '@/lib/boards';
import prisma from '@/lib/prisma';
import redis from '@/lib/redis';
import { json, notFound } from '@/lib/response';
import type { WhiteLabel } from '@/lib/types';
import { getShareByCode, getWebsite } from '@/queries/prisma';
import { getBoard, getShareByCode, getWebsite } from '@/queries/prisma';
async function getAccountId(website: { userId?: string; teamId?: string }): Promise<string | null> {
if (website.userId) {
@@ -52,10 +53,35 @@ export async function GET(_request: Request, { params }: { params: Promise<{ slu
return notFound();
}
if (share.shareType === ENTITY_TYPE.board) {
const board = await getBoard(share.entityId);
if (!board) {
return notFound();
}
const data: Record<string, any> = {
shareId: share.id,
shareType: share.shareType,
boardId: share.entityId,
parameters: share.parameters,
websiteIds: getBoardWebsiteIds(board),
};
data.token = createToken(data, secret());
return json(data);
}
const website = await getWebsite(share.entityId);
if (!website) {
return notFound();
}
const data: Record<string, any> = {
shareId: share.id,
shareType: share.shareType,
websiteId: share.entityId,
parameters: share.parameters,
};