AWSTemplateFormatVersion: "2010-09-09"
Description: >
  Creates a Lambda function that polls the Workable API and finds new
  applicants, which get sent to Slack
Parameters:
  WorkableAccessToken:
    Type: String
  SlackMessageRelayTopicArn:
    Type: String
  PollingPeriod:
    Type: Number
    Default: 5
    Description: In minutes
Resources:
  PollerFunctionIamRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      Path: "/"
      Policies:
        - PolicyName: SnsPolicy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "sns:Publish"
                Resource:
                  - !Ref SlackMessageRelayTopicArn
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
  PollerFunction:
    Type: "AWS::Lambda::Function"
    Properties:
      Code:
        ZipFile: |
          const url = require('url');
          const https = require('https');
          const AWS = require('aws-sdk');

          const sns = new AWS.SNS({ apiVersion: '2010-03-31' });

          const API_URL_BASE = 'https://www.workable.com/spi/v3/accounts/prx';

          const SLACK_CHANNEL = '#applicants';
          const SLACK_ICON = ':workable:';
          const SLACK_USERNAME = 'Workable';

          const PERIOD = (60 * process.env.POLLING_PERIOD);

          function workableApiData(apiUrl) {
              return new Promise((resolve, reject) => {
                  const options = url.parse(apiUrl);
                  options.headers = {
                      'Authorization': `Bearer ${process.env.WORKABLE_ACCESS_TOKEN}`,
                  };

                  https.get(options, (res) => {
                      let body = '';

                      res.setEncoding('utf8');
                      res.on('error', e => { reject(e) });
                      res.on('data', chunk => body += chunk);

                      res.on('end', () => {
                          try {
                              const data = JSON.parse(body);
                              resolve(data);
                          } catch (e) {
                              reject(e);
                          }
                      });
                  }).on('error', e => { reject(e) });
              });
          }

          async function publishedJobs() {
              const apiUrl = `${API_URL_BASE}/jobs?state=published`;

              const payload = await workableApiData(apiUrl);
              return payload.jobs;
          }

          async function candidatesForJob(job) {
              const after = Math.round((+ new Date()) / 1000) - PERIOD;
              const apiUrl = `${API_URL_BASE}/jobs/${job.shortcode}/candidates?created_after=${after}`;

              const payload = await workableApiData(apiUrl);
              return payload.candidates;
          }

          function messageForCandidates(candidates) {
              return {
                  channel: SLACK_CHANNEL,
                  username: SLACK_USERNAME,
                  icon_emoji: SLACK_ICON,
                  attachments: candidates.map(c => {
                      return {
                          title: `${c.job.title} – ${c.firstname} ${c.lastname}`,
                          title_link: c.profile_url
                      };
                  }),
              };
          }

          function publishMessage(message) {
              return new Promise((resolve, reject) => {
                  const json = JSON.stringify(message);

                  sns.publish({
                      TopicArn: process.env.SLACK_MESSAGE_RELAY_TOPIC_ARN,
                      Message: json,
                  }, (err) => {
                      console.log(1);
                      if (err) {
                          reject(err);
                      } else {
                          resolve();
                      }
                  });
              });
          }

          exports.handler = async (event) => {
              const jobs = await publishedJobs();

              const candidatesByJob = await Promise.all(jobs.map(j => candidatesForJob(j)));
              const candidates = [].concat(...candidatesByJob);

              await publishMessage(messageForCandidates(candidates));
          };
      Description: >
        Gets recent job applicants from Workable and sends them to Slack via the
        Slack relay
      Environment:
        Variables:
          WORKABLE_ACCESS_TOKEN: !Ref WorkableAccessToken
          SLACK_MESSAGE_RELAY_TOPIC_ARN: !Ref SlackMessageRelayTopicArn
          POLLING_PERIOD: !Ref PollingPeriod
      Handler: index.handler
      MemorySize: 128
      Role: !GetAtt PollerFunctionIamRole.Arn
      Runtime: nodejs6.10
      Tags:
        - Key: Project
          Value: Misc
        - Key: "prx:cloudformation:stack-name"
          Value: !Ref AWS::StackName
        - Key: "prx:cloudformation:stack-id"
          Value: !Ref AWS::StackId
      Timeout: 10
  PollerEventRuleIamRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "events.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      Path: "/"
      Policies:
        - PolicyName: LambdaPolicy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "lambda:InvokeFunction"
                Resource:
                  - !GetAtt PollerFunction.Arn
  PollerEventRule:
    Type: "AWS::Events::Rule"
    Properties:
      Description: >
        Invokes the Workable poller function
      RoleArn: !GetAtt PollerEventRuleIamRole.Arn
      ScheduleExpression: !Sub "rate(${PollingPeriod} minutes)"
      State: ENABLED
      Targets:
        - Arn: !GetAtt PollerFunction.Arn
          Id: !Sub "${AWS::StackName}.PollerFunction"
  PollerFunctionEventInvokePermission:
    Type: "AWS::Lambda::Permission"
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref PollerFunction
      Principal: events.amazonaws.com
      SourceArn: !GetAtt PollerEventRule.Arn
