Skip to content

Commit

Permalink
Events v2: Skeleton GraphQL resolvers for basic operations (#8033)
Browse files Browse the repository at this point in the history
Co-authored-by: Markus <markus@agilepursuit.fi>
  • Loading branch information
rikukissa and makelicious authored Nov 25, 2024
1 parent 73dcb48 commit 89ce49a
Show file tree
Hide file tree
Showing 29 changed files with 1,256 additions and 107 deletions.
41 changes: 29 additions & 12 deletions .github/workflows/lint-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,36 @@ jobs:
with:
node-version-file: .nvmrc

- name: Extract dependencies for ${{ matrix.package }}
if: steps.check-scripts.outputs.skip != 'true'
id: extract-dependencies
run: |
DEPENDENCIES=$(node -e "
const { execSync } = require('child_process');
const output = execSync('yarn --silent workspaces info', { encoding: 'utf-8' });
const json = JSON.parse(output.replaceAll('@opencrvs', 'packages'));
const getDependencies = (pkg) =>
json[pkg].workspaceDependencies.concat(
json[pkg].workspaceDependencies.flatMap(getDependencies)
);
console.log(
getDependencies('${{ matrix.package }}').join(' ')
);
")
echo "DEPENDENCIES=${DEPENDENCIES}" >> $GITHUB_ENV
echo "Found dependencies: $DEPENDENCIES"
- name: Remove other package directories
if: steps.check-scripts.outputs.skip != 'true'
run: |
for dir in packages/*; do
if [ "$dir" != "${{ matrix.package }}" ] && [ "$dir" != "packages/commons" ] && [ "$dir" != "packages/components" ]; then
if [ "${{ matrix.package }}" == "packages/client" ] && [ "$dir" == "packages/gateway" ] ; then
echo "Skipping $dir"
else
echo "Removing $dir"
rm -rf "$dir"
fi
if echo "${{ matrix.package }} $DEPENDENCIES" | grep -q -w "$dir"; then
echo "Skipping $dir"
else
echo "Removing $dir"
rm -rf "$dir"
fi
done
Expand All @@ -119,15 +138,13 @@ jobs:

# TODO: Move out of the matrix to be built once and shared
- name: Build common package
if: steps.check-scripts.outputs.skip != 'true'
if: steps.check-scripts.outputs.skip != 'true' && contains(env.DEPENDENCIES, 'packages/commons')
run: cd packages/commons && yarn build

- name: Build components client and login
if: steps.check-scripts.outputs.skip != 'true'
if: steps.check-scripts.outputs.skip != 'true' && contains(env.DEPENDENCIES, 'packages/components')
run: |
if [[ "${{ matrix.package }}" == "packages/client" || "${{ matrix.package }}" == "packages/login" ]]; then
cd packages/components && yarn build
fi
cd packages/components && yarn build
# TODO: should run parallel to unit tests as can take as much as unit tests
- name: Run linting
Expand Down
13 changes: 13 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ services:
- CONFIG_SMS_CODE_EXPIRY_SECONDS=600
- CONFIG_TOKEN_EXPIRY_SECONDS=604800
- NODE_ENV=development
- EVENTS_URL=http://events:5555/
- FHIR_URL=http://hearth:3447/fhir
- USER_MANAGEMENT_URL=http://user-mgnt:3030/
- SEARCH_URL=http://search:9090/
Expand All @@ -86,6 +87,18 @@ services:
- CHECK_INVALID_TOKEN=true
- MINIO_BUCKET=ocrvs
- DOCUMENTS_URL=http://documents:9050
events:
image: opencrvs/ocrvs-events:${VERSION}
#platform: linux/amd64
build:
context: .
dockerfile: ./packages/events/Dockerfile
restart: unless-stopped
depends_on:
- base
environment:
- MONGO_URL=mongodb://mongo1/events

# User facing services
workflow:
image: opencrvs/ocrvs-workflow:${VERSION}
Expand Down
4 changes: 4 additions & 0 deletions packages/client/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ FROM opencrvs/ocrvs-base:${BRANCH}

USER node

COPY --chown=node:node packages/gateway /app/packages/gateway
COPY --chown=node:node packages/events /app/packages/events

WORKDIR /app/packages/components
COPY --chown=node:node packages/components /app/packages/components

RUN yarn install --frozen-lockfile && yarn build
ENV CONTENT_SECURITY_POLICY_WILDCARD "{{CONTENT_SECURITY_POLICY_WILDCARD}}"
ENV COUNTRY_CONFIG_URL_INTERNAL "{{COUNTRY_CONFIG_URL_INTERNAL}}"
Expand Down
3 changes: 2 additions & 1 deletion packages/client/Dockerfile.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ packages/*
!packages/commons
!packages/components
!packages/client
!packages/gateway
!packages/gateway
!packages/events
1 change: 1 addition & 0 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"xregexp": "^4.2.0"
},
"devDependencies": {
"@opencrvs/gateway": "^1.5.0",
"@graphql-codegen/add": "^5.0.0",
"@graphql-codegen/cli": "^5.0.0",
"@graphql-codegen/introspection": "^3.0.0",
Expand Down
5 changes: 5 additions & 0 deletions packages/commons/src/authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,8 @@ export const getTokenPayload = (token: string): ITokenPayload => {
}
return decoded
}

export const getUserId = (token: string): string => {
const tokenPayload = getTokenPayload(token.split(' ')[1])
return tokenPayload.sub
}
33 changes: 21 additions & 12 deletions packages/commons/src/events/Action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ export const ActionConfig = z.object({
})

export const ActionInputBase = z.object({
type: z.enum(actionTypes as NonEmptyArray<ActionType>),
fields: z.array(
z.object({
id: z.string(),
Expand All @@ -62,18 +61,28 @@ export const ActionInputBase = z.object({
)
})

export const ActionInput = z.union([
ActionInputBase.extend({
type: z.enum([ActionType.CREATE])
}),
ActionInputBase.extend({
type: z.enum([ActionType.REGISTER]),
identifiers: z.object({
trackingId: z.string(),
registrationNumber: z.string()
})
export const CreateActionInput = ActionInputBase.extend({})
export const NotifyActionInput = ActionInputBase.extend({})
export const DeclareActionInput = ActionInputBase.extend({})
export const RegisterActionInput = ActionInputBase.extend({
identifiers: z.object({
trackingId: z.string(),
registrationNumber: z.string()
})
])
})

export const ActionInput = z
.union([
CreateActionInput.extend({ type: z.enum([ActionType.CREATE]) }),
NotifyActionInput.extend({ type: z.enum([ActionType.NOTIFY]) }),
DeclareActionInput.extend({ type: z.enum([ActionType.DECLARE]) }),
RegisterActionInput.extend({ type: z.enum([ActionType.REGISTER]) })
])
.and(
z.object({
createdBy: z.string()
})
)

export type ActionInput = z.infer<typeof ActionInput>

Expand Down
20 changes: 1 addition & 19 deletions packages/commons/src/events/Event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,7 @@ import { Label, Summary } from './utils'
* A subset of an event. Describes fields that can be sent to the system with the intention of either creating or mutating a an event
*/
export const EventInput = z.object({
type: z.string(),
fields: z.array(
z.object({
id: z.string(),
value: z.union([
z.string(),
z.number(),
z.array(
// @TODO: Check if we could make this stricter
z.object({
optionValues: z.array(z.string()),
type: z.string(),
data: z.string(),
fileSize: z.number()
})
)
])
})
)
type: z.string()
})
export type EventInput = z.infer<typeof EventInput>

Expand Down
45 changes: 18 additions & 27 deletions packages/events/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@
import { vi } from 'vitest'
import { appRouter, t } from './router'
import {
setupServer,
getClient,
resetServer
resetServer,
setupServer
} from './storage/__mocks__/mongodb'
import { ActionType } from '@opencrvs/commons'

const { createCallerFactory } = t

Expand All @@ -31,15 +30,17 @@ afterEach(async () => {

function createClient() {
const createCaller = createCallerFactory(appRouter)
const caller = createCaller({})
const caller = createCaller({
user: { id: '1' }
})
return caller
}

const client = createClient()
test('event can be created and fetched', async () => {
const event = await client.event.create({
transactionId: '1',
event: { type: 'birth', fields: [] }
event: { type: 'birth' }
})

const fetchedEvent = await client.event.get(event.id)
Expand All @@ -52,12 +53,12 @@ test('creating an event is an idempotent operation', async () => {

await client.event.create({
transactionId: '1',
event: { type: 'birth', fields: [] }
event: { type: 'birth' }
})

await client.event.create({
transactionId: '1',
event: { type: 'birth', fields: [] }
event: { type: 'birth' }
})

expect(await db.collection('events').find().toArray()).toHaveLength(1)
Expand All @@ -66,13 +67,12 @@ test('creating an event is an idempotent operation', async () => {
test('stored events can be modified', async () => {
const originalEvent = await client.event.create({
transactionId: '1',
event: { type: 'birth', fields: [] }
event: { type: 'birth' }
})

const event = await client.event.patch({
id: originalEvent.id,
type: 'death',
fields: []
type: 'death'
})

expect(event.updatedAt).not.toBe(originalEvent.updatedAt)
Expand All @@ -82,28 +82,19 @@ test('stored events can be modified', async () => {
test('actions can be added to created events', async () => {
const originalEvent = await client.event.create({
transactionId: '1',
event: { type: 'birth', fields: [] }
event: { type: 'birth' }
})

const event = await client.event.actions.create({
const event = await client.event.actions.declare({
eventId: originalEvent.id,
transactionId: '2',
action: {
type: ActionType.REGISTER,
fields: [],
identifiers: {
trackingId: '123',
registrationNumber: '456'
}
fields: []
}
})

expect(event.actions).toContainEqual(
expect.objectContaining({
type: ActionType.REGISTER,
identifiers: {
trackingId: '123',
registrationNumber: '456'
}
})
)
expect(event.actions).toEqual([
expect.objectContaining({ type: 'CREATE' }),
expect.objectContaining({ type: 'DECLARE' })
])
})
26 changes: 25 additions & 1 deletion packages/events/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,33 @@ require('app-module-path').addPath(require('path').join(__dirname, '../'))

import { appRouter } from './router'
import { createHTTPServer } from '@trpc/server/adapters/standalone'
import { getUserId } from '@opencrvs/commons/authentication'
import { TRPCError } from '@trpc/server'

const server = createHTTPServer({
router: appRouter
router: appRouter,
createContext: function createContext(opts) {
const token = opts.req.headers.authorization
if (!token) {
throw new TRPCError({
code: 'UNAUTHORIZED'
})
}

const userId = getUserId(token)

if (!userId) {
throw new TRPCError({
code: 'UNAUTHORIZED'
})
}

return {
user: {
id: userId
}
}
}
})

server.listen(5555)
Loading

0 comments on commit 89ce49a

Please sign in to comment.