npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

code-challenge-remote

v0.0.8

Published

Code challenges with a server component for running tests in the cloud

Downloads

10

Readme

code-challenge-remote

This module acts as both the client and Express server middleware to handle code challenges.

This documentation is broken up into three sections:

  1. Client Installation and Usage
  2. Server Setup
  3. Challenge Setup

Client Installation and Usage

Installation

You must have node 8 or newer installed, then run this command:

npm install -g code-challenge-remote

Usage

Log In

This command requires you know the URL for the code challenge server which should be provided by the server offering the code challenge:

challenge login <remote_url> <session_id>

Help

Get general help, or if the command is specified then get help about a specific command.

challenge help [command]

Log Out

Kill the client's session.

challenge logout

Initialize a Challenge

Download a challenge from the challenge server. If you omit the output directory then the current directory will be used.

You can use the status command to find out which challenges exist.

challenge init <name> [output_dir]

Get Status

This will get you a list of all challenges and for any that you have submitted you will also see a submission date and percentage of what was passed.

challenge status

Submit a Challenge

Once you've completed a challenge you can submit it to the server for additional testing. If you omit the challenge directory then the current directory will be used.

challenge submit <challenge> [challenge_dir]

Server Setup

Example

'use strict'
const express = require('express')
const session = require('express-session')
const Challenge = require('code-challenge-remote')
const path = require('path')

const app = express()
const cookieName = 'my-cookie'

// configure the code challenge 
// (see the server challenge configuration section below this example for details)
const challenge = new Challenge({
  challengePath: path.resolve(__dirname, 'challenges'),
  challengeUrl: 'http://localhost:' + port + '/challenge',
  getUserId: async req => {
    if (!req.session || !req.session.user) return null
    return {
      id: req.session.user,
      username: req.session.user
    }
  },
  sessionCookieName: cookieName,
  store: Challenge.fileStore(path.resolve(__dirname, 'store'))
})

// run session middleware
app.use(session({
  name: cookieName,
  secret: 'secret',
  resave: false,
  saveUninitialized: false
}))

// run challenge middleware
app.use('/challenge', challenge.middleware())

// A URL endpoint to log in to the server
// You should use something better like passportjs
app.get('/login', (req, res) => {
  const user = req.query.user
  if (user) {
    req.session.user = user
    req.session.save()
    res.status(200)
    res.send(challenge.getLoginCommand(req))
  } else {
    res.sendStatus(403)
  }
})

// An endpoint to kill the session
app.get('/logout', (req, res) => {
  req.session.destroy(function (err) {
    if (err) {
      console.error(err.stack)
      res.sendStatus(500)
    } else {
      res.sendStatus(200)
    }
  })
})

app.listen(port, function (err) {
  if (err) return console.error(err.stack);
  console.log('Listening on port: ' + port)
})

Server Challenge Configuration

These are the options to configure the challenge middleware:

  • challengeUrl - The URL that points to the challenge middleware path.

  • getUserId - A function that gets the request object and needs to return an object with the user's unique identifier and their username. This function can return a promise that resolves to the object.

  • sessionCookieName - The cookie name being used to store the session information.

  • store - The data store controller for submitted challenges.

Data Store Controller

The data store controller is used to save and load user's challenge submissions. This package includes a built in file store module, but that is not recommended for production. You'll probably want to write your own data store controller that saves and loads data from a database.

A data store controller needs to export an object with two properties:

  • save - A function to save submission results. It will receive the following parameters:

    • userId - The user's unique ID

    • challengeName - The name of the challenge being saved

    • date - A date object for the date this challenge was submitted

    • score - A numeric float between 0 and 1 that represents the ratio of tests passed to total tests. For example, if 3 out of 4 tests passed then this value would be 0.75.

    This function should return a promise that resolves if the save was successful or rejects if the save failed.

  • load - A function to load submission results. It will receive these parameters:

    • userId - The user's unique id

    • add - A function for adding entries to be returned at the end of the load sequence. This function should be called by your loaded to add the submission entries. It takes the following parameters:

      • challenge - The name of the challenge

      • date - A Date object for the date that the challenge was submitted

      • score - The numeric float, a value between 0 and 1

    This function should return a promise that resolves on successful load or rejects on failure to load. If a user has not submitted any challenges that should still return a promise that resolves.

Challenge Setup

  • All challenges will be run in Docker containers to enable code isolation and to prevent the user from running malicious code on your machine.

  • Every challenge exists within it's own directory. The name of the directory is the challenge name that will be used to download and submit challenges.

  • All challenge directories are kept within a single directory as defined in the server challenge configuration.

Each directory has the following components:

  1. config.json - An optional configuration file that can set the max upload size and max submission run time. Defaults to { "maxUploadSize": "2M", "maxRunTime": 30000 }.

  2. docker-compose.yml - A Docker compose file if using compose. Either this or a Dockerfile (or both) must exist.

    Configure this docker-compose file to your liking. The environment variable UPLOADED_CHALLENGE_DIR can be used to create a volume mount. (See the example directory's first-challenge for an example.)

  3. Dockerfile - A Dockerfile. This must exist if a docker-compose.yml does not exist.

  4. hooks.js - A NodeJS module that can export these functions as properties to be used as hooks. This file is run in its own memory space for each submitted result. That means if you declare module scoped variables they will only exist for that challenge submission.

    • beforeOverwrite - Run before the overwrite directory merges into the uploaded content. This function receives two parameters 1) uploadedFilesDir and 2) overwriteFilesDir and should return a promise.

    • afterOverwrite - Run after the overwrite directory merges into the uploaded content. This function receives two parameters 1) uploadedFilesDir and 2) overwriteFilesDir and should return a promise.

    • parseTestResults - This function is required if you want to be able to record scores for submitted challenges. It receives a string containing the output generated by running the Docker container. With this function, parse the output and return a Promise that resolves to an object with these properties:

      • body - The body to send to the client for output. Generally you'd want this to be the test output.

      • passed - The number of tests passed.

      • failed - The number of tests failed

  5. ignore.txt - Defines the patterns for file names or directories that should not be transferred between the server and the client. Put one pattern per line within the file. Currently only plain text matching works. A good use case for this would be to exclude a node_modules directory.

  6. starter - A required directory that contains the contents that will be downloaded to a client computer when a challenge is initialized. All contents of this file will be downloaded unless it matches an entry in the ignore.txt file.

  7. overwrite - An optional directory that will be merged into and overwrite existing files uploaded by the user. This is useful if you want to control some of the files submitted by the user.

Example Directory Structure

- /challenges                         [DIR]
  - first-challenge                   [DIR]
    - overwrite                       [DIR]
      - ...                           [FILES AND FOLDERS]
    - starter                         [DIR]
      - README.md                     [FILE]
      - ... set up files              [FILES AND FOLDERS]
      - ... initial project files     [FILES AND FOLDERS]
    - config.json                     [FILE]
    - hooks.js                        [FILE]
    - ignore.txt                      [FILE]