Files
stats/packages/trpc/src/routers/import.ts
Carl-Gerhard Lindesvärd 9cafd61b25 feat: new billing and restrict access when trial has ended
* fix: simply billing

* fix usage graph

* imporve billing more + supporter prompt on self-hosting

* revert service change

* revert query builder

* fix: comments
2025-11-11 11:09:11 +01:00

205 lines
5.0 KiB
TypeScript

import { z } from 'zod';
import { db } from '@openpanel/db';
import { importQueue } from '@openpanel/queue';
import { zCreateImport } from '@openpanel/validation';
import { getProjectAccess } from '../access';
import {
TRPCAccessError,
TRPCBadRequestError,
TRPCNotFoundError,
} from '../errors';
import { createTRPCRouter, protectedProcedure } from '../trpc';
export const importRouter = createTRPCRouter({
list: protectedProcedure
.input(z.object({ projectId: z.string() }))
.query(async ({ input, ctx }) => {
const access = await getProjectAccess({
projectId: input.projectId,
userId: ctx.session.userId,
});
if (!access) {
throw TRPCAccessError('You do not have access to this project');
}
return db.import.findMany({
where: {
projectId: input.projectId,
},
orderBy: {
createdAt: 'desc',
},
});
}),
get: protectedProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input, ctx }) => {
const importRecord = await db.import.findUniqueOrThrow({
where: {
id: input.id,
},
include: {
project: true,
},
});
const access = await getProjectAccess({
projectId: importRecord.projectId,
userId: ctx.session.userId,
});
if (!access) {
throw TRPCAccessError('You do not have access to this import');
}
return importRecord;
}),
create: protectedProcedure
.input(zCreateImport)
.mutation(async ({ input, ctx }) => {
const access = await getProjectAccess({
projectId: input.projectId,
userId: ctx.session.userId,
});
if (!access || (typeof access !== 'boolean' && access.level === 'read')) {
throw TRPCAccessError(
'You do not have permission to create imports for this project',
);
}
const organization = await db.organization.findFirst({
where: {
projects: {
some: {
id: input.projectId,
},
},
},
});
if (!organization) {
throw TRPCNotFoundError(
'Could not start import, organization not found',
);
}
if (!organization.isActive) {
throw TRPCBadRequestError(
'You cannot start an import without an active subscription!',
);
}
// Create import record
const importRecord = await db.import.create({
data: {
projectId: input.projectId,
config: input.config,
status: 'pending',
},
});
// Add job to queue
const job = await importQueue.add('import', {
type: 'import',
payload: {
importId: importRecord.id,
},
});
// Update import record with job ID
await db.import.update({
where: { id: importRecord.id },
data: { jobId: job.id },
});
return {
...importRecord,
jobId: job.id,
};
}),
delete: protectedProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ input, ctx }) => {
const importRecord = await db.import.findUniqueOrThrow({
where: {
id: input.id,
},
});
const access = await getProjectAccess({
projectId: importRecord.projectId,
userId: ctx.session.userId,
});
if (!access || (typeof access !== 'boolean' && access.level === 'read')) {
throw TRPCAccessError(
'You do not have permission to delete imports for this project',
);
}
if (importRecord.jobId) {
const job = await importQueue.getJob(importRecord.jobId);
if (job) {
await job.remove();
}
}
return db.import.delete({
where: {
id: input.id,
},
});
}),
retry: protectedProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ input, ctx }) => {
const importRecord = await db.import.findUniqueOrThrow({
where: {
id: input.id,
},
});
const access = await getProjectAccess({
projectId: importRecord.projectId,
userId: ctx.session.userId,
});
if (!access || (typeof access !== 'boolean' && access.level === 'read')) {
throw TRPCAccessError(
'You do not have permission to retry imports for this project',
);
}
// Only allow retry for failed imports
if (importRecord.status !== 'failed') {
throw new Error('Only failed imports can be retried');
}
// Add new job to queue
const job = await importQueue.add('import', {
type: 'import',
payload: {
importId: importRecord.id,
},
});
// Update import record
return db.import.update({
where: { id: importRecord.id },
data: {
jobId: job.id,
status: 'pending',
errorMessage: null,
},
});
}),
});