Skip to content

Commit 976d168

Browse files
committed
implement caching in routes
1 parent bf191cc commit 976d168

File tree

16 files changed

+106
-43
lines changed

16 files changed

+106
-43
lines changed

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,6 @@ ACCESS_TOKEN_VALIDITY_SEC=172800
5252
REFRESH_TOKEN_VALIDITY_SEC=604800
5353
TOKEN_ISSUER=api.dev.xyz.com
5454
TOKEN_AUDIENCE=xyz.com
55+
56+
# Caching
57+
CONTENT_CACHE_DURATION_MILLIS=600000

src/cache/keys.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export enum Key {
33
}
44

55
export enum DynamicKey {
6-
BLOGS_TAG = 'BLOGS_TAG',
6+
BLOGS_SIMILAR = 'BLOGS_SIMILAR',
77
BLOG = 'BLOG',
88
}
99

src/cache/repository/BlogCache.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,35 @@ import { getJson, setJson } from '../query';
22
import { Types } from 'mongoose';
33
import Blog from '../../database/model/Blog';
44
import { DynamicKey, getDynamicKey } from '../keys';
5+
import { caching } from '../../config';
6+
import { addMillisToCurrentDate } from '../../helpers/utils';
57

6-
function getKey(blogId: Types.ObjectId) {
8+
function getKeyForId(blogId: Types.ObjectId) {
79
return getDynamicKey(DynamicKey.BLOG, blogId.toHexString());
810
}
911

10-
async function save(blog: Blog, expireAt: Date) {
11-
return setJson(getKey(blog._id), { ...blog }, expireAt);
12+
function getKeyForUrl(blogUrl: string) {
13+
return getDynamicKey(DynamicKey.BLOG, blogUrl);
1214
}
1315

14-
async function fetch(blogId: Types.ObjectId) {
15-
return getJson<Blog>(getKey(blogId));
16+
async function save(blog: Blog) {
17+
return setJson(
18+
getKeyForId(blog._id),
19+
{ ...blog },
20+
addMillisToCurrentDate(caching.contentCacheDuration),
21+
);
22+
}
23+
24+
async function fetchById(blogId: Types.ObjectId) {
25+
return getJson<Blog>(getKeyForId(blogId));
26+
}
27+
28+
async function fetchByUrl(blogUrl: string) {
29+
return getJson<Blog>(getKeyForUrl(blogUrl));
1630
}
1731

1832
export default {
1933
save,
20-
fetch,
34+
fetchById,
35+
fetchByUrl,
2136
};

src/cache/repository/BlogsCache.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
import { getListRange, setList } from '../query';
22
import Blog from '../../database/model/Blog';
33
import { DynamicKey, getDynamicKey } from '../keys';
4+
import { addMillisToCurrentDate } from '../../helpers/utils';
5+
import { caching } from '../../config';
6+
import { Types } from 'mongoose';
47

5-
function getTagKey(tag: string) {
6-
return getDynamicKey(DynamicKey.BLOGS_TAG, tag);
8+
function getKeyForSimilar(blogId: Types.ObjectId) {
9+
return getDynamicKey(DynamicKey.BLOGS_SIMILAR, blogId.toHexString());
710
}
811

9-
async function saveForTag(tag: string, blogs: Blog[], expireAt: Date) {
10-
return setList(getTagKey(tag), blogs, expireAt);
12+
async function saveSimilarBlogs(blogId: Types.ObjectId, blogs: Blog[]) {
13+
return setList(
14+
getKeyForSimilar(blogId),
15+
blogs,
16+
addMillisToCurrentDate(caching.contentCacheDuration),
17+
);
1118
}
1219

13-
async function fetch(tag: string) {
14-
return getListRange<Blog>(getTagKey(tag));
20+
async function fetchSimilarBlogs(blogId: Types.ObjectId) {
21+
return getListRange<Blog>(getKeyForSimilar(blogId));
1522
}
1623

1724
export default {
18-
saveForTag,
19-
fetch,
25+
saveSimilarBlogs,
26+
fetchSimilarBlogs,
2027
};

src/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,7 @@ export const redis = {
2929
port: parseInt(process.env.REDIS_PORT || '0'),
3030
password: process.env.REDIS_USER_PASSWORD || '',
3131
};
32+
33+
export const caching = {
34+
contentCacheDuration: parseInt(process.env.CONTENT_CACHE_DURATION_MILLIS || '600000'),
35+
};

src/database/model/ApiKey.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,6 @@ const schema = new Schema<ApiKey>(
7474
},
7575
);
7676

77+
schema.index({ key: 1, status: 1 });
78+
7779
export const ApiKeyModel = model<ApiKey>(DOCUMENT_NAME, schema, COLLECTION_NAME);

src/database/model/Blog.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,16 @@ const schema = new Schema<Blog>(
142142
{
143143
versionKey: false,
144144
},
145-
).index(
145+
);
146+
147+
schema.index(
146148
{ title: 'text', description: 'text' },
147149
{ weights: { title: 3, description: 1 }, background: false },
148150
);
151+
schema.index({ _id: 1, status: 1 });
152+
schema.index({ blogUrl: 1, status: 1 });
153+
schema.index({ isPublished: 1, status: 1 });
154+
schema.index({ _id: 1, isPublished: 1, status: 1 });
155+
schema.index({ tag: 1, isPublished: 1, status: 1 });
149156

150157
export const BlogModel = model<Blog>(DOCUMENT_NAME, schema, COLLECTION_NAME);

src/database/model/Keystore.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ const schema = new Schema<Keystore>(
5151
},
5252
);
5353

54-
schema.index({ client: 1, primaryKey: 1 });
54+
schema.index({ client: 1 });
55+
schema.index({ client: 1, primaryKey: 1, status: 1 });
5556
schema.index({ client: 1, primaryKey: 1, secondaryKey: 1 });
5657

5758
export const KeystoreModel = model<Keystore>(DOCUMENT_NAME, schema, COLLECTION_NAME);

src/database/model/Role.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,6 @@ const schema = new Schema<Role>(
4545
},
4646
);
4747

48+
schema.index({ code: 1, status: 1 });
49+
4850
export const RoleModel = model<Role>(DOCUMENT_NAME, schema, COLLECTION_NAME);

src/database/model/User.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const schema = new Schema<User>(
7474
);
7575

7676
schema.index({ _id: 1, status: 1 });
77+
schema.index({ email: 1 });
7778
schema.index({ status: 1 });
78-
schema.index({ email: 1, status: 1 });
7979

8080
export const UserModel = model<User>(DOCUMENT_NAME, schema, COLLECTION_NAME);

src/database/repository/BlogRepo.ts

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,14 @@ async function findInfoById(id: Types.ObjectId): Promise<Blog | null> {
2424
.exec();
2525
}
2626

27-
async function findInfoWithTextById(id: Types.ObjectId): Promise<Blog | null> {
28-
return BlogModel.findOne({ _id: id, status: true })
27+
async function findInfoForPublishedById(id: Types.ObjectId): Promise<Blog | null> {
28+
return BlogModel.findOne({ _id: id, isPublished: true, status: true })
2929
.select('+text')
3030
.populate('author', AUTHOR_DETAIL)
3131
.lean()
3232
.exec();
3333
}
3434

35-
async function findInfoWithTextAndDraftTextById(id: Types.ObjectId): Promise<Blog | null> {
36-
return BlogModel.findOne({ _id: id, status: true })
37-
.select('+text +draftText +isSubmitted +isDraft +isPublished +status')
38-
.populate('author', AUTHOR_DETAIL)
39-
.lean()
40-
.exec();
41-
}
42-
4335
async function findBlogAllDataById(id: Types.ObjectId): Promise<Blog | null> {
4436
return BlogModel.findOne({ _id: id, status: true })
4537
.select('+text +draftText +isSubmitted +isDraft +isPublished +status +createdBy +updatedBy')
@@ -48,8 +40,8 @@ async function findBlogAllDataById(id: Types.ObjectId): Promise<Blog | null> {
4840
.exec();
4941
}
5042

51-
async function findByUrl(blogUrl: string): Promise<Blog | null> {
52-
return BlogModel.findOne({ blogUrl: blogUrl, status: true })
43+
async function findPublishedByUrl(blogUrl: string): Promise<Blog | null> {
44+
return BlogModel.findOne({ blogUrl: blogUrl, isPublished: true, status: true })
5345
.select('+text')
5446
.populate('author', AUTHOR_DETAIL)
5547
.lean()
@@ -182,10 +174,9 @@ export default {
182174
create,
183175
update,
184176
findInfoById,
185-
findInfoWithTextById,
186-
findInfoWithTextAndDraftTextById,
177+
findInfoForPublishedById,
187178
findBlogAllDataById,
188-
findByUrl,
179+
findPublishedByUrl,
189180
findUrlIfExists,
190181
findByTagAndPaginated,
191182
findAllPublishedForAuthor,

src/helpers/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Request } from 'express';
2+
import moment from 'moment';
23
import Logger from '../core/Logger';
34

45
export function findIpAddress(req: Request) {
@@ -14,3 +15,7 @@ export function findIpAddress(req: Request) {
1415
return undefined;
1516
}
1617
}
18+
19+
export function addMillisToCurrentDate(millis: number) {
20+
return moment().add(millis, 'ms').toDate();
21+
}

src/routes/v1/blog/index.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import BlogRepo from '../../../database/repository/BlogRepo';
88
import { Types } from 'mongoose';
99
import writer from './writer';
1010
import editor from './editor';
11+
import BlogCache from '../../../cache/repository/BlogCache';
1112

1213
const router = express.Router();
1314

@@ -18,18 +19,32 @@ router.get(
1819
'/url',
1920
validator(schema.blogUrl, ValidationSource.QUERY),
2021
asyncHandler(async (req, res) => {
21-
const blog = await BlogRepo.findByUrl(req.query.endpoint as string);
22-
if (!blog) throw new BadRequestError('Blog do not exists');
23-
new SuccessResponse('success', blog).send(res);
22+
const blogUrl = req.query.endpoint as string;
23+
let blog = await BlogCache.fetchByUrl(blogUrl);
24+
25+
if (!blog) {
26+
blog = await BlogRepo.findPublishedByUrl(blogUrl);
27+
if (blog) await BlogCache.save(blog);
28+
}
29+
30+
if (!blog) throw new BadRequestError('Blog not found');
31+
return new SuccessResponse('success', blog).send(res);
2432
}),
2533
);
2634

2735
router.get(
2836
'/id/:id',
2937
validator(schema.blogId, ValidationSource.PARAM),
3038
asyncHandler(async (req, res) => {
31-
const blog = await BlogRepo.findInfoWithTextById(new Types.ObjectId(req.params.id));
32-
if (!blog) throw new BadRequestError('Blog do not exists');
39+
const blogId = new Types.ObjectId(req.params.id);
40+
let blog = await BlogCache.fetchById(blogId);
41+
42+
if (!blog) {
43+
blog = await BlogRepo.findInfoForPublishedById(new Types.ObjectId(req.params.id));
44+
if (blog) await BlogCache.save(blog);
45+
}
46+
47+
if (!blog) throw new BadRequestError('Blog not found');
3348
return new SuccessResponse('success', blog).send(res);
3449
}),
3550
);

src/routes/v1/blogs/index.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { BadRequestError } from '../../../core/ApiError';
77
import BlogRepo from '../../../database/repository/BlogRepo';
88
import { Types } from 'mongoose';
99
import User from '../../../database/model/User';
10+
import BlogsCache from '../../../cache/repository/BlogsCache';
1011

1112
const router = express.Router();
1213

@@ -51,11 +52,18 @@ router.get(
5152
'/similar/id/:id',
5253
validator(schema.blogId, ValidationSource.PARAM),
5354
asyncHandler(async (req, res) => {
54-
const blog = await BlogRepo.findBlogAllDataById(new Types.ObjectId(req.params.id));
55-
if (!blog || !blog.isPublished) throw new BadRequestError('Blog is not available');
55+
const blogId = new Types.ObjectId(req.params.id);
56+
let blogs = await BlogsCache.fetchSimilarBlogs(blogId);
5657

57-
const blogs = await BlogRepo.searchSimilarBlogs(blog, 6);
58-
return new SuccessResponse('success', blogs).send(res);
58+
if (!blogs) {
59+
const blog = await BlogRepo.findInfoForPublishedById(new Types.ObjectId(req.params.id));
60+
if (!blog) throw new BadRequestError('Blog is not available');
61+
blogs = await BlogRepo.searchSimilarBlogs(blog, 6);
62+
63+
if (blogs && blogs.length > 0) await BlogsCache.saveSimilarBlogs(blogId, blogs);
64+
}
65+
66+
return new SuccessResponse('success', blogs ? blogs : []).send(res);
5967
}),
6068
);
6169

tests/.env.test.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,6 @@ ACCESS_TOKEN_VALIDITY_SEC=172800
4747
REFRESH_TOKEN_VALIDITY_SEC=604800
4848
TOKEN_ISSUER=api.dev.xyz.com
4949
TOKEN_AUDIENCE=xyz.com
50+
51+
# Caching
52+
CONTENT_CACHE_DURATION_MILLIS=5000

tests/routes/v1/blog/index/mock.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jest.mock('../../../../../src/database/repository/BlogRepo', () => ({
3030
get findByUrl() {
3131
return mockBlogFindByUrl;
3232
},
33-
get findInfoWithTextById() {
33+
get findInfoForPublishedById() {
3434
return mockFindInfoWithTextById;
3535
},
3636
}));

0 commit comments

Comments
 (0)