diff --git a/src/handler.ts b/src/handler.ts index a540c81..16086e8 100644 --- a/src/handler.ts +++ b/src/handler.ts @@ -5,7 +5,12 @@ import { bookmarkStackExchange } from './bookmark-stackexchange'; import { bookmarkTweets } from './bookmark-tweets'; import { bookmarkVimeo } from './bookmark-vimeos'; import { bookmarkYouTube } from './bookmark-youtubes'; -import { queryBookmarkItems, queryTags, searchBookmarkItems } from './hasura'; +import { + queryBookmarkAggregateCount, + queryBookmarkItems, + queryTags, + searchBookmarkItems, +} from './hasura'; import { BookmarkingResponse, RecordData, RequestPayload } from './typings.d'; @@ -114,6 +119,22 @@ const handleAction = async (payload: RequestPayload): Promise => { responseInit ); } + case payload.type === 'Count': { + location = `${payload.type}-${payload.table}`; + + const queryResults = await queryBookmarkAggregateCount( + payload.table, + payload.countColumn + ); + + return new Response( + JSON.stringify({ + count: queryResults, + location, + }), + responseInit + ); + } case payload.table === 'Podcasts': location = payload.table; response = await bookmarkPodcasts(data.url, data.tags); @@ -224,6 +245,16 @@ export const handleRequest = async (request: Request): Promise => { JSON.stringify({ error: "Missing 'column' parameter." }), badReqBody ); + case payload.type === 'Count' && !payload.countColumn: + return new Response( + JSON.stringify({ error: "Missing 'countColumn' parameter." }), + badReqBody + ); + case payload.type === 'Count' && !payload.table: + return new Response( + JSON.stringify({ error: "Missing 'table' parameter." }), + badReqBody + ); case payload.table === 'Articles' && !payload.data?.title: return new Response( JSON.stringify({ error: "Missing 'data.title' parameter." }), diff --git a/src/hasura.ts b/src/hasura.ts index 55d0d7c..c98b34f 100644 --- a/src/hasura.ts +++ b/src/hasura.ts @@ -1,8 +1,11 @@ import { HasuraErrors, HasuraInsertResp, + HasuraQueryAggregateResp, HasuraQueryResp, HasuraQueryTagsResp, + CountColumn, + RecordColumnAggregateCount, RecordData, } from './typings.d'; @@ -31,6 +34,22 @@ const objToQueryString = (obj: { [key: string]: any }) => return `${key}: ${fmtValue}`; }); +const countUnique = (iterable: string[]) => + iterable.reduce((acc: RecordColumnAggregateCount, item) => { + acc[item] = (acc[item] || 0) + 1; + return acc; + }, {}); + +const countUniqueSorted = (iterable: string[]) => + // sort descending by count + Object.entries(countUnique(iterable)) + .sort((a, b) => b[1] - a[1]) + .reduce( + (acc: RecordColumnAggregateCount, [key, val]) => + ({ ...acc, [key]: val } as RecordColumnAggregateCount), + {} + ); + /** * Get bookmark tags from Hasura. * @function @@ -133,6 +152,65 @@ export const queryBookmarkItems = async ( } }; +/** + * Get aggregated count of bookmark column from Hasura. + * @function + * @async + * + * @param {string} table + * @param {CountColumn} column + * @returns {Promise} + */ +export const queryBookmarkAggregateCount = async ( + table: string, + column: CountColumn +): Promise => { + const sort = column === 'tags' ? 'title' : column; + const query = ` + { + bookmarks_${table}(order_by: {${sort}: asc}) { + ${column} + } + } + `; + + try { + const request = await fetch(`${HASURA_ENDPOINT}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Hasura-Admin-Secret': `${HASURA_ADMIN_SECRET}`, + }, + body: JSON.stringify({ query }), + }); + const response: any = await request.json(); + + if (response.errors) { + const { errors } = response as HasuraErrors; + + throw `(queryBookmarkAggregateCount) - ${table}: \n ${errors + .map(err => `${err.extensions.path}: ${err.message}`) + .join('\n')} \n ${query}`; + } + + const data = (response as HasuraQueryAggregateResp).data[ + `bookmarks_${table}` + ]; + let collection: string[]; + + if (column === 'tags') { + collection = data.map(item => item[column] as string[]).flat(); + } else { + collection = data.map(item => item[column] as string); + } + + return countUniqueSorted(collection); + } catch (error) { + console.log(error); + throw error; + } +}; + /** * Search bookmark entries from Hasura. * @function diff --git a/src/typings.d.ts b/src/typings.d.ts index a2aa981..0c08014 100644 --- a/src/typings.d.ts +++ b/src/typings.d.ts @@ -1126,6 +1126,19 @@ export interface RecordData { archive?: string; } +export interface RecordColumnAggregateCount { + [key: string]: number; +} + +export type CountColumn = + | 'author' + | 'creator' + | 'site' + | 'dead' + | 'subreddit' + | 'tags' + | 'user'; + export interface BookmarkingResponse { success: boolean; message: string; @@ -1138,6 +1151,7 @@ export interface RequestPayload { tagList?: string; query?: string; column?: string; + countColumn?: CountColumn; data?: PageData; } @@ -1163,6 +1177,12 @@ export interface HasuraQueryResp { }; } +export interface HasuraQueryAggregateResp { + data: { + [key: string]: string | string[]; + }; +} + export interface HasuraQueryTagsResp { data: { meta_tags: { name: string }[];