Changelog

What's new in Pushify

Every release across the platform, API and dashboard — features, fixes and improvements as they ship.

v0.2.0-beta.66

Dashboard

Added

  • Data Browser for PostgreSQL/MySQL databases (/dashboard/databases/[id]/studio, linked from the database detail header while the database is running). A table list with search, row estimates and size; a data grid with column sorting, paging and per-column filters (equals, contains, starts/ends with, comparisons, is null); add / edit / delete rows through a type-aware editor that respects nullability, defaults and binary columns; and a SQL console that is read-only by default, with write mode behind an explicit confirmation. Views and primary-key-less tables are clearly marked read-only. EN/TR i18n.
  • Install the Pushify GitHub App from the new-project screen when the platform has one configured — offered above the OAuth connect, with the plain reason next to it. GitHub sends the browser to /auth/github/app-setup, which links the installation to the organisation and hands the person back to what they were doing.
  • Per-member data-browser permissions. The team page gains a data-access selector for members and viewers — no access (the default), read, or edit — beside the existing project-access chip. A read-only user gets the same browser without the actions that would fail: no new table, no add/edit/delete row, no CSV import, no row selection.
  • Cancel a running query from the Performance tab's running-queries list.
  • MongoDB and Redis get their own browsers. Mongo: collection list with document counts, a paged document view with JSON filter and sort inputs, a JSON editor for adding and editing documents, multi-select delete, create/drop collection. Redis: pattern search over a cursor-paged SCAN, type badges and TTL per key, a type-aware value view (string editor, list items, hash/zset field tables), TTL editing and bulk delete. The Data Browser link now appears for all four engines.
  • Index management and a Performance tab. The structure panel lists a table's indexes with their columns, uniqueness and size, and creates or drops them (click columns in order to compose a composite index). The new Performance tab shows the slowest statements and what is running right now, and a slow query opens straight into the SQL console.
  • CSV import. Pick a file, confirm the header and delimiter (auto-detected), map each CSV column to a table column, preview the first rows, then import in batches with a progress bar. Empty cells become NULL by default. The CSV reader is RFC 4180 (quotes, embedded commas and newlines, CRLF, BOM) and ships with the frontend's first unit test suite — npm test, 11 tests.
  • A professional SQL console. CodeMirror 6 editor with SQL syntax highlighting, line numbers, bracket matching and autocomplete fed by the live schema (tables, qualified names, columns) — the editor is lazy-loaded and themed from the dashboard's own CSS variables, so it follows light/dark. Around it: a schema explorer that inserts names at the cursor, ⌘↵ to run, run just the selected text, an EXPLAIN button, one-click SQL formatting, tabbed Result / Messages / History panes, query history kept per database (timing, row count, click to reload), a full-value cell viewer with JSON pretty-printing, and CSV/JSON export of the complete result — not just the page on screen. The same cell viewer now opens from the data grid.
  • Data Browser feels interactive now. Row counts above 10k are shown as estimates (~1.2M rows (estimated)) instead of blocking the page on a scan, paging stays enabled while pages come back full, and a revisited table is served from cache for 10s rather than re-crossing the network.
  • Schema editing in the Data Browser. "New table" opens a column builder (name, type from the engine's own type list, length/scale, nullable, unique, primary key, auto-increment, default) and a Structure panel per table adds or drops columns, renames the table, and holds a danger zone for emptying or deleting it — each destructive step behind its own confirmation.

v0.2.0-beta.62

Platform & API

Added

  • Database Studio — a data browser for managed PostgreSQL/MySQL databases. New endpoints under /databases/:id/studio: GET /tables (tables and views with row estimates, size and primary-key info), GET /rows (paged, sortable, filterable rows for one table), POST/PATCH /rows and POST /rows/delete (row insert / update / bulk delete, always addressed by primary key), and POST /query (SQL console). Queries reach the database the same way the rest of the database service does — SSH to the server, then docker exec the engine's own client — so nothing has to be exposed to the network. Owner/admin only; API keys need databases:read, and databases:write for writes.
  • Schema editing from the studio. POST /studio/tables (create), DELETE /studio/tables (drop table or view), POST /studio/tables/truncate, POST /studio/tables/rename, POST /studio/columns (add) and DELETE /studio/columns (drop). Column types cannot be catalog-checked the way names can — they do not exist yet — so every type token comes from a per-engine allowlist and only the matched token is emitted; lengths and scales must be integers in range; defaults are quoted literals unless they match a short allowlist of expressions (now(), CURRENT_TIMESTAMP, gen_random_uuid(), uuid(), …). Auto-increment maps to serial/bigserial on Postgres and to AUTO_INCREMENT (primary key required) on MySQL. Every schema change is logged as database.schema_changed (migration 0042). 19 unit tests on the DDL renderer.
  • GitHub App: repository access that outlives the person who set it up. Every deploy used to borrow the organisation *owner's* personal OAuth token, with the repo scope over every repository that person could reach — so the owner leaving, revoking the grant or rotating the token stopped every project in the organisation. Now an installation (github_app_installations, migration 0044) is the credential: it belongs to the GitHub account, covers only the repositories that account picked, and is never stored as a long-lived token — lib/github-app-auth.ts signs a short app JWT and mints one-hour installation tokens on demand, cached with a five-minute refresh margin (16 tests, including the PKCS#1 key GitHub hands out and the escaped newlines a .env produces). New endpoints: GET /integrations/github/app/install-url, POST /integrations/github/app/setup, GET /integrations/github/app/installations[/:id/repositories], and one webhook at POST /webhooks/github/app that keeps installations in sync and fans push/pull_request events out to every project tracking that repository.
  • Both paths run side by side. The token resolver prefers an installation and falls back to the owner's OAuth token, so projects connected before the App keep deploying untouched. The App path is tried *before* the owner lookup, which is what makes it survive the case OAuth cannot. A repository the App now covers makes the legacy per-repo webhook stand down, so a migrated project cannot deploy twice from one push. 10 tests on the resolver.
  • The OAuth app is identity-only once an App is configured — the requested scope drops from repo read:user user:email to read:user user:email. Deployments without an App keep the old scope, so nothing breaks for them.
  • Push and pull-request handling extracted to lib/github-deploy-trigger.ts so the per-project route and the App endpoint run the same code instead of two copies that drift.
  • Data-browser permissions. organization_members.studio_access (none | read | write, migration 0043) with PUT /organizations/members/:userId/studio-access. Owners and admins keep full access by virtue of their role; everyone else defaults to none, so nothing changes for an organisation that does not opt in. A read grant browses tables, rows, indexes, diagnostics and read-only console queries; every write — row edits, DDL, imports, write-mode queries, cancels — needs a write grant. listTables reports the caller's level so the UI can hide what would 403. 26 access-control tests with the repositories and SSH mocked (role gate, cross-organisation 404, engine gate, container state, read-vs-write split) plus 6 on the API-key scope middleware.
  • Query cancellation. POST /studio/cancel stops a running statement — pg_cancel_backend scoped to the current database, or an ownership check followed by KILL QUERY on MySQL. Cancelling something that already finished reports that, rather than failing.
  • SSH connection pooling rolled out. The pool had existed for a long time but only the studio used it; 12 short, frequent operations across metrics, log collection, app-sleep, usage metering, health scans, container resolution and remote cleanup now reuse a pooled connection instead of paying a fresh handshake (~250-500ms) every time. Deployments, backups, server provisioning, certbot and user cron keep their own connection: they run long or stream for minutes, and OpenSSH caps concurrent channels per connection. A pooled client now ignores disconnect() from its callers — the pool's reaper owns the lifecycle — so a shared connection can never be closed out from under another caller.
  • `npm run lint` works again. The backend had ESLint 9 installed but no config file at all, so linting never ran. Added a flat config (typescript-eslint, non-type-checked so it stays fast) and cleared what it found: 33 pieces of dead code removed (unused imports, unused bindings whose calls were kept, two unreferenced private functions), a lexical declaration escaped from a case arm, catch {} on shutdown paths allowed, and control-character regexes permitted since sanitisation is exactly what they do. 69 errors → 0; the 28 remaining any uses are warnings so new ones still surface.
  • Studio engine layer extracted and tested against real databases. The SQL the studio generates, the command that carries it and the parsing of what comes back now live in lib/studio-sql.ts / lib/studio-nosql.ts — pure modules with no SSH, database or HTTP — leaving the services to own auth, sessions and orchestration (database-studio.service.ts 1635 → ~1150 lines, sessions shared via studio-session.service.ts). On top of that: 50 integration tests (npm run test:studio) that boot real PostgreSQL 16, MySQL 8.0, MongoDB 7 and Redis 7 containers and run the exact command production sends, with only the SSH hop replaced. They already earned their keep — they caught mongosh returning a REPL prompt instead of script output (every Mongo call would have failed in production) and an envelope parser that broke on any payload containing an ok field. Plus 60 new unit tests on the builders and parsers. Backend suite: 198 unit tests, 50 integration.
  • Index management and query diagnostics. GET/POST/DELETE /studio/indexes lists, creates and drops indexes (method from a per-engine allowlist, columns checked against the table, the primary key refused), and GET /studio/performance reports the slowest statements (pg_stat_statements / performance_schema) and what is running right now — an unavailable statistics source is reported to the UI, never thrown.
  • MongoDB and Redis studios. New /studio/mongo/* (collections, paged documents with a user-supplied filter and sort, insert/replace/delete by _id, create/drop collection) and /studio/redis/* (cursor SCAN with pattern, per-type value preview, TTL, string edit, bulk delete). Each engine gets its own injection defence: for Mongo every piece of user input enters the script as a JSON.stringify string literal parsed with EJSON.parse, so it is data and never code; for Redis the Lua program is fixed and every value is hex-encoded into ARGV and decoded inside Lua, which is also what makes binary keys and values survive the round trip.
  • CSV import. POST /studio/import appends a batch of rows (max 500 per request, client loops for progress) with every cell escaped exactly like a hand-edited value; empty cells become NULL unless the caller opts out.
  • SQL console became a real console. GET /studio/schema returns every table with its columns in one round trip (the editor's autocomplete source, capped at 500 tables); POST /studio/query takes a maxRows ceiling and now reports the statement's command and, where the engine tells us, the number of rows it affected (psql's command tag; ROW_COUNT() on MySQL); and POST /studio/query/export streams a query's full result as CSV or JSON, always read-only, up to 20k rows. CSV writing is RFC 4180 (5 unit tests).
  • Studio latency work — the transport was the cost, not the SQL. Every request used to pay a fresh SSH handshake (~250-500ms) plus a docker exec spawn (~150-400ms) for a query that runs in single-digit milliseconds, and reading rows paid the exec twice because the catalog lookup was its own round trip. Now: the studio uses the existing getSSHConnection pool instead of dialling a new connection each time (a pooled connection that died between requests is re-acquired *before* sending, never retried after, so a write can't apply twice); resolved table schemas are cached for 60s and invalidated by our own DDL, which takes paging/sorting/filtering down to one round trip; and unfiltered row counts stop at 10k and fall back to the planner's estimate (reltuples / TABLE_ROWS, flagged as totalEstimated) so a large table is never scanned to draw a page number.
  • Safety rails on the studio. The SQL script is base64'd onto the client's stdin (the shell never sees user input); identifiers are resolved against the live catalog before they can appear in a statement; literals are escaped with the session pinned to a known escaping mode (standard_conforming_strings on / NO_BACKSLASH_ESCAPES off). Tables without a primary key and views are read-only, binary columns are preview-only, and every read runs in a read-only transaction. The console is read-only unless the caller explicitly opts into write mode, in which case read mode accepts a single read statement only. Statement timeout 20s, output capped, row counts capped at 100k, console results capped at 500 rows. Row edits and every console query land in the activity log (database.data_modified, database.query_executed, migration 0041). 18 unit tests on the escaping and statement-classification rules.

v0.2.0-beta.60

Platform & API

Added

  • Config-as-code: `pushify.yaml`. A file at the repo root (or the project's root directory) now declares build & runtime settings and wins over dashboard values when present — the repo becomes the source of truth: build, install, start, output, port, framework, plus declared cron jobs (name/schedule/command/timezone, validated with the same cron/timezone rules as the UI) and volumes (name/path, same shell-safety validation). Cron and volume declarations sync on production deploys as upsert-only — removing an entry from the file never deletes data; the dashboard stays authoritative for removals. A malformed file is reported in the deploy log and ignored — it can never break a deploy. Applied on both the remote and local deploy paths; volume declarations take effect in the same deploy. 7 parser tests.

Changed

  • Install cache now covers yarn and pnpm too — the BuildKit cache mounts (npm + framework caches shipped earlier) gain yarn/pnpm store targets, so custom install commands hit a warm cache as well.

v0.2.0-beta.64

Dashboard

Changed

  • Notification preferences moved from localStorage to the account (pairs with backend beta.59). The Notifications tab now reads and saves through the API with optimistic toggles — settings finally follow you across browsers and devices, and the backend actually honors them (security alerts gate the new-sign-in email; Weekly Digest opts you into the new Monday summary). New "Getting-started emails" toggle controls the onboarding sequence from the same screen (same switch as the email unsubscribe link). EN/TR i18n.

v0.2.0-beta.63

Dashboard

Changed

  • Real company identity across the site. The global Organization schema now carries legalName: Pushify LLC, the registered US address (30 N Gould St Ste N, Sheridan, WY) and the founder (M. Aziz Kurt) as structured data on every page. The About page's company card shows the legal entity, address and a Founder row with a short "why I built this" note (EN/TR), and the footer copyright reads Pushify LLC. Closes the audit's two biggest trust gaps: no named human and no legal entity anywhere on the site.

v0.2.0-beta.62

Dashboard

Fixed

  • Modals now render through the global portal. The cancellation dialog and the two domain dialogs (purchase, transfer-in) drew their own overlay inside the page tree, so an ancestor with a transform trapped the backdrop to one card instead of the whole screen. All three now use the shared portal-based Modal (renders to document.body, full-page blurred backdrop, ESC to close, scroll lock).

Added

  • In-app cancellation with a one-question exit survey (pairs with backend beta.58). Paid plans get a quiet "Cancel subscription" link under the Current Plan card; the dialog asks a single honest question (too expensive / missing features / bugs / switched / project ended / other + optional comment, "a human reads these"), records it best-effort, then cancels at period end with a clear "your data is not deleted" note. EN/TR i18n.

v0.2.0-beta.61

Dashboard

Added

  • Three SEO growth pages, built on verified data only (competitor facts checked against live public sources, July 2026 — no invented benchmarks, stars or testimonials; every page carries a "spot an error? email us" correction note): - `/vs/heroku` — the existing honest-comparison template applied to Heroku: real pricing (free tier removed Nov 2022; $5 Eco sleeps after 30 min; $7 Basic dyno + $5 smallest Postgres ≈ $12/mo minimum), give-them-their-due rows (zero-ops, 10+ year add-on ecosystem), migration FAQ, FAQPage schema. EN/TR. - `/alternatives` — the roundup-format hub the "coolify alternative / self-hosted heroku" SERPs actually reward: at-a-glance matrices (license, self-host, cloud, real starting prices, pricing model) for Coolify, Dokploy, CapRover, Dokku, Heroku, Railway, Render, plus honest per-tool reviews with "best for" verdicts — including Pushify's own weaknesses (younger ecosystem, smaller community) stated in its card. EN/TR, CollectionPage schema. - `/guides/deploy-nextjs` — a genuine step-by-step tutorial for the 100%-tutorial "deploy nextjs own server" SERP: Node 22 via NodeSource, swap for 1 GB builds, PM2 with systemd startup, full nginx reverse-proxy config, certbot SSL and a redeploy script with its downtime trade-off explained — then the automated Pushify route. Fully static SSR, TechArticle schema. - All three added to the sitemap and the footer's Resources column.

v0.2.0-beta.60

Dashboard

Changed

  • Ship only the active language — the Turkish dictionary is now a lazy chunk. Both full translation dictionaries (~4,400 lines each) were statically bundled into every page for every visitor. The bundle now contains only English; the Turkish dictionary loads on demand (once, then cached) when the locale is tr, with English fallback during the brief fetch and an automatic re-render when it lands. Homepage JS drops 1,518 → 1,408 KB raw and the TR chunk is no longer referenced by any page's initial load — the same saving applies to every route, including the dashboard. Verified on a production build: EN default renders English, a stored tr preference renders Turkish end-to-end.
  • Google Analytics moved fully off the critical path (lazyOnload instead of afterInteractive) — it no longer competes with hydration for main-thread time during the INP-sensitive window.

v0.2.0-beta.59

Dashboard

Changed

  • All settings tab contents brought into the quiet design language. Appearance: the theme picker is now three miniature dashboard previews rendered in each theme's own colors (System = half dark / half light) with a small check badge — the option shows itself instead of an icon; language options became calm pills. Sessions: session rows match the settings row pattern (small icon plate, wrapped meta line), "This device" is a green chip instead of an accent-tinted card, and the "you're only signed in here" copy no longer shows above a list of other sessions. API Keys: rows get the same treatment plus a green Active chip, the duplicated card header was deduped ("Your keys"), and meta wraps properly on mobile.
  • Settings navigation redesigned. The tab rail is now grouped the way the content actually splits — Account (Profile, Appearance, Notifications) and Access & security (Security, Sessions, API Keys) with quiet uppercase eyebrows. The loud accent-tinted active state and rotating chevrons are gone: active is a calm neutral pill with the icon as the only accent. On mobile the rail becomes a horizontally scrollable chip strip that auto-centers the active tab (deep links like ?tab=security land correctly). Verified in dark + light, desktop + 390px mobile.
  • Security settings visual refresh. The 2FA card grew a proper status header (shield icon plate — green when protected, muted when off) with plain-language state copy, and when 2FA is off, a quiet checklist of what enabling gets you (any authenticator app, 10 backup codes, every-device protection); when on, the footer hints how backup-code regeneration behaves. New Sign-in method card below shows how the account authenticates — password accounts see "Password is set · Active", Google/GitHub accounts see their social sign-in row plus a one-click Set a password shortcut to the Profile tab. Verified in both dark and Clean Pro light themes. EN/TR i18n.

v0.2.0-beta.58

Dashboard

Fixed

  • Settings adapt to Google/GitHub accounts without a password (pairs with backend beta.57). The 2FA disable and backup-code-regenerate dialogs now ask for a 6-digit authenticator code (or backup code) instead of a password when the account has none, with an explanatory hint. The Profile tab's password card becomes "Set password" for these accounts — no current-password field — and flips back to the normal change-password form once one is set. EN/TR i18n.

v0.2.0-beta.57

Dashboard

Fixed

  • Post-auth redirect now works end-to-end (professional `?redirect=` structure). Buying a domain from the public /domains page previously dumped users on the dashboard, losing the domain they picked — on both the login and logged-in paths. Now: the buy CTA is session-aware (logged-in users go straight to /dashboard/domains?domain=<name>; others to /register?redirect=…), registration finally consumes the saved redirect instead of hard-coding /dashboard, the login↔register cross-links carry the redirect along, and the dashboard auth guard captures the attempted URL so any deep link survives a login round-trip. A central sanitizeRedirectPath guard hardens every consumer against open redirects (absolute URLs, //host, backslash tricks, auth-page loops) — including the previously unsanitized login query param. The domains page pre-fills and auto-runs the search from ?domain= (kept in the URL as a shareable deep link). Verified with an end-to-end browser test: register with a picked domain → land on the domains page, search pre-filled and running.

v0.2.0-beta.56

Dashboard

Fixed (SEO audit follow-up)

  • `/domains` was invisible to Google. The page had no layout of its own, so it inherited the homepage's title, description and — critically — its canonical URL, telling Google it was a duplicate of /. It now has unique metadata, a self-referencing canonical, WebPage+Breadcrumb structured data, a sitemap entry, and nav + footer links (it was an orphan page reachable from nowhere).
  • Prices are now in the server-rendered HTML on `/pricing`. Plan prices previously existed only in the client-side data payload — AI crawlers and non-JS fetchers saw a pricing page with no prices. The page now fetches plans server-side (ISR, 1h) and seeds the client cache, so real dollar amounts render into the HTML; the interactive toggle still works as before. Title upgraded from generic "Pricing".
  • `/docs` had 10 `<h1>` tags — the 9 section headers are now <h2>, restoring a proper document outline for crawlers and AI section-extraction. Also: og:title separator aligned, TechArticle schema gains image/datePublished/dateModified.
  • `/changelog` split for Core Web Vitals: the page rendered 92 releases (~2,000 DOM nodes) in one document. It now shows the latest 30 with a link to the new /changelog/archive; CollectionPage + Breadcrumb structured data added.
  • Sitemap `lastmod` was one identical build timestamp for all 19 URLs — now per-route content dates (changelog keeps the build date, which is accurate for it).
  • Titles/metas: /features and /about got descriptive titles; /about's meta no longer promises "team" content the page doesn't have.
  • CSP was blocking Cloudflare Web Analytics (static.cloudflareinsights.com beacon 100% of loads) — now allowlisted. /vs/* schema gains datePublished/dateModified; global AggregateOffer gains highPrice; footer links got larger tap targets (WCAG).

v0.2.0-beta.55

Dashboard

Added

  • Domain management console (pairs with backend beta.56). Every purchased domain now has a Manage page with three tabs: DNS records (add/delete A, AAAA, CNAME, MX, TXT, SRV, NS with TTL/priority), Email forwarding (info@yourdomain → your inbox aliases), and Settings — transfer-lock toggle, custom nameservers (point at Cloudflare etc.), and an ICANN-compliant transfer-out section that reveals the EPP/auth code (with copy button and security warning).
  • Transfer a domain in. New dialog on the Domains page: enter the domain, get the live price (includes 1-year renewal), paste the auth code from your current registrar, and start — paid from credits, auto-refunded if the transfer is rejected. Pending/failed transfers show as status chips on the list.
  • Public `/domains` search page. Marketing-site domain search (rate-limited, no login needed) showing live availability and prices, with a "Sign up to buy" CTA — a Vercel-style acquisition funnel. EN/TR i18n throughout.

v0.2.0-beta.54

Dashboard

Added

  • Multi-year domain terms + pay by card (pairs with backend beta.55). The purchase dialog now has a 1/2/3/5-year term selector with a live total (year 1 at registration price, later years at renewal price) and two payment options: buy with credits as before, or Pay with card — a Stripe Checkout redirect that registers the domain automatically after payment (returning to the Domains page shows a "being registered" toast and refreshes the list). EN/TR i18n.

v0.2.0-beta.59

Platform & API

Added

  • Notification preferences are now real. They lived only in the browser's localStorage — the backend never saw them. New users.notification_prefs (migration 0038) with GET/PUT /auth/me/notification-prefs, which also exposes the onboarding-email opt-out as a toggle. Two preferences gained actual consumers immediately: securityAlerts now gates the new-device sign-in email, and weeklyDigest powers a brand-new weekly digest worker — Mondays (UTC), opt-in only, real per-organization numbers (deployments and failures this week, active projects, running servers, credit balance), atomic per-week dedupe, and skipped entirely when there is nothing to report. deploymentAlerts/productUpdates are stored and ready for their future senders.

v0.2.0-beta.58

Platform & API

Added

  • Onboarding email sequence (state-driven, not a dumb timer). A new hourly worker walks organizations created in the last 30 days and sends at most one lifecycle email per state: ~day 1 "deploy your first app" (only if they haven't), ~day 3 either "need a hand?" (still no deploy) or "connect a domain" (deployed, no custom domain), day 7 "add a database" (deployed, no DB). Each email links a signed unsubscribe URL (GET /auth/unsubscribe-onboarding?token=) that sets a per-user opt-out honored by the whole sequence; sends are claimed atomically in a new onboarding_emails table (unique per org+email) so concurrent sweeps can never double-send, and failed sends retry next sweep. The 30-day cap guarantees existing users are never spammed at rollout. Migration 0038* — tables onboarding_emails, cancellation_feedback, column users.onboarding_emails_opt_out. 6 unit tests on the state machine.
  • Cancellation exit surveyPOST /billing/cancellation-feedback records a one-question reason (too_expensive | missing_features | bugs | switched | project_ended | other + optional comment) and notifies the operator (feedback.cancellation admin event). Never blocks the cancel flow.

v0.2.0-beta.57

Platform & API

Fixed

  • OAuth (Google/GitHub) accounts can now manage 2FA and set a password. Accounts without a password hit dead ends on every password-confirmation flow. Now: /auth/me exposes hasPassword; disabling 2FA and regenerating backup codes accept either the account password or (for passwordless accounts) a current authenticator/backup code via the shared re-auth guard; and /auth/me/change-password lets a passwordless account set its first password without currentPassword (the authenticated session is the proof) — password accounts still verify the current password and the not-same-as-old rule.
  • 2FA disable/backup-code regeneration was broken for everyone: the password check passed its arguments to verifyPassword in the wrong order, so the correct password always failed verification. Fixed alongside the guard rework.

v0.2.0-beta.56

Platform & API

Added

  • Full domain management for sold domains. Customers' domains live in Pushify's reseller account, so the platform is their only control panel — this release makes it a complete one: - DNS records — list/create/update/delete A, AAAA, CNAME, MX, TXT, SRV, NS records (host/TTL/priority validation) via /domains/:domain/dns. - Domain transfer-inGET /domains/transfer/quote prices a transfer at the TLD's renewal rate (probed live from the registrar); POST /domains/transfer charges the wallet, starts the transfer with the auth/EPP code (refund if it fails to start), and records it as transfer_pending (migration 0036). The renewal worker now also polls in-flight transfers every sweep: completed → domain becomes active with its real expiry; cancelled/rejected → automatic refund + status transfer_failed. Start/result emails (EN/TR) + operator events. - Transfer-out (ICANN compliance)POST /domains/:domain/auth-code unlocks the domain and returns its EPP code so users can leave freely; viewing it triggers a security notice email to the owner and an operator event. - Registrar lock toggle and custom nameservers (2-6, validated) — point a domain at Cloudflare or any external DNS. - Email forwarding[email protected] → anywhere aliases (list/create/delete). - Public availability searchGET /domains/public-search (no auth, 10 req/min/IP) to power a marketing domain-search page.

v0.2.0-beta.55

Platform & API

Added

  • Multi-year domain registration (1–5 years). POST /domains/purchase and the new quote logic accept a years term; the total is priced as year-1 registration + (years−1) renewals on both the wholesale and retail side, so multi-year never undercuts cost. Term is stored per domain and reflected in emails/admin events.
  • Post-redirect purchase confirmPOST /domains/purchase/confirm fulfills a paid checkout session directly when the user returns from Stripe (org-verified, idempotent with the webhook per session id), so domains register instantly even before the webhook lands — and local/dev setups work without stripe listen.
  • Pay by card when credits don't cover a domain. New POST /domains/purchase/checkout creates a Stripe Checkout session for the exact quoted amount; on checkout.session.completed the webhook credits the wallet with the paid amount (idempotent per session id) and registers the domain through the normal purchase path. If registration fails after payment, the paid amount stays as wallet credit (never lost) and the operator is notified.

v0.2.0-beta.53

Dashboard

Added

  • Domains page (/dashboard/domains, pairs with backend beta.54): search a name or keyword and see availability + prices across 10 popular TLDs, buy in one click (paid from infrastructure credits), and optionally connect the domain to a project during checkout — DNS records and SSL are set up automatically. Purchased domains list shows expiry, attached project, renewal problems, and a per-domain auto-renew toggle. New Domains item in the sidebar (Globe icon). When the platform has no registrar configured, the page shows a quiet "not enabled" note instead. EN/TR i18n.

v0.2.0-beta.54

Platform & API

Added

  • Domain sales (registrar reseller integration). Users can now search, buy, and auto-connect custom domains without leaving Pushify: - Registrar adapter layer (REGISTRAR_PROVIDER=namecom + NAMECOM_USERNAME/NAMECOM_TOKEN, optional NAMECOM_API_URL for name.com's test environment). The adapter interface is provider-agnostic so higher-volume wholesalers (OpenSRS/CentralNic) can be added later without touching the product layer. Unset = feature hidden everywhere. - Retail pricing with margin — wholesale price + DOMAIN_MARGIN_PERCENT (default 20%), rounded up to a x.49/x.99 ending, never below cost; DOMAIN_MAX_PRICE_CENTS (default $300) and a premium-domain block guard against expensive surprises. - API: GET /api/v1/domains/config (feature discovery), GET /domains/search?q= (availability + retail prices across 10 popular TLDs), POST /domains/purchase, GET /domains, PATCH /domains/:domain/auto-renew. - Payment from infra credits: purchase debits the wallet (new domain_purchase/domain_renewal transaction types); the charge is taken first and automatically refunded if registration fails. WHOIS privacy is enabled on registration. - Auto-connect to a project: optional projectId creates apex A + www CNAME records at the registrar pointing at the project's server and registers the domain on the project (existing verify → nginx → SSL flow takes over). Best-effort — a DNS/attach hiccup never voids the purchase. - Renewal worker (12h sweep): domains expiring within 30 days auto-renew from the wallet at the registrar's live renewal price (falls back to the price captured at purchase), refund on failure; insufficient credits / auto-renew-off / failures send the owner a reminder email (throttled to one per 7 days); past-expiry domains are marked expired. New tables: purchased_domains (migration 0035). - Emails + admin events: purchase/renewal confirmations and renewal reminders (EN/TR); domain.purchased, domain.renewed, domain.renewal_failed operator notifications.

v0.2.0-beta.53

Platform & API

Fixed

  • Public port no longer changes on every compose-stack redeploy. Marketplace stacks (Supabase, Cal.com, Appwrite…) re-scanned for a "free" port on each deploy while the previous stack was still running — so the stack saw its own port as busy and shifted to a new port (and a new URL) every redeploy. The port is now sticky: the server-side port registry (and, for stacks deployed before this fix, the PUSHIFY_PUBLIC_PORT recorded in the stack's .env) is reused as long as the project's own containers hold the port or it is otherwise free; a brand-new port is picked only on first deploy or if another process took the old one while the stack was down.
  • New port assignments now avoid every genuinely busy port. The used-port scan only matched 127.0.0.1: Docker bindings, but app containers publish on 0.0.0.0 — so the scan saw almost nothing, and host daemons (user services, databases) weren't checked at all. Assignment now skips all Docker-published host ports and all host TCP listeners, and a registry entry squatted by a foreign process is released and reassigned instead of producing a doomed docker run. Ownership checks are exact (pushify-<slug>, its -blue/-green variants, or the compose project label) so project app can never claim app-2's port. 6 unit tests.

v0.2.0-beta.52

Platform & API

Fixed

  • Custom env vars now reach Supabase (and other compose) containers. Adding e.g. GOTRUE_EXTERNAL_GOOGLE_SKIP_NONCE_CHECK in a Supabase project's Environment tab wrote it to the stack's .env, but Docker Compose only injects variables explicitly listed under a service's environment: — so the value never appeared inside the GoTrue container. Marketplace templates can now declare envPassthrough (service → env-key prefixes); at deploy time a docker-compose.override.yml is generated that forwards matching user vars to the right service (user values win over template defaults on collision; stale overrides are removed). The Supabase template forwards GOTRUE_*auth and PGRST_*rest, unlocking all GoTrue/PostgREST tuning knobs. Redeploy required after changing env vars, as before. 5 unit tests.

v0.2.0-beta.52

Dashboard

Added

  • Invoices in Billing. New section on the Billing page (pairs with backend beta.50): your Stripe invoice history with number, date, status chip, amount, a hosted View link and PDF download. Hidden until the organization has invoices. EN/TR i18n.

v0.2.0-beta.51

Dashboard

Added

  • SECURITY.md — vulnerability disclosure policy (matching the backend's).
  • Real product screenshots in the README — the redesigned landing hero and the dashboard preview, captured at 2× from the live page, stored under .github/assets/.

v0.2.0-beta.50

Dashboard

Changed

  • Navbar scroll animation + mobile audit. The capsule navbar now reacts to scroll: quiet and airy at the top (h-14, soft shadow), it condenses smoothly on scroll (h-12, closer to the edge, more opaque, deeper shadow) with a 300ms transition — the rAF-throttled listener is passive, so scrolling stays smooth. A full 390px-wide sweep of the homepage (8 scroll depths, plus footer/CTA) confirmed the responsive layout is clean end-to-end; no fixes were needed.

v0.2.0-beta.51

Platform & API

Changed

  • Payment confirmation emails now link to the invoice/receipt. The "plan activated" email includes the Stripe hosted invoice link (resolved from the checkout session's invoice) and the "credits added" email includes the Stripe receipt link (resolved from the payment intent's charge) — both best-effort: if Stripe lookup fails, the email still goes out without the link. Complements Stripe's native customer receipt/invoice emails (enabled in the dashboard) without duplicating them.

v0.2.0-beta.50

Platform & API

Security

  • Secrets are now masked in every log surface. User builds routinely print env values (console.log(process.env), framework error dumps, connection-string errors) — those secrets used to land verbatim in build logs, the persisted 7-day runtime logs, and live log streams. A per-project masker (built from the project's decrypted env values via a sensitive-key/long-value heuristic, plus ad-hoc secrets like git access tokens) now replaces occurrences with •••••• at write/stream time: the deploy-log choke point (addLog), the log-collector's persisted chunks (10-min-cached masker), and both live SSE container streams. Multi-line values (PEM keys) are masked line-by-line; trivial values (production, ports…) are left alone so logs stay readable. 8 unit tests.

Added

  • Invoice history endpointGET /api/v1/billing/invoices lists the organization's Stripe invoices (number, date, amount, status, hosted/PDF links; last 24). Returns [] when Stripe isn't configured. Pairs with the dashboard's new Billing → Invoices section.

v0.2.0-beta.49

Platform & API

Added

  • Admin event notification emails. Set ADMIN_NOTIFY_EMAILS (comma-separated, multiple operators supported) and every significant platform event emails the list: user registration, subscription activated/canceled, payment failed, infra wallet credited, server created (managed & BYOS) / deleted / suspended for billing, project created/deleted, database created/deleted, and failed deployments — 14 instrumentation points. Delivery rides the existing BullMQ infrastructure (new admin-notify queue + worker, 3 retries with backoff) with a direct-send fallback when Redis is unset; every call site is fire-and-forget so a mail failure can never break or slow the underlying operation. Emails are a clean field-table template (HTML+text, HTML-escaped). Unset = feature off.

v0.2.0-beta.48

Platform & API

Fixed

  • CRITICAL — hourly infra billing overcharged small servers up to ~2.5×. The integer hourly price was derived with a double rounding (EUR→USD round on a sub-cent amount, then margin ceil): a server quoted $5.75/mo was actually billed 2¢/hour = $14.60/mo, draining a month of credits in ~2 weeks and then auto-suspending the server. Billing now accrues from the accurate MONTHLY price prorated over elapsed wall-clock time, carrying sub-cent remainders in millicents (new infra_billing_carry_millicents column, migration 0034) — the long-run total equals monthly/730 per hour exactly (unit-tested: 730 hourly ticks bill the monthly price ±1¢; restart-heavy schedules bill the same as regular ones). The displayed hourly price is now derived from the customer monthly with a single rounding, and the monthly-burn estimate uses the monthly price directly.
  • Restarting a suspended server no longer demands a full month's balance. start required customerPriceMonthlyCents in the wallet; it now requires 72 hours of coverage — and starting resets the billing anchor so stopped time is never billed.

Added

  • `npm run refund:infra-overcharge` — computes, per organization, the difference between what server_hourly_charge transactions actually debited and the fair monthly-rate amount (each old charge was intended to be one hour), and credits it back as an adjustment. Dry-run by default; --apply to execute.