Project owner packs

This commit is contained in:
Konstantin Wohlwend 2024-08-21 12:55:25 -07:00
parent 82e0cf505a
commit 448c45fca4
3 changed files with 56 additions and 27 deletions

View File

@ -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 },

View File

@ -192,11 +192,13 @@ export const getCommonUserColumns = <T extends ExtendedServerUser>() => [
accessorKey: "id",
header: ({ column }) => <DataTableColumnHeader column={column} columnTitle="ID" />,
cell: ({ row }) => <TextCell size={60}>{row.original.id}</TextCell>,
enableGlobalFilter: true,
},
{
accessorKey: "displayName",
header: ({ column }) => <DataTableColumnHeader column={column} columnTitle="Display Name" />,
cell: ({ row }) => <TextCell size={120}>{row.original.displayName}</TextCell>,
cell: ({ row }) => <TextCell size={120}><span className={row.original.displayName === null ? 'text-slate-400' : ''}>{row.original.displayName ?? ''}</span></TextCell>,
enableGlobalFilter: true,
},
{
accessorKey: "primaryEmail",
@ -206,12 +208,14 @@ export const getCommonUserColumns = <T extends ExtendedServerUser>() => [
icon={row.original.emailVerified === "unverified" && <SimpleTooltip tooltip='Email not verified' type='warning'/>}>
{row.original.primaryEmail}
</TextCell>,
enableGlobalFilter: true,
},
{
accessorKey: "emailVerified",
header: ({ column }) => <DataTableColumnHeader column={column} columnTitle="Email Verified" />,
cell: ({ row }) => <TextCell>{row.original.emailVerified === 'verified' ? '✓' : '✗'}</TextCell>,
filterFn: standardFilterFn
filterFn: standardFilterFn,
enableGlobalFilter: false,
},
] satisfies ColumnDef<T>[];

View File

@ -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<TData, TValue> {
columns: ColumnDef<TData, TValue>[],
@ -45,7 +47,7 @@ export function DataTable<TData, TValue>({
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
const [sorting, setSorting] = React.useState<SortingState>([]);
const table = useReactTable({
const table: TableType<TData> = useReactTable({
data,
columns,
state: {
@ -59,6 +61,7 @@ export function DataTable<TData, TValue>({
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
getColumnCanGlobalFilter: (c) => c.columnDef.enableGlobalFilter ?? GlobalFiltering.getDefaultOptions!(table).getColumnCanGlobalFilter!(c),
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),