Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "TeamMember" ADD COLUMN "displayName" TEXT,
ADD COLUMN "profileImageUrl" TEXT;
4 changes: 3 additions & 1 deletion 4 apps/backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ model TeamMember {
projectUserId String @db.Uuid
teamId String @db.Uuid

displayName String?
profileImageUrl String?

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

Expand Down Expand Up @@ -539,7 +542,6 @@ enum StandardOAuthProviderType {

//#endregion


//#region Events

model Event {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const POST = createSmartRouteHandler({
},
},
});
console.log("AAAAAAAAA", sessionObj);

if (!sessionObj || (sessionObj.expiresAt && sessionObj.expiresAt < new Date())) {
throw new KnownErrors.RefreshTokenNotFoundOrExpired();
}
Expand Down
5 changes: 3 additions & 2 deletions 5 apps/backend/src/app/api/v1/email-templates/crud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { emailTemplateCrud, emailTemplateTypes } from "@stackframe/stack-shared/
import { yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { StatusError } from "@stackframe/stack-shared/dist/utils/errors";
import { typedEntries } from "@stackframe/stack-shared/dist/utils/objects";
import { createLazyProxy } from "@stackframe/stack-shared/dist/utils/proxies";
import { typedToLowercase, typedToUppercase } from "@stackframe/stack-shared/dist/utils/strings";


Expand All @@ -19,7 +20,7 @@ function prismaToCrud(prisma: Prisma.EmailTemplateGetPayload<{}>, isDefault: boo
};
}

export const emailTemplateCrudHandlers = createCrudHandlers(emailTemplateCrud, {
export const emailTemplateCrudHandlers = createLazyProxy(() => createCrudHandlers(emailTemplateCrud, {
paramsSchema: yupObject({
type: yupString().oneOf(emailTemplateTypes).required(),
}),
Expand Down Expand Up @@ -123,4 +124,4 @@ export const emailTemplateCrudHandlers = createCrudHandlers(emailTemplateCrud, {
is_paginated: false,
};
}
});
}));
5 changes: 3 additions & 2 deletions 5 apps/backend/src/app/api/v1/internal/api-keys/crud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { KnownErrors } from "@stackframe/stack-shared";
import { apiKeysCrud } from "@stackframe/stack-shared/dist/interface/crud/api-keys";
import { yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { createLazyProxy } from "@stackframe/stack-shared/dist/utils/proxies";

export const apiKeyCrudHandlers = createPrismaCrudHandlers(apiKeysCrud, "apiKeySet", {
export const apiKeyCrudHandlers = createLazyProxy(() => createPrismaCrudHandlers(apiKeysCrud, "apiKeySet", {
paramsSchema: yupObject({
api_key_id: yupString().uuid().required(),
}),
Expand Down Expand Up @@ -68,4 +69,4 @@ export const apiKeyCrudHandlers = createPrismaCrudHandlers(apiKeysCrud, "apiKeyS
manually_revoked_at_millis: prisma.manuallyRevokedAt?.getTime(),
};
},
});
}));
5 changes: 3 additions & 2 deletions 5 apps/backend/src/app/api/v1/internal/projects/crud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { KnownErrors } from "@stackframe/stack-shared";
import { internalProjectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects";
import { projectIdSchema, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { StackAssertionError, 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";

export const internalProjectsCrudHandlers = createCrudHandlers(internalProjectsCrud, {
export const internalProjectsCrudHandlers = createLazyProxy(() => createCrudHandlers(internalProjectsCrud, {
paramsSchema: yupObject({
projectId: projectIdSchema.required(),
}),
Expand Down Expand Up @@ -177,4 +178,4 @@ export const internalProjectsCrudHandlers = createCrudHandlers(internalProjectsC
is_paginated: false,
} as const;
}
});
}));
5 changes: 3 additions & 2 deletions 5 apps/backend/src/app/api/v1/projects/current/crud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { createCrudHandlers } from "@/route-handlers/crud-handler";
import { projectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects";
import { yupObject } from "@stackframe/stack-shared/dist/schema-fields";
import { StatusError, 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";

export const projectsCrudHandlers = createCrudHandlers(projectsCrud, {
export const projectsCrudHandlers = createLazyProxy(() => createCrudHandlers(projectsCrud, {
paramsSchema: yupObject({}),
onUpdate: async ({ auth, data }) => {
const oldProject = auth.project;
Expand Down Expand Up @@ -271,4 +272,4 @@ export const projectsCrudHandlers = createCrudHandlers(projectsCrud, {
onRead: async ({ auth }) => {
return auth.project;
},
});
}));
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { teamMemberProfilesCrudHandlers } from "../../crud";

export const GET = teamMemberProfilesCrudHandlers.readHandler;
export const PATCH = teamMemberProfilesCrudHandlers.updateHandler;
149 changes: 149 additions & 0 deletions 149 apps/backend/src/app/api/v1/team-member-profiles/crud.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { ensureTeamExist, ensureTeamMembershipExist, ensureUserExist, ensureUserHasTeamPermission } from "@/lib/request-checks";
import { prismaClient } from "@/prisma-client";
import { createCrudHandlers } from "@/route-handlers/crud-handler";
import { getIdFromUserIdOrMe } from "@/route-handlers/utils";
import { Prisma } from "@prisma/client";
import { KnownErrors } from "@stackframe/stack-shared";
import { teamMemberProfilesCrud } from "@stackframe/stack-shared/dist/interface/crud/team-member-profiles";
import { userIdOrMeSchema, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { StatusError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { createLazyProxy } from "@stackframe/stack-shared/dist/utils/proxies";

const fullInclude = { projectUser: true };

function prismaToCrud(prisma: Prisma.TeamMemberGetPayload<{ include: typeof fullInclude }>) {
return {
team_id: prisma.teamId,
user_id: prisma.projectUserId,
display_name: prisma.displayName ?? prisma.projectUser.displayName,
profile_image_url: prisma.profileImageUrl ?? prisma.projectUser.profileImageUrl,
};
}

export const teamMemberProfilesCrudHandlers = createLazyProxy(() => createCrudHandlers(teamMemberProfilesCrud, {
querySchema: yupObject({
user_id: userIdOrMeSchema.optional().meta({ openapiField: { onlyShowInOperations: ['List'] }}),
team_id: yupString().uuid().optional().meta({ openapiField: { onlyShowInOperations: ['List'] }}),
}),
paramsSchema: yupObject({
team_id: yupString().uuid().required(),
user_id: userIdOrMeSchema.required(),
}),
onList: async ({ auth, query }) => {
return await prismaClient.$transaction(async (tx) => {
const userId = getIdFromUserIdOrMe(query.user_id, auth.user);
if (auth.type === 'client') {
// Client can only:
// - list users in their own team if they have the $read_members permission
// - list their own profile

const currentUserId = auth.user?.id ?? throwErr("Client must be authenticated");

if (!query.team_id) {
throw new StatusError(StatusError.BadRequest, 'team_id is required for access type client');
}

await ensureTeamMembershipExist(tx, { projectId: auth.project.id, teamId: query.team_id, userId: currentUserId });

if (userId !== currentUserId) {
await ensureUserHasTeamPermission(tx, {
project: auth.project,
teamId: query.team_id,
userId: currentUserId,
permissionId: '$read_members',
});
}
} else {
if (query.team_id) {
await ensureTeamExist(tx, { projectId: auth.project.id, teamId: query.team_id });
}
if (userId) {
await ensureUserExist(tx, { projectId: auth.project.id, userId: userId });
}
}

const db = await tx.teamMember.findMany({
where: {
projectId: auth.project.id,
teamId: query.team_id,
projectUserId: userId,
},
orderBy: {
createdAt: 'asc',
},
include: fullInclude,
});

return {
items: db.map(prismaToCrud),
is_paginated: false,
};
});
},
onRead: async ({ auth, params }) => {
return await prismaClient.$transaction(async (tx) => {
const userId = getIdFromUserIdOrMe(params.user_id, auth.user);

if (auth.type === 'client' && userId !== auth.user?.id) {
await ensureUserHasTeamPermission(tx, {
project: auth.project,
teamId: params.team_id,
userId: auth.user?.id ?? throwErr("Client must be authenticated"),
permissionId: '$read_members',
});
}

await ensureTeamMembershipExist(tx, { projectId: auth.project.id, teamId: params.team_id, userId: userId });

const db = await tx.teamMember.findUnique({
where: {
projectId_projectUserId_teamId: {
projectId: auth.project.id,
projectUserId: userId,
teamId: params.team_id,
},
},
include: fullInclude,
});

if (!db) {
// This should never happen because of the check above
throw new KnownErrors.TeamMembershipNotFound(params.team_id, userId);
}

return prismaToCrud(db);
});
},
onUpdate: async ({ auth, data, params }) => {
return await prismaClient.$transaction(async (tx) => {
const userId = getIdFromUserIdOrMe(params.user_id, auth.user);

if (auth.type === 'client' && userId !== auth.user?.id) {
throw new StatusError(StatusError.Forbidden, 'Cannot update another user\'s profile');
}

await ensureTeamMembershipExist(tx, {
projectId: auth.project.id,
teamId: params.team_id,
userId: auth.user?.id ?? throwErr("Client must be authenticated"),
});

const db = await tx.teamMember.update({
where: {
projectId_projectUserId_teamId: {
projectId: auth.project.id,
projectUserId: userId,
teamId: params.team_id,
},
},
data: {
displayName: data.display_name,
profileImageUrl: data.profile_image_url,
},
include: fullInclude,
});

return prismaToCrud(db);
});
},
}));
3 changes: 3 additions & 0 deletions 3 apps/backend/src/app/api/v1/team-member-profiles/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { teamMemberProfilesCrudHandlers } from "./crud";

export const GET = teamMemberProfilesCrudHandlers.listHandler;
88 changes: 61 additions & 27 deletions 88 apps/backend/src/app/api/v1/team-memberships/crud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,51 @@ import { getIdFromUserIdOrMe } from "@/route-handlers/utils";
import { teamMembershipsCrud } from "@stackframe/stack-shared/dist/interface/crud/team-memberships";
import { userIdOrMeSchema, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { KnownErrors } from "@stackframe/stack-shared";
import { ProjectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects";
import { PrismaTransaction } from "@/lib/types";
import { createLazyProxy } from "@stackframe/stack-shared/dist/utils/proxies";


export const teamMembershipsCrudHandlers = createCrudHandlers(teamMembershipsCrud, {
export async function addUserToTeam(tx: PrismaTransaction, options: {
project: ProjectsCrud['Admin']['Read'],
teamId: string,
userId: string,
type: 'member' | 'creator',
}) {
const permissionAttributeName = options.type === 'creator' ? 'team_creator_default_permissions' : 'team_member_default_permissions';

await tx.teamMember.create({
data: {
projectUserId: options.userId,
teamId: options.teamId,
projectId: options.project.id,
directPermissions: {
create: options.project.config[permissionAttributeName].map((p) => {
if (isTeamSystemPermission(p.id)) {
return {
systemPermission: teamSystemPermissionStringToDBType(p.id),
};
} else {
return {
permission: {
connect: {
projectConfigId_queryableId: {
projectConfigId: options.project.config.id,
queryableId: p.id,
},
}
}
};
}
}),
}
},
});
}


export const teamMembershipsCrudHandlers = createLazyProxy(() => createCrudHandlers(teamMembershipsCrud, {
paramsSchema: yupObject({
team_id: yupString().uuid().required(),
user_id: userIdOrMeSchema.required(),
Expand All @@ -28,33 +70,25 @@ export const teamMembershipsCrudHandlers = createCrudHandlers(teamMembershipsCru
userId,
});

await tx.teamMember.create({
data: {
projectUserId: userId,
teamId: params.team_id,
projectId: auth.project.id,
directPermissions: {
create: auth.project.config.team_member_default_permissions.map((p) => {
if (isTeamSystemPermission(p.id)) {
return {
systemPermission: teamSystemPermissionStringToDBType(p.id),
};
} else {
return {
permission: {
connect: {
projectConfigId_queryableId: {
projectConfigId: auth.project.config.id,
queryableId: p.id,
},
}
}
};
}
}),
}
const user = await tx.projectUser.findUnique({
where: {
projectId_projectUserId: {
projectId: auth.project.id,
projectUserId: userId,
},
},
});

if (!user) {
throw new KnownErrors.UserNotFound();
}

await addUserToTeam(tx, {
project: auth.project,
teamId: params.team_id,
userId,
type: 'member',
});
});

return {};
Expand Down Expand Up @@ -85,4 +119,4 @@ export const teamMembershipsCrudHandlers = createCrudHandlers(teamMembershipsCru
});
});
},
});
}));
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { createTeamPermissionDefinition, deleteTeamPermissionDefinition, listTea
import { prismaClient } from "@/prisma-client";
import { createCrudHandlers } from "@/route-handlers/crud-handler";
import { teamPermissionDefinitionsCrud } from '@stackframe/stack-shared/dist/interface/crud/team-permissions';
import { teamPermissionDefinitionIdSchema, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { teamPermissionDefinitionIdSchema, yupObject } from "@stackframe/stack-shared/dist/schema-fields";
import { createLazyProxy } from "@stackframe/stack-shared/dist/utils/proxies";

export const teamPermissionDefinitionsCrudHandlers = createCrudHandlers(teamPermissionDefinitionsCrud, {
export const teamPermissionDefinitionsCrudHandlers = createLazyProxy(() => createCrudHandlers(teamPermissionDefinitionsCrud, {
paramsSchema: yupObject({
permission_id: teamPermissionDefinitionIdSchema.required(),
}),
Expand Down Expand Up @@ -41,4 +42,4 @@ export const teamPermissionDefinitionsCrudHandlers = createCrudHandlers(teamPerm
};
});
},
});
}));
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.