Skip to content

feat(shop): silo s3 storage grants#42

Open
deployor wants to merge 1 commit into
hackclub:mainfrom
deployor:moscow
Open

feat(shop): silo s3 storage grants#42
deployor wants to merge 1 commit into
hackclub:mainfrom
deployor:moscow

Conversation

@deployor

@deployor deployor commented Jul 2, 2026

Copy link
Copy Markdown

same deal as hcb card grants but calls silo instead for 60gb s3 storage

after merge:

  • set SILO_API_KEY=silo_ysws_... in env
  • db migration runs itself

for the shop item:
right now hcb is 1 pipe = (HCB_CENTS_PER_PIPE=500), so = 2 pipes
create a shop item for "60 GB SILO Storage" costing 2 pipes
if hcb rate ever changes just tweak the pipe cost to match

for fulfillers it's the exact same flow — they just click "SILO grant" instead of "Card grant"

safety copied from hcb: can't double grant, can't refund/merge after, ban flow excludes them, locks the row in a transaction, etc

Copilot AI review requested due to automatic review settings July 2, 2026 19:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for issuing “SILO S3 storage” grants from the admin fulfillment UI, mirroring the existing HCB card-grant flow, and persists the resulting SILO grant id on orders for idempotency/safety.

Changes:

  • Adds a SILO admin API (status, prefill, grant) and wires it into the backend app module.
  • Extends orders with siloGrantId (DB migration + entity) and blocks refund/merge/ban-refund flows once a SILO grant exists.
  • Updates the admin fulfillment UI to display SILO configuration status and issue SILO grants via a new modal.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
frontend/src/routes/admin/+page.svelte Adds SILO status loading/banner and a SILO grant action + modal wiring in fulfillment UI.
frontend/src/lib/components/admin/SiloGrantModal.svelte New modal to prefill and submit a SILO storage grant.
backend/src/silo/silo.service.ts Implements SILO grant creation with transaction locking + audit logging.
backend/src/silo/silo.module.ts Registers SILO controller/service and dependencies.
backend/src/silo/silo.controller.ts Exposes super-admin SILO status/prefill/grant endpoints.
backend/src/shop/shop.service.ts Includes siloGrantId in admin fulfillment payload and blocks refund/merge if present.
backend/src/migrations/1780000000000-AddSiloGrantId.ts Adds silo_grant_id column + unique partial index.
backend/src/entities/order.entity.ts Adds siloGrantId field mapping on Order.
backend/src/entities/audit-log.entity.ts Adds silo_grant_issued audit action constant.
backend/src/app.module.ts Imports SiloModule.
backend/src/admin/admin.service.ts Excludes SILO-granted orders from the ban flow’s refund loop.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +3324 to +3328
<SiloGrantModal
order={siloGrantModalOrder}
onClose={() => (siloGrantModalOrder = null)}
onGranted={() => { siloGrantModalOrder = null; onSiloGrantIssued(); }}
/>
Comment on lines +81 to +83
onkeydown={(e) => {
if (e.key === 'Escape') onClose();
}}
Comment on lines +99 to +123
const recipientEmail = (orderForRecipient?.user?.email ?? '').trim().toLowerCase();

let issuedGrantId: string | null = null;
let recipientUserId: string | undefined;
let transactionResult: { grantId: string; amount: number; unit: string } | undefined;

try {
transactionResult = await this.orderRepo.manager.transaction(async (em) => {
const order = await em.findOne(Order, {
where: { id: orderId },
relations: ['user'],
lock: { mode: 'pessimistic_write' },
});
if (!order) throw new NotFoundException('Order not found');
if (order.siloGrantId) {
throw new ConflictException(
`A SILO grant (${order.siloGrantId}) was already issued for this order`,
);
}

const email = (order.user?.email ?? '').trim().toLowerCase();
if (!EMAIL_RE.test(email)) {
throw new BadRequestException('Order owner has no valid email');
}
if (email === admin.email.trim().toLowerCase()) {
Comment on lines +203 to +204

try {
@deployor

deployor commented Jul 2, 2026

Copy link
Copy Markdown
Author

cc @EDRipper

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants