Skip to content
This repository was archived by the owner on Mar 3, 2020. It is now read-only.

Commit d835255

Browse files
committed
Implement basic search api
1 parent 04bb244 commit d835255

File tree

7 files changed

+128
-13
lines changed

7 files changed

+128
-13
lines changed

GUIDELINES.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@ $ psql postgres
6161
psql (10.4)
6262
Type "help" for help.
6363
64-
postgres=# CREATE DATABASE velog;
64+
postgres=# CREATE DATABASE velog
65+
LC_COLLATE 'C'
66+
LC_CTYPE 'C'
67+
ENCODING 'UTF8'
68+
TEMPLATE template0;
6569
CREATE DATABASE
6670
postgres=# CREATE USER velog WITH ENCRYPTED PASSWORD 'velogpw';
6771
CREATE ROLE

velog-backend/sql/posts_tsv.sql

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
alter table posts
2+
add column tsv tsvector;
3+
4+
create index posts_tsv on posts using gin(tsv);
5+
6+
update posts set tsv = to_tsvector(coalesce(title)) || to_tsvector(coalesce(body));
7+
8+
create or replace function posts_tsv_trigger() returns trigger as $$
9+
begin
10+
new.tsv := to_tsvector(coalesce(new.title)) || to_tsvector(coalesce(new.body));
11+
return new;
12+
end
13+
$$ language plpgsql;
14+
15+
16+
create trigger tsvectorupdate before insert or update on posts
17+
for each row execute procedure posts_tsv_trigger();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// @flow
2+
import Sequelize from 'sequelize';
3+
import db from 'database/db';
4+
5+
type SearchParameter = {
6+
tsquery: string,
7+
fk_user_id?: string,
8+
authorized?: boolean,
9+
page?: number,
10+
};
11+
12+
type SearchDataRow = {
13+
id: string,
14+
rank: number,
15+
};
16+
17+
export const searchPosts = async ({
18+
tsquery,
19+
fk_user_id,
20+
authorized,
21+
page = 1,
22+
}: SearchParameter): Promise<SearchDataRow[]> => {
23+
const query = `
24+
SELECT id, ts_rank(tsv, TO_TSQUERY($tsquery)) * 1000 + total_score AS rank
25+
FROM posts
26+
JOIN (select fk_post_id, SUM(score) as total_score from post_scores group by fk_post_id) as q on q.fk_post_id = posts.id
27+
WHERE tsv @@ TO_TSQUERY($tsquery)
28+
ORDER BY rank DESC
29+
OFFSET ${10 * (page - 1)}
30+
LIMIT 10
31+
`;
32+
33+
try {
34+
const rows = await db.query(query, {
35+
bind: { tsquery },
36+
});
37+
return rows[0];
38+
} catch (e) {
39+
throw e;
40+
}
41+
};
42+
43+
export const countSearchPosts = async ({
44+
tsquery,
45+
fk_user_id,
46+
authorized,
47+
page = 1,
48+
}: SearchParameter): Promise<number> => {
49+
const query = `
50+
SELECT COUNT(*) as count
51+
FROM posts
52+
WHERE tsv @@ TO_TSQUERY($tsquery)
53+
`;
54+
55+
try {
56+
const rows = await db.query(query, {
57+
bind: { tsquery },
58+
});
59+
return rows[0][0].count;
60+
} catch (e) {
61+
throw e;
62+
}
63+
};

velog-backend/src/router/index.js

+3-6
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
import Router from 'koa-router';
33
import type { Context } from 'koa';
44
import needsAuth from 'lib/middlewares/needsAuth';
5-
import downloadImage from 'lib/downloadImage';
6-
import crypto from 'crypto';
7-
import { PostReadcounts } from 'database/views';
85
import auth from './auth';
96
import posts from './posts';
107
import files from './files';
@@ -15,8 +12,7 @@ import common from './common';
1512
import sitemaps from './sitemaps';
1613
import internal from './internal';
1714
import atom from './atom';
18-
import Post from '../database/models/Post';
19-
import { getTagsList } from '../database/rawQuery/tags';
15+
import search from './search/search';
2016

2117
const router: Router = new Router();
2218

@@ -30,11 +26,12 @@ router.use('/common', common.routes());
3026
router.use('/sitemaps', sitemaps.routes());
3127
router.use('/internal', internal.routes());
3228
router.use('/atom', atom.routes());
29+
router.use('/search', search.routes());
3330

3431
router.get('/check', (ctx: Context) => {
3532
console.log('avoiding cold start...');
3633
ctx.body = {
37-
version: '1.0.0-alpha.0',
34+
version: '1.0.0',
3835
origin: ctx.origin,
3936
env: process.env.NODE_ENV,
4037
};

velog-backend/src/router/posts/posts.ctrl.js

-6
Original file line numberDiff line numberDiff line change
@@ -457,25 +457,19 @@ export const listTrendingPosts = async (ctx: Context) => {
457457

458458
// check cursor
459459
try {
460-
console.time('getTrendingPosts');
461460
const postIds = await getTrendingPosts(offset || 0);
462-
console.timeEnd('getTrendingPosts');
463461
if (!postIds || postIds.length === 0) {
464462
ctx.body = [];
465463
return;
466464
}
467-
console.time('readPostsByIds');
468465
const posts = await Post.readPostsByIds(postIds.map(postId => postId.post_id));
469466
const filtered = posts.filter(p => !p.is_private);
470-
console.timeEnd('readPostsByIds');
471467
const data = filtered
472468
.map(serializePost)
473469
.map(post => ({ ...post, body: formatShortDescription(post.body) }));
474470

475471
// retrieve commentCounts and inject
476-
console.time('getCommentCounts');
477472
const commentCounts = await getCommentCountsOfPosts(filtered.map(p => p.id));
478-
console.timeEnd('getCommentCounts');
479473
ctx.body = injectCommentCounts(data, commentCounts);
480474
} catch (e) {
481475
ctx.throw(500, e);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// @flow
2+
import type { Context } from 'koa';
3+
import Post, { serializePost } from 'database/models/Post';
4+
import { searchPosts, countSearchPosts } from 'database/rawQuery/search';
5+
import { formatShortDescription } from 'lib/common';
6+
7+
export const publicSearch = async (ctx: Context) => {
8+
const { q } = ctx.query;
9+
const transformed = `${q.replace(/ /, '|')}:*`;
10+
try {
11+
const [count, searchResult] = await Promise.all([
12+
countSearchPosts({
13+
tsquery: transformed,
14+
}),
15+
searchPosts({
16+
tsquery: transformed,
17+
}),
18+
]);
19+
20+
const postIds = searchResult.map(r => r.id);
21+
const posts = await Post.readPostsByIds(postIds);
22+
const data = posts
23+
.map(serializePost)
24+
.map(post => ({ ...post, body: formatShortDescription(post.body) }));
25+
ctx.body = {
26+
count,
27+
data,
28+
};
29+
} catch (e) {
30+
ctx.throw(500, e);
31+
}
32+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// @flow
2+
import Router from 'koa-router';
3+
import { publicSearch } from './search.ctrl';
4+
5+
const search = new Router();
6+
7+
search.get('/public', publicSearch);
8+
export default search;

0 commit comments

Comments
 (0)