Oracle3
Oracle3: 예측시장 거래 엔진·MCP 서버
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
PWA de gestion des dettes entre amis. Chaque pote est un « compte » : un seul chiffre net par personne, et une timeline complete des mouvements (cf. Hasbni_CDC.pdf). Le mode demonstration (jeu de donnees local, persistance IndexedDB, aucune requete reseau) demande NEXT_PUBLIC_DEMO=1. En production, un build sans variables Supabase et sans ce drapeau echoue volontairement : une app deployee avec des potes fictifs ressemble a une app qui marche.README 전문 보기 ↗
PWA de gestion des dettes entre amis. Chaque pote est un « compte » : un seul chiffre net par
personne, et une timeline complete des mouvements (cf. Hasbni_CDC.pdf).
npm install
npm run dev # http://localhost:3000 — mode demonstration
Le mode demonstration (jeu de donnees local, persistance IndexedDB, aucune requete
reseau) demande NEXT_PUBLIC_DEMO=1. En production, un build sans variables Supabase et
sans ce drapeau echoue volontairement : une app deployee avec des potes fictifs ressemble
a une app qui marche.
supabase/schema.sql dans l'editeur SQL et l'executer. Ce fichier suffit :
il contient l'etat consolide (tables, contraintes, triggers, RPC, RLS, vue des soldes,
realtime). Les anciens patches sont archives dans supabase/patches/ et ne doivent
pas etre rejoues.cp .env.example .env.local puis renseigner NEXT_PUBLIC_SUPABASE_URL et
NEXT_PUBLIC_SUPABASE_ANON_KEY (Project Settings > API).npm run dev — les variables ne sont lues qu'au demarrage.Base deja en service ? Executer, dans l'ordre :
supabase/patch-06-durcissement.sql, supabase/patch-07-admin.sql,
supabase/patch-08-droits-et-groupes.sql, supabase/patch-09-pseudos-et-recherche.sql,
supabase/patch-10-pseudo-a-inscription.sql. Tous idempotents.
Pour verifier ce qui est reellement applique : coller supabase/diagnostic.sql
dans l'editeur SQL — lecture seule. Si une fonction existe en base mais que
l'app repond « Could not find the function public.… », c'est le cache de schema
de PostgREST : executer notify pgrst, 'reload schema';.
L'authentification est email + mot de passe (provider Email, actif par defaut). Pour tester a plusieurs sans boite mail, desactiver Authentication > Sign In / Providers > Email > Confirm email : les comptes sont alors utilisables immediatement.
Au premier login, le trigger on_auth_user_created cree automatiquement le profil avec le prenom
saisi a l'inscription.
| Ecran | Route | CDC |
|---|---|---|
| Accueil — liste des potes, totaux globaux, recherche/tri | / | 2.2 |
| Relation — timeline anti-chronologique + solde net sticky | /relation/[id] | 2.3 |
| Ajout de depense — repartition egale / personnalisee / par items | modale | 2.4 |
| Remboursement — saisie + confirmation bilaterale | modale | 2.5 |
| Groupes — membres, soldes, bouton « Simplifier » | /groupes | 2.6 / 2.7 |
| Activite — tous les mouvements, file de confirmation | /activite | — |
| Profil — compte, pseudo, potes, recherche, etat de synchro | /profil | — |
| Connexion / inscription — email + mot de passe | /login | 4 (flow 1) |
Une depense appartient a celui qui a paye. Lui seul peut l'annuler ou la corriger : si Youba paie pour moi, je ne peux pas effacer ma dette. La correction etait sinon une porte derobee — il suffisait de ramener le montant a 1 DA. Un groupe, lui, est supprimable par son createur ; ses depenses sont detachees, jamais supprimees, et aucun solde ne bouge.
Regles de gestion (CDC 3) : solde net sur mouvements confirmes, signe positif = l'autre me doit, statut « en attente » jusqu'a confirmation, annulation par ecriture inverse (jamais de suppression physique), DA arrondi a l'unite, reste d'arrondi au payeur.
src/lib/types.ts — modele calque sur le CDC 5.2.src/lib/ledger.ts — moteur de grand livre : eclatement bilateral, soldes, timeline,
simplification de dettes.src/lib/ops.ts — toute mutation est une operation serialisable (Op) appliquee par un
reducteur pur. C'est ce qui rend la saisie hors ligne possible : meme fonction pour l'etat
optimiste local et pour le rejeu.src/context/AuthContext.tsx — session Supabase (email + mot de passe), resolution du profil.src/context/AppContext.tsx — etat, file de synchronisation, temps reel.supabase/schema.sql)profiles, groups, group_members, expenses, expense_shares, settlements,
ledger_entries, audit_log.SUM(ledger_entries confirmes entre A et B).relation_balances). Le client ne charge
qu'une fenetre recente de mouvements, pour la timeline — pas tout l'historique.create_expense,
amend_expense) : jamais de depense sans ses parts, jamais de montant modifie sans que le
grand livre suive. Une correction solde l'ancienne position par des ecritures d'ajustement
puis en regenere des neuves — le journal reste strictement additif.can_share_with() exige un lien d'amitie ou un
groupe commun. Sans ca, connaitre un identifiant de profil suffisait a fabriquer une dette
contre son proprietaire.auth.users.email : c'est la cle
de recherche des demandes de pote, la laisser modifiable permettait de squatter une adresse.deleted_at, cles en RESTRICT.
Les soldes des potes restent justes.current_profile_id(), is_group_member(), shares_context() et
can_see_expense() sont SECURITY DEFINER pour eviter les recursions de politiques. On ne voit
que les relations et groupes dont on fait partie.settlements_confirm
n'autorise le passage a confirmed que par le beneficiaire (to_user).ledger_entries, settlements, expenses — les soldes se mettent a
jour tout seuls quand un pote saisit quelque chose.public/sw.js — app shell precachee, navigations en network-first avec repli /offline,
statiques Next en cache-first, reste en stale-while-revalidate.src/lib/idb.ts) : hors ligne, chaque operation part dans
IndexedDB. Le retour du reseau (evenement online ou Background Sync via le service
worker) la vide dans l'ordre, avec upserts idempotents et abandon des erreurs non rejouables.beforeinstallprompt) et indicateur hors ligne / synchro.Le service worker n'est enregistre qu'en production (npm run build && npm start) — en dev, le
cache des chunks entre en conflit avec le rechargement a chaud.
Palette dans tailwind.config.ts, utilitaires dans src/app/globals.css :
| Token | Valeur | Usage |
|---|---|---|
navy | #0B3A2B | texte, titres |
brand | #22A06B | CTA, etats actifs, focus |
ocean | #14724F | hover des CTA |
cream | #FFFAE6 | badges « en attente » |
mist | #E9FFF4 | base du fond |
frost | #A9FBD7 | milieu du degrade |
silver | #E4EEE8 | bordures |
credit / debit | #15A05F / #E5484D | il me doit / je lui dois |
Classes : .glass, .glass-sm, .glass-sidebar, .glass-nav, .blob-1/2/3.
Animations : variantes partagees dans src/lib/motion.ts.
Chacun a un pseudo public (@youba), choisi a l'inscription et modifiable
dans Profil. La disponibilite est verifiee pendant la frappe :
username_available est un exists sur l'index unique qui ne renvoie qu'un
booleen — pas de count, aucune ligne rapatriee, aucune donnee personnelle. La recherche se
fait par DEBUT de pseudo ou de nom, jamais par sous-chaine — et c'est le coeur
de sa tenue en charge : un index btree sait servir like 'abc%' en descendant
l'arbre, alors que like '%abc%' l'ignore et balaye toute la table, a chaque
caractere tape par chaque utilisateur.
Les garde-fous, cote serveur (search_profiles) : trois caracteres minimum,
dix resultats maximum, et ni email ni telephone dans la reponse — la recherche
est ouverte a tous, elle ne doit pas devenir un annuaire de coordonnees. Cote
client : anti-rebond de 300 ms et abandon de la requete precedente, pour qu'une
frappe rapide ne parte pas en rafale et qu'une reponse lente n'ecrase pas une
plus recente.
/adminApplication a part, servie par le meme deploiement mais ne partageant rien avec
l'app : ni fournisseur de session, ni etat, ni temps reel, ni service worker
(exclu dans public/sw.js). Les routes applicatives vivent dans le groupe
src/app/(app)/, la console dans src/app/admin/.
supabase/patch-07-admin.sql..env.local : SUPABASE_SERVICE_ROLE_KEY (Project Settings >
API) et ADMIN_SESSION_SECRET (openssl rand -base64 48)./admin.SUPABASE_SERVICE_ROLE_KEY n'est jamais prefixee NEXT_PUBLIC_. Elle
contourne la RLS : dans un bundle navigateur, elle donnerait la base complete.
src/lib/admin/server.ts ouvre sur import 'server-only' — un import depuis un
composant client fait echouer le BUILD.profiles : le declencheur guard_profile_columns ne fige qu'une liste nommee
de colonnes, et chacun peut ecrire sa propre ligne — un is_admin sur
profiles serait auto-attribuable.admin_* sont retirees a public, anon et
authenticated. Postgres accorde EXECUTE a PUBLIC par defaut : sans ce
revoke, ces fonctions security definer seraient un aspirateur a donnees
ouvert a tout compte connecte.httpOnly :
l'app garde la sienne dans localStorage, qu'un middleware ne peut pas lire.admin_audit_log — avant que la donnee soit renvoyee.| Ecran | Repond a |
|---|---|
| Pulse | Est-ce que quelque chose brule ? |
| Activation | Ou perd-on les nouveaux inscrits ? |
| Retention | Reviennent-ils la semaine suivante ? |
| Utilisateurs | Files de support : sans pote, bloque, en attente depuis 7 j |
| Sante du grand livre | SUM(ledger_entries) colle-t-il encore aux parts et remboursements ? |
| Moderation | Cadence anormale, taux d'annulation, montants inhabituels |
« Sante du grand livre » est la raison de construire cette console plutot que de tout confier a un outil tiers : aucun produit d'analytics ne peut verifier cet invariant, il demande la logique metier de Hasbni. Un ecart y est toujours un bug — declencheur SQL ou rejeu de la file hors ligne.
src/lib/analytics.ts envoie les evenements par lots vers /api/events, qui les
ecrit avec la cle de service. Deux regles tenues partout : jamais de montant
dans un evenement, seulement une tranche (<1k, 1-5k, 5-20k, 20k+) ; et
un evenement porte le nom de ce que la personne a fait, pas du composant touche.
Le profil est deduit du jeton, jamais du corps de la requete — sinon n'importe
qui pourrait attribuer son activite a quelqu'un d'autre.
Agregation journaliere : POST /api/admin/rollup avec l'en-tete
x-rollup-secret, a brancher sur un cron.
git push. C'est tout — rien a faire cote appareils, et surtout personne n'a a
supprimer puis reinstaller son raccourci.
Au retour au premier plan, l'app verifie s'il y a du neuf (au plus une fois par heure). Si oui, un bandeau « Nouvelle version disponible » apparait ; un tap bascule et recharge. Les donnees locales — instantane et file de synchronisation — sont conservees.
Quatre choses sont necessaires pour qu'une PWA deja installee se mette a jour, et il en manquait quatre :
/sw.js?v=<build>,
l'identifiant vient de next.config.ts). Sinon le navigateur compare un
fichier souvent identique et conclut qu'il n'y a rien de neuf — c'est ce qui
obligeait a reinstaller le raccourci.updateViaCache: 'none' a l'enregistrement : sans ca, sw.js lui-meme
peut etre servi depuis le cache HTTP pendant des heures.update() explicite au retour au premier plan. Une PWA installee sur
iPhone n'est jamais fermee, seulement suspendue : elle peut passer des
semaines sans jamais verifier.Les charges utiles React Server Components ne sont jamais mises en cache : elles decrivent le rendu des pages, et les servir depuis le cache reafficherait l'ancienne interface apres une mise a jour.
Si un appareil reste bloque malgre tout, /admin et l'app partagent le meme
domaine : ouvrir le site dans Safari (pas le raccourci) force une navigation
reseau et declenche la bascule.
npm run verify # types + lint + tests
npm run test:watch
Les tests couvrent le coeur comptable : repartition avec reste, signe et statut des soldes, idempotence du reducteur d'operations (rejouer une operation ne double jamais un montant), correction d'une depense, et simplification de groupe. La CI GitHub Actions rejoue tout plus le build.