-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add Badge api #357
base: master
Are you sure you want to change the base?
add Badge api #357
Changes from all commits
91c69b3
2ff4870
2067348
350e3b2
20177e7
497610b
227aaeb
8431d58
ca9c1ee
06793bf
677c1e2
ef3cd11
9937092
c86d194
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import type { User } from 'rumors-db/schema/users'; | ||
import type { Badge } from 'rumors-db/schema/badges'; | ||
|
||
export default { | ||
'/users/doc/user-to-award-badge': { | ||
name: 'user-to-award-badge', | ||
createdAt: '2020-01-01T00:00:00.000Z', | ||
googleId: 'some-google-id', | ||
badges: [], | ||
} satisfies User, | ||
|
||
'/users/doc/user-already-award-badge': { | ||
name: 'user-already-award-badge', | ||
createdAt: '2020-01-01T00:00:00.000Z', | ||
googleId: 'some-google-id', | ||
badges: [ | ||
{ | ||
badgeId: 'test-certification-001', | ||
badgeMetaData: '{"from":"some-orgnization"}', | ||
isDisplayed: false, | ||
createdAt: '2020-01-01T00:00:00.000Z', | ||
updatedAt: '2020-01-01T00:00:00.000Z', | ||
}, | ||
], | ||
} satisfies User, | ||
|
||
'/badges/doc/test-certification-001': { | ||
name: 'Test Certification', | ||
displayName: 'Test Certification', | ||
description: 'A test certification badge', | ||
link: 'https://badge.source.com', | ||
icon: 'https://badge.source.com/icon.png', | ||
borderImage: 'https://badge.source.com/border.png', | ||
issuers: ['[email protected]', 'service-token-123'], | ||
createdAt: '2020-01-01T00:00:00.000Z', | ||
updatedAt: '2020-01-01T00:00:00.000Z', | ||
} satisfies Badge, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import MockDate from 'mockdate'; | ||
|
||
import { loadFixtures, unloadFixtures } from 'util/fixtures'; | ||
import client from 'util/client'; | ||
import awardBadge from '../awardBadge'; | ||
import fixtures from '../__fixtures__/awardBadge'; | ||
|
||
const FIXED_DATE = 612921600000; | ||
|
||
beforeEach(async () => { | ||
await loadFixtures(fixtures); | ||
MockDate.set(FIXED_DATE); | ||
}); | ||
|
||
afterEach(async () => { | ||
await unloadFixtures(fixtures); | ||
MockDate.reset(); | ||
}); | ||
|
||
describe('awardBadge', () => { | ||
it('fails if userId is not valid', async () => { | ||
await expect( | ||
awardBadge({ | ||
userId: 'not-exist', | ||
badgeId: 'badge id', | ||
badgeMetaData: '{}', | ||
request: { userId: '[email protected]' }, | ||
}) | ||
).rejects.toMatchInlineSnapshot( | ||
`[HTTPError: User with ID=not-exist does not exist]` | ||
); | ||
}); | ||
|
||
it('correctly sets the awarded badge id when authorized', async () => { | ||
const result = await awardBadge({ | ||
userId: 'user-to-award-badge', | ||
badgeId: 'test-certification-001', | ||
badgeMetaData: '{"from":"some-orgnization"}', | ||
request: { userId: '[email protected]' }, | ||
}); | ||
|
||
expect(result).toMatchInlineSnapshot(` | ||
Object { | ||
"badgeId": "test-certification-001", | ||
"badgeMetaData": "{\\"from\\":\\"some-orgnization\\"}", | ||
} | ||
`); | ||
|
||
const { | ||
body: { _source: userWithBadge }, | ||
} = await client.get({ | ||
index: 'users', | ||
type: 'doc', | ||
id: 'user-to-award-badge', | ||
}); | ||
|
||
// Assert that badgeId is written on the user | ||
expect(userWithBadge).toMatchInlineSnapshot(` | ||
Object { | ||
"badges": Array [ | ||
Object { | ||
"badgeId": "test-certification-001", | ||
"badgeMetaData": "{\\"from\\":\\"some-orgnization\\"}", | ||
"createdAt": "1989-06-04T00:00:00.000Z", | ||
"isDisplayed": true, | ||
"updatedAt": "1989-06-04T00:00:00.000Z", | ||
}, | ||
], | ||
"createdAt": "2020-01-01T00:00:00.000Z", | ||
"googleId": "some-google-id", | ||
"name": "user-to-award-badge", | ||
} | ||
`); | ||
}); | ||
|
||
it('allows service token to award badge', async () => { | ||
const result = await awardBadge({ | ||
userId: 'user-to-award-badge', | ||
badgeId: 'test-certification-001', | ||
badgeMetaData: '{"from":"service"}', | ||
request: { userId: 'service-token-123' }, | ||
}); | ||
|
||
expect(result).toMatchInlineSnapshot(` | ||
Object { | ||
"badgeId": "test-certification-001", | ||
"badgeMetaData": "{\\"from\\":\\"service\\"}", | ||
} | ||
`); | ||
|
||
const { | ||
body: { _source: userWithBadge }, | ||
} = await client.get({ | ||
index: 'users', | ||
type: 'doc', | ||
id: 'user-to-award-badge', | ||
}); | ||
|
||
expect(userWithBadge.badges).toHaveLength(1); | ||
expect(userWithBadge.badges[0].badgeId).toBe('test-certification-001'); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
/** | ||
* Given userId & award badge (Id and metadata). | ||
* | ||
*/ | ||
import { HTTPError } from 'fets'; | ||
|
||
import client from 'util/client'; | ||
|
||
/** | ||
* Update user to write badgeId. Throws if user does not exist. | ||
* | ||
* @param userId | ||
* @param badgeId | ||
* @param badgeMetaData | ||
*/ | ||
async function appendBadgeToList( | ||
userId: string, | ||
badgeId: string, | ||
badgeMetaData: string | ||
) { | ||
const now = new Date().toISOString(); | ||
|
||
try { | ||
const { | ||
body: { result: setbadgeIdResult }, | ||
} = await client.update({ | ||
index: 'users', | ||
type: 'doc', | ||
id: userId, | ||
body: { | ||
script: { | ||
source: ` | ||
if (ctx._source.badges == null) { | ||
ctx._source.badges = []; | ||
} | ||
ctx._source.badges.add(params.badge); | ||
`, | ||
params: { | ||
badge: { | ||
badgeId: badgeId, | ||
badgeMetaData: badgeMetaData, | ||
createdAt: now, | ||
isDisplayed: true, | ||
updatedAt: now, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
/* istanbul ignore if */ | ||
if (setbadgeIdResult === 'noop') { | ||
console.log(`Info: user ID ${userId} already has set the same badgeId.`); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems that the script above do not check if the same badge has been awarded or not, and always performs |
||
} | ||
} catch (e) { | ||
console.log(e); | ||
/* istanbul ignore else */ | ||
if ( | ||
e && | ||
typeof e === 'object' && | ||
'message' in e && | ||
e.message === 'document_missing_exception' | ||
) { | ||
throw new HTTPError(400, `User with ID=${userId} does not exist`); | ||
} | ||
|
||
throw e; | ||
} | ||
} | ||
|
||
/** | ||
* Verify if the badge exists and if the current user is authorized to issue it | ||
* | ||
* @param badgeId - ID of the badge to verify | ||
* @param requestUserId - ID of the user making the request | ||
* @throws {HTTPError} if badge doesn't exist or user is not authorized | ||
*/ | ||
async function verifyBadgeIssuer(badgeId: string, requestUserId: string) { | ||
try { | ||
const { | ||
body: { _source: badge }, | ||
} = await client.get({ | ||
index: 'badges', | ||
type: 'doc', | ||
id: badgeId, | ||
}); | ||
|
||
if (!badge) { | ||
throw new HTTPError(404, `Badge with ID=${badgeId} does not exist`); | ||
} | ||
|
||
if (!badge.issuers?.includes(requestUserId)) { | ||
throw new HTTPError( | ||
403, | ||
`User ${requestUserId} is not authorized to issue badge ${badgeId}` | ||
); | ||
} | ||
} catch (e) { | ||
if (e instanceof HTTPError) throw e; | ||
throw new HTTPError(404, `Badge with ID=${badgeId} does not exist`); | ||
} | ||
} | ||
|
||
type awardBadgeReturnValue = { | ||
badgeId: string; | ||
badgeMetaData: string; | ||
}; | ||
|
||
async function main({ | ||
userId, | ||
badgeId, | ||
badgeMetaData, | ||
request, | ||
}: { | ||
userId: string; | ||
badgeId: string; | ||
badgeMetaData: string; | ||
request: { userId: string }; | ||
}): Promise<awardBadgeReturnValue> { | ||
// Check if user exists first | ||
try { | ||
const { body } = await client.get({ | ||
index: 'users', | ||
type: 'doc', | ||
id: userId, | ||
}); | ||
if (!body._source) { | ||
throw new HTTPError(400, `User with ID=${userId} does not exist`); | ||
} | ||
} catch (e) { | ||
if (e instanceof HTTPError) throw e; | ||
throw new HTTPError(400, `User with ID=${userId} does not exist`); | ||
} | ||
|
||
// Verify if the current user/service is authorized to issue this badge | ||
await verifyBadgeIssuer(badgeId, request.userId); | ||
|
||
await appendBadgeToList(userId, badgeId, badgeMetaData); | ||
|
||
return { | ||
badgeId, | ||
badgeMetaData, | ||
}; | ||
} | ||
|
||
export default main; |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remove this unused loader implementation |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import DataLoader from 'dataloader'; | ||
import client, { processMeta } from 'util/client'; | ||
|
||
export default () => | ||
new DataLoader(async (slugs) => { | ||
const body = []; | ||
|
||
slugs.forEach(({ slug }) => { | ||
body.push({ index: 'badges', type: 'doc' }); | ||
|
||
body.push({ | ||
query: { | ||
term: { slug }, | ||
}, | ||
size: 1, | ||
}); | ||
}); | ||
|
||
return ( | ||
await client.msearch({ | ||
body, | ||
}) | ||
).body.responses.map(({ hits }) => { | ||
if (!hits || !hits.hits || hits.hits.length == 0) return null; | ||
return processMeta(hits.hits[0]); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forgot to ask: what is this for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should revert this one, I found that Mac default use 5000 port for airplay receiver