Commit graph

15 commits

Author SHA1 Message Date
a6a155ee46
rate limit: prune stale keys so @hits cannot grow unbounded 2026-07-02 17:31:19 +04:00
ef3cb2a304
invoices: numeric number allocation, retry create on collision 2026-07-02 17:31:19 +04:00
16680be309
invoices: rework of PDF
All checks were successful
Build and Deploy to Production / build (push) Successful in 50s
Build and Deploy to Production / deploy (push) Successful in 27s
2026-06-09 03:52:12 +03:00
7dbeb0dd49
nbg and auto fill GEL exchange rate
All checks were successful
Build and Deploy to Production / build (push) Successful in 47s
Build and Deploy to Production / deploy (push) Successful in 27s
2026-06-08 19:29:25 +03:00
37a491cf79
fix: pending/paid invoices 2026-06-08 19:00:03 +03:00
c9bcbe7ff5
ltc payments 2026-06-08 18:43:01 +03:00
8f3afcfcd2
logo in pdf
All checks were successful
Build and Deploy to Production / build (push) Successful in 1m2s
Build and Deploy to Production / deploy (push) Successful in 39s
2026-06-08 18:15:23 +03:00
a34b8cd33d
fix: assign uuid after Invoice.new to avoid mass-assignment restriction
All checks were successful
Build and Deploy to Production / build (push) Successful in 47s
Build and Deploy to Production / deploy (push) Successful in 25s
Sequel guards primary keys against mass assignment, so passing uuid: to
Invoice.new raised MassAssignmentRestriction and turned every invoice
creation into a 500. Build the instance with the rest of the fields, then
set uuid as an attribute before returning.
2026-05-26 18:34:45 +03:00
c7fe108ecf
fix: AdminAuth prefix matched too greedily
All checks were successful
Build and Deploy to Production / build (push) Successful in 46s
Build and Deploy to Production / deploy (push) Successful in 25s
start_with?('/admin') also matched /admin-invoice-form.js, returning 401
for the public JS asset that the New Invoice form depends on. Without it,
the "+ add line" button did nothing.

Require either the bare /admin path or a /admin/-prefixed one so sibling
assets in public/ that happen to share the prefix stay reachable.
2026-05-26 18:31:32 +03:00
0dc31fbafe
storage: S3 client wrapper and admin Basic-auth middleware
S3 module wraps aws-sdk-s3 against MinIO. Two clients on purpose:

- The bytes-mover client points at S3_ENDPOINT (internal minio:9000 over
  the storage Docker network).
- The presign-only client points at S3_PUBLIC_ENDPOINT (https://s3.asxp.io
  via Traefik) so the URLs it mints resolve from a browser.

force_path_style on both because subdomain-style URLs would require wildcard
DNS under s3.asxp.io.

AdminAuth is a tiny Rack middleware that 401s any request under /admin
unless HTTP Basic credentials match ADMIN_USER / ADMIN_PASSWORD from env.
Fails closed if either env var is missing.
2026-05-26 17:43:48 +03:00
a605ccdc52
invoicing: Prawn PDF renderer with Noto fonts
InvoicePdf.render(invoice) returns A4 PDF bytes. Layout: title + number,
horizontal rule, two-column FROM/BILL TO, meta strip (issued/due/currency/
status) bracketed by rules, line items table, right-aligned totals with
optional GEL equivalent, payment block (Bank of Georgia IBAN), optional
notes, repeating footer with issuer ID and page number.

Both party columns capture pdf.y after rendering and the global cursor jumps
to whichever ran longer — avoids the bbox cursor-restore footgun that was
collapsing the meta strip into the FROM column.

Noto Sans Georgian is registered as a fallback family so the Georgian legal
name renders without needing a separate text run.

Issuer details (legal name, tax ID, registration, address, bank) are
hardcoded as a constant — these are public on the rendered site already and
change rarely enough that a config file would be overkill.
2026-05-26 17:43:38 +03:00
1220f37d5f
invoicing: Invoice model with number allocation and totals
Sequel::Model wrapper over the invoices table. Three responsibilities:

- Invoice.build takes form params (client, currency, GEL rate, items array)
  and returns an unsaved Invoice with a freshly-allocated number, normalized
  line items, and computed subtotal.
- Invoice.allocate_number scans for the highest INV-<year>-NNNN and
  increments. Single-process deploy means no contention; if that changes
  this needs a DB-side sequence per year.
- Instance methods: paid?, status, total, total_gel for the PDF renderer
  and the public landing page.

Items are stored as JSONB; qty and unit_price are kept as strings on the
way in/out so BigDecimal arithmetic stays exact through the JSON round-trip.
2026-05-26 17:43:28 +03:00
3fdb0f36b3
db: connection helper and invoices migration
DB.connect! opens a single Sequel connection from DATABASE_URL; DB.migrate!
runs Sequel migrations from db/migrations/.

The first migration creates the invoices table: uuid PK, unique invoice
number, client + currency fields, JSONB for line items, GEL conversion rate
captured at issue time, paid_at for the status toggle, pdf_key for the
MinIO object.
2026-05-26 17:43:18 +03:00
7608cf15fc
useful icon and links
All checks were successful
Build and Deploy to Production / build (push) Successful in 45s
Build and Deploy to Production / deploy (push) Successful in 32s
2026-05-05 00:57:11 +04:00
1ab09043ee
ruby rework with contact form
All checks were successful
Build and Deploy to Production / build (push) Successful in 51s
Build and Deploy to Production / deploy (push) Successful in 34s
2026-05-05 00:45:27 +04:00