feat(api): tiered cumulative quota + self-service registration
Anonymous callers get a free cumulative quota (1000 calls per IP); once exhausted they get 403 quota_exhausted and must register. Public users can self-register (email+password) to obtain a higher-quota API key, view usage, and regenerate the key. Quota counters live in Redis; the public API stays read-only except for the registration writes. - migration 0011: app_user table + api_key.quota_total + 'registered' tier - ratelimit: IncrTotal/TotalUsed/CopyTotal lifetime counters - middleware: enforce cumulative quota + X-Quota-* headers - store: RegisterUser/Authenticate/RegenerateKey (bcrypt) - handlers: POST /api/v1/register, /account, /account/regenerate - admin: quota_total column + registered tier - public: 'API 密钥' account page + API docs quota section Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -1,16 +1,18 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Boxes, Search, PlusCircle, Code2 } from "lucide-react";
|
||||
import { Boxes, Search, PlusCircle, Code2, KeyRound } from "lucide-react";
|
||||
import Home from "./components/Home";
|
||||
import ProductView from "./components/ProductView";
|
||||
import Contribute from "./components/Contribute";
|
||||
import ApiDocs from "./components/ApiDocs";
|
||||
import Account from "./components/Account";
|
||||
import { api } from "./api";
|
||||
|
||||
type View =
|
||||
| { name: "home" }
|
||||
| { name: "product"; id: string }
|
||||
| { name: "contribute" }
|
||||
| { name: "api" };
|
||||
| { name: "api" }
|
||||
| { name: "account" };
|
||||
|
||||
export default function App() {
|
||||
const [view, setView] = useState<View>({ name: "home" });
|
||||
@@ -59,6 +61,14 @@ export default function App() {
|
||||
>
|
||||
<Code2 className="w-4 h-4" /> API
|
||||
</button>
|
||||
<button
|
||||
className={`px-3 py-1.5 rounded-md flex items-center gap-1.5 ${
|
||||
view.name === "account" ? "bg-emerald-50 text-emerald-700" : "text-gray-600 hover:bg-gray-100"
|
||||
}`}
|
||||
onClick={() => setView({ name: "account" })}
|
||||
>
|
||||
<KeyRound className="w-4 h-4" /> API 密钥
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
@@ -77,7 +87,8 @@ export default function App() {
|
||||
{view.name === "contribute" && (
|
||||
<Contribute onDone={() => setView({ name: "home" })} />
|
||||
)}
|
||||
{view.name === "api" && <ApiDocs />}
|
||||
{view.name === "api" && <ApiDocs onRegister={() => setView({ name: "account" })} />}
|
||||
{view.name === "account" && <Account />}
|
||||
</main>
|
||||
|
||||
<footer className="border-t bg-white">
|
||||
|
||||
Reference in New Issue
Block a user