Skip to content
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 create and update mutations for Medication events. Closes #286 #299

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import streetfindEventResolvers from './resolvers/eventStreetfind';
import giveawayEventResolvers from './resolvers/eventGiveaway';
import favoriteAnimalResolvers from './resolvers/favoriteAnimal';
import customScalarsResolvers from './resolvers/scalars';
import medicationEventResolvers from './resolvers/eventMedication';

const schema = loadSchemaSync('src/schema/typeDefs/*.graphql', {
loaders: [new GraphQLFileLoader()],
Expand Down Expand Up @@ -56,6 +57,7 @@ const schemaWithResolvers = addResolversToSchema({
streetfindEventResolvers,
giveawayEventResolvers,
favoriteAnimalResolvers,
medicationEventResolvers,
),
inheritResolversFromInterfaces: true,
});
Expand Down
40 changes: 40 additions & 0 deletions src/schema/resolvers/eventMedication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { IResolvers } from 'graphql-tools';
import { ValidationError } from 'apollo-server-express';
import {
createMedicationEventQuery,
updateMedicationEventQuery
} from '../../sql-queries/eventMedication';
import { getAuthor } from './author';

const medicationEventResolvers: IResolvers = {
MedicationEvent: {
author: getAuthor
},
Mutation: {
createMedicationEvent: async (_, { input }, {
pgClient,
userId
}) => {
const dbResponse = await pgClient.query(
createMedicationEventQuery({ ...input, author: userId })
);
return dbResponse.rows[0];
},
updateMedicationEvent: async (_, { input }, { pgClient, userId }) => {
if (Object.keys(input).length < 2) {
throw new ValidationError(
'You have to provide at least one data field when updating an entity'
);
}

const dbResponse = await pgClient.query(
updateMedicationEventQuery({...input, author: userId})

);

return dbResponse.rows[0];
}
}
};

export default medicationEventResolvers;
48 changes: 48 additions & 0 deletions src/schema/typeDefs/eventMedication.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"Represents Medication event"
type MedicationEvent {
"Event id"
id: Int!
"Animal id"
animalId: Int!
"Event date"
dateTime: Date!
author: Author!
treatment: String!
expenses: Float
comments: String
}

extend type Mutation {
"Create Medication event"
createMedicationEvent(input: CreateMedicationEventInput!): MedicationEvent
"Update Medication event"
updateMedicationEvent(input: UpdateMedicationEventInput!): MedicationEvent
}

input CreateMedicationEventInput {
"Animal id, e.g. 2"
animalId: Int!
"Medication date in YYYY-MM-DD format"
dateTime: Date
"Event comments"
comments: String
"Treatment"
treatment: String!
"Event expenses"
expenses: Float
}

input UpdateMedicationEventInput {
"Event id"
id: Int!
"Animal id, e.g. 2"
animalId: Int
"Medication date in YYYY-MM-DD format"
dateTime: Date
"Event comments"
comments: String
"Treatment"
treatment: String
"Event expenses"
expenses: Float
}
37 changes: 37 additions & 0 deletions src/sql-queries/eventMedication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { QueryConfig } from 'pg';
import { insert, update } from 'sql-bricks-postgres';
import snakeCaseKeys from 'snakecase-keys';

const table = 'event_medication';
const returnFields = 'id, treatment, expenses, date_time, animal_id, author, comments';
export interface CreateMedicationEventData {
animalId: number
dateTime?: string | null
comments?: string | null
treatment: string
expenses?: string | null
author: string
}

export interface UpdateMedicationEventData {
id: number
animalId?: number
dateTime?: string | null
comments?: string | null
treatment?: string
expenses?: number | null
author?: string
}

export const createMedicationEventQuery = (
data: CreateMedicationEventData
): QueryConfig => insert(table, snakeCaseKeys(data))
.returning(returnFields)
.toParams();

export const updateMedicationEventQuery = (
data: UpdateMedicationEventData
): QueryConfig => update(table, snakeCaseKeys(data))
.where({ id: data.id })
.returning(returnFields)
.toParams();
124 changes: 124 additions & 0 deletions test/eventMedication.graphql.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import supertest from 'supertest';
import { expect } from 'chai';
import { authorFields } from './authorFields';
import validate from './validators/eventMedication.interface.validator';

require('dotenv').config({ path: './test/.env' });

const url = process.env.TEST_URL || 'http://localhost:8081';
const request = supertest(url);

const eventMedicationFields = `
{
id,
animalId,
dateTime,
comments,
treatment,
expenses,
author ${authorFields}
}
`;

describe('GraphQL medication event integration tests', () => {

const expectedCreateResult = {
animalId: 4,
dateTime: '2021-10-08',
comments: '1 dose have been administered already',
treatment: 'Antibiotics 1 tablet per day for 10 days',
expenses: 35.00,
author: {
id: 'userIdForTesting',
name: 'Ąžuolas',
surname: 'Krušna'
}
};

it('Creates medication event with all fields', (done) => {
let req = request
.post('/graphql')
.send({
query: `mutation {
createMedicationEvent(
input: {
animalId: 4
dateTime: "2021-10-08"
comments: "1 dose have been administered already"
treatment: "Antibiotics 1 tablet per day for 10 days"
expenses: 35.00
}
) ${eventMedicationFields}
}`
})
.expect(200);
if (process.env.BEARER_TOKEN) {
req = req.set('authorization', `Bearer ${process.env.BEARER_TOKEN}`);
}
req.end((err, res) => {
if (err) return done(err);
const { body: { data: { createMedicationEvent } } } = res;
validate(createMedicationEvent);
expect(createMedicationEvent).to.deep.include(expectedCreateResult);
return done();
});
});

const expectedUpdateResult = {
animalId: 4,
dateTime: '2021-10-07',
comments: 'Bacteria sample has been taken, results will come back in 7 days',
treatment: 'Anti-inflammatory syrup 6ml per day, 1cm vitamin gel per day with food',
expenses: 44.00,
author: {
id: 'userIdForTesting',
name: 'Ąžuolas',
surname: 'Krušna'
}
};

it('Update Medication event with all fields', (done) => {
let req = request
.post('/graphql')
.send({
query: `mutation {
updateMedicationEvent (
input: {
id: 1
animalId: 4
dateTime: "2021-10-07"
comments: "Bacteria sample has been taken, results will come back in 7 days"
treatment: "Anti-inflammatory syrup 6ml per day, 1cm vitamin gel per day with food"
expenses: 44.00
}
) {
id
animalId
dateTime
comments
treatment
expenses
author {
id
name
surname
}
}
}`,
})
.expect(200);
if (process.env.BEARER_TOKEN) {
req = req.set('authorization', `Bearer ${process.env.BEARER_TOKEN}`);
}
req.end((err, res) => {
if (err) return done(err);
const { body: { data: { updateMedicationEvent } } } = res;
validate(updateMedicationEvent);
expect(updateMedicationEvent).to.deep.include({
id: 1,
...expectedUpdateResult,
});
return done();
});
});
});
11 changes: 11 additions & 0 deletions test/interfaces/eventMedication.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Author from './author.interface';

export default interface MedicationEvent {
id: number
animalId: number
dateTime: string | null
comments: string | null
treatment: string
expenses: number | null
author: Author
}
100 changes: 100 additions & 0 deletions test/validators/eventMedication.interface.validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/* tslint:disable */
// generated by typescript-json-validator
import { inspect } from 'util';
import MedicationEvent from '../interfaces/eventMedication.interface';
import Ajv = require('ajv');

export const ajv = new Ajv({"allErrors":true,"coerceTypes":false,"format":"fast","nullable":true,"unicode":true,"uniqueItems":true,"useDefaults":true});

ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));

export {MedicationEvent};
export const MedicationEventSchema = {
"$schema": "http://json-schema.org/draft-07/schema#",
"defaultProperties": [
],
"definitions": {
"default": {
"defaultProperties": [
],
"properties": {
"id": {
"type": "string"
},
"name": {
"type": [
"null",
"string"
]
},
"surname": {
"type": [
"null",
"string"
]
}
},
"required": [
"id",
"name",
"surname"
],
"type": "object"
}
},
"properties": {
"animalId": {
"type": "number"
},
"author": {
"$ref": "#/definitions/default"
},
"comments": {
"type": [
"null",
"string"
]
},
"dateTime": {
"type": [
"null",
"string"
]
},
"expenses": {
"type": [
"null",
"number"
]
},
"id": {
"type": "number"
},
"treatment": {
"type": "string"
}
},
"required": [
"animalId",
"author",
"comments",
"dateTime",
"expenses",
"id",
"treatment"
],
"type": "object"
};
export type ValidateFunction<T> = ((data: unknown) => data is T) & Pick<Ajv.ValidateFunction, 'errors'>
export const isMedicationEvent = ajv.compile(MedicationEventSchema) as ValidateFunction<MedicationEvent>;
export default function validate(value: unknown): MedicationEvent {
if (isMedicationEvent(value)) {
return value;
} else {
throw new Error(
ajv.errorsText(isMedicationEvent.errors!.filter((e: any) => e.keyword !== 'if'), {dataVar: 'MedicationEvent'}) +
'\n\n' +
inspect(value),
);
}
}