1
0
Fork 0
polaris-task-force/src/lib/storageRules.ts

115 lines
3.8 KiB
TypeScript

import type { Structure } from "@/payload-types";
type ResourceRef = {
relationTo: "resources";
value: number | { id: number };
};
export type StorageRulesSource = Pick<
Structure,
"allowedStorage" | "prohibitedStorage" | "restrictToAllowed"
>;
export type StorageRuleViolation = "prohibited" | "not_whitelisted" | "over_cap";
export interface StorageRuleCheck {
allowed: boolean;
violation?: StorageRuleViolation;
cap?: number;
currentStored?: number;
}
function isResourceRef(item: unknown): item is ResourceRef {
if (!item || typeof item !== "object") return false;
const ref = item as { relationTo?: string };
return ref.relationTo === "resources";
}
function resolveResourceId(value: number | { id: number }): number {
return typeof value === "object" && value !== null ? value.id : value;
}
/**
* Whether this resource is on the structure's prohibited list.
*/
export function isResourceProhibited(rules: StorageRulesSource, resourceId: number): boolean {
return (rules.prohibitedStorage ?? []).some((rule) => {
if (!isResourceRef(rule.item)) return false;
return resolveResourceId(rule.item.value) === resourceId;
});
}
/**
* Whether this resource passes the whitelist gate. When `restrictToAllowed` is
* enabled, only resources listed in `allowedStorage` pass. Otherwise everything
* passes (prohibited list still applies separately).
*/
export function isResourceWhitelisted(rules: StorageRulesSource, resourceId: number): boolean {
if (!rules.restrictToAllowed) return true;
return (rules.allowedStorage ?? []).some((rule) => {
if (!isResourceRef(rule.item)) return false;
return resolveResourceId(rule.item.value) === resourceId;
});
}
/**
* The per-item quantity cap defined in `allowedStorage`, or null if none set.
*/
export function getResourceStorageCap(
rules: StorageRulesSource,
resourceId: number,
): number | null {
const entry = (rules.allowedStorage ?? []).find((rule) => {
if (!isResourceRef(rule.item)) return false;
return resolveResourceId(rule.item.value) === resourceId;
});
return entry?.amount ?? null;
}
/**
* Validates a deposit/transfer of `amount` of `resourceId` into a structure,
* given how much of that resource is currently stored (grid + void).
*
* Checks, in order: prohibited list, whitelist gate, per-item quantity cap.
*/
export function checkStorageDeposit(
rules: StorageRulesSource,
resourceId: number,
amount: number,
currentStored: number,
): StorageRuleCheck {
if (isResourceProhibited(rules, resourceId)) {
return { allowed: false, violation: "prohibited" };
}
if (!isResourceWhitelisted(rules, resourceId)) {
return { allowed: false, violation: "not_whitelisted" };
}
const cap = getResourceStorageCap(rules, resourceId);
if (cap != null && currentStored + amount > cap) {
return { allowed: false, violation: "over_cap", cap, currentStored };
}
return { allowed: true, cap: cap ?? undefined };
}
/**
* Human-readable message describing why a deposit/transfer was rejected.
*/
export function storageViolationMessage(resourceName: string, check: StorageRuleCheck): string {
switch (check.violation) {
case "prohibited":
return `This resource is prohibited from storage at this structure.`;
case "not_whitelisted":
return `This structure only accepts items on its allowed storage list: "${resourceName}" is not permitted.`;
case "over_cap": {
const available = Math.max(0, (check.cap ?? 0) - (check.currentStored ?? 0));
return available <= 0
? `${resourceName} is at its storage allowance here (${check.cap?.toLocaleString()}). No more can be stored.`
: `${resourceName} is limited to ${check.cap?.toLocaleString()} units here. You can add at most ${available.toLocaleString()} more.`;
}
default:
return `This resource cannot be stored here.`;
}
}