Initial commit: Port Nimara CRM (Layers 0-4)
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>
This commit is contained in:
173
src/lib/services/saved-views.service.ts
Normal file
173
src/lib/services/saved-views.service.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
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;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user