Source: public.js

import express from 'express';
import debug from 'debug';
import RSS from 'rss-generator';
import dotenv from 'dotenv';

import { getNote, isMyPost, getAccount, getOutboxPosts, ifAccount } from '../lib/account.js';
import { getActivity, getLikesForNote, getReplyCountForNote } from '../lib/notes.js';
import { INDEX } from '../lib/storage.js';
import { ActivityPub } from '../lib/ActivityPub.js';

import { fetchUser } from '../lib/users.js';
export const router = express.Router();
dotenv.config();

const { USER_NAME, DOMAIN } = process.env;

const logger = debug('notes');

/**
 * publish the post to go up the stream
 * check all the posts below and above the threads
 */
const unrollThread = async (noteId, results = [], ascend = true, descend = true) => {
  let post, actor;
  let stats;
  if (
    isMyPost({
      id: noteId
    })
  ) {
    try {
      post = await getNote(noteId);
      actor = ActivityPub.actor;
      const likes = getLikesForNote(post.id);
      stats = {
        likes: likes.likes.length,
        boosts: likes.boosts.length,
        replies: getReplyCountForNote(post.id)
      };
    } catch (err) {
      logger('could not fetch own post in thread', err);
    }
  } else {
    try {
      post = await getActivity(noteId);
      const account = await fetchUser(post.attributedTo);
      actor = account.actor;
    } catch (err) {
      logger('Could not load a post in a thread. Possibly deleted.', err);
    }
  }

  // can only check up stream if you can look at the post itself.
  // if it has been deleted, that info is lost.
  if (post) {
    results.push({
      stats,
      note: post,
      actor
    });

    // if this is a reply, get the parent and any other parents straight up the chain
    // this does NOT get replies to those parents that are not part of the active thread right now.
    if (ascend && post.inReplyTo) {
      try {
        await unrollThread(post.inReplyTo, results, true, false);
      } catch (err) {
        logger('Failed to unroll thread parents.', err);
      }
    }
  }

  // now, find all posts that are below this one...
  if (descend) {
    const replies = INDEX.filter(p => p.inReplyTo === noteId);
    for (let r = 0; r < replies.length; r++) {
      try {
        await unrollThread(replies[r].id, results, false, true);
      } catch (err) {
        logger('Failed to unroll thread children', err);
      }
    }
  }

  return results;
};

/**
 * Renders the home page with the outbox posts fetched through the api
 */
router.get('/', async (req, res) => {
  if (!ifAccount()) {
    console.log('No account found. Redirecting to account creation.');
    res.redirect('/private');
  }
  const offset = parseInt(req.query.offset) || 0;
  const { posts } = await getOutboxPosts(offset);

  const myaccount = getAccount();
  ActivityPub.account = myaccount;
  const actor = ActivityPub.actor;

  res.render('public/home', {
    me: ActivityPub.actor,
    actor,
    activitystream: posts,
    layout: 'public',
    next: offset + posts.length,
    domain: DOMAIN,
    user: USER_NAME
  });
});

/**
 * Fetch the feed for the user and display it in the html
 */
router.get('/feed', async (req, res) => {
  const {
    // total,
    posts
  } = await getOutboxPosts(0);

  const feed = new RSS({
    title: `${USER_NAME}@${DOMAIN}`,
    site_url: DOMAIN,
    pubDate: posts[0].published
  });

  posts.forEach(post => {
    /* loop over data and add to feed */
    feed.item({
      title: post.subject,
      description: post.content,
      url: post.url,
      date: post.published // any format that js Date can parse.
    });
  });

  res.set('Content-Type', 'text/xml');
  res.send(
    feed.xml({
      indent: true
    })
  );
});

router.get('/notes/:guid', async (req, res) => {
  const guid = req.params.guid;

  if (!guid) {
    return res.status(400).send('Bad request.');
  } else {
    const actor = ActivityPub.actor;
    const note = await getNote(`https://${DOMAIN}/m/${guid}`);
    if (note === undefined) {
      return res.status(404).send(`No record found for ${guid}.`);
    } else {
      const notes = await unrollThread(note.id);
      notes.sort((a, b) => {
        const ad = new Date(a.note.published).getTime();
        const bd = new Date(b.note.published).getTime();
        if (ad > bd) {
          return 1;
        } else if (ad < bd) {
          return -1;
        } else {
          return 0;
        }
      });
      res.render('public/note', {
        me: ActivityPub.actor,
        actor,
        activitystream: notes,
        layout: 'public',
        domain: DOMAIN,
        user: USER_NAME
      });
    }
  }
});