mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-25 23:18:51 -07:00
[api] add influencer badges
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
-- Expose the account's influencer flag (`isInfluencer` in the JSON blob) as an indexed
|
||||
-- generated column, so "every influencer" is an index walk rather than a scan of every
|
||||
-- account's JSON. The reader is econ's `GET /api/influencerpartnerprogram/influencers`,
|
||||
-- which the client asks for the whole list on every launch so it can badge influencers
|
||||
-- wherever they turn up.
|
||||
--
|
||||
-- Same construction as `has_plus` (0009): a PARTIAL index holding only the rows where the
|
||||
-- flag is set, so it is as small as the influencer list. A query must say exactly
|
||||
-- `is_influencer = 1` to use it. `json_extract` yields 1 for JSON true, 0 for false and
|
||||
-- NULL when the key is absent, so neither a revoked nor a never-granted account is in it.
|
||||
-- Kept in sync with SCHEMA_DDL in @repo/domain's accounts-db.ts (and its mirror in
|
||||
-- apps/econ/src/avatar-db.ts).
|
||||
|
||||
ALTER TABLE account ADD COLUMN is_influencer INTEGER GENERATED ALWAYS AS (json_extract(data, '$.isInfluencer')) VIRTUAL;
|
||||
CREATE INDEX IF NOT EXISTS idx_account_is_influencer ON account (is_influencer) WHERE is_influencer = 1;
|
||||
@@ -295,11 +295,16 @@ async function roleFilterAnswer(c: Context<App>, role: 'developer' | 'moderator'
|
||||
*
|
||||
* `betastudio` rides on the account's `hasStudio` flag, and developer does not imply it.
|
||||
* RecFlare Studio treats that exact claim as Full access, which is what lets the editor
|
||||
* upload; staff set the flag from the website. Order is stable so tokens are
|
||||
* deterministic: screenshare, developer, moderator, junior, then betastudio.
|
||||
* upload; staff set the flag from the website. `influencer` rides on `isInfluencer`
|
||||
* (`runx admin grant-influencer`) and lives only in the token — no `/role/*` lookup
|
||||
* answers it. Order is stable so tokens are deterministic: screenshare, developer,
|
||||
* moderator, junior, betastudio, then influencer.
|
||||
*/
|
||||
function accountRoles(
|
||||
account: Pick<Account, 'isDeveloper' | 'isModerator' | 'isJunior' | 'hasStudio'> | null
|
||||
account: Pick<
|
||||
Account,
|
||||
'isDeveloper' | 'isModerator' | 'isJunior' | 'hasStudio' | 'isInfluencer'
|
||||
> | null
|
||||
): string[] {
|
||||
const roles = ['screenshare']
|
||||
if (!account) return roles
|
||||
@@ -307,6 +312,7 @@ function accountRoles(
|
||||
if (account.isModerator) roles.push('moderator')
|
||||
if (account.isJunior) roles.push('junior')
|
||||
if (account.hasStudio) roles.push('betastudio')
|
||||
if (account.isInfluencer) roles.push('influencer')
|
||||
return roles
|
||||
}
|
||||
|
||||
|
||||
@@ -895,6 +895,29 @@ describe('auth worker routes', () => {
|
||||
expect(payload.scope).not.toContain('rn.privilege')
|
||||
})
|
||||
|
||||
test('POST /connect/token stamps the influencer role for an isInfluencer account', async () => {
|
||||
await env.DB.prepare('INSERT OR IGNORE INTO account (data) VALUES (?1)')
|
||||
.bind(
|
||||
JSON.stringify({
|
||||
accountId: 95,
|
||||
username: 'InfluencerPlayer',
|
||||
passwordHash: await hashPassword(LOGIN_PASSWORD),
|
||||
isInfluencer: true,
|
||||
})
|
||||
)
|
||||
.run()
|
||||
const payload = await tokenFor(`account_id=95&password=${LOGIN_PASSWORD}`)
|
||||
expect(payload.role).toEqual(
|
||||
expect.arrayContaining(['gameClient', 'screenshare', 'influencer'])
|
||||
)
|
||||
expect(payload.role).not.toContain('developer')
|
||||
|
||||
// Off the flag, off the token — no other flag implies it.
|
||||
await updateAccount(env.DB, 95, { isInfluencer: false })
|
||||
const revoked = await tokenFor(`account_id=95&password=${LOGIN_PASSWORD}`)
|
||||
expect(revoked.role).not.toContain('influencer')
|
||||
})
|
||||
|
||||
test('POST /connect/token 400s when no account_id is posted (never defaults to 1)', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/connect/token`, { method: 'POST' })
|
||||
expect(res.status).toBe(400)
|
||||
|
||||
Reference in New Issue
Block a user