metis-desk/modules/resources/src/api/infrastructure/persistence/directory-domain-options-reader.ts
2026-10-20 13:20:22 +02:00

57 lines
2.0 KiB
TypeScript

import type { RelationshipContext, RelationshipReadAccess } from "@metis/relationship-contracts";
import { 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";
import type { DirectoryDomainOptionsQuery } from "../../../common/directory-domain-options";
import {
DirectoryDomainOptionSchema,
DirectoryDomainOptionsResponseSchema,
} from "../../../common/directory-domain-options";
/** Opciones activas y estructuradas; no descarga el inventario ni sus revisiones. */
export class DirectoryDomainOptionsReader {
constructor(
private readonly database: Sequelize,
private readonly access: RelationshipReadAccess
) {}
async list(query: DirectoryDomainOptionsQuery, context: RelationshipContext) {
const authorized = await this.access.authorize(context);
if (!authorized.isSuccess) {
return authorized;
}
if (query.search.length > 100) {
return Result.fail(new RelationshipReadError("INVALID_QUERY"));
}
const rows = z.array(DirectoryDomainOptionSchema).parse(
await this.database.query(
`SELECT r.id,r.name,d.normalizedDns FROM resources r
JOIN resource_directory_domains d ON d.resourceId=r.id AND d.companyId=r.companyId
WHERE r.companyId=:companyId AND r.active=1
AND (:search = '' OR LOCATE(:search,r.name)>0 OR LOCATE(:search,d.normalizedDns)>0)
${query.cursor ? "AND r.id>:cursor" : ""} ORDER BY r.id LIMIT 21`,
{
type: QueryTypes.SELECT,
replacements: {
companyId: context.companyId,
search: query.search,
cursor: query.cursor ?? null,
},
}
)
);
const items = rows.slice(0, 20);
return Result.ok(
DirectoryDomainOptionsResponseSchema.parse({
items,
nextCursor: rows.length > 20 ? items.at(-1)?.id : null,
})
);
}
}