From 448c45fca49086c63c0581970bbbdac1c4ab0973 Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Wed, 21 Aug 2024 12:55:25 -0700 Subject: [PATCH] Project owner packs --- .../src/app/api/v1/internal/projects/crud.tsx | 70 ++++++++++++------- .../src/components/data-table/user-table.tsx | 8 ++- .../src/components/data-table/data-table.tsx | 5 +- 3 files changed, 56 insertions(+), 27 deletions(-) diff --git a/apps/backend/src/app/api/v1/internal/projects/crud.tsx b/apps/backend/src/app/api/v1/internal/projects/crud.tsx index 7389fee9b..3d61e8705 100644 --- a/apps/backend/src/app/api/v1/internal/projects/crud.tsx +++ b/apps/backend/src/app/api/v1/internal/projects/crud.tsx @@ -5,11 +5,20 @@ import { createCrudHandlers } from "@/route-handlers/crud-handler"; import { KnownErrors } from "@stackframe/stack-shared"; import { internalProjectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects"; import { projectIdSchema, yupObject } from "@stackframe/stack-shared/dist/schema-fields"; -import { StackAssertionError, throwErr } from "@stackframe/stack-shared/dist/utils/errors"; +import { StackAssertionError, captureError, throwErr } from "@stackframe/stack-shared/dist/utils/errors"; import { createLazyProxy } from "@stackframe/stack-shared/dist/utils/proxies"; import { typedToUppercase } from "@stackframe/stack-shared/dist/utils/strings"; import { generateUuid } from "@stackframe/stack-shared/dist/utils/uuids"; +// if one of these users creates a project, the others will be added as owners +const ownerPacks = [ + new Set([ + "c2c03bd1-5cbe-4493-8e3f-17d1e2d7ca43", + "60b859bf-e148-4eff-9985-fe6e31c58a2a", + "1343e3e7-dd7a-44a1-8752-701c0881da72", + ]), +]; + export const internalProjectsCrudHandlers = createLazyProxy(() => createCrudHandlers(internalProjectsCrud, { paramsSchema: yupObject({ projectId: projectIdSchema.required(), @@ -24,6 +33,8 @@ export const internalProjectsCrudHandlers = createLazyProxy(() => createCrudHand }, onCreate: async ({ auth, data }) => { const user = auth.user ?? throwErr('auth.user is required'); + const ownerPack = ownerPacks.find(p => p.has(user.id)); + const userIds = ownerPack ? [...ownerPack] : [user.id]; const result = await prismaClient.$transaction(async (tx) => { const project = await tx.project.create({ @@ -126,34 +137,45 @@ export const internalProjectsCrudHandlers = createLazyProxy(() => createCrudHand }, }); - const projectUserTx = await tx.projectUser.findUniqueOrThrow({ - where: { - projectId_projectUserId: { - projectId: "internal", - projectUserId: user.id, + // Update owner metadata + for (const userId of userIds) { + const projectUserTx = await tx.projectUser.findUnique({ + where: { + projectId_projectUserId: { + projectId: "internal", + projectUserId: userId, + }, }, - }, - }); + }); + if (!projectUserTx) { + if (userId === user.id) { + throw new StackAssertionError(`User with id '${userId}' not found after creation`, { user }); + } else { + captureError("project-creation-owner-packs", new StackAssertionError(`User ${userId} in owner pack not found. The user that created this project is in an owner pack, but no user with that ID was found. Did they delete their account? Continuing silently, but you should probably update the owner pack.`, { userId, creator: user })); + continue; + } + } - const serverMetadataTx: any = projectUserTx.serverMetadata ?? {}; + const serverMetadataTx: any = projectUserTx.serverMetadata ?? {}; - await tx.projectUser.update({ - where: { - projectId_projectUserId: { - projectId: "internal", - projectUserId: projectUserTx.projectUserId, + await tx.projectUser.update({ + where: { + projectId_projectUserId: { + projectId: "internal", + projectUserId: projectUserTx.projectUserId, + }, }, - }, - data: { - serverMetadata: { - ...serverMetadataTx ?? {}, - managedProjectIds: [ - ...serverMetadataTx?.managedProjectIds ?? [], - project.id, - ], + data: { + serverMetadata: { + ...serverMetadataTx ?? {}, + managedProjectIds: [ + ...serverMetadataTx?.managedProjectIds ?? [], + project.id, + ], + }, }, - }, - }); + }); + } const result = await tx.project.findUnique({ where: { id: project.id }, diff --git a/apps/dashboard/src/components/data-table/user-table.tsx b/apps/dashboard/src/components/data-table/user-table.tsx index 940e39838..f4d66f3e0 100644 --- a/apps/dashboard/src/components/data-table/user-table.tsx +++ b/apps/dashboard/src/components/data-table/user-table.tsx @@ -192,11 +192,13 @@ export const getCommonUserColumns = () => [ accessorKey: "id", header: ({ column }) => , cell: ({ row }) => {row.original.id}, + enableGlobalFilter: true, }, { accessorKey: "displayName", header: ({ column }) => , - cell: ({ row }) => {row.original.displayName}, + cell: ({ row }) => {row.original.displayName ?? '–'}, + enableGlobalFilter: true, }, { accessorKey: "primaryEmail", @@ -206,12 +208,14 @@ export const getCommonUserColumns = () => [ icon={row.original.emailVerified === "unverified" && }> {row.original.primaryEmail} , + enableGlobalFilter: true, }, { accessorKey: "emailVerified", header: ({ column }) => , cell: ({ row }) => {row.original.emailVerified === 'verified' ? '✓' : '✗'}, - filterFn: standardFilterFn + filterFn: standardFilterFn, + enableGlobalFilter: false, }, ] satisfies ColumnDef[]; diff --git a/packages/stack-ui/src/components/data-table/data-table.tsx b/packages/stack-ui/src/components/data-table/data-table.tsx index 30b1f7440..8e29bbba5 100644 --- a/packages/stack-ui/src/components/data-table/data-table.tsx +++ b/packages/stack-ui/src/components/data-table/data-table.tsx @@ -11,6 +11,7 @@ import { import { ColumnDef, ColumnFiltersState, + GlobalFiltering, SortingState, Table as TableType, VisibilityState, @@ -26,6 +27,7 @@ import { import React from "react"; import { DataTablePagination } from "./pagination"; import { DataTableToolbar } from "./toolbar"; +import { ColumnFilter } from "@tanstack/react-table"; interface DataTableProps { columns: ColumnDef[], @@ -45,7 +47,7 @@ export function DataTable({ const [columnFilters, setColumnFilters] = React.useState([]); const [sorting, setSorting] = React.useState([]); - const table = useReactTable({ + const table: TableType = useReactTable({ data, columns, state: { @@ -59,6 +61,7 @@ export function DataTable({ onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, onColumnVisibilityChange: setColumnVisibility, + getColumnCanGlobalFilter: (c) => c.columnDef.enableGlobalFilter ?? GlobalFiltering.getDefaultOptions!(table).getColumnCanGlobalFilter!(c), getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(),