From 4867f76904d127b32b0028b99e7b1bcc028fc7a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kalle=20L=C3=A4ms=C3=A4?= <1397804+coocos@users.noreply.github.com> Date: Wed, 7 Apr 2021 19:54:29 +0300 Subject: [PATCH] Refactor and remove in-memory event service --- .../{eventController.ts => events.ts} | 10 +- src/poll.ts | 13 ++- src/routes/events.test.ts | 26 +++-- src/routes/events.ts | 2 +- src/services/eventService.ts | 12 --- src/services/events.ts | 96 +++++++++++++++++++ .../{feedService.test.ts => feed.test.ts} | 2 +- src/services/{feedService.ts => feed.ts} | 2 +- src/services/inMemoryEventService.test.ts | 38 -------- src/services/inMemoryEventService.ts | 21 ---- src/services/sqlEventService.ts | 93 ------------------ 11 files changed, 121 insertions(+), 194 deletions(-) rename src/controllers/{eventController.ts => events.ts} (59%) delete mode 100644 src/services/eventService.ts create mode 100644 src/services/events.ts rename src/services/{feedService.test.ts => feed.test.ts} (97%) rename src/services/{feedService.ts => feed.ts} (96%) delete mode 100644 src/services/inMemoryEventService.test.ts delete mode 100644 src/services/inMemoryEventService.ts delete mode 100644 src/services/sqlEventService.ts diff --git a/src/controllers/eventController.ts b/src/controllers/events.ts similarity index 59% rename from src/controllers/eventController.ts rename to src/controllers/events.ts index 6f1dcf8..9667bdb 100644 --- a/src/controllers/eventController.ts +++ b/src/controllers/events.ts @@ -1,19 +1,19 @@ import { Request, Response } from "express"; import logger from "../logger"; -import { sqlEventService } from "../services/sqlEventService"; +import { events } from "../services/events"; export async function listEvents(req: Request, res: Response): Promise { try { - const events = await sqlEventService.findAll(); + const allEvents = await events.findAll(); res.json( - events.map(({ type, location, time }) => ({ + allEvents.map(({ type, location, time }) => ({ type, location, time: time.toISOString(), })) ); - } catch (e) { - logger.error(`Failed to list events: ${e}`); + } catch (err) { + logger.error(`Failed to list events: ${err.message}`); res.status(500).json({ error: "failed to list events", }); diff --git a/src/poll.ts b/src/poll.ts index 1d8141a..29a9318 100644 --- a/src/poll.ts +++ b/src/poll.ts @@ -2,20 +2,19 @@ import cron from "node-cron"; import logger from "./logger"; import config from "./config"; -import { sqlEventService } from "./services/sqlEventService"; -import { fetchFeedEvents } from "./services/feedService"; -import { RescueEvent } from "./services/eventService"; +import { events, RescueEvent } from "./services/events"; +import { fetchFeedEvents } from "./services/feed"; export const pollFeed = ( eventCallback: (event: RescueEvent) => void ): cron.ScheduledTask => { const task = cron.schedule(config.feed.schedule, async () => { try { - const events = await fetchFeedEvents(); - for (const event of events) { - if (!(await sqlEventService.exists(event))) { + const feedEvents = await fetchFeedEvents(); + for (const event of feedEvents) { + if (!(await events.exists(event))) { logger.info("New event", event); - await sqlEventService.add(event); + await events.add(event); eventCallback(event); } } diff --git a/src/routes/events.test.ts b/src/routes/events.test.ts index fd87f57..37a7dc1 100644 --- a/src/routes/events.test.ts +++ b/src/routes/events.test.ts @@ -1,24 +1,20 @@ import request from "supertest"; import app from "../app"; -import { inMemoryEventService } from "../services/inMemoryEventService"; +import { events } from "../services/events"; -jest.mock("../services/sqlEventService", () => { - const { inMemoryEventService } = jest.requireActual( - "../services/inMemoryEventService" - ); - return { - sqlEventService: inMemoryEventService, - }; -}); +jest.mock("../services/events"); describe("/events", () => { it("returns list of events", async () => { - inMemoryEventService.add({ - type: "rakennuspalo: keskisuuri", - location: "Tuusula", - time: new Date("2021-01-31T22:00:00.000Z"), - hash: "2a39407ee0570aae8f3ba2842e11aa28ce0f5d9f", - }); + const mockEvents = events as jest.Mocked; + mockEvents.findAll.mockResolvedValue([ + { + type: "rakennuspalo: keskisuuri", + location: "Tuusula", + time: new Date("2021-01-31T22:00:00.000Z"), + hash: "2a39407ee0570aae8f3ba2842e11aa28ce0f5d9f", + }, + ]); const response = await request(app).get("/api/v1/events"); expect(response.status).toBe(200); expect(response.body).toEqual([ diff --git a/src/routes/events.ts b/src/routes/events.ts index 5da2e60..a9a137d 100644 --- a/src/routes/events.ts +++ b/src/routes/events.ts @@ -1,5 +1,5 @@ import express from "express"; -import * as eventController from "../controllers/eventController"; +import * as eventController from "../controllers/events"; const router = express.Router(); diff --git a/src/services/eventService.ts b/src/services/eventService.ts deleted file mode 100644 index 4013a53..0000000 --- a/src/services/eventService.ts +++ /dev/null @@ -1,12 +0,0 @@ -export type RescueEvent = { - type: string; - location: string; - time: Date; - hash: string; -}; - -export type EventService = { - findAll(): Promise; - exists(event: RescueEvent): Promise; - add(event: RescueEvent): Promise; -}; diff --git a/src/services/events.ts b/src/services/events.ts new file mode 100644 index 0000000..09a7ee4 --- /dev/null +++ b/src/services/events.ts @@ -0,0 +1,96 @@ +import db from "../db"; +import logger from "../logger"; + +export type RescueEvent = { + type: string; + location: string; + time: Date; + hash: string; +}; + +type Location = { + id: number; + name: string; +}; +type Type = { + id: number; + name: string; +}; +type Event = { + id: number; + location_id: number; + type_id: number; + time: Date; + hash: string; +}; + +export const events = { + async findAll(): Promise { + const events = await db("events") + .join("types", "types.id", "events.type_id") + .join("locations", "locations.id", "events.location_id") + .select( + "events.time", + "events.hash", + "locations.name as location", + "types.name as type" + ); + return events; + }, + async exists(event: RescueEvent): Promise { + const knownEvent = await db("events") + .select("*") + .where({ hash: event.hash }) + .first(); + return knownEvent !== undefined; + }, + async add(event: RescueEvent): Promise { + try { + return await db.transaction(async (trx) => { + const knownEvent = await trx("events") + .select("*") + .where({ + hash: event.hash, + }) + .first(); + if (knownEvent !== undefined) { + return; + } + let location = await trx("locations") + .select("*") + .where({ + name: event.location, + }) + .first(); + if (location === undefined) { + [location] = await trx("locations") + .insert({ + name: event.location, + }) + .returning("*"); + } + let type = await trx("types") + .select("*") + .where({ + name: event.type, + }) + .first(); + if (type === undefined) { + [type] = await trx("types") + .insert({ + name: event.type, + }) + .returning("*"); + } + await trx("events").insert({ + type_id: type.id, + location_id: location.id, + time: event.time, + hash: event.hash, + }); + }); + } catch (err) { + logger.error(err); + } + }, +}; diff --git a/src/services/feedService.test.ts b/src/services/feed.test.ts similarity index 97% rename from src/services/feedService.test.ts rename to src/services/feed.test.ts index 958a7bd..4e2b076 100644 --- a/src/services/feedService.test.ts +++ b/src/services/feed.test.ts @@ -1,5 +1,5 @@ import axios from "axios"; -import { fetchFeedEvents } from "./feedService"; +import { fetchFeedEvents } from "./feed"; jest.mock("axios"); diff --git a/src/services/feedService.ts b/src/services/feed.ts similarity index 96% rename from src/services/feedService.ts rename to src/services/feed.ts index ffd75a6..d9dd5e7 100644 --- a/src/services/feedService.ts +++ b/src/services/feed.ts @@ -4,7 +4,7 @@ import hash from "object-hash"; import Parser from "rss-parser"; import config from "../config"; -import { RescueEvent } from "./eventService"; +import { RescueEvent } from "./events"; const cache = { etag: "", diff --git a/src/services/inMemoryEventService.test.ts b/src/services/inMemoryEventService.test.ts deleted file mode 100644 index 617faa1..0000000 --- a/src/services/inMemoryEventService.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { inMemoryEventService as eventService } from "./inMemoryEventService"; - -describe("EventRepo", () => { - it("adds event to repository", async () => { - eventService.add({ - type: "rakennuspalo: keskisuuri", - location: "Tuusula", - time: new Date("2021-01-31T22:00:00.000Z"), - hash: "2a39407ee0570aae8f3ba2842e11aa28ce0f5d9f", - }); - expect(await eventService.findAll()).toEqual([ - { - type: "rakennuspalo: keskisuuri", - location: "Tuusula", - time: new Date("2021-01-31T22:00:00.000Z"), - hash: "2a39407ee0570aae8f3ba2842e11aa28ce0f5d9f", - }, - ]); - }); - it("does not add duplicate event to repository", async () => { - for (let i = 0; i < 2; i++) { - eventService.add({ - type: "rakennuspalo: keskisuuri", - location: "Tuusula", - time: new Date("2021-01-31T22:00:00.000Z"), - hash: "2a39407ee0570aae8f3ba2842e11aa28ce0f5d9f", - }); - } - expect(await eventService.findAll()).toEqual([ - { - type: "rakennuspalo: keskisuuri", - location: "Tuusula", - time: new Date("2021-01-31T22:00:00.000Z"), - hash: "2a39407ee0570aae8f3ba2842e11aa28ce0f5d9f", - }, - ]); - }); -}); diff --git a/src/services/inMemoryEventService.ts b/src/services/inMemoryEventService.ts deleted file mode 100644 index 78840e8..0000000 --- a/src/services/inMemoryEventService.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { RescueEvent, EventService } from "./eventService"; - -export const inMemoryEventService: EventService = (() => { - const events: { - [hash: string]: RescueEvent; - } = {}; - return { - async findAll(): Promise { - return Object.values(events); - }, - async exists(event: RescueEvent): Promise { - if (event.hash in events) { - return true; - } - return false; - }, - async add(event: RescueEvent): Promise { - events[event.hash] = { ...event }; - }, - }; -})(); diff --git a/src/services/sqlEventService.ts b/src/services/sqlEventService.ts deleted file mode 100644 index 063ba98..0000000 --- a/src/services/sqlEventService.ts +++ /dev/null @@ -1,93 +0,0 @@ -import db from "../db"; -import logger from "../logger"; - -import { RescueEvent, EventService } from "./eventService"; - -export const sqlEventService: EventService = (() => { - type Location = { - id: number; - name: string; - }; - type Type = { - id: number; - name: string; - }; - type Event = { - id: number; - location_id: number; - type_id: number; - time: Date; - hash: string; - }; - - return { - async findAll(): Promise { - const events = await db("events") - .join("types", "types.id", "events.type_id") - .join("locations", "locations.id", "events.location_id") - .select( - "events.time", - "events.hash", - "locations.name as location", - "types.name as type" - ); - return events; - }, - async exists(event: RescueEvent): Promise { - const knownEvent = await db("events") - .select("*") - .where({ hash: event.hash }) - .first(); - return knownEvent !== undefined; - }, - async add(event: RescueEvent): Promise { - try { - return await db.transaction(async (trx) => { - const knownEvent = await trx("events") - .select("*") - .where({ - hash: event.hash, - }) - .first(); - if (knownEvent !== undefined) { - return; - } - let location = await trx("locations") - .select("*") - .where({ - name: event.location, - }) - .first(); - if (location === undefined) { - [location] = await trx("locations") - .insert({ - name: event.location, - }) - .returning("*"); - } - let type = await trx("types") - .select("*") - .where({ - name: event.type, - }) - .first(); - if (type === undefined) { - [type] = await trx("types") - .insert({ - name: event.type, - }) - .returning("*"); - } - await trx("events").insert({ - type_id: type.id, - location_id: location.id, - time: event.time, - hash: event.hash, - }); - }); - } catch (error) { - logger.error(error); - } - }, - }; -})();