fix: read-after-write issues (#215)

* fix: read-after-write issues

* fix: coderabbit comments

* fix: clear cache on invite

* fix: use primary after a read
This commit is contained in:
Carl-Gerhard Lindesvärd
2025-10-31 09:56:07 +01:00
committed by GitHub
parent abacf66155
commit f454449365
19 changed files with 470 additions and 167 deletions

View File

@@ -0,0 +1,96 @@
import { cacheable } from '@openpanel/redis';
import { db } from '../prisma-client';
import { getProjectById } from './project.service';
export const getProjectAccess = cacheable(
'getProjectAccess',
async ({
userId,
projectId,
}: {
userId: string;
projectId: string;
}) => {
try {
// Check if user has access to the project
const project = await getProjectById(projectId);
if (!project?.organizationId) {
return false;
}
const [projectAccess, member] = await Promise.all([
db.$primary().projectAccess.findMany({
where: {
userId,
organizationId: project.organizationId,
},
}),
db.$primary().member.findFirst({
where: {
organizationId: project.organizationId,
userId,
},
}),
]);
if (projectAccess.length === 0 && member) {
return true;
}
return projectAccess.find((item) => item.projectId === projectId);
} catch (err) {
return false;
}
},
60 * 5,
);
export const getOrganizationAccess = cacheable(
'getOrganizationAccess',
async ({
userId,
organizationId,
}: {
userId: string;
organizationId: string;
}) => {
return db.$primary().member.findFirst({
where: {
userId,
organizationId,
},
});
},
60 * 5,
);
export async function getClientAccess({
userId,
clientId,
}: {
userId: string;
clientId: string;
}) {
const client = await db.client.findFirst({
where: {
id: clientId,
},
});
if (!client) {
return false;
}
if (client.projectId) {
return getProjectAccess({ userId, projectId: client.projectId });
}
if (client.organizationId) {
return getOrganizationAccess({
userId,
organizationId: client.organizationId,
});
}
return false;
}

View File

@@ -5,7 +5,8 @@ import { chQuery, formatClickhouseDate } from '../clickhouse/client';
import type { Invite, Prisma, ProjectAccess, User } from '../prisma-client';
import { db } from '../prisma-client';
import { createSqlBuilder } from '../sql-builder';
import type { IServiceProject } from './project.service';
import { getOrganizationAccess, getProjectAccess } from './access.service';
import { type IServiceProject, getProjectById } from './project.service';
export type IServiceOrganization = Awaited<
ReturnType<typeof db.organization.findUniqueOrThrow>
>;
@@ -61,7 +62,7 @@ export async function getOrganizationByProjectId(projectId: string) {
export const getOrganizationByProjectIdCached = cacheable(
getOrganizationByProjectId,
60 * 60 * 24,
60 * 5,
);
export async function getInvites(organizationId: string) {
@@ -168,8 +169,14 @@ export async function connectUserToOrganization({
},
});
await getOrganizationAccess.clear({
userId: user.id,
organizationId: invite.organizationId,
});
if (invite.projectAccess.length > 0) {
for (const projectId of invite.projectAccess) {
await getProjectAccess.clear({ userId: user.id, projectId });
await db.projectAccess.create({
data: {
projectId,