TooAngel/democratic-collaboration

View on GitHub
src/index.js

Summary

Maintainability
A
3 hrs
Test Coverage
import express from 'express';
import got from 'got';
import session from 'express-session';
import sessionSequelize from 'express-session-sequelize';
import Umzug from 'umzug';

import {getPullRequestData} from './helpers/pullRequest.js';
import {sequelize, Sequelize, models} from '../models/index.js';
import cron from 'node-cron';
import {createIssueComment, getPullRequests, mergePullRequest} from './helpers/github.js';

const SessionStore = sessionSequelize(session.Store);

const umzug = new Umzug({
  migrations: {
    path: './migrations',
    pattern: /(.*)\.cjs/,
    params: [
      sequelize.getQueryInterface(),
      Sequelize,
    ],
  },
  storage: 'sequelize',
  storageOptions: {
    sequelize: sequelize,
  },
});

(async () => {
  await umzug.up();
})();

const sequelizeSessionStore = new SessionStore({
  db: sequelize,
});

const app = express();

const sess = {
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  store: sequelizeSessionStore,
  cookie: {},
  name: 'session',
};

if (app.get('env') === 'production') {
  app.set('trust proxy', 1); // trust first proxy
  sess.cookie.secure = true; // serve secure cookies
}

app.use(express.json());
app.use(session(sess));

app.use(express.static('./static'));

app.get('/', function(req, res) {
  res.sendFile('./static/index.html', {root: '.'});
});

app.get('/dashboard', function(req, res) {
  res.sendFile('./static/dashboard.html', {root: '.'});
});

// Properly define favicon to not need this route
app.get('/favicon.ico', function(req, res) {
  res.sendFile('./static/images/favicon.png', {root: '.'});
});

app.get('/imprint', function(req, res) {
  res.sendFile('./static/imprint.html', {root: '.'});
});

app.get('/privacyPolicy', function(req, res) {
  res.sendFile('./static/privacyPolicy.html', {root: '.'});
});

app.get('/js/main.js', function(req, res) {
  res.sendFile('./dist/main.js', {root: '.'});
});

app.get('/login', function(req, res) {
  if (req.session.userId) {
    res.redirect('/dashboard');
  } else {
    // TODO use `code`, too (https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps)
    res.redirect(`https://github.com/login/oauth/authorize?client_id=${process.env.GITHUB_CLIENT_ID}&scope=public_repo,read:org,admin:repo_hook`);
  }
});

app.get('/github-callback', async function(req, res) {
  const url = 'https://github.com/login/oauth/access_token';
  const options = {
    json: {
      client_id: process.env.GITHUB_CLIENT_ID,
      client_secret: process.env.GITHUB_CLIENT_SECRET,
      code: req.query.code,
    },
    responseType: 'json',
  };
  const response = await got.post(url, options);
  if (!response.body.access_token) {
    return res.redirect('/');
  }

  let user = await models.User.findOne({where: {githubAccessToken: response.body.access_token}});
  if (!user) {
    user = models.User.build({
      githubAccessToken: response.body.access_token,
    });
    await user.save();
  }
  req.session.userId = user.id;
  res.redirect('/dashboard');
});

app.get('/v1/user', async function(req, res) {
  if (!req.session.userId) {
    return res.status(401).end();
  }
  const user = await models.User.findOne({where: {id: req.session.userId}});
  if (!user) {
    return res.status(401).end();
  }
  const url = 'https://api.github.com/user';
  const options = {
    headers: {
      Accept: 'application/vnd.github.v3+json',
      Authorization: `token ${user.githubAccessToken}`,
    },
    responseType: 'json',
  };
  try {
    const response = await got.get(url, options);
    const data = {
      name: response.body.name,
    };
    res.send(data);
  } catch (e) {
    console.log(e);
    console.log(e.response.body);
    console.log(options);
    res.status(503).end();
  }
});

app.get('/v1/repositories', async function(req, res) {
  if (!req.session.userId) {
    return res.status(401).end();
  }
  const user = await models.User.findOne({where: {id: req.session.userId}});
  if (!user) {
    return res.status(401).end();
  }
  const url = 'https://api.github.com/user/repos?type=public&per_page=100';
  const options = {
    headers: {
      Accept: 'application/vnd.github.v3+json',
      Authorization: `token ${user.githubAccessToken}`,
    },
    responseType: 'json',
  };
  try {
    // TODO handle pagination
    const response = await got.get(url, options);
    const repositories = [];
    for (const repository of response.body) {
      const [owner, repo] = repository.full_name.split('/');
      let configured = false;
      const dbRepository = await models.Repository.findOne({where: {owner: owner, repo: repo}});
      if (dbRepository) {
        configured = dbRepository.configured;
      }
      repositories.push({
        fullName: repository.full_name,
        configured: configured,
        description: repository.description,
        htmlUrl: repository.html_url,
      });
    }
    res.send(repositories);
  } catch (e) {
    console.log(e);
    console.log(e.response.body);
    console.log(options);
    res.status(503).end();
  }
});

app.get('/v1/repositories/:owner/:repo/pulls', async function(req, res) {
  if (!req.session.userId) {
    return res.status(401).end();
  }
  const user = await models.User.findOne({where: {id: req.session.userId}});
  if (!user) {
    return res.status(401).end();
  }
  res.send(await getPullRequests(user, req.params.owner, req.params.repo));
});

app.put('/v1/repositories/:owner/:repo', async function(req, res) {
  if (!req.session.userId) {
    return res.status(401).end();
  }
  const user = await models.User.findOne({where: {id: req.session.userId}});
  if (!user) {
    return res.status(401).end();
  }
  console.log(req.body);
  const repository = await models.Repository.findOne({where: {owner: req.params.owner, repo: req.params.repo}});
  if (repository) {
    repository.configured = req.body.checked;
    repository.save();
    return;
  }

  await models.Repository.create({
    owner: req.params.owner,
    repo: req.params.repo,
    configured: req.body.checked,
    userId: user.id,
  });


  // TODO create or delete webhook
  // checked = request.json['checked']
  // full_name = '{}/{}'.format(org, repo)
  // github_client = github.Github(g.user.github_access_token)
  // repository = github_client.get_repo('{}/{}'.format(org, repo))
  // config = {
  //     'url': '{}/github/'.format(DOMAIN),
  //     'insecure_ssl': '0',
  //     'content_type': 'json'
  // }
  // events = [u'commit_comment', u'pull_request', u'pull_request_review', u'push']
  // db_repository = Repository.query.filter_by(full_name=full_name).first()
  // if checked:
  //     try:
  //         repository.create_hook('web', config, events=events, active=True)
  //     except github.GithubException as e:
  //         logging.error(e)

  //     if not db_repository:
  //         db_repository = Repository(full_name=full_name, github_access_token=g.user.github_access_token)
  //         db.session.add(db_repository)
  //         db.session.commit()
  // else:
  //     for hook in repository.get_hooks():
  //         if 'url' not in hook.config:
  //             continue

  //         if hook.config['url'] == '{}/github/'.format(DOMAIN):
  //             hook.delete()

  //     if db_repository:
  //         db.session.delete(db_repository)
  //         db.session.commit()
  // return {}

  res.end();
});


app.get('/v1/repositories/:owner/:repo/pulls/:number', async function(req, res) {
  if (!req.session.userId) {
    return res.status(401).end();
  }

  const user = await models.User.findOne({where: {id: req.session.userId}});
  if (!user) {
    return res.status(401).end();
  }
  const pullRequestData = await getPullRequestData(user, req.params.owner, req.params.repo, req.params.number);
  res.send(pullRequestData);
});

// @static.route('/test/dashboard')
// def testdashboard():
//     return static.send_static_file('dashboard.html')


// @static.route('/<org_name>/<project_name>/pull/<int:pull_request_number>', strict_slashes=False)
// def show_pull_request(org_name, project_name, pull_request_number):
//     return static.send_static_file('pull_request.html')


// @static.route('/test/<org_name>/<project_name>/pull/<int:pull_request_number>', strict_slashes=False)
// def testshow_pull_request(org_name, project_name, pull_request_number):
//     return static.send_static_file('dashboard.html')

const server = app.listen(process.env.PORT || 3000, function() {
  const {address, port} = server.address();
  console.log('App listening at http://%s:%s', address, port);
});

process.on('uncaughtException', (error, source) => {
  console.log(error, source);
});

cron.schedule('51 * * * *', async () => {
  console.log('-----------------------------------------------------------------');
  const repositories = await models.Repository.findAll({
    where: {configured: true},
    include: [
      {
        model: models.User,
        required: false,
        attributes: ['githubAccessToken'],
      },
    ],
  });
  for (const repository of repositories) {
    const pullRequests = await getPullRequests(repository.User.dataValues, repository.owner, repository.repo);
    for (const pullRequest of pullRequests) {
      const pullRequestData = await getPullRequestData(repository.User.dataValues, repository.owner, repository.repo, pullRequest.number);
      if (pullRequestData.times.daysToMerge < 0) {
        console.log(`Would merge ${repository.owner}/${repository.repo} - ${pullRequestData.title}`);
        const mergeResponse = await mergePullRequest(repository.User.dataValues, repository.owner, repository.repo, pullRequest.number);
        if (mergeResponse) {
          const comment = 'This pull request was merged by [worlddriven](https://www.worlddriven.org).';
          const issueResponse = await createIssueComment(repository.User.dataValues, repository.owner, repository.repo, pullRequest.number, comment);
          console.log(issueResponse);
          console.log('Merged');
        } else {
          console.log('Can not merge');
        }
      }
    }
  }
});