categories.js 3.9 KB
Newer Older
1 2 3 4
import express from 'express';
import fs from 'fs';
import path from 'path';
import { NotFoundError } from '/common/error';
J
Jason Park 已提交
5
import { exec } from 'child_process';
J
Jason Park 已提交
6
import { GitHubApi } from '/apis';
7 8 9

const router = express.Router();

10 11
const getPath = (...args) => path.resolve(__dirname, '..', 'public', 'algorithms', ...args);
const createKey = name => name.toLowerCase().replace(/ /g, '-');
J
Jason Park 已提交
12
const list = dirPath => fs.readdirSync(dirPath).filter(fileName => !fileName.startsWith('.'));
13

14
const cacheCategories = () => {
J
Jason Park 已提交
15 16
  const allFiles = [];
  const cacheCategory = categoryName => {
17 18
    const categoryKey = createKey(categoryName);
    const categoryPath = getPath(categoryName);
J
Jason Park 已提交
19
    const algorithms = list(categoryPath).map(algorithmName => cacheAlgorithm(categoryName, algorithmName));
20 21 22 23 24 25
    return {
      key: categoryKey,
      name: categoryName,
      algorithms,
    };
  };
J
Jason Park 已提交
26
  const cacheAlgorithm = (categoryName, algorithmName) => {
27 28
    const algorithmKey = createKey(algorithmName);
    const algorithmPath = getPath(categoryName, algorithmName);
J
Jason Park 已提交
29 30
    const files = list(algorithmPath).map(fileName => cacheFile(categoryName, algorithmName, fileName));
    allFiles.push(...files);
31 32 33 34
    return {
      key: algorithmKey,
      name: algorithmName,
      files,
35
    };
36
  };
J
Jason Park 已提交
37 38 39 40 41 42 43 44 45 46 47 48
  const cacheFile = (categoryName, algorithmName, fileName) => {
    const filePath = getPath(categoryName, algorithmName, fileName);
    const content = fs.readFileSync(filePath, 'utf-8');
    return {
      name: fileName,
      path: filePath,
      content,
      contributors: [],
      toJSON: () => fileName,
    };
  };

49
  const categories = list(getPath()).map(cacheCategory);
50

J
Jason Park 已提交
51
  const per_page = 100;
J
Jason Park 已提交
52
  const cacheCommitAuthors = (page = 1, commitAuthors = {}) => GitHubApi.listCommits('algorithm-visualizer', 'algorithms', {
J
Jason Park 已提交
53 54
    per_page,
    page,
J
Jason Park 已提交
55 56
  }).then(commits => {
    commits.forEach(({ sha, commit, author }) => {
J
Jason Park 已提交
57 58 59 60
      if (!author) return;
      const { login, avatar_url } = author;
      commitAuthors[sha] = { login, avatar_url };
    });
J
Jason Park 已提交
61
    if (commits.length < per_page) {
J
Jason Park 已提交
62 63 64 65 66 67
      return commitAuthors;
    } else {
      return cacheCommitAuthors(page + 1, commitAuthors);
    }
  });
  const cacheContributors = (commitAuthors, fileIndex = 0) => {
J
Jason Park 已提交
68 69 70
    const file = allFiles[fileIndex];
    if (file) {
      const cwd = getPath();
71
      exec(`git --no-pager log --follow --no-merges --format="%H" "${file.path}"`, { cwd }, (error, stdout, stderr) => {
J
Jason Park 已提交
72 73 74 75 76 77 78 79 80 81 82 83
        if (!error && !stderr) {
          const output = stdout.toString().replace(/\n$/, '');
          const shas = output.split('\n').reverse();
          const contributors = [];
          for (const sha of shas) {
            const author = commitAuthors[sha];
            if (author && !contributors.find(contributor => contributor.login === author.login)) {
              contributors.push(author);
            }
          }
          file.contributors = contributors;
        }
J
Jason Park 已提交
84
        cacheContributors(commitAuthors, fileIndex + 1);
J
Jason Park 已提交
85
      });
J
Jason Park 已提交
86
    }
J
Jason Park 已提交
87
  };
J
Jason Park 已提交
88
  cacheCommitAuthors().then(cacheContributors);
J
Jason Park 已提交
89

90
  return categories;
J
Jason Park 已提交
91
};
92
const cachedCategories = cacheCategories(); // TODO: cache again when webhooked
93

94 95
const getCategories = (req, res, next) => {
  res.json({ categories: cachedCategories });
96 97
};

98 99
const getAlgorithm = (req, res, next) => {
  const { categoryKey, algorithmKey } = req.params;
100

101
  const category = cachedCategories.find(category => category.key === categoryKey);
102 103 104 105
  if (!category) return next(new NotFoundError());
  const algorithm = category.algorithms.find(algorithm => algorithm.key === algorithmKey);
  if (!algorithm) return next(new NotFoundError());

J
Jason Park 已提交
106
  const titles = [category.name, algorithm.name];
107
  const files = algorithm.files.map(({ name, content, contributors }) => ({ name, content, contributors }));
J
Jason Park 已提交
108
  res.json({ algorithm: { titles, files } });
109 110 111
};

router.route('/')
112
  .get(getCategories);
113

114 115
router.route('/:categoryKey/:algorithmKey')
  .get(getAlgorithm);
116 117

export default router;