Files
stats/packages/db/src/prisma-client.ts
Carl-Gerhard Lindesvärd 81a7e5d62e feat: dashboard v2, esm, upgrades (#211)
* esm

* wip

* wip

* wip

* wip

* wip

* wip

* subscription notice

* wip

* wip

* wip

* fix envs

* fix: update docker build

* fix

* esm/types

* delete dashboard :D

* add patches to dockerfiles

* update packages + catalogs + ts

* wip

* remove native libs

* ts

* improvements

* fix redirects and fetching session

* try fix favicon

* fixes

* fix

* order and resize reportds within a dashboard

* improvements

* wip

* added userjot to dashboard

* fix

* add op

* wip

* different cache key

* improve date picker

* fix table

* event details loading

* redo onboarding completely

* fix login

* fix

* fix

* extend session, billing and improve bars

* fix

* reduce price on 10M
2025-10-16 12:27:44 +02:00

275 lines
7.7 KiB
TypeScript

import { createLogger } from '@openpanel/logger';
import { readReplicas } from '@prisma/extension-read-replicas';
import { type Organization, PrismaClient } from './generated/prisma/client';
export * from './generated/prisma/client';
const logger = createLogger({ name: 'db' });
const isWillBeCanceled = (
organization: Pick<
Organization,
'subscriptionStatus' | 'subscriptionCanceledAt' | 'subscriptionEndsAt'
>,
) =>
organization.subscriptionStatus === 'active' &&
organization.subscriptionCanceledAt &&
organization.subscriptionEndsAt;
const isCanceled = (
organization: Pick<
Organization,
'subscriptionStatus' | 'subscriptionCanceledAt'
>,
) =>
organization.subscriptionStatus === 'canceled' &&
organization.subscriptionCanceledAt &&
organization.subscriptionCanceledAt < new Date();
const getPrismaClient = () => {
const prisma = new PrismaClient({
log: ['error'],
})
.$extends(
readReplicas({
url: process.env.DATABASE_URL_REPLICA ?? process.env.DATABASE_URL!,
}),
)
.$extends({
query: {
async $allOperations({ operation, model, args, query }) {
if (
operation === 'create' ||
operation === 'update' ||
operation === 'delete'
) {
logger.info('Prisma operation', {
operation,
args,
model,
});
}
return query(args);
},
},
})
.$extends({
result: {
organization: {
subscriptionStatus: {
needs: { subscriptionStatus: true, subscriptionCanceledAt: true },
compute(org) {
if (process.env.VITE_SELF_HOSTED === 'true') {
return 'active';
}
return org.subscriptionStatus || 'trialing';
},
},
hasSubscription: {
needs: { subscriptionStatus: true, subscriptionEndsAt: true },
compute(org) {
if (process.env.VITE_SELF_HOSTED === 'true') {
return false;
}
if (
[null, 'canceled', 'trialing'].includes(org.subscriptionStatus)
) {
return false;
}
return true;
},
},
slug: {
needs: { id: true },
compute(org) {
return org.id;
},
},
subscriptionChartEndDate: {
needs: {
subscriptionEndsAt: true,
subscriptionPeriodEventsCountExceededAt: true,
},
compute(org) {
if (process.env.VITE_SELF_HOSTED === 'true') {
return null;
}
if (
org.subscriptionEndsAt &&
org.subscriptionPeriodEventsCountExceededAt
) {
return org.subscriptionEndsAt >
org.subscriptionPeriodEventsCountExceededAt
? org.subscriptionPeriodEventsCountExceededAt
: org.subscriptionEndsAt;
}
if (org.subscriptionEndsAt) {
return org.subscriptionEndsAt;
}
// Hedge against edge cases :D
return new Date(Date.now() + 1000 * 60 * 60 * 24);
},
},
isTrial: {
needs: { subscriptionStatus: true, subscriptionEndsAt: true },
compute(org) {
const isSubscriptionInFuture =
org.subscriptionEndsAt && org.subscriptionEndsAt > new Date();
return (
(org.subscriptionStatus === 'trialing' ||
org.subscriptionStatus === null) &&
isSubscriptionInFuture
);
},
},
isCanceled: {
needs: { subscriptionStatus: true, subscriptionCanceledAt: true },
compute(org) {
if (process.env.VITE_SELF_HOSTED === 'true') {
return false;
}
return isCanceled(org);
},
},
isWillBeCanceled: {
needs: {
subscriptionStatus: true,
subscriptionCanceledAt: true,
subscriptionEndsAt: true,
},
compute(org) {
if (process.env.VITE_SELF_HOSTED === 'true') {
return false;
}
return isWillBeCanceled(org);
},
},
isExpired: {
needs: {
subscriptionEndsAt: true,
subscriptionStatus: true,
subscriptionCanceledAt: true,
},
compute(org) {
if (process.env.VITE_SELF_HOSTED === 'true') {
return false;
}
if (isCanceled(org)) {
return false;
}
if (isWillBeCanceled(org)) {
return false;
}
return (
org.subscriptionEndsAt && org.subscriptionEndsAt < new Date()
);
},
},
isExceeded: {
needs: {
subscriptionPeriodEventsCount: true,
subscriptionPeriodEventsLimit: true,
},
compute(org) {
if (process.env.VITE_SELF_HOSTED === 'true') {
return false;
}
return (
org.subscriptionPeriodEventsCount >
org.subscriptionPeriodEventsLimit
);
},
},
subscriptionCurrentPeriodStart: {
needs: { subscriptionStartsAt: true, subscriptionInterval: true },
compute(org) {
if (process.env.VITE_SELF_HOSTED === 'true') {
return null;
}
if (!org.subscriptionStartsAt) {
return null;
}
if (org.subscriptionInterval === 'year') {
const startDay = org.subscriptionStartsAt.getUTCDate();
const now = new Date();
return new Date(
Date.UTC(
now.getUTCFullYear(),
now.getUTCMonth(),
startDay,
0,
0,
0,
0,
),
);
}
return org.subscriptionStartsAt;
},
},
subscriptionCurrentPeriodEnd: {
needs: {
subscriptionStartsAt: true,
subscriptionEndsAt: true,
subscriptionInterval: true,
},
compute(org) {
if (process.env.VITE_SELF_HOSTED === 'true') {
return null;
}
if (!org.subscriptionStartsAt) {
return null;
}
if (org.subscriptionInterval === 'year') {
const startDay = org.subscriptionStartsAt.getUTCDate();
const now = new Date();
return new Date(
Date.UTC(
now.getUTCFullYear(),
now.getUTCMonth() + 1,
startDay - 1,
0,
0,
0,
0,
),
);
}
return org.subscriptionEndsAt;
},
},
},
},
});
return prisma;
};
const globalForPrisma = globalThis as unknown as {
prisma: ReturnType<typeof getPrismaClient>;
};
export const db = globalForPrisma.prisma ?? getPrismaClient();
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = db;
}