Skip to content

Commit

Permalink
training events #1
Browse files Browse the repository at this point in the history
  • Loading branch information
georgeenciu committed Jul 8, 2023
1 parent eab8f84 commit 9b453e5
Show file tree
Hide file tree
Showing 42 changed files with 1,175 additions and 0 deletions.
3 changes: 3 additions & 0 deletions libs/training-events/.env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SYSTEM_ID=rovacc-system-id
FIRESTORE_EMULATOR_HOST="localhost:8080"
GCLOUD_PROJECT="dummy-project"
46 changes: 46 additions & 0 deletions libs/training-events/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"extends": [
"../../.eslintrc.json"
],
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.json"
],
"rules": {
"array-element-newline": [
"error",
{
"minItems": 3
}
]
}
},
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.ts",
"*.tsx"
],
"rules": {}
},
{
"files": [
"*.js",
"*.jsx"
],
"rules": {}
}
]
}
9 changes: 9 additions & 0 deletions libs/training-events/Dockerfile.firebase
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM node:alpine

RUN apk add openjdk11

RUN npm install -g firebase-tools

WORKDIR /app

CMD [ "firebase", "--project=training-events", "emulators:start", "--only", "firestore" ]
11 changes: 11 additions & 0 deletions libs/training-events/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# training-events

This library was generated with [Nx](https://nx.dev).

## Building

Run `nx build training-events` to build the library.

## Running unit tests

Run `nx test training-events` to execute the unit tests via [Jest](https://jestjs.io).
14 changes: 14 additions & 0 deletions libs/training-events/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: '3.5'
services:
training_events_firebase:
image: matthewkrupnik/docker-firestore-emulator-with-ui
ports:
- "8080:8080"
- "4000:4000"
# trainingeevents_firebase:
# build:
# dockerfile: ./Dockerfile.firebase
# context: .
# ports:
# - "8080:8080"
# - "4000:4000"
5 changes: 5 additions & 0 deletions libs/training-events/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "@rovacc/training-events",
"version": "0.0.1",
"type": "commonjs"
}
48 changes: 48 additions & 0 deletions libs/training-events/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "training-events",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/training-events/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/vite:build",
"outputs": [
"{options.outputPath}"
],
"options": {
"outputPath": "dist/libs/training-events"
}
},
"test": {
"executor": "@nx/vite:test",
"dependsOn": [
"pre-test"
],
"outputs": [
"coverage/libs/training-events"
],
"options": {
"passWithNoTests": true,
"reportsDirectory": "../../coverage/libs/training-events"
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": [
"{options.outputFile}"
],
"options": {
"lintFilePatterns": [
"libs/training-events/**/*.ts"
]
}
},
"pretest": {
"command": "docker-compose -f libs/training-events/docker-compose.yml up -d"
},
"posttest": {
"command": "docker-compose -f libs/training-events/docker-compose.yml down"
}
},
"tags": []
}
101 changes: 101 additions & 0 deletions libs/training-events/src/actions/emit-training-event.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { afterEach, beforeEach, describe, it, expect } from "vitest";
import { buildTrainingIntentEvent } from '@rovacc/test-helpers'

import { emitTrainingEvent } from "./emit-training-event";
import { deleteCollection } from "@rovacc/test-helpers";
import { getDatabaseCollection } from "@rovacc/clients";
import { Timestamp } from "firebase-admin/firestore";


const TRAINING_ID = 'trainingId'
const EVENT_ID = 'eventId'
const DATE = new Date()

describe('emitTrainingEvent', () => {

beforeEach(async () => {
await deleteCollection('training')
vi.mock('uuid', () => ({
v4: () => EVENT_ID
}))
vi.mock('../helpers/get-date', () => ({
getDate: () => DATE
}))
})

afterEach(async () => {
vi.restoreAllMocks()
})

it('should emit the event correctly and create the empty training object', async () => {
const eventData = buildTrainingIntentEvent({
trainingId: TRAINING_ID,
emittedAt: DATE,
eventId: EVENT_ID
})

const reducedTraining = await emitTrainingEvent(eventData, null, 'correlationId')

expect(reducedTraining).toEqual({
trainingId: TRAINING_ID,
status: 'QUEUED',
purpose: 'acquire_rating',
rating: 2,
student: 123123123,
requestedAt: DATE
})
const trainingCollection = getDatabaseCollection('training')
const event = await trainingCollection.doc(TRAINING_ID).collection('events').doc(EVENT_ID).get()

expect(event.data()).toEqual({
eventId: EVENT_ID,
emittedAt: Timestamp.fromDate(DATE),
system: 'rovacc-system-id',
trainingId: TRAINING_ID,
payload: { student: 123123123, purpose: 'acquire_rating', rating: 2 },
name: 'training-intent',
correlationId: 'correlationId'
})
})

it('should not emit the same event twice', async () => {
const eventData1 = buildTrainingIntentEvent({
trainingId: TRAINING_ID,
emittedAt: DATE,
eventId: EVENT_ID
})

const reducedTraining1 = await emitTrainingEvent(eventData1, null, 'correlationId')

expect(reducedTraining1).toEqual({
trainingId: TRAINING_ID,
status: 'QUEUED',
purpose: 'acquire_rating',
rating: 2,
student: 123123123,
requestedAt: DATE
})
const trainingCollection = getDatabaseCollection('training')
const event1 = await trainingCollection.doc(TRAINING_ID).collection('events').doc(EVENT_ID).get()

expect(event1.data()).toEqual({
eventId: EVENT_ID,
emittedAt: Timestamp.fromDate(DATE),
system: 'rovacc-system-id',
trainingId: TRAINING_ID,
payload: { student: 123123123, purpose: 'acquire_rating', rating: 2 },
name: 'training-intent',
correlationId: 'correlationId'
})

const eventData2 = buildTrainingIntentEvent({
trainingId: TRAINING_ID,
emittedAt: DATE,
eventId: 'eventId2'
})
await emitTrainingEvent(eventData2, reducedTraining1, 'correlationId2')
const eventCount = await trainingCollection.doc(TRAINING_ID).collection('events').count().get()

expect(eventCount.data().count).toEqual(1)
})
})
47 changes: 47 additions & 0 deletions libs/training-events/src/actions/emit-training-event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { v4 as uuidv4 } from 'uuid';
import { TrainingEvent, TrainingEventData } from '../types';
import {
SYSTEM_ID,
TRAINING_COLLECTION,
TRAINING_EVENTS_SUBCOLLECTION,
} from '../config';
import { getFirestore } from 'firebase-admin/firestore';
import { FirestoreNotInitialzedException } from '../exception/firestore-not-initialized';

export const emitTrainingEvent = async (
eventData: TrainingEventData,
training: Training | null,
correlationId: string
): Promise<Training | null> => {
const trainingId = eventData.trainingId;
const event: TrainingEvent = {
eventId: uuidv4(),
emittedAt: getDate(),
system: SYSTEM_ID,
correlationId,
...eventData,
};

if (isEmitted[event.name] && isEmitted[event.name](training, event)) {
return training;
}

const db = getFirestore();
if (!db) {
throw new FirestoreNotInitialzedException();
}
const trainingCollection = db.collection(TRAINING_COLLECTION);

const trainingObj = await trainingCollection.doc(trainingId).get();
if (!trainingObj.exists) {
await trainingCollection.doc(trainingId).set({ trainingId });
}

await trainingCollection
.doc(trainingId)
.collection(TRAINING_EVENTS_SUBCOLLECTION)
.doc(event.eventId)
.set(event);

return reduceEvent(training, event);
};
49 changes: 49 additions & 0 deletions libs/training-events/src/actions/get-training-events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { getDatabaseCollection } from "@rovacc/clients";
import { Training, TrainingEvent } from "@rovacc/training-events-types";
import { TRAINING_COLLECTION, TRAINING_EVENTS_SUBCOLLECTION } from "../config";
import { TrainingNotFound } from "../exception/training-not-found";

export const getTrainingEvents = async (trainingId: string): Promise<TrainingEvent[] | undefined> => {
const trainingCollection = getDatabaseCollection(TRAINING_COLLECTION)

const trainingEvents = await trainingCollection
.doc(trainingId)
.collection(TRAINING_EVENTS_SUBCOLLECTION)
.get()

if (!trainingEvents || !trainingEvents.docs || trainingEvents.docs.length === 0) {
return undefined
}

return trainingEvents.docs.map(event => event.data() as TrainingEvent)
}

export const tryGetTrainingEvents = async (trainingId: string): Promise<TrainingEvent[]> => {
const trainingEvents = await getTrainingEvents(trainingId)
if (!trainingEvents) {
throw new TrainingNotFound(trainingId)
}
return trainingEvents
}

export const getTraining = async (trainingId: string): Promise<Training | undefined> => {
const trainingCollection = getDatabaseCollection(TRAINING_COLLECTION)

const training = await trainingCollection
.doc(trainingId)
.get()

if (!training.exists) {
return undefined
}

return training.data() as Training
}

export const tryGetTraining = async (trainingId: string): Promise<Training> => {
const training = await getTraining(trainingId)
if (!training) {
throw new TrainingNotFound(trainingId)
}
return training
}
3 changes: 3 additions & 0 deletions libs/training-events/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const SYSTEM_ID = process.env['SYSTEM_ID'] as string
export const TRAINING_COLLECTION = 'training'
export const TRAINING_EVENTS_SUBCOLLECTION = 'events'
19 changes: 19 additions & 0 deletions libs/training-events/src/events/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export { trainingCompleted } from "./training-completed"
export { trainingCptPerformed } from "./training-cpt-performed"
export { trainingCptRequested } from "./training-cpt-requested"
export { trainingCptScheduled } from "./training-cpt-scheduled"
export { trainingIntent } from "./training-intent";
export { trainingIntentConfirmationExpired } from "./training-intent-confirmation-expired";
export { trainingIntentConfirmationRejected } from "./training-intent-confirmation-rejected";
export { trainingIntentConfirmationRequested } from "./training-intent-confirmation-requested";
export { trainingIntentConfirmationResponded } from "./training-intent-confirmation-responded";
export { trainingMentorAssigned } from "./training-mentor-assigned";
export { trainingMentorReassigned } from "./training-mentor-reassigned";
export { trainingSessionPerformed } from "./training-session-performed";
export { trainingSessionScheduled } from "./training-session-scheduled";
export { trainingSoloPerformed } from "./training-solo-performed"
export { trainingSoloRequested } from "./training-solo-requested"
export { trainingSoloScheduled } from "./training-solo-scheduled"
export { trainingTestAssigned } from "./training-test-assigned";
export { trainingTestCompleted } from "./training-test-completed";
export { reducer, isEmitted, reduceEvent } from "./reducer";
Loading

0 comments on commit 9b453e5

Please sign in to comment.