Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM, PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source files covering clients, berths, interests/pipeline, documents/EOI, expenses/invoices, email, notifications, dashboard, admin, and client portal. CI/CD via Gitea Actions with Docker builds. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
174 lines
4.7 KiB
TypeScript
174 lines
4.7 KiB
TypeScript
import { and, eq } from 'drizzle-orm';
|
|
|
|
import { db } from '@/lib/db';
|
|
import { savedViews } from '@/lib/db/schema';
|
|
import { NotFoundError } from '@/lib/errors';
|
|
import type { CreateSavedViewInput, UpdateSavedViewInput } from '@/lib/validators/saved-views';
|
|
|
|
export const savedViewsService = {
|
|
async list(portId: string, userId: string, entityType?: string) {
|
|
const conditions = [
|
|
eq(savedViews.portId, portId),
|
|
eq(savedViews.userId, userId),
|
|
];
|
|
if (entityType) {
|
|
conditions.push(eq(savedViews.entityType, entityType));
|
|
}
|
|
return db
|
|
.select()
|
|
.from(savedViews)
|
|
.where(and(...conditions));
|
|
},
|
|
|
|
async create(portId: string, userId: string, data: CreateSavedViewInput) {
|
|
if (data.isDefault) {
|
|
await db
|
|
.update(savedViews)
|
|
.set({ isDefault: false })
|
|
.where(
|
|
and(
|
|
eq(savedViews.portId, portId),
|
|
eq(savedViews.userId, userId),
|
|
eq(savedViews.entityType, data.entityType),
|
|
eq(savedViews.isDefault, true),
|
|
),
|
|
);
|
|
}
|
|
|
|
const [view] = await db
|
|
.insert(savedViews)
|
|
.values({
|
|
portId,
|
|
userId,
|
|
entityType: data.entityType,
|
|
name: data.name,
|
|
filters: data.filters ?? {},
|
|
sortConfig: data.sortConfig ?? null,
|
|
columnConfig: data.columnConfig ?? null,
|
|
isShared: data.isShared ?? false,
|
|
isDefault: data.isDefault ?? false,
|
|
})
|
|
.returning();
|
|
|
|
return view;
|
|
},
|
|
|
|
async update(portId: string, userId: string, viewId: string, data: UpdateSavedViewInput) {
|
|
const existing = await db.query.savedViews.findFirst({
|
|
where: and(
|
|
eq(savedViews.id, viewId),
|
|
eq(savedViews.portId, portId),
|
|
eq(savedViews.userId, userId),
|
|
),
|
|
});
|
|
|
|
if (!existing) {
|
|
throw new NotFoundError('Saved view');
|
|
}
|
|
|
|
if (data.isDefault) {
|
|
const entityType = data.entityType ?? existing.entityType;
|
|
await db
|
|
.update(savedViews)
|
|
.set({ isDefault: false })
|
|
.where(
|
|
and(
|
|
eq(savedViews.portId, portId),
|
|
eq(savedViews.userId, userId),
|
|
eq(savedViews.entityType, entityType),
|
|
eq(savedViews.isDefault, true),
|
|
),
|
|
);
|
|
}
|
|
|
|
const [updated] = await db
|
|
.update(savedViews)
|
|
.set({
|
|
...(data.name !== undefined && { name: data.name }),
|
|
...(data.entityType !== undefined && { entityType: data.entityType }),
|
|
...(data.filters !== undefined && { filters: data.filters }),
|
|
...(data.sortConfig !== undefined && { sortConfig: data.sortConfig }),
|
|
...(data.columnConfig !== undefined && { columnConfig: data.columnConfig }),
|
|
...(data.isShared !== undefined && { isShared: data.isShared }),
|
|
...(data.isDefault !== undefined && { isDefault: data.isDefault }),
|
|
updatedAt: new Date(),
|
|
})
|
|
.where(
|
|
and(
|
|
eq(savedViews.id, viewId),
|
|
eq(savedViews.portId, portId),
|
|
eq(savedViews.userId, userId),
|
|
),
|
|
)
|
|
.returning();
|
|
|
|
return updated;
|
|
},
|
|
|
|
async delete(portId: string, userId: string, viewId: string) {
|
|
const existing = await db.query.savedViews.findFirst({
|
|
where: and(
|
|
eq(savedViews.id, viewId),
|
|
eq(savedViews.portId, portId),
|
|
eq(savedViews.userId, userId),
|
|
),
|
|
});
|
|
|
|
if (!existing) {
|
|
throw new NotFoundError('Saved view');
|
|
}
|
|
|
|
await db
|
|
.delete(savedViews)
|
|
.where(
|
|
and(
|
|
eq(savedViews.id, viewId),
|
|
eq(savedViews.portId, portId),
|
|
eq(savedViews.userId, userId),
|
|
),
|
|
);
|
|
},
|
|
|
|
async setDefault(portId: string, userId: string, entityType: string, viewId: string) {
|
|
const existing = await db.query.savedViews.findFirst({
|
|
where: and(
|
|
eq(savedViews.id, viewId),
|
|
eq(savedViews.portId, portId),
|
|
eq(savedViews.userId, userId),
|
|
eq(savedViews.entityType, entityType),
|
|
),
|
|
});
|
|
|
|
if (!existing) {
|
|
throw new NotFoundError('Saved view');
|
|
}
|
|
|
|
// Unset any existing default for this entityType + user + port
|
|
await db
|
|
.update(savedViews)
|
|
.set({ isDefault: false })
|
|
.where(
|
|
and(
|
|
eq(savedViews.portId, portId),
|
|
eq(savedViews.userId, userId),
|
|
eq(savedViews.entityType, entityType),
|
|
eq(savedViews.isDefault, true),
|
|
),
|
|
);
|
|
|
|
const [updated] = await db
|
|
.update(savedViews)
|
|
.set({ isDefault: true, updatedAt: new Date() })
|
|
.where(
|
|
and(
|
|
eq(savedViews.id, viewId),
|
|
eq(savedViews.portId, portId),
|
|
eq(savedViews.userId, userId),
|
|
),
|
|
)
|
|
.returning();
|
|
|
|
return updated;
|
|
},
|
|
};
|