metis-desk/modules/resources/src/api/infrastructure/persistence/network-share-summary-resolver.ts
David 0a34ddf752 feat: add local identity management features and UI components
- Implement folder account options helper for filtering access items based on person ID and date.
- Create FolderAccessAccountField component for selecting accounts with profile integration.
- Develop AssetLocalIdentities component for managing local identities associated with assets.
- Introduce LocalIdentityOwner component to display and manage local identity ownership.
- Add LocalIdentityEditor for creating, associating, and retiring local identities.
- Implement local identity API for fetching and executing local identity commands.
- Create tests for local identity UI components to ensure functionality and correctness.
- Add NetworkShareOptions class for structured and paginated SMB selection.
- Introduce NetworkShareAddress component for displaying and copying network share addresses.
- Create RemoteOptionList and RemoteOptionPicker components for remote option selection with pagination and search.
- Add tests for RemoteOptionList to validate rendering and behavior under various states.
2026-10-21 12:37:59 +02:00

102 lines
3.5 KiB
TypeScript

import type {
EntityReference,
EntitySummaryResolver,
RelationshipContext,
RelationshipReadAccess,
SummaryResolution,
} from "@metis/relationship-contracts";
import {
EntityReferenceSchema,
EntitySummarySchema,
RelationshipContextSchema,
RelationshipReadError,
} from "@metis/relationship-contracts";
import { Result } from "@repo/rdx-utils";
import type { Sequelize } from "sequelize";
import { QueryTypes } from "sequelize";
import { z } from "zod/v4";
const RowSchema = z.object({
id: z.uuid(),
name: z.string().min(1),
active: z.union([z.boolean(), z.literal(0), z.literal(1)]).transform(Boolean),
address: z.string().min(1),
});
/** Una proyección por lote, sin hidratar inventarios ni cargar sus historiales. */
export class NetworkShareSummaryResolver implements EntitySummaryResolver {
readonly providerId = "resources";
readonly entityTypes = Object.freeze(["resources:resource"] as const);
constructor(
private readonly database: Sequelize,
private readonly access: RelationshipReadAccess
) {}
async resolveMany(
references: readonly EntityReference[],
context: RelationshipContext
): Promise<Result<readonly SummaryResolution[], RelationshipReadError>> {
if (!RelationshipContextSchema.safeParse(context).success) {
return Result.fail(new RelationshipReadError("FORBIDDEN"));
}
const authorized = await this.access.authorize(context);
if (!authorized.isSuccess) {
return authorized;
}
const parsed = z.array(EntityReferenceSchema).max(100).safeParse(references);
if (!parsed.success) {
return Result.fail(new RelationshipReadError("INVALID_QUERY"));
}
const accepted = parsed.data.filter(
(reference) =>
reference.companyId === context.companyId &&
reference.providerId === this.providerId &&
reference.entityType === "resources:resource"
);
const ids = [...new Set(accepted.map((reference) => reference.id))];
const rows =
ids.length === 0
? []
: z
.array(RowSchema)
.parse(
await this.database.query(
"SELECT r.id,r.name,r.active,c.textValue AS address FROM resources r JOIN resource_network_shares d ON d.resourceId=r.id AND d.companyId=r.companyId JOIN resource_characteristics c ON c.resourceId=r.id JOIN resource_definitions f ON f.id=c.fieldId AND f.stockKey='field:unc' WHERE r.companyId=:companyId AND r.id IN (:ids)",
{ type: QueryTypes.SELECT, replacements: { companyId: context.companyId, ids } }
)
);
const byId = new Map(rows.map((row) => [row.id, row]));
return Result.ok(
Object.freeze(
parsed.data.map((reference): SummaryResolution => {
const row =
reference.companyId === context.companyId &&
reference.providerId === this.providerId &&
reference.entityType === "resources:resource"
? byId.get(reference.id)
: undefined;
return row
? Object.freeze({
status: "resolved",
summary: EntitySummarySchema.parse({
reference,
name: row.name,
subtitle: row.address,
state: {
code: row.active ? "active" : "inactive",
labelKey: row.active ? "resources:active" : "resources:inactive",
},
}),
})
: Object.freeze({ status: "unavailable", reference });
})
)
);
}
}