Javis OS
Javis OS는 기업가를 돕는 에이전시 AI로, 업무를 지원하고 함께 성장합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Rezervo – online rezervace pro malé podniky (Next.js 16, Supabase, Cloudflare Workers)
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Rezervační systém pro kadeřnictví, fyzioterapie, masáže a podobné malé podniky. Majitel si nastaví služby a otevírací dobu, zákazníci si sami rezervují volný termín na veřejné stránce podniku a obě strany dostanou e-mail.
Portfolio projekt · Next.js 16 (App Router) · TypeScript · Tailwind CSS 4 · Supabase · Resend · Cloudflare Workers
https://rezervo.bukac-adam.workers.dev
| Veřejná rezervační stránka | /u-nuzek – fiktivní „Kadeřnictví U Nůžek“ |
| Administrace majitele | /login – [email protected] / demo-heslo-123 |
| API klíč demo podniku | po přihlášení v /dashboard/api |
| REST API | https://rezervo.bukac-adam.workers.dev/api/v1 – viz Veřejné REST API |
curl -H "Authorization: Bearer <API klíč>" https://rezervo.bukac-adam.workers.dev/api/v1/services
Testovací účet je sdílený, nevyplňujte skutečné osobní údaje. V ukázkovém účtu nelze měnit heslo, adresu stránky, e-mail pro upozornění ani API klíč; tyto údaje se navíc každou hodinu obnovují (/api/cron/demo-reset). Data (služby, rezervace) obnoví npm run seed.
| Veřejná rezervační stránka | Výběr termínu a údaje zákazníka |
|---|---|
Screenshoty jsou z produkčního nasazení s ukázkovými daty.
/u-nuzek), kontakty, služby (délka, cena), otevírací doba pro každý den v týdnu, krok nabízených časů./[slug] – výběr služby → (pracovníka) → dne → volného času → kontaktní údaje.EXCLUDE constraint nad časovým rozsahem pro každého pracovníka), takže to platí i pro dva zákazníky, kteří kliknou ve stejnou chvíli./rezervace/<id>/<token>, HMAC – nic se neukládá) na stránku, kde zákazník rezervaci zruší nejpozději 2 hodiny předem; majitel dostane e-mail.| Oblast | Řešení |
|---|---|
| Integrita dat | bookings_no_overlap – EXCLUDE USING gist (staff_id WITH =, tstzrange(starts_at, ends_at, '[)') WITH &&) WHERE status = 'confirmed'. Navazující termíny (10:00–11:00 a 11:00–12:00) jsou povolené; triggery hlídají, že pracovník i služba patří stejnému podniku. |
| Vytvoření rezervace | create_booking() – SECURITY DEFINER funkce, která ověří službu, pracovníka, otevírací dobu, zarovnání času a vloží záznam. Pro „Kdokoli“ zkouší pracovníky v pořadí a o obsazenosti rozhoduje atomicky exclusion constraint. Anonymní uživatel nemá k tabulce bookings žádný přímý přístup. |
| Obsazenost | get_staff_busy_intervals() vrací jen časové intervaly a pracovníka, žádná osobní data zákazníků. Volné časy = sjednocení volných časů pracovníků, kteří službu dělají (computeStaffSlots()). |
| Row Level Security | Majitel vidí a mění jen svůj podnik a jeho rezervace; anonym vidí jen veřejné sloupce podniku (bez e-mailu majitele). |
| Časová pásma | Vše se ukládá v UTC, sloty se počítají v časovém pásmu podniku (Europe/Prague) přes Intl – funguje i na serveru v UTC a přes přechody letního času. |
| Výpočet slotů | Čistá funkce computeAvailableSlots() v src/lib/slots.ts, pokrytá unit testy. |
| E-maily | Odesílají se přes after() až po odpovědi, takže pomalé SMTP nezdrží zákazníka. Bez RESEND_API_KEY se jen vypisují do konzole. |
| Veřejné API | Klíče se ověřují podle SHA-256 hashe; pro zobrazení v administraci jsou navíc uložené šifrovaně (AES-256-GCM, tajemství jen na serveru). Rotace je atomická (rotate_api_key()), rate limiting běží v Postgresu (api_rate_limit_hit()), takže platí napříč serverless instancemi. Handlery v src/lib/api/handlers.ts nemají přímou závislost na Supabase a jsou plně pokryté testy. |
| Bezpečnostní hlavičky | CSP (jen vlastní origin, frame-ancestors 'none'), HSTS, X-Content-Type-Options, , , COOP – v . |
src/
app/
page.tsx úvodní stránka
(auth)/ přihlášení, registrace, server actions
auth/callback/ potvrzení e-mailu ze Supabase
dashboard/setup/ onboarding nového podniku
dashboard/(app)/ kalendář, služby, otevírací doba, nastavení, API klíč
[slug]/ veřejná rezervační stránka + booking action
api/slots/ volné časy pro službu a den
api/cron/reminders/ denní připomínky
api/v1/ veřejné REST API (služby, sloty, rezervace)
lib/
slots.ts, time.ts výpočet slotů a práce s časovými pásmy
notifications.ts e-mailové notifikace
email/ Resend klient a šablony
supabase/ klienti (server, public, admin, proxy)
api/ API klíče (generování, šifrování), validace, handlery, napojení na Supabase
proxy.ts obnova session, ochrana /dashboard
supabase/migrations/ SQL schéma, RLS, funkce
supabase/tests/ test schématu v PGlite (in-memory Postgres)
scripts/seed.mts demo data „Kadeřnictví U Nůžek“ + API klíč
scripts/cf-strip-env.mjs odstraní serverová tajemství z bundlu workeru
custom-worker.ts vstup Cloudflare Workeru (OpenNext + cron pro připomínky)
wrangler.jsonc konfigurace Workeru (nodejs_compat, Images, cron)
Požadavky: Node.js 20+ a účet na supabase.com (free tier stačí).
npm install
supabase/migrations/ v pořadí podle názvu
(…_initial_schema.sql, …_public_api.sql, …_reserved_slugs.sql, …_closures.sql, …_staff.sql, …_staff_hours.sql, …_staff_closures.sql),
případně npx supabase link + npx supabase db push..env.example do .env.local a doplňte:
NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY, SUPABASE_SECRET_KEY – Project Settings → API Keys (secret key je nutný pro e-maily, připomínky, seed a veřejné API)RESEND_API_KEY, EMAIL_FROM – volitelné; bez nich se e-maily jen vypisují do termináluCRON_SECRET – libovolný dlouhý náhodný řetězecAPI_KEY_ENCRYPTION_SECRET – náhodný řetězec (min. 32 znaků), kterým se šifrují uložené API klíče; vygenerujete ho příkazem node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))". Hodnotu později neměňte – jinak stávající klíč nepůjde v administraci zobrazit (fungovat ale bude dál). Slouží zároveň k podpisu odkazů „Zobrazit nebo zrušit rezervaci“ v e-mailech, pokud nenastavíte samostatný BOOKING_LINK_SECRET.BOOKING_LINK_SECRET – volitelné, min. 32 znaků; samostatné tajemství pro odkazy zákazníků. Jeho změna zneplatní odkazy v už odeslaných e-mailech.http://localhost:3000, do Redirect URLs přidejte http://localhost:3000/auth/callback.
(Pro rychlé testování lze v Authentication → Providers → Email vypnout „Confirm email“.)npm run seed
Vytvoří podnik Kadeřnictví U Nůžek na /u-nuzek, účet [email protected] / demo-heslo-123 a API klíč (vypíše ho; stejný klíč mezi běhy zachováte proměnnou ).npm test # unit testy (sloty, časová pásma, e-mailové šablony, veřejné API)
npm run test:db # SQL migrace + pravidla rezervací, RLS, API klíče a rate limit v in-memory Postgresu
npm run lint
Připomínky lze vyzkoušet ručně:
curl -H "Authorization: Bearer $CRON_SECRET" http://localhost:3000/api/cron/reminders
API pro externí aplikace (AI asistent, vlastní web, integrace). Každý požadavek se vztahuje k podniku, kterému patří použitý API klíč.
/dashboard/api) a klikněte na Vygenerovat API klíč.rzv_live_…) se zobrazí hned po vygenerování a kdykoli později přes Zobrazit klíč (s tlačítkem Kopírovat). V administraci vidíte i datum vytvoření a posledního použití.Base URL demo: https://rezervo.bukac-adam.workers.dev/api/v1. Klíč posílejte v hlavičce každého požadavku:
Authorization: Bearer rzv_live_…
Klíč je tajný – patří na server (backend AI asistenta), ne do kódu v prohlížeči nebo mobilní aplikaci. API proto neposílá CORS hlavičky.
| Základní URL | https://<vaše-doména>/api/v1 (lokálně http://localhost:3000/api/v1) |
| Formát | JSON (UTF-8); požadavky s tělem vyžadují Content-Type: application/json, max. 10 kB |
| Čas | Vstupy i výstupy v ISO 8601. starts_at musí obsahovat časovou zónu (Z nebo +02:00). Odpovědi vrací UTC a navíc local_time / local_date v časovém pásmu podniku. |
| Úspěch | { "data": … } (u seznamu služeb navíc meta) |
| Chyba | { "error": { "code": "…", "message": "…", "details": … } } – code je stabilní, message je lidsky čitelný popis (anglicky) |
| Limit | Hodnota |
|---|---|
| Všechny požadavky | 120 / minutu na klíč |
| Vytvoření a zrušení rezervace | navíc 20 / minutu na klíč |
Každá ověřená odpověď obsahuje hlavičky RateLimit-Limit, RateLimit-Remaining a RateLimit-Reset (sekundy do obnovení). Po překročení API vrací 429 rate_limited s hlavičkou Retry-After.
GET /staff – týmcurl https://<doména>/api/v1/staff -H "Authorization: Bearer $REZERVO_API_KEY"
{
"data": [
{ "id": "7a1e…", "name": "Jana", "service_ids": ["5b0c6f8e-…"], "working_hours": null, "days_off": [] },
{ "id": "c03b…", "name": "Tomáš", "service_ids": ["5b0c6f8e-…"], "working_hours": [{ "weekday": 3, "opens_at": "12:00", "closes_at": "19:00" }], "days_off": [{ "starts_on": "2026-10-21", "ends_on": "2026-10-22" }] }
]
}
Aktivní pracovníci v pořadí, ve kterém se přidělují rezervace „Kdokoli“, služby, které dělají, a pracovní doba (null = celá otevírací doba podniku, jinak směny po dnech v týdnu, weekday 1 = pondělí; chybějící den = volno) a nadcházející dny volna days_off (včetně obou krajních dnů).
GET /services – seznam služebcurl https://<doména>/api/v1/services -H "Authorization: Bearer $REZERVO_API_KEY"
{
"data": [
{ "id": "5b0c6f8e-…", "name": "Pánský střih", "description": "Střih nůžkami i strojkem", "duration_minutes": 30, "price_czk": 450, "staff_ids": ["7a1e…"] }
],
"meta": { "business": { "name": "Kadeřnictví U Nůžek", "slug": "u-nuzek", "timezone": "Europe/Prague" } }
}
Vrací jen aktivní služby, které dělá aspoň jeden aktivní pracovník, v pořadí jako na rezervační stránce.
GET /slots?service_id=<uuid>&date=YYYY-MM-DD[&staff_id=<uuid>] – volné termínycurl "https://<doména>/api/v1/slots?service_id=5b0c6f8e-…&date=2026-10-14" -H "Authorization: Bearer $REZERVO_API_KEY"
{
"data": {
"date": "2026-10-14",
"timezone": "Europe/Prague",
"closed": false,
"service": { "id": "5b0c6f8e-…", "name": "Pánský střih", "duration_minutes": 30 },
"slots": [
{ "starts_at": "2026-10-14T07:00:00.000Z", "ends_at": "2026-10-14T07:30:00.000Z", "local_time": "09:00", "staff_ids": ["7a1e…"] }
]
}
}
date je kalendářní den v časovém pásmu podniku. Rezervovat lze od dneška do 60 dnů dopředu (jinak 422 date_out_of_range s details.min_date / details.max_date). Zavřený den vrací closed: true, prázdné slots a closed_reason ("regular" – mimo otevírací dny, "holiday" – dovolená/svátek). Termíny začínající dřív než za 30 minut se nenabízejí. Bez staff_id jsou to časy, kdy je volný aspoň jeden pracovník (staff_ids = kdo je volný); se staff_id jen časy daného pracovníka (404 staff_not_found, pokud službu nedělá).
POST /bookings – vytvoření rezervacecurl -X POST https://<doména>/api/v1/bookings \
-H "Authorization: Bearer $REZERVO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"service_id": "5b0c6f8e-…",
"staff_id": null,
"starts_at": "2026-10-14T09:00:00+02:00",
"customer": { "name": "Jana Nováková", "email": "[email protected]", "phone": "+420 777 123 456" }
}'
Odpověď 201 Created (+ hlavička Location):
{
"data": {
"id": "9d4f…",
"status": "confirmed",
"starts_at": "2026-10-14T07:00:00.000Z",
"ends_at": "2026-10-14T07:30:00.000Z",
"local_date": "2026-10-14",
"local_time": "09:00",
"timezone": "Europe/Prague",
"service": { "id": "5b0c6f8e-…", "name": "Pánský střih", "duration_minutes": 30, "price_czk": 450 },
"staff": { "id": "7a1e…", "name": "Jana" },
"customer": { "name": "Jana Nováková", "email": "[email protected]", "phone": "+420 777 123 456" },
"created_at": "2026-10-10T09:12:00.000Z"
}
}
starts_at použijte přesně z odpovědi /slots. staff_id je nepovinné – bez něj (nebo null) se přidělí první volný pracovník, který službu dělá; odpověď vždy obsahuje přiděleného staff. Zákazník i majitel dostanou stejné e-maily jako u rezervace z webu. Validace: customer.name 2–100 znaků, platný customer.email, customer.phone 6–30 znaků (číslice, mezery, (), -, volitelně + na začátku). Dvojí rezervaci téhož času brání databáze i při souběžných požadavcích – druhý dostane 409 slot_taken.
GET /bookings/{id} – detail rezervacecurl https://<doména>/api/v1/bookings/9d4f… -H "Authorization: Bearer $REZERVO_API_KEY"
Vrací stejný objekt jako vytvoření rezervace (včetně aktuálního status). Na tuto adresu ukazuje hlavička Location z POST /bookings.
POST /bookings/{id}/cancel – zrušení rezervacecurl -X POST https://<doména>/api/v1/bookings/9d4f…/cancel -H "Authorization: Bearer $REZERVO_API_KEY"
Vrací 200 a rezervaci se "status": "cancelled"; zákazník dostane e-mail o zrušení a termín se uvolní. Zrušit lze jen potvrzenou rezervaci, která ještě nezačala.
| HTTP | code | Kdy |
|---|---|---|
| 400 | validation_error | Neplatné parametry nebo tělo; details obsahuje seznam { field, message } |
| 400 | invalid_json | Tělo není platný JSON |
| 401 | missing_api_key | Chybí hlavička Authorization |
| 401 | invalid_authorization_header | Hlavička není ve tvaru Bearer <klíč> |
| 401 | invalid_api_key | Neznámý nebo zneplatněný klíč |
| 404 | service_not_found | Služba neexistuje, není aktivní nebo patří jinému podniku |
| 404 | staff_not_found | Pracovník neexistuje, není aktivní nebo službu nedělá |
| 404 | booking_not_found | Rezervace neexistuje nebo patří jinému podniku |
| 404 | not_found | Neznámý endpoint |
| 405 | – | Nepodporovaná HTTP metoda |
| 409 | slot_taken | Termín je už obsazený – načtěte znovu /slots |
| 409 | booking_already_cancelled | Rezervace už je zrušená |
| 409 | booking_in_past | Rezervace už začala nebo proběhla |
| 413 | payload_too_large | Tělo je větší než 10 kB |
| 415 | unsupported_media_type | Chybí Content-Type: application/json |
| 422 | slot_in_past | Termín je v minulosti |
| 422 | slot_too_soon | Termín začíná dřív než za 30 minut |
| 422 | date_out_of_range | Datum je mimo rezervační okno (dnes až +60 dní) |
| 422 | outside_opening_hours | Služba se nevejde do otevírací doby |
GET /services → nabídnout službu a zjistit timezone podniku. Pokud zákazník chce konkrétního pracovníka, GET /staff.GET /slots pro zvolený den (případně se staff_id) → nabídnout časy (local_time).POST /bookings se starts_at vybraného slotu. Při 409 slot_taken načíst sloty znovu a nabídnout jiný čas.data.id – stav zjistíte přes GET /bookings/{id}, zrušíte přes POST /bookings/{id}/cancel.Aplikace běží na Cloudflare Workers přes @opennextjs/cloudflare (nodejs_compat pokrývá node:crypto i AsyncLocalStorage).
npx wrangler login.env.production.local s NEXT_PUBLIC_SITE_URL=https://<worker>.<subdoména>.workers.dev
(hodnoty NEXT_PUBLIC_* se vkládají do bundlu při buildu)..env.local):
npx wrangler secret put SUPABASE_SECRET_KEY # stejně CRON_SECRET, API_KEY_ENCRYPTION_SECRET,
# EMAIL_FROM, RESEND_API_KEY, BOOKING_LINK_SECRET
npm run deploy (lokální náhled ve workerd: npm run preview).
Skript scripts/cf-strip-env.mjs po buildu odebere z bundlu vše kromě NEXT_PUBLIC_* – OpenNext jinak přibalí hodnoty ze všech .env* souborů.https://<vaše-doména>/auth/callback.wrangler.jsonc: připomínky denně v 15:00 UTC (17:00 SELČ) a obnova demo účtu každou hodinu – custom-worker.ts zavolá příslušnou route s CRON_SECRET.EMAIL_FROM (testovací [email protected] doručuje jen na e-mail vašeho Resend účtu).Poznámka: patches/@opennextjs+cloudflare+*.patch (aplikuje patch-package při npm install) doplňuje inlinování manifestu preview-props.json, který Next.js 16.4 načítá a OpenNext 1.20 zatím nezná.
Supabase ve výchozím nastavení posílá odkaz pro obnovu hesla v režimu PKCE – funguje jen v prohlížeči, ve kterém uživatel o obnovu požádal. Aby šel odkaz otevřít kdekoli (třeba žádost na počítači, e-mail v telefonu), změňte v Supabase Authentication → Emails → Reset Password odkaz na:
{{ .SiteURL }}/auth/callback?token_hash={{ .TokenHash }}&type=recovery&next=/reset-password
(Stejně lze upravit šablonu Confirm signup s type=signup&next=/dashboard/setup.) Callback /auth/callback umí oba způsoby.
Referrer-PolicyPermissions-Policy| Sdílené demo | src/lib/demo.ts – blokuje změny, kterými by návštěvník zamkl účet ostatním nebo přesměroval e-maily; hodinový cron je vrací i při přímém volání Supabase. |
| Připomínky | /api/cron/reminders – rezervace se nejdřív „zabere“ nastavením reminder_sent_at, takže dva souběžné běhy nepošlou připomínku dvakrát. |
DEMO_API_KEYnpm run dev
Otevřete http://localhost:3000.| 422 | business_closed | Podnik má v tento den zavřeno (dovolená, svátek) |
| 422 | outside_staff_hours | V tento čas nepracuje žádný vhodný pracovník (nebo ten zvolený) |
| 422 | slot_misaligned | Čas neodpovídá nabízeným slotům (krok podniku) |
| 429 | rate_limited | Překročený limit, viz Retry-After |
| 500 | internal_error | Chyba na straně serveru (detaily jen v logu) |