v2.0-2
[rss-reader.git] / src / server.ts
index 6c9453275605ae8ab687f9b2f0ae57b86b66b973..c2a3fdaf1163b43d80da4e7dd6ce0f1525fac57a 100644 (file)
@@ -5,6 +5,7 @@ import { v4 as uuidv4 } from 'uuid';
 import { ingest, ingestSingle } from './ingester';
 import {RSSParser} from './parsers/rss';
 import bodyParser from 'body-parser';
+import {BaseParser} from './parsers/base';
 
 
 const app = express();
@@ -19,7 +20,7 @@ app.use((req, res, next) => {
 
 type WrappedApiHandler = (req: Request, res: Response) => Promise<any>;
 
-function apiWrapper(method: 'get' | 'post', endpoint: string, fn: WrappedApiHandler, view?: (args: any) => string) : void {
+function apiWrapper(method: 'get' | 'post' | 'delete', endpoint: string, fn: WrappedApiHandler, view?: (args: any) => string) : void {
   app[method](endpoint, async(req, res) => {
     try {
       const output = await fn(req, res);
@@ -28,15 +29,12 @@ function apiWrapper(method: 'get' | 'post', endpoint: string, fn: WrappedApiHand
         return;
       }
 
-      if(!view) {
-        res.send(output);
-        return;
-      }
-
-      const viewOutput = view(output);
-      if(viewOutput.length) {
-        res.send(viewOutput);
-        return;
+      if(view) {
+        const viewOutput = view(output);
+        if(viewOutput.length) {
+          res.send(viewOutput);
+          return;
+        }
       }
 
       res.status(204);
@@ -45,23 +43,33 @@ function apiWrapper(method: 'get' | 'post', endpoint: string, fn: WrappedApiHand
       console.error(e);
       res.status(500);
     }
+    finally {
+      res.end();
+    }
   });
 }
 
-function apiGet(endpoint: string, fn: WrappedApiHandler, view: (arr: any) => string): void {
+function apiGet(endpoint: string, fn: WrappedApiHandler, view?: (arr: any) => string): void {
   apiWrapper('get', endpoint, fn, view);
 }
 
-function apiPost(endpoint: string, fn: WrappedApiHandler, view: (arr: any) => string): void {
+function apiPost(endpoint: string, fn: WrappedApiHandler, view?: (arr: any) => string): void {
   apiWrapper('post', endpoint, fn, view);
 }
+function apiDelete(endpoint: string, fn: WrappedApiHandler, view?: (arr: any) => string): void {
+  apiWrapper('delete', endpoint, fn, view);
+}
 
 apiPost('/feeds', async (req, res): Promise<any> => {
   // get info about the feed
   const url = req.body.link;
 
-  const rss = new RSSParser();
-  const feedData = await rss.parse(url);
+  let parser: BaseParser;
+
+  // based on the url, we should figure out if this is a reddit or rss feed
+  parser = new RSSParser();
+
+  const feedData = await parser.parse(url);
 
   const title = feedData.title;
   const id = uuidv4();
@@ -79,8 +87,6 @@ apiPost('/feeds', async (req, res): Promise<any> => {
     title,
     url
   }
-}, (feed: any): string => {
-  return '';
 });
 
 apiGet('/feeds', async (req, res): Promise<any> => {
@@ -107,7 +113,7 @@ apiGet('/feeds', async (req, res): Promise<any> => {
     const display = feed.unread ? `(${feed.unread})` : '';
     const first = idx === 0;
 
-    return `<li><a href="#" class="${first ? 'active' : ''} ${feed.unread ? 'unread' : ''}" data-actions="activate" hx-get="/feeds/${feed.id}/items" hx-trigger="${first ? 'load,' : ''}click" hx-target="#feed-item-list" data-feed-id="${feed.id}">${feed.title} 
+    return `<li><a href="#" class="${first ? 'active' : ''} ${feed.unread ? 'unread' : ''}" data-actions="activate" hx-get="/feeds/${feed.id}/items" hx-trigger="${first ? 'load,' : ''}click" hx-target="#list-pane" data-feed-id="${feed.id}">${feed.title} 
     <span class="unread-count">${display}</span>
     </a></li>`
   }).join("\n")}</ul>`;
@@ -119,13 +125,36 @@ function reasonable(date: Date): string {
   return `${date.getFullYear()}-${month}-${day}`;
 }
 
-apiGet('/feeds/:feed_id/items', async (req, res): Promise<any> => {
+apiGet('/feeds/:feed_id', async (req, res): Promise<any> => {
   const id = req.params.feed_id;
+  return query.getFeedInfo.get(id);
+}, (feed: any): string => {
+  return `
+  <b>Feed:</b> ${feed.title}<br>
+  <b>Link:</b> ${feed.link}<br>
+  `;
+});
 
-  return query.getFeedsFor.all(id, 0);
+apiGet('/feeds/:feed_id/items', async (req, res): Promise<any> => {
+  const id = req.params.feed_id;
+  return {
+    items: query.getFeedsFor.all(id, 0),
+    info: query.getFeedInfo.get(id)
+  }
 }, (feedData: any): string => {
   return `
-  ${feedData.map((item: any, index: number) => {
+      <div id="feed-info">
+      <div id="feed-actions">
+        <a href="#" class="btn" hx-post="/feeds/${feedData.info.id}/items/markAsRead" hx-trigger="click">Mark all as Read</a>
+        <a href="#" class="btn" hx-delete="/feeds/${feedData.info.id}" hx-trigger="click" hx-confirm="Are you sure you want to delete this feed?">Delete Feed</a>
+      </div>
+      <b>Feed: </b>${feedData.info.title}<br>
+      </div>
+      <div>
+        <table><thead><tr><th style="width: 80%">Title</th><th>Publish Date</th></head></table>
+      </div>
+      <ul class="scrollable list" id="feed-item-list">
+  ${feedData.items.map((item: any, index: number) => {
     const read = !!item.read_at;
     return `<li>
       <a href="#" class="${index === 0 ? 'active': ''} ${read ? '': 'unread'}" data-actions="activate" hx-get="/feeds/${item.feed_id}/items/${item.id}" hx-trigger="${index === 0 ? 'load,': ''}click" hx-target="#reading-pane" data-feed-item-id="${item.id}" data-feed-id="${item.feed_id}">${item.title}
@@ -133,9 +162,16 @@ apiGet('/feeds/:feed_id/items', async (req, res): Promise<any> => {
       </a>
     </li>
   `}).join("\n")}
+  </ul>
   `;
 });
 
+apiPost('/feeds/:feed_id/items/markAsRead', async (req, res): Promise<any> => {
+  const id = req.params.feed_id;
+  query.readAllItems.run(id);
+  return;
+});
+
 apiGet('/feeds/:feed_id/items/:item_id', async (req, res) => {
   const feed_id = req.params.feed_id;
   const item_id = req.params.item_id;
@@ -156,6 +192,14 @@ apiGet('/feeds/:feed_id/items/:item_id', async (req, res) => {
   return output.content;
 });
 
+apiDelete('/feeds/:feed_id', async (req, res) => {
+  const id = req.params.feed_id;
+
+  query.deleteFeed.run(id);
+  res.setHeader('HX-Trigger', 'newFeed');
+  return;
+});
+
 async function periodicIngest() {
   await ingest();
   setTimeout(periodicIngest, 1000 * 60 * 10);