Skip to content

Commit 565f5f3

Browse files
history service setup:
- Docker - Express setup - Route & models
1 parent 0cafb5b commit 565f5f3

File tree

17 files changed

+7766
-0
lines changed

17 files changed

+7766
-0
lines changed

history/.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

history/Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FROM node:alpine
2+
3+
WORKDIR /app
4+
COPY package.json .
5+
RUN npm install --only=prod
6+
COPY . .
7+
8+
CMD [ "npm", "start" ]

history/package-lock.json

Lines changed: 7213 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

history/package.json

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "history",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"start": "ts-node-dev --poll src/index.ts",
8+
"test": "jest --watchAll --no-cache",
9+
"test:ci": "jest"
10+
},
11+
"jest": {
12+
"preset": "ts-jest",
13+
"testEnvironment": "node",
14+
"setupFilesAfterEnv": [
15+
"./src/test/setup.ts"
16+
]
17+
},
18+
"keywords": [],
19+
"author": "",
20+
"license": "ISC",
21+
"dependencies": {
22+
"@hn-hub/common": "^1.0.6",
23+
"@types/express": "^4.17.6",
24+
"@types/mongoose": "^5.7.27",
25+
"express": "^4.17.1",
26+
"express-async-errors": "^3.1.1",
27+
"express-validator": "^6.6.0",
28+
"mongoose": "^5.9.19",
29+
"node-nats-streaming": "^0.3.2",
30+
"ts-node-dev": "^1.0.0-pre.49",
31+
"typescript": "^3.9.5"
32+
},
33+
"devDependencies": {
34+
"@types/jest": "^26.0.3",
35+
"@types/supertest": "^2.0.9",
36+
"jest": "^26.1.0",
37+
"mongodb-memory-server": "^6.6.1",
38+
"supertest": "^4.0.2",
39+
"ts-jest": "^26.1.1"
40+
}
41+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const natsWrapper = {
2+
client: {
3+
publish: jest.fn().mockImplementation((subject: string, data: string, callback: () => void) => {
4+
callback();
5+
})
6+
}
7+
};

history/src/app.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { errorHandler, NotFoundError } from '@hn-hub/common';
2+
import { json } from 'body-parser';
3+
import express from 'express';
4+
import 'express-async-errors';
5+
import { getStoriesRouter } from './routes/get-stories';
6+
7+
const app = express();
8+
app.set('trust proxy', true);
9+
app.use(json());
10+
11+
app.use(getStoriesRouter);
12+
13+
app.all('*', async (req, res) => {
14+
throw new NotFoundError();
15+
});
16+
17+
app.use(errorHandler);
18+
19+
export { app };
20+
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { ExpirationCompleteEvent } from '@hn-hub/common';
2+
import { Message } from 'node-nats-streaming';
3+
import { Story, StoryDoc } from '../../../models/stories';
4+
import { natsWrapper } from '../../../nats-wrapper';
5+
import { ExpirationCompleteListener } from '../expiration-complete-listener';
6+
7+
const storyData = [
8+
{
9+
"comments": [
10+
24124834
11+
],
12+
"isExpired": false,
13+
"title": "Mozilla lays off 250 employees while it refocuses on commercial products",
14+
"url": "https://blog.mozilla.org/blog/2020/08/11/changing-world-changing-mozilla/",
15+
"score": 1583,
16+
"createdAt": 1597154636,
17+
"user": "rebelwebmaster",
18+
"storyId": 24120336
19+
},
20+
{
21+
"comments": [
22+
24135784
23+
],
24+
"isExpired": false,
25+
"title": "Mozilla Lifeboat",
26+
"url": "https://mozillalifeboat.com",
27+
"score": 1432,
28+
"createdAt": 1597256213,
29+
"user": "gkoberger",
30+
"storyId": 24135032
31+
}
32+
];
33+
34+
const setup = async () => {
35+
const listener = new ExpirationCompleteListener(natsWrapper.client);
36+
const storyBuildObject: any[] = [];
37+
storyData.forEach(story => {
38+
const st = Story.build({
39+
by: story.user,
40+
score: story.score,
41+
time: story.createdAt + '',
42+
title: story.title,
43+
url: story.url,
44+
kids: story.comments!,
45+
id: story.storyId
46+
}); // building the story object
47+
storyBuildObject.push(st.save()); // chainning it into the Promise array
48+
});
49+
// Saving all records at once
50+
const stories: Array<StoryDoc> = await Promise.all(storyBuildObject);
51+
const data: ExpirationCompleteEvent['data'] = {
52+
stories: stories.map(e => e.storyId)
53+
};
54+
55+
// @ts-ignore
56+
const msg: Message = {
57+
ack: jest.fn()
58+
};
59+
60+
return { listener, stories, data, msg };
61+
};
62+
63+
64+
it('delete all the given stories', async () => {
65+
const { listener, data, msg } = await setup();
66+
67+
await listener.onMessage(data, msg);
68+
69+
const updatedStory = await Story.find({});
70+
71+
expect(updatedStory.length).toEqual(0);
72+
});
73+
74+
it('ack the message', async () => {
75+
const { listener, data, msg } = await setup();
76+
77+
await listener.onMessage(data, msg);
78+
79+
expect(msg.ack).toHaveBeenCalled();
80+
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { ExpirationCompleteEvent, Listener, Subjects } from '@hn-hub/common';
2+
import { Message } from 'node-nats-streaming';
3+
import { Story } from '../../models/stories';
4+
import { queueGroupName } from './queue-group-name';
5+
6+
export class ExpirationCompleteListener extends Listener<ExpirationCompleteEvent> {
7+
subject: Subjects.ExpirationComplete = Subjects.ExpirationComplete;
8+
queueGroupName = queueGroupName;
9+
10+
async onMessage(data: ExpirationCompleteEvent['data'], msg: Message) {
11+
12+
const { stories } = data;
13+
// Find all the stories
14+
const storiesExists = await Story.find({ storyId: { $in: stories } });
15+
16+
// If no stories, throw error
17+
if (!storiesExists) {
18+
throw new Error('Stories not found');
19+
}
20+
21+
// delete the expired story
22+
const deletedStories = await Story.deleteMany({ storyId: { $in: stories } });
23+
24+
//todo: clear from the cache
25+
console.log('Deleted stories: ', deletedStories);
26+
27+
// ack the message
28+
msg.ack();
29+
}
30+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const queueGroupName = 'stories-service';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { Publisher, StoryCreatedEvent, Subjects } from '@hn-hub/common';
2+
3+
export class StoryCreatedPublisher extends Publisher<StoryCreatedEvent> {
4+
subject: Subjects.StoryCreated = Subjects.StoryCreated;
5+
}

0 commit comments

Comments
 (0)