Javis OS
Javis OS는 기업가를 돕는 에이전시 AI로, 업무를 지원하고 함께 성장합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기A production-ready multi-tenant SaaS billing platform built with Django, Django REST Framework, PostgreSQL, React, and Razorpay-ready payment architecture. Tenora provides tenant isolation, authentication, subscriptions, usage metering, proration, reconciliation, background billing jobs, and a modern customer-facing UI.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Tenora is a multi-tenant SaaS for property owners: workspaces, properties and
units, residents and leases, electricity meters and tariffs, monthly bill
generation, payments and receipts, reminders and reports — plus online payment
collection from residents, and Tenora's own subscription billing for the
owners.README 전문 보기 ↗Tenora is a multi-tenant SaaS for property owners: workspaces, properties and units, residents and leases, electricity meters and tariffs, monthly bill generation, payments and receipts, reminders and reports — plus online payment collection from residents, and Tenora's own subscription billing for the owners.
It is a portfolio project, and the engineering problems are the point rather than feature count: tenant isolation, idempotency, concurrency correctness, webhook-driven state, and reconciliation. Where a shortcut would have made a demo look better but the system less honest, the honest option was taken and written down.
Live: app · API (Render free tier — the first request after idling wakes the service and can take ~30s)
The single most important thing to understand about this codebase is that it contains two separate money flows, deliberately kept apart down to the database table:
apps.billing | apps.properties | |
|---|---|---|
| Who pays whom | workspace owner → Tenora | resident → workspace owner |
| What | SaaS subscription (Basic / Pro) | monthly property bills (rent, electricity, charges) |
| Provider | Cashfree Subscriptions (recurring mandate) | Cashfree Payment Gateway (one-off order) |
| Gateway seam | apps.billing.gateway.get_gateway() | apps.properties.gateway.get_property_gateway() |
| Webhook route | /api/webhooks/cashfree/subscriptions/ | /api/webhooks/cashfree/property-payments/ |
| Settings | CASHFREE_SUBSCRIPTION_* | CASHFREE_*, PROPERTY_* |
Neither imports the other. They share a vendor and nothing else — different API families, credentials, event vocabularies and routes — so a change to one cannot quietly alter the other.
Workspaces and people. A Tenant is a workspace. Owners manage it;
residents (Membership.Role.MEMBER) see only their own billing. Nobody is ever
added silently — an invitation is created PENDING and only the invited user
accepting it creates the membership and resident profile. Authorization is by
capability (property.manage, billing.manage, billing.view_own, …), not
scattered role checks. Plan limits on workspaces and seats are enforced under
row locks, with pending invitations reserving a seat.
Property setup. Properties → units → leases (with monthly rent) → meters (with multipliers) → readings, plus an electricity tariff with effective dates and full rate history.
Billing. Open a monthly cycle, generate draft bills from the leases,
readings and the tariff in force, review, then publish. Issued bills are
immutable history: they snapshot names, rent, readings, rates and totals, and
change afterwards only through a BillCorrection (a visible adjustment line
with a reason and an author) or a payment. Overdue is always derived from the
server date, never stored. Money is integer minor units end to end; rounding
happens in exactly one place.
Payments and receipts. Offline payments (cash, UPI, bank transfer) are recorded by the owner; online payments are collected from residents through Cashfree. Every payment issues a numbered receipt, and both bills and receipts download as PDFs. Aging and collection reports, portfolio roll-ups across workspaces, and scheduled due/overdue reminders are included.
Online resident payments (P9). A resident pays their bill from the resident
portal. The browser never supplies an amount or an outcome: the server fixes the
amount, creates the provider order, and only a verified capture settles the
bill — through the same PaymentService.record path an offline payment uses.
Transient checkout state lives on OnlinePaymentAttempt, never on Payment.
Tenora subscriptions. Owners subscribe through Cashfree Subscriptions
(recurring mandates: UPI AutoPay, card, eNACH). A local Subscription row is
never created from anything the client reports — only a verified webhook creates
or changes it. Plan changes follow explicit billing rules (below).
Operator control plane. A staff-only admin surface spanning tenants, plans, subscriptions, webhook events, reconciliation, users and an audit log, with root-tier controls and workspace suspension.
Accounts. JWT auth (SimpleJWT) with refresh rotation, Google sign-in, email verification, password change, and account deletion that anonymises rather than hard-deletes.
These are the parts a reviewer should look at; each was deliberated, and most are pinned by tests that fail if the reasoning is undone.
Tenant isolation. Tenant resolution lives in a DRF authentication class
(apps/tenants/authentication.py), not Django middleware — SimpleJWT
authenticates after the middleware stack, so middleware could not rely on
request.user. tenant_id is never accepted from a request body on any
endpoint; it comes only from the X-Tenant-ID header plus an ACTIVE
Membership. Cross-tenant access returns 404, never 403 — a 403 confirms
the object exists. The exemption list is an exact-match frozenset, because a
prefix would silently exempt every future sub-route.
Idempotency is a database constraint, not a check. Webhook deduplication is
a unique index on the provider's delivery id (or a digest of the verified raw
body); one open checkout per bill, one payment per provider capture, and one
subscription per tenant are all unique constraints. IntegrityError is caught
outside the atomic() block that raised it, because Postgres marks the
transaction broken otherwise.
Webhooks are the authority. Signatures are verified on the raw body
before anything is parsed, and a failed check stores nothing. Provider events
are normalised into the project's own vocabulary inside the adapter, so domain
code never sees provider JSON. Subscription state changes only through
SubscriptionService and its explicit legal-transition table.
Money the browser cannot influence. Amounts, orders and outcomes are all server-side. The browser receives an opaque session token, never a key, never a price it can edit.
Plan changes are billing decisions. One policy answers both entry points, so a client cannot get a different answer by picking a different endpoint: same plan is a no-op; Basic → Pro is a paid upgrade that must go through checkout; downgrades and monthly↔annual switches are refused (no proration semantics have been designed, and inventing one would be a guess about the customer's money); an unclassified plan code fails closed. An upgrade takes effect only when the new mandate's webhook activates — until then the existing subscription keeps running untouched, so an abandoned checkout or a failed authorisation changes nothing.
Failing closed. When a provider is unreachable or returns a status the adapter does not recognise, the answer is "unknown" — never "safe to discard". An abandoned checkout can be replaced only when it is unconfirmed, past a staleness window, and the provider confirms it was never taken up.
| Layer | Choice |
|---|---|
| Backend | Python 3.12, Django 5, Django REST Framework, SimpleJWT |
| Database | PostgreSQL |
| Async | Celery + Redis (webhook retry sweep, reminders, reconciliation, usage snapshots) |
| Payments | Cashfree (subscriptions + payment gateway), behind per-domain adapters |
| Frontend | React 19, TypeScript, Vite, TanStack Query, Tailwind |
| Frontend tests | Vitest, Testing Library, MSW |
| Packaging | Docker Compose (frontend, backend, Postgres, Redis, worker, beat) |
| Hosting | Render (two services: static frontend + Dockerised backend) |
apps/
users/ accounts, auth, email verification, Google sign-in
tenants/ workspaces, memberships, invitations, plan limits, capabilities
properties/ the property domain: properties, units, residents, leases,
meters, readings, tariffs, cycles, bills, payments, receipts,
reports, and P9 online resident payments (own gateway package)
billing/ Tenora's own subscriptions: plans, checkout, webhooks,
proration records, reconciliation (own gateway package)
notifications/ in-app notifications and reminder delivery
platform/ operator control plane (staff-only, cross-tenant)
config/ settings, URLs, Celery app
frontend/ React app (see frontend/README.md)
docs/ specifications — one per stage, written before the code
Two independent ways; Docker is additive, not a replacement.
cp .env.example .env
docker compose up --build
Open http://localhost:8080 and sign in with the seeded demo account:
[email protected] / demo-pass-12345
That seed is throwaway public demo data (one workspace, plans, a trialing
subscription) so the dashboard has something to show. It is idempotent, and
SEED_DEMO_DATA=false disables it.
Default host ports, each overridable in .env if taken: frontend 8080
(FRONTEND_HOST_PORT), backend 8001 (BACKEND_HOST_PORT), Postgres 5433
(DB_HOST_PORT).
Prerequisites: Python 3.12, Node 20+, PostgreSQL (Redis only if you want the background jobs — not needed for the app or the tests).
python -m venv .venv
.venv\Scripts\activate # Windows; source .venv/bin/activate elsewhere
pip install -r requirements/base.txt
cp .env.example .env # set POSTGRES_PASSWORD etc.
python manage.py migrate
python manage.py runserver
cd frontend
npm install
npm run dev # http://localhost:5173
celery -A config worker --loglevel=info
celery -A config beat --loglevel=info
The same work is available on demand: process_webhook_events, meter_usage,
reconcile_subscriptions, reconcile_online_payments, send_billing_reminders.
The test suite runs tasks in-process and never needs Redis.
Registration creates an unverified account and password login is refused
until the emailed link is used. No real email provider is configured (a
deliberate boundary — docs/email-verification-spec.md §4.6): Django's console
backend prints the message. Find the http://.../verify-email?token=... link in
the backend terminal, or docker compose logs backend.
Every variable the stack reads is documented in .env.example; .env itself is
gitignored and no secret is ever committed. The groups that matter:
| Group | Purpose |
|---|---|
POSTGRES_*, DJANGO_*, CORS_ALLOWED_ORIGINS | core service configuration |
PAYMENT_GATEWAY | which subscription adapter is active: cashfree, razorpay or mock |
CASHFREE_SUBSCRIPTION_* | Tenora subscription credentials, environment, API version, webhook tolerance |
PROPERTY_PAYMENT_GATEWAY, PROPERTY_ONLINE_PAYMENTS_ENABLED, CASHFREE_* | P9 resident payments (kill switch defaults to off) |
SUBSCRIPTION_CHECKOUT_STALE_AFTER_MINUTES | how long an in-flight checkout is presumed live |
EMAIL_*, GOOGLE_OAUTH_CLIENT_ID, FRONTEND_URL, BACKEND_PUBLIC_URL | delivery, sign-in and the URLs providers redirect to |
Use sandbox credentials for anything but production. Both webhooks must be
registered in the Cashfree dashboard — the subscription one under the
Subscriptions tab, at API version 2026-01-01.
python manage.py test # backend — 961 tests
cd frontend && npm test # frontend — 557 tests
Backend tests use a mock gateway (@override_settings(PAYMENT_GATEWAY="mock")
or by patching get_gateway) and never touch the network; provider adapters are
tested against recorded fixtures, including signature verification with known
vectors. Concurrency guarantees are tested with real threads on real
connections (TransactionTestCase), not simulated. Frontend tests run against
the real app with MSW-stubbed endpoints.
Stated plainly, because a README that overclaims is worse than one that admits where the edges are.
tenora_basic_monthly, tenora_pro_monthly, tenora_pro_annual), a mandate
was authorised in the browser, and its signed webhook activated exactly one
subscription on the intended plan through SubscriptionService. The
downgrade and billing-cycle refusals were confirmed on production too./subscription.
Cosmetic but real: the webhook has already activated the subscription by
then, so no state is lost — the page just fails to show it. Not yet
diagnosed.current_period_start of 16 Oct, because the period
is taken from the CHARGED event's next_schedule_date rather than the cycle
actually being paid for.python manage.py process_webhook_events is the
manual path. Subscription state is unaffected; the event is simply pending.docs/worker-scheduler-operations.md.docs/ holds a specification per stage, written before the code and kept as the
record of what was decided and why — project-master-spec.md for the
subscription platform, TENORA_PROPERTY_BILLING_MASTER_PLAN.md for the property
domain, payment-gateway-adapter-spec.md for the gateway seam,
operator-control-plane-spec.md for the admin surface, and the numbered stage
specs for everything else. CLAUDE.md lists the architecture rules that must
not be "fixed" back.
No card numbers, CVVs or expiry dates are ever stored — the provider owns payment collection. Webhook signatures are verified on the raw body before parsing. Credentials live only in the environment, never in the repository, and are never logged: provider diagnostics record the error code, type, message and a sanitised request body, with subscriber contact details redacted.