506 lines
14 KiB
TypeScript
506 lines
14 KiB
TypeScript
import {
|
|
hardDeleteRowById,
|
|
updateRowWithValues,
|
|
type LegacyRowWithDetails,
|
|
type RowMutationData,
|
|
type RowMutationValueInput,
|
|
type RowMutationRelationshipChildInput,
|
|
type RowMutationRelationshipParentInput,
|
|
} from "@toolctrl/db/rows.server";
|
|
import {
|
|
Prisma,
|
|
Row,
|
|
Tenant,
|
|
RowValue,
|
|
ApiKey,
|
|
RowMedia,
|
|
RowRelationship,
|
|
SampleCustomEntity,
|
|
RowPermission,
|
|
RowValueMultiple,
|
|
RowValueRange,
|
|
} from "@toolctrl/db/prisma-browser";
|
|
import { RowFiltersDto } from "~/application/dtos/data/RowFiltersDto";
|
|
import { MediaDto } from "~/application/dtos/entities/MediaDto";
|
|
import { deleteRowMediaFromStorageProvider, storeRowMediaInStorageProvider } from "~/utils/helpers/.server/MediaService";
|
|
import RowFiltersHelper from "~/utils/helpers/RowFiltersHelper";
|
|
import TenantHelper from "~/utils/helpers/TenantHelper";
|
|
import { db } from "../../db.server";
|
|
import { UserSimple } from "../users.db.server";
|
|
import { EntityWithDetails, getEntityById } from "./entities.db.server";
|
|
import { RowTagWithDetails } from "./rowTags.db.server";
|
|
import { getRowPermissionsCondition } from "~/utils/helpers/.server/PermissionsService";
|
|
import { RowValueMultipleDto } from "~/application/dtos/entities/RowValueMultipleDto";
|
|
import { RowValueRangeDto } from "~/application/dtos/entities/RowValueRangeDto";
|
|
import RowModelHelper from "~/utils/helpers/models/RowModelHelper";
|
|
|
|
export type RowValueWithDetails = RowValue & {
|
|
media: RowMedia[];
|
|
multiple: RowValueMultiple[];
|
|
range: RowValueRange | null;
|
|
};
|
|
export type RowWithValues = Row & {
|
|
createdByUser: UserSimple | null;
|
|
createdByApiKey: ApiKey | null;
|
|
values: RowValueWithDetails[];
|
|
};
|
|
export type RowWithCreatedBy = Row & {
|
|
createdByUser: UserSimple | null;
|
|
createdByApiKey: ApiKey | null;
|
|
};
|
|
export type RowWithDetails = Row & {
|
|
createdByUser: UserSimple | null;
|
|
createdByApiKey: ApiKey | null;
|
|
tenant: Tenant | null;
|
|
values: RowValueWithDetails[];
|
|
tags: RowTagWithDetails[];
|
|
parentRows:
|
|
| (RowRelationship & { parent: RowWithValues })[]
|
|
| (RowRelationship & {
|
|
parent: RowWithValues & {
|
|
parentRows: (RowRelationship & {
|
|
parent: { values: RowValueWithDetails[] };
|
|
})[];
|
|
childRows: (RowRelationship & {
|
|
child: { values: RowValueWithDetails[] };
|
|
})[];
|
|
};
|
|
})[];
|
|
childRows: (RowRelationship & {
|
|
child: RowWithValues & {
|
|
parentRows: (RowRelationship & {
|
|
parent: { values: RowValueWithDetails[] };
|
|
})[];
|
|
childRows: (RowRelationship & {
|
|
child: { values: RowValueWithDetails[] };
|
|
})[];
|
|
};
|
|
})[];
|
|
permissions: RowPermission[];
|
|
sampleCustomEntity: SampleCustomEntity | null;
|
|
};
|
|
export type RowMutationScope = { id: string; entityId: string; tenantId: string | null };
|
|
|
|
export async function getAllRows(entityId: string): Promise<RowWithDetails[]> {
|
|
return await db.row.findMany({
|
|
where: {
|
|
deletedAt: null,
|
|
entityId,
|
|
},
|
|
include: RowModelHelper.includeRowDetails,
|
|
});
|
|
}
|
|
|
|
export async function getRows({
|
|
entityId,
|
|
entityName,
|
|
tenantId,
|
|
|
|
userId,
|
|
take,
|
|
skip,
|
|
orderBy,
|
|
filters,
|
|
ids,
|
|
rowWhere,
|
|
includePublic,
|
|
}: {
|
|
entityId?: string;
|
|
entityName?: string;
|
|
tenantId?: string | null;
|
|
|
|
userId?: string;
|
|
take?: number;
|
|
skip?: number;
|
|
orderBy?: Prisma.RowOrderByWithRelationInput[];
|
|
filters?: RowFiltersDto;
|
|
ids?: string[];
|
|
rowWhere?: Prisma.RowWhereInput;
|
|
includePublic?: boolean;
|
|
}): Promise<RowWithDetails[]> {
|
|
const whereFilters = RowFiltersHelper.getRowFiltersCondition(filters);
|
|
let filterEntity: Prisma.RowWhereInput | undefined = undefined;
|
|
if (entityId) {
|
|
filterEntity = { entityId };
|
|
} else if (entityName) {
|
|
filterEntity = { entity: { name: entityName } };
|
|
}
|
|
|
|
const accessFilters = await getAccessFilters({ tenantId });
|
|
const where: Prisma.RowWhereInput = {
|
|
AND: [
|
|
{ deletedAt: null },
|
|
{ ...rowWhere },
|
|
whereFilters,
|
|
{ id: { in: ids } },
|
|
{ ...filterEntity },
|
|
{ ...accessFilters },
|
|
// ...getSearchCondition(filters?.query),
|
|
],
|
|
};
|
|
return await db.row.findMany({
|
|
take,
|
|
skip,
|
|
where,
|
|
include: RowModelHelper.createScopedIncludeRowDetails(tenantId ?? null),
|
|
orderBy,
|
|
});
|
|
}
|
|
|
|
export async function getRowsBetween(name: string, tenantId: string | null, startDate: Date, endDate: Date): Promise<RowWithDetails[]> {
|
|
return await db.row.findMany({
|
|
where: {
|
|
AND: [
|
|
{ deletedAt: null },
|
|
{
|
|
entity: { name },
|
|
...TenantHelper.tenantCondition({ tenantId }),
|
|
},
|
|
{
|
|
createdAt: {
|
|
lte: endDate,
|
|
gte: startDate,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
include: RowModelHelper.createScopedIncludeRowDetails(tenantId),
|
|
orderBy: { createdAt: "asc" },
|
|
});
|
|
}
|
|
|
|
export async function getRowsInIds(ids: string[], tenantId?: string | null): Promise<RowWithDetails[]> {
|
|
return await db.row.findMany({
|
|
where: {
|
|
deletedAt: null,
|
|
id: {
|
|
in: ids,
|
|
},
|
|
tenantId,
|
|
},
|
|
include: tenantId === undefined ? RowModelHelper.includeRowDetails : RowModelHelper.createScopedIncludeRowDetails(tenantId),
|
|
});
|
|
}
|
|
|
|
export async function countRows({
|
|
entityId,
|
|
entityName,
|
|
tenantId,
|
|
|
|
userId,
|
|
filters,
|
|
rowWhere,
|
|
includePublic,
|
|
}: {
|
|
entityId?: string;
|
|
entityName?: string;
|
|
tenantId?: string | null;
|
|
|
|
userId?: string | undefined;
|
|
filters?: RowFiltersDto;
|
|
rowWhere?: Prisma.RowWhereInput;
|
|
includePublic?: boolean;
|
|
}): Promise<number> {
|
|
const whereFilters = RowFiltersHelper.getRowFiltersCondition(filters);
|
|
let filterEntity: { entityId: string } | { entity: { name: string } } | undefined = undefined;
|
|
if (entityId) {
|
|
filterEntity = { entityId };
|
|
} else if (entityName) {
|
|
filterEntity = { entity: { name: entityName } };
|
|
}
|
|
|
|
const accessFilters = await getAccessFilters({ tenantId });
|
|
const where: Prisma.RowWhereInput = {
|
|
AND: [
|
|
{ deletedAt: null },
|
|
{ ...rowWhere },
|
|
whereFilters,
|
|
// { id: { in: ids } },
|
|
{ ...filterEntity },
|
|
{ ...accessFilters },
|
|
// ...getSearchCondition(filters?.query),
|
|
],
|
|
};
|
|
return await db.row.count({
|
|
where,
|
|
});
|
|
}
|
|
|
|
export async function getRow({ entityId, id, tenantId }: { entityId: string; id: string; tenantId?: string | null }): Promise<RowWithDetails | null> {
|
|
const accessFilters = await getAccessFilters({ tenantId });
|
|
return await db.row.findFirst({
|
|
where: {
|
|
deletedAt: null,
|
|
id,
|
|
entityId,
|
|
...accessFilters,
|
|
},
|
|
include: {
|
|
...RowModelHelper.createScopedIncludeRowDetails(tenantId ?? null),
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function getRowById(id: string, scope: { entityId?: string; tenantId: string | null }): Promise<RowWithDetails | null> {
|
|
return await db.row.findFirst({
|
|
where: {
|
|
deletedAt: null,
|
|
id,
|
|
...(scope.entityId ? { entityId: scope.entityId } : {}),
|
|
tenantId: scope.tenantId,
|
|
},
|
|
include: {
|
|
...RowModelHelper.createScopedIncludeRowDetails(scope.tenantId),
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function getMaxRowFolio({ tenantId, entityId }: { tenantId: string | null; entityId: string }) {
|
|
let where: any = { tenantId, entityId };
|
|
return await db.row.aggregate({
|
|
where,
|
|
_max: {
|
|
folio: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function getNextRowFolio({ tenantId, entityId }: { tenantId: string | null; entityId: string }) {
|
|
const maxFolio = await getMaxRowFolio({ tenantId, entityId });
|
|
let next = 1;
|
|
if (maxFolio && maxFolio._max.folio !== null) {
|
|
next = maxFolio._max.folio + 1;
|
|
}
|
|
return next;
|
|
}
|
|
|
|
export async function getMaxRowOrder({ tenantId, entityId }: { tenantId: string | null; entityId: string }) {
|
|
let where: any = { tenantId, entityId };
|
|
return await db.row.aggregate({
|
|
where,
|
|
_max: {
|
|
order: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function getNextRowOrder({ tenantId, entityId }: { tenantId: string | null; entityId: string }) {
|
|
const maxOrder = await getMaxRowOrder({ tenantId, entityId });
|
|
let next = 1;
|
|
if (maxOrder && maxOrder._max.order !== null) {
|
|
next = maxOrder._max.order + 1;
|
|
}
|
|
return next;
|
|
}
|
|
|
|
export async function createRow({
|
|
entity,
|
|
data,
|
|
nextFolio,
|
|
nextOrder,
|
|
}: {
|
|
entity: EntityWithDetails;
|
|
data: {
|
|
tenantId: string | null;
|
|
|
|
createdByUserId?: string | null;
|
|
createdByApiKeyId?: string | null;
|
|
properties: any;
|
|
dynamicProperties?: {
|
|
propertyId: string;
|
|
id?: string | null;
|
|
textValue?: string | null;
|
|
numberValue?: number | string | null;
|
|
dateValue?: Date | string | null;
|
|
booleanValue?: boolean | null;
|
|
media?: MediaDto[];
|
|
multiple?: RowValueMultipleDto[];
|
|
range?: RowValueRangeDto;
|
|
}[];
|
|
parentRows?: { relationshipId: string; parentId: string }[];
|
|
childRows?: { relationshipId: string; childId: string }[];
|
|
rowCreateInput?: Partial<Prisma.RowCreateInput>;
|
|
};
|
|
nextFolio?: number | undefined;
|
|
nextOrder?: number | undefined;
|
|
}) {
|
|
let folio = nextFolio ?? 1;
|
|
if (!nextFolio) {
|
|
const maxFolio = await getMaxRowFolio({ tenantId: data.tenantId, entityId: entity.id });
|
|
if (maxFolio && maxFolio._max.folio !== null) {
|
|
folio = maxFolio._max.folio + 1;
|
|
}
|
|
}
|
|
|
|
let order = nextOrder ?? 1;
|
|
if (!nextOrder) {
|
|
const maxOrder = await getMaxRowOrder({ tenantId: data.tenantId, entityId: entity.id });
|
|
if (maxOrder && maxOrder._max.order !== null) {
|
|
order = maxOrder._max.order + 1;
|
|
}
|
|
}
|
|
|
|
const createInput: Prisma.XOR<Prisma.RowCreateInput, Prisma.RowUncheckedCreateInput> = {
|
|
folio,
|
|
order,
|
|
entityId: entity.id,
|
|
tenantId: data.tenantId,
|
|
createdByUserId: data.createdByUserId ?? null,
|
|
createdByApiKeyId: data.createdByApiKeyId ?? null,
|
|
...data.properties,
|
|
values: {
|
|
create: data.dynamicProperties
|
|
// .filter((f) => !f.id)
|
|
?.map((value) => {
|
|
return {
|
|
propertyId: value.propertyId,
|
|
textValue: value.textValue,
|
|
numberValue: value.numberValue,
|
|
dateValue: value.dateValue,
|
|
booleanValue: value.booleanValue,
|
|
media: {
|
|
create: value.media?.map((m) => {
|
|
return {
|
|
name: m.name,
|
|
title: m.title,
|
|
type: m.type,
|
|
file: m.file,
|
|
publicUrl: m.publicUrl,
|
|
};
|
|
}),
|
|
},
|
|
multiple: {
|
|
create: value.multiple?.map((m) => {
|
|
return {
|
|
order: m.order,
|
|
value: m.value,
|
|
};
|
|
}),
|
|
},
|
|
range: {
|
|
create: {
|
|
numberMin: value.range?.numberMin,
|
|
numberMax: value.range?.numberMax,
|
|
dateMin: value.range?.dateMin,
|
|
dateMax: value.range?.dateMax,
|
|
},
|
|
},
|
|
};
|
|
}),
|
|
},
|
|
parentRows: {
|
|
create: data.parentRows?.map(({ relationshipId, parentId }) => {
|
|
return {
|
|
relationshipId,
|
|
parentId,
|
|
};
|
|
}),
|
|
},
|
|
childRows: {
|
|
create: data.childRows?.map(({ relationshipId, childId }) => {
|
|
return {
|
|
relationshipId,
|
|
childId,
|
|
};
|
|
}),
|
|
},
|
|
...data.rowCreateInput,
|
|
};
|
|
const row = await db.row.create({
|
|
data: createInput,
|
|
});
|
|
|
|
return row;
|
|
}
|
|
|
|
export async function updateRow(
|
|
scope: RowMutationScope,
|
|
data: {
|
|
properties: any;
|
|
dynamicProperties: {
|
|
propertyId: string;
|
|
id?: string | null;
|
|
textValue?: string | null;
|
|
numberValue?: number | string | null;
|
|
dateValue?: Date | string | null;
|
|
booleanValue?: boolean | null;
|
|
media?: MediaDto[];
|
|
multiple?: RowValueMultipleDto[];
|
|
range?: RowValueRangeDto | undefined;
|
|
}[];
|
|
parentRows?: { relationshipId: string; parentId: string }[];
|
|
childRows?: { relationshipId: string; childId: string }[];
|
|
rowUpdateInput?: Prisma.RowUpdateInput;
|
|
}
|
|
) {
|
|
const row = await updateRowWithValues(scope, {
|
|
properties: data.properties,
|
|
dynamicProperties: data.dynamicProperties as RowMutationValueInput[],
|
|
parentRows: data.parentRows as RowMutationRelationshipParentInput[] | undefined,
|
|
childRows: data.childRows as RowMutationRelationshipChildInput[] | undefined,
|
|
rowUpdateInput: data.rowUpdateInput,
|
|
} satisfies Omit<RowMutationData, "tenantId" | "createdByUserId" | "createdByApiKeyId" | "rowCreateInput">) as LegacyRowWithDetails as RowWithDetails;
|
|
const entity = await getEntityById({ tenantId: row.tenantId, id: row.entityId });
|
|
if (entity) {
|
|
await storeRowMediaInStorageProvider(entity, row);
|
|
}
|
|
return row;
|
|
}
|
|
|
|
export async function deleteRow(scope: RowMutationScope) {
|
|
const row = await getRowById(scope.id, { entityId: scope.entityId, tenantId: scope.tenantId });
|
|
await deleteRowMediaFromStorageProvider(row);
|
|
return await hardDeleteRowById(scope);
|
|
}
|
|
|
|
export async function deleteRows({ entityId, tenantId }: { entityId: string; tenantId: string | null }) {
|
|
return await db.row.deleteMany({
|
|
where: { entityId, tenantId },
|
|
});
|
|
}
|
|
|
|
export async function deleteRowsInIds({ ids, entityId, tenantId }: { ids: string[]; entityId: string; tenantId: string | null }) {
|
|
return await db.row.deleteMany({
|
|
where: { id: { in: ids }, entityId, tenantId },
|
|
});
|
|
}
|
|
|
|
export async function updateRowMedia(
|
|
scope: { id: string; rowId: string; entityId: string; tenantId: string | null },
|
|
data: {
|
|
file?: string;
|
|
publicUrl?: string | null;
|
|
storageBucket?: string | null;
|
|
storageProvider?: string | null;
|
|
}
|
|
) {
|
|
const result = await db.rowMedia.updateMany({
|
|
where: {
|
|
id: scope.id,
|
|
rowValue: {
|
|
rowId: scope.rowId,
|
|
row: { entityId: scope.entityId, tenantId: scope.tenantId, deletedAt: null },
|
|
},
|
|
},
|
|
data,
|
|
});
|
|
if (result.count !== 1) {
|
|
throw new Error(`Row media ${scope.id} not found`);
|
|
}
|
|
}
|
|
|
|
async function getAccessFilters({ tenantId, userId }: { tenantId?: string | null; userId?: string }) {
|
|
let tenantFilters: Prisma.RowWhereInput | undefined = undefined;
|
|
let userPermissionFilters: Prisma.RowWhereInput | undefined = undefined;
|
|
if (tenantId !== undefined) {
|
|
tenantFilters = TenantHelper.tenantCondition({ tenantId });
|
|
}
|
|
if (userId) {
|
|
userPermissionFilters = await getRowPermissionsCondition({ tenantId, userId });
|
|
}
|
|
const filters: Prisma.RowWhereInput = {
|
|
AND: [{ ...tenantFilters }, { ...userPermissionFilters }],
|
|
};
|
|
return filters;
|
|
}
|