Files
Felitendo 1c0e3b8d11 feat: a pin on the front door, and a list of who is behind it
Private mode closes the whole deployment behind a PIN. The landing page draws
a passcode pad instead of itself, the public install routes behind it refuse,
and the feed refuses too, so a store that was never let in is empty rather
than a catalogue. Nothing is published to a crawler while it is on.

A client keeps a pass once it has answered: a cookie in a browser, a keychain
item in the app. Passes are rows rather than signed tokens, so they can be
taken away and take effect on the next request, one device at a time, one
person's code at a time, or every one of them at once.

Two things open a pass and both are digits, so both are typed on the same pad.
The shared PIN is four to ten digits an operator sets. A personal code is ten
digits the server generates for one person, shown once, withdrawn on its own,
and it takes that person's devices with it when it goes.

The switch, the codes, the device list and thirty days of country totals sit
under Access in both halves of the operator console. The country comes from
the reverse proxy in front of the server and never from a lookup service, and
what is kept is a counter per day and country rather than a record of who
loaded what and when.

Every way of not knowing reads as shut. A switch with no PIN behind it, which
is where a restored backup lands because a backup carries the switch and never
the key, and a settings read that could not reach the database. Locked out is
one field away from fixed; the other direction is not.
2026-08-15 15:16:50 +02:00

67 lines
1.7 KiB
JSON

{
"name": "ftstore-admin",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint",
"db:generate": "prisma generate",
"db:migrate": "prisma migrate dev",
"db:push": "prisma db push",
"db:pre-push": "prisma db execute --schema prisma/schema.prisma --file prisma/pre-push.sql",
"db:seed": "tsx prisma/seed.ts",
"worker": "tsx worker/index.ts"
},
"prisma": {
"seed": "tsx prisma/seed.ts"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@octokit/rest": "^22.0.1",
"@prisma/client": "^6.19.2",
"@types/qrcode": "^1.5.6",
"bplist-parser": "^0.3.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"lucide-react": "^0.575.0",
"motion": "^12.43.0",
"next": "16.1.6",
"next-themes": "^0.4.6",
"plist": "^3.1.0",
"prebuilt-tdlib": "^0.1008033.1",
"prisma": "^6.19.2",
"qrcode": "^1.5.4",
"radix-ui": "^1.4.3",
"react": "19.2.3",
"react-dom": "19.2.3",
"recharts": "^3.7.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",
"tdl": "^8.0.2",
"web-haptics": "^0.0.6",
"yaml": "^2.8.2",
"yauzl": "^3.2.0",
"zod": "^4.3.6"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/plist": "^3.0.5",
"@types/react": "^19",
"@types/react-dom": "^19",
"@types/yauzl": "^2.10.3",
"eslint": "^9",
"eslint-config-next": "16.1.6",
"shadcn": "^3.8.5",
"tailwindcss": "^4",
"tsx": "^4.21.0",
"tw-animate-css": "^1.4.0",
"typescript": "^5"
}
}