dumbtv
dumbtv가 ADB로 Fire TV에서 광고·추적·업데이트 알림을 제거, 루트·앱 변경 없이, 무료 오픈소스
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Your content is your business. Protect the work behind your brand.
A validation pilot: a creator signs up, subscribes, uploads the files their
business depends on, and can download or delete them at any time.
The application is complete and builds clean. It needs three external accounts
before anyone can sign up — see Setup below.
Without credentials the site still builds and deploys: the landing page is fully
functional and the auth pages show an honest "not switched on yet" notice rather
than erroring.README 전문 보기 ↗Your content is your business. Protect the work behind your brand.
A validation pilot: a creator signs up, subscribes, uploads the files their business depends on, and can download or delete them at any time.
Stack: Next.js 16 · TypeScript · Supabase (Auth, Postgres, Storage) · Paddle (subscriptions) · Vercel
The application is complete and builds clean. It needs three external accounts before anyone can sign up — see Setup below.
| Area | State |
|---|---|
| Landing page | Works with no configuration |
| Auth, vault, uploads | Needs a Supabase project |
| Subscriptions | Needs a Paddle sandbox account |
| Deployment | Needs a Vercel project |
Without credentials the site still builds and deploys: the landing page is fully functional and the auth pages show an honest "not switched on yet" notice rather than erroring.
Create a project at https://supabase.com/dashboard — choose the
South Asia (Mumbai) ap-south-1 region for creators in India.
Project Settings → API, copy:
NEXT_PUBLIC_SUPABASE_URLanon public key → NEXT_PUBLIC_SUPABASE_ANON_KEYservice_role key → SUPABASE_SERVICE_ROLE_KEY (server-only, never
expose this to the browser)SQL Editor, run every file in supabase/migrations/ in numeric order,
as eight separate executions — 0001_init.sql, 0002_storage.sql,
0003_admin_stats.sql, 0004_quota_enforcement.sql,
0005_checkout_sessions.sql, 0006_retire_pro_plan.sql,
0007_harden_quota_and_isolation.sql,
0008_storage_hardening_hosted.sql.
They create the tables, all RLS policies, the private vault bucket, and the
triggers that enforce size and quota in the database.
Separate executions matter: a multi-statement send is one implicit transaction, so a failure in a later file would silently discard the earlier ones.
Two things to check rather than assume, because both can fail quietly:
storage.objects and storage.buckets belong to supabase_storage_admin,
so some of what 0002, 0004 and 0007 do there can be refused. Measured
on the live project: postgres is not a member of that role, yet
CREATE POLICY and CREATE TRIGGER on storage.objects both still
succeed — only ALTER TABLE ... ENABLE ROW LEVEL SECURITY fails. 0008
exists for exactly that one statement, which is why it must be applied.
Do not try set role supabase_storage_admin; as a workaround — with
postgres not a member, that statement fails with 42501 too. If the
bucket write in 0002 is refused, create the bucket through
Storage → New bucket instead.
select pg_has_role(current_user,'supabase_storage_admin','USAGE') as can_act_as_storage_admin,
has_table_privilege('storage.buckets','INSERT') as can_write_buckets;
A refused UPDATE under RLS raises no error, it just matches zero rows, so
0004's MIME allowlist can fail silently. Afterwards confirm:
select relrowsecurity from pg_class where oid='storage.objects'::regclass; -- must be true
select array_length(allowed_mime_types,1), 'image/svg+xml' = any(allowed_mime_types)
from storage.buckets where id='vault'; -- 34, false
0007 closes a set of quota bypasses and cross-user leaks that were measured
against 0001..0006; holds the harness that proves it, and
explains how to run it locally.
Authentication → URL Configuration: set Site URL to your deployed origin
and add <origin>/auth/callback to Redirect URLs.
Create a sandbox account at https://sandbox-vendors.paddle.com.
Catalog → Products: create one "Creator" product with a recurring
monthly price, and copy its price id (pri_...) into
PADDLE_CREATOR_PRICE_ID.
The amount must match what the site advertises. PLANS.creator.priceLabel
in src/lib/plans.ts is a display string — the app never reads an amount back
from Paddle, so a price created at a different figure would be charged silently
while the landing page still quoted the old one. Pilot pricing is $4 / month
for up to 100 GB; if that changes, change both. Leaving
PADDLE_CREATOR_PRICE_ID empty keeps billing off entirely, which is the
intended state until the price is confirmed.
Developer Tools → Authentication: create an API key → PADDLE_API_KEY.
Developer Tools → Client-side tokens: create one →
NEXT_PUBLIC_PADDLE_CLIENT_TOKEN.
Developer Tools → Notifications: add a destination pointing at
https://<your-domain>/api/paddle/webhook, subscribed to the
subscription.* events. Copy the signing secret → PADDLE_WEBHOOK_SECRET.
Leave NEXT_PUBLIC_PADDLE_ENVIRONMENT=sandbox until you go live.
cp .env.example .env.local # then fill in the values
npm install
npm run dev
npm run dev # development server
npm run build # production build
npm run lint # ESLint
npm run typecheck # tsc --noEmit
npm run test:e2e # Playwright (public surface only by default)
# The full signed-in journey, once Supabase is live:
E2E_SUPABASE_READY=1 npm run test:e2e
src/
app/
page.tsx landing page
(auth)/ login, signup, password reset
dashboard/ overview, vault, upload, plan
admin/ internal pilot stats (ADMIN_EMAILS gated)
api/
assets/ upload authorisation, list, download, delete
paddle/ checkout price lookup, webhook
lib/
storage/ provider-agnostic file storage
vault.ts quota + asset lifecycle
paddle.ts subscription mapping
supabase/ browser / server / admin clients
supabase/migrations/ schema, RLS, storage policies
e2e/ Playwright specs
assets rows store storage_provider and storage_key, and all file
operations go through the StorageProvider interface in
src/lib/storage/types.ts. No application code builds or parses a
Supabase-specific URL.
Supabase Storage is the only implementation today. Adding another private object
store later means writing one class and registering it in
src/lib/storage/index.ts. Reads dispatch on each row's own
storage_provider, so existing files keep resolving through whichever provider
actually holds them — creators never re-upload.
The browser asks /api/assets/upload-url for a short-lived signed target, then
sends the bytes straight to storage and calls /api/assets to record the file.
Vercel caps a function request body at 4.5 MB; this path sidesteps that
entirely, so multi-gigabyte video works.
Every tile on the landing page is real creator photography, and the hero backdrop is real footage, all sourced from Pexels under the Pexels License (free for commercial use, modification allowed, attribution not required). Provenance for every file — source page, photographer, licence — is recorded in public/media/CREDITS.md.
scripts/photo-manifest.json is the source of truth: one entry per slot with
the photo's URL, page, credit and licence, plus the hero video. Run
FFMPEG=/path/to/ffmpeg node scripts/ingest-photos.mjs
to (re)build public/media/photos/*.webp (attention-cropped to each tile's
exact aspect ratio, ≤1280 px, WebP q72) and the hero loop
(hero-creator.webm VP9 + hero-creator.mp4 H.264 fallback + poster, 720p,
9-second seamless cross-faded loop, no audio track). Without FFMPEG the
photos still import and the video is reported as skipped. The script also
regenerates CREDITS.md, crediting everything that is
on disk rather than only the current run's downloads, so a partial run never
drops the record for a file that is still shipping.
The hero video is muted, playsInline, loop and poster-backed. Motion is
opt-in: the element ships with no autoplay and no <source> children, and
the client adds them only once it knows the viewer has not asked for reduced
motion. A browser that blocks autoplay — or a visitor with reduced motion —
simply sees the poster, and in the reduced-motion case the 618 KB loop is never
requested at all.
Changing a picture: edit the manifest entry (URL, credit, optional crop
position, optional crop box) and re-run the script. crop takes
{left, top, width, height} as fractions of the framed tile and is how a shot
whose full frame carries a legible third-party wordmark earns its place — see
brand-01. src/lib/media.ts keeps a swapHint per
slot describing the brief, so a replacement stays on-brief and at the same
aspect ratio without any layout change.
scripts/generate-media.mjs is the earlier fallback that produced original
gradient artwork when no stock library was reachable; it is kept for that
scenario but its output is no longer committed.
Scroll reveals use IntersectionObserver with a rect-based backstop, and the
hidden state is gated on html[data-js="1"] — set by an inline script in the
document head. If scripting never runs, the page renders fully visible rather
than blank. prefers-reduced-motion disables every animation and settles the
product demo on its final frame.
auth.uid(). Isolation is enforced by Postgres, not by
frontend filtering.<user_id>/<uuid>.<ext> — never derived from the
uploaded filename, so path traversal is structurally impossible.assets overwrites file_size_bytes
from the real stored object and checks the plan limit under a per-user lock
(which also closes the race where parallel uploads each read the same
pre-insert total); a trigger on storage.objects rejects over-quota uploads
that skip the metadata table entirely.finalizeUpload re-validates against the stored object.profiles.email is not user-writable (column-level grant), so no future
feature can trust it as an identity..env* is gitignored. No secret belongs in this repository.storage.objects. The Supabase
SQL Editor runs as postgres and can do this; if a future hosted change
restricts it, the assets trigger still enforces quota for every file the
app records.current_period_end, so a lost webhook could
leave a paid allowance in place longer than it should. There is no
event_id idempotency guard either, so a delayed retry can overwrite newer
state. Both are worth adding before charging at scale.supabase/tests/supabase/tests/README.md