SPB Git forge

spb/hfmarketdata

Public

Open high-frequency market data platform — FirstRate full-history downloader, DuckDB/Parquet lake, open REST API and React docs platform (www.hfmarketdata.io)

127commits 1branches 0releases
24.7 MBsize
maindefault branch
11 days agolast push
JavaScript 53.7% Python 38.3% CSS 4.6% TypeScript 3.1%

accounts: niveau interne « unlimited » (invitation seulement, hors /v1/limits), commande keys rotate, options admin/dashboard

Simon-Pierre Boucher committed 20 days ago (Sep 5, 2026) parent ae95537

4 changed files +25 −6

modified hfmarketdata/api/accounts/cli.py +18 −2
@@ -1,6 +1,6 @@
1 1 """`hfmd` — accounts administration from the shell (wrapper: scripts/hfmd).
2 2
3 − hfmd users add "Name" email [--tier free|high_usage] [--admin] [--show-key] [--no-mail]
3 + hfmd users add "Name" email [--tier free|high_usage|unlimited] [--admin] [--show-key] [--no-mail]
4 4 hfmd users list
5 5 hfmd users set-tier <email> <free|high_usage>
6 6 hfmd users disable <email> | enable <email>
@@ -137,6 +137,21 @@ def cmd_users_invite_resend(a) -> int:
137 137 return 0
138 138
139 139
140 +def cmd_keys_rotate(a) -> int:
141 + """Revoke the active key(s) and create a fresh one; prints the new key ONLY with --show-key."""
142 + with session() as s:
143 + u = _user_or_die(s, a.email)
144 + keys = service.active_keys(s, u)
145 + if keys:
146 + raw, k = service.rotate_key(s, u, keys[0], actor=ACTOR)
147 + for extra in keys[1:]:
148 + service.revoke_key(s, extra, actor=ACTOR)
149 + else:
150 + raw, k = service.create_key(s, u, "default", actor=ACTOR, notify=False)
151 + print(f"{u.email}: new key {k.prefix}… " + (raw if a.show_key else "(hidden — use --show-key)"))
152 + return 0
153 +
154 +
140 155 def cmd_keys_list(a) -> int:
141 156 with session() as s:
142 157 u = _user_or_die(s, a.email)
@@ -174,7 +189,7 @@ def build_parser() -> argparse.ArgumentParser:
174 189 add.add_argument("--no-mail", action="store_true", help="never send e-mail, print the link")
175 190 add.set_defaults(fn=cmd_users_add)
176 191 users.add_parser("list").set_defaults(fn=cmd_users_list)
177 − st = users.add_parser("set-tier"); st.add_argument("email"); st.add_argument("tier", choices=("free", "high_usage"))
192 + st = users.add_parser("set-tier"); st.add_argument("email"); st.add_argument("tier", choices=("free", "high_usage", "unlimited"))
178 193 st.set_defaults(fn=cmd_users_set_tier)
179 194 d = users.add_parser("disable"); d.add_argument("email"); d.set_defaults(fn=lambda a: cmd_users_status(a, "disabled"))
180 195 e = users.add_parser("enable"); e.add_argument("email"); e.set_defaults(fn=lambda a: cmd_users_status(a, "active"))
@@ -183,6 +198,7 @@ def build_parser() -> argparse.ArgumentParser:
183 198
184 199 keys = sub.add_parser("keys", help="inspect API keys").add_subparsers(dest="sub", required=True)
185 200 kl = keys.add_parser("list"); kl.add_argument("email"); kl.set_defaults(fn=cmd_keys_list)
201 + kr = keys.add_parser("rotate", help="revoke + recreate the user's key"); kr.add_argument("email"); kr.add_argument("--show-key", action="store_true"); kr.set_defaults(fn=cmd_keys_rotate)
186 202
187 203 seed = sub.add_parser("seed", help="create the initial users (idempotent)")
188 204 seed.add_argument("--show-key", action="store_true")
modified hfmarketdata/api/ratelimit/tiers.py +5 −2
@@ -60,8 +60,11 @@ TIERS: dict[str, Tier] = {
60 60 "keyless": Tier("keyless", "ip", 3600, 30, 100_000, 5_000),
61 61 "free": Tier("free", "key", 60, 120, 1_000_000, 50_000),
62 62 "high_usage": Tier("high_usage", "key", 60, 600, 10_000_000, 200_000),
63 + # internal, invitation-only: no practical limit (kept finite so the Lua counters stay integers)
64 + "unlimited": Tier("unlimited", "key", 60, 1_000_000_000, 1_000_000_000_000, 5_000_000),
63 65 }
64 −ACCOUNT_TIERS = ("free", "high_usage")
66 +ACCOUNT_TIERS = ("free", "high_usage", "unlimited")
67 +INTERNAL_TIERS = ("unlimited",)
65 68 DEFAULT_ACCOUNT_TIER = "free"
66 69
67 70
@@ -90,7 +93,7 @@ def upgrade_hint(tier: Tier) -> str:
90 93
91 94
92 95 def tiers_public() -> dict[str, dict]:
93 − out = {name: t.public() for name, t in TIERS.items()}
96 + out = {name: t.public() for name, t in TIERS.items() if name not in INTERNAL_TIERS}
94 97 out["high_usage"]["how_to_get"] = f"e-mail {settings.contact_email}"
95 98 out["free"]["how_to_get"] = f"{settings.public_url}/signup"
96 99 return out
modified hfmarketdata/web/src/pages/admin/AdminUsers.jsx +1 −1
@@ -4,7 +4,7 @@ import { api } from '../../app/api.js'
4 4 import PgCallout from '../../components/PgCallout.jsx'
5 5 import PgCopyButton from '../../components/PgCopyButton.jsx'
6 6
7 −const TIERS = ['free', 'high_usage']
7 +const TIERS = ['free', 'high_usage', 'unlimited']
8 8 const ROLES = ['user', 'admin']
9 9 const STATUSES = ['invited', 'active', 'disabled']
10 10 const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
modified hfmarketdata/web/src/pages/dashboard/Account.jsx +1 −1
@@ -42,7 +42,7 @@ export default function Account() {
42 42 <li>{tier.rows.toLocaleString()} rows / {tier.window}</li>
43 43 <li>{tier.maxRows.toLocaleString()} rows per request</li>
44 44 </ul>
45 − {tier.id !== 'high_usage' && (
45 + {tier.id !== 'high_usage' && tier.id !== 'unlimited' && (
46 46 <a className="btn btn-primary" href={`mailto:${CONTACT_EMAIL}?subject=${encodeURIComponent('HF Market Data — high usage tier')}&body=${encodeURIComponent(`Hello,\n\nI would like the high-usage tier for ${user.email}.\nUse case: `)}`}>Need more? {CONTACT_EMAIL}</a>
47 47 )}
48 48 </div>
49 49