Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc(README): expand with curl session usage #5

Merged
merged 19 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .eslintrc.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
env:
node: true
es6: true
es2021: true
es2024: true
extends: eslint:recommended
parser: '@babel/eslint-parser'
parserOptions:
babelOptions:
configFile: false
plugins: [ "@babel/plugin-syntax-import-attributes" ]
requireConfigFile: false
ecmaVersion: latest
sourceType: module
rules: {}
19 changes: 12 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jobs:
- name: Start MySQL
run: sudo /etc/init.d/mysql start
- uses: actions/setup-node@v4
with:
node-version: 20
- uses: actions/checkout@v4
- run: npm install
- name: Initialize MySQL
Expand All @@ -43,12 +45,13 @@ jobs:
active: ${{ steps.get.outputs.active }}

test:
needs: [ lint, get-lts ]
needs: [ get-lts ]
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
node-version: ${{ fromJson(needs.get-lts.outputs.active) }}
# node-version: ${{ fromJson(needs.get-lts.outputs.active) }}
node-version: [ 20 ]
fail-fast: false
steps:
- run: sudo /etc/init.d/mysql start
Expand All @@ -61,11 +64,12 @@ jobs:
- run: npm test

test-mac:
needs: [ lint, get-lts ]
needs: [ get-lts ]
runs-on: macos-latest
strategy:
matrix:
node-version: ${{ fromJson(needs.get-lts.outputs.active) }}
# node-version: ${{ fromJson(needs.get-lts.outputs.active) }}
node-version: [ 20 ]
fail-fast: false
steps:
- name: Install & Start MySQL
Expand All @@ -84,11 +88,12 @@ jobs:

test-win:
# if: false
needs: [ lint, get-lts ]
needs: [ get-lts ]
runs-on: windows-latest
strategy:
matrix:
node-version: ${{ fromJson(needs.get-lts.outputs.active) }}
node-version: [ 20 ]
# node-version: ${{ fromJson(needs.get-lts.outputs.active) }}
experimental: [true]
fail-fast: false
steps:
Expand All @@ -102,4 +107,4 @@ jobs:
node-version: ${{ matrix.node-version }}
- run: sh sql/init-mysql.sh
- run: npm install
- run: npm test
- run: sh test.sh
112 changes: 110 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ npm install

Edit the files in conf.d to reflect your local settings.

Each config file has a default section which lists all available config settings. Below the `default` section are optional deployment environments such as `production`, `development`, and `test`. When a config file is loaded, the environment variable `NODE_ENV` is checked and if defined, any overrides in the matching deployment section are applied.
Each config file has a default section which lists all available config settings. Below the `default` section are optional deployment environments such as `production`, `development`, and `test`. When a deployment environment is detected, overrides in the matching deployment section are applied.

## Start the service

Expand All @@ -33,5 +33,113 @@ or

`npm run develop (development)`

will start up the HTTP service on the port specified in conf.d/http.yml.
will start up the HTTP service on the port specified in `conf.d/http.yml`. The default URL for the service is [http://localhost:3000](http://localhost:3000) and the API methods have documentation at [http://localhost:3000/documentation](http://localhost:3000/documentation).


## Using the API service

Until the NicTool 3.0 HTTP client is written, using a web browser (in Developer mode) or a CLI HTTP utility like curl can be used. Here's a quick tutorial:

### Start a New Session

`curl -X POST http://localhost:3000/session`

```json
{"statusCode":400,"error":"Bad Request","message":"Invalid request payload input"}
```

The request was rejected because it's missing the required parameters, as shown in the documentation. Create a file called nt-auth.json and store the credentials of a NicTool user therein. Then try the auth request again:

`curl -X POST http://localhost:3000/session --header "Content-Type: application/json" -d @nt-auth.json`

```json
{"user":{"id":4096,"first_name":"Unit","last_name":"Test","username":"unit-test","email":"unit-test@example.com"},"group":{"id":4096,"name":"example.com"},"session":{"id":162},"meta":{"api":{"version":"3.0.0"},"msg":"you are logged in"}
```

That's not the easiest to read so lets pipe it through `json_pp`:

`curl -X POST http://localhost:3000/session --header "Content-Type: application/json" -d @nt-auth.json | json_pp`

```json
{
"group" : {
"id" : 4096,
"name" : "example.com"
},
"meta" : {
"api" : {
"version" : "3.0.0"
},
"msg" : "you are logged in"
},
"session" : {
"id" : 162
},
"user" : {
"email" : "unit-test@example.com",
"first_name" : "Unit",
"id" : 4096,
"last_name" : "Test",
"username" : "unit-test"
}
}
```

Now we're talking. But we're missing something. The point of sending `POST /session` is to establish a session we can use with subsequent requests. Let's also take a look at the HTTP response headers with the `-i` option to curl.

```
~ curl -i -X POST http://localhost:3000/session --header "Content-Type: application/json" -d @nt-auth.json
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
cache-control: no-cache
set-cookie: sid-nictool=Fe26.2**19f7d4f243faa77b048119b4a2bcbdcaa7826cdd853d8bdd3110f330ac6932c8*pzn_-OSy1SfoNpWbNvY3xw*RZQ8EgV2IGphwBz-Fb0AvBGofBwct-GnExEdxW-P-mtc1CWLuBJF0IyI7da_tMtp**07d92c1e89978b270fbdd449adcecbab3078b746c4167fe586f417be866c54d8*nDSOqzX79qmsztrHHjub7FgC7XiAxqGNdB-txLq8L84; Max-Age=3600; Expires=Sun, 25 Feb 2024 21:51:20 GMT; HttpOnly; SameSite=Strict; Path=/
content-length: 237
Date: Sun, 25 Feb 2024 20:51:20 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"user":{"id":4096,"first_name":"Unit","last_name":"Test","username":"unit-test","email":"unit-test@example.com"},"group":{"id":4096,"name":"example.com"},"session":{"id":162},"meta":{"api":{"version":"3.0.0"},"msg":"you are logged in"}}
```

Notice the `set-cookie` header. We can add that cookie to each CLI request, making the requests very long, or save the cookie to a `cookie-jar` file, and then tell curl to sent that cookie with future requests:

```
curl --cookie-jar nt-session -X POST http://localhost:3000/session --header "Content-Type: application/json" -d @nt-auth.json
{"user":{"id":4096,"first_name":"Unit","last_name":"Test","username":"unit-test","email":"unit-test@example.com"},"group":{"id":4096,"name":"example.com"},"session":{"id":162},"meta":{"api":{"version":"3.0.0"},"msg":"you are logged in"}}
```

and if we peek inside the cookie jar:

```sh
➜ ~ cat nt-session
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

#HttpOnly_localhost FALSE / FALSE 1708898204 sid-nictool Fe26.2**7a4db1aa0d250c5ba5dda0560ef6cb2c33652f412ee385ebe022313f4fd206f1*g8kgix2HyZUvCKdc60ITMA*Pk3tlc4lYvDAs2J_ZyVHOhYyKWAsGZzbkMdHleLxNPQ55EDmO0vfZWTSILzhceQn**46883c6f21a76dddc10d7c1b0bc3a82302b989057bed459fe61f00eba7d7cacd*bBpV_eKE8VJEz-IDDobcI0nmJT54IndUmoWfE1Eu4fM
```

We can see that our session cookie has been saved. Now we can make other requests to the API using that session cookie:

```
curl -b nt-session -X GET http://localhost:3000/user/4096 --header "Content-Type: application/json" | json_pp
{
"group" : {
"id" : 4096
},
"meta" : {
"api" : {
"version" : "3.0.0"
},
"msg" : "here's your user"
},
"user" : {
"email" : "unit-test@example.com",
"first_name" : "Unit",
"id" : 4096,
"last_name" : "Test",
"username" : "unit-test"
}
}
```

26 changes: 19 additions & 7 deletions lib/config.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
const fs = require('fs/promises')
import fs from 'node:fs/promises'
import fsSync from 'node:fs'

const YAML = require('yaml')
import YAML from 'yaml'

import { setEnv } from './util.js'
setEnv()

class Config {
constructor(opts = {}) {
this.cfg = {}
this.debug = process.env.NODE_DEBUG ? true : false
this.env = process.env.NODE_ENV ?? opts.env
this.getEnv(opts)
}

async getEnv(opts = {}) {
this.env = process.env.NODE_ENV ?? opts.env ?? ''
this.debug = Boolean(process.env.NODE_DEBUG)
if (this.debug) console.log(`debug: true, env: ${this.env}`)
}

async get(name, env) {
this.getEnv()

const cacheKey = [name, env ?? this.env].join(':')
if (this.cfg?.[cacheKey]) return this.cfg[cacheKey] // cached

Expand All @@ -23,10 +33,12 @@ class Config {
}

getSync(name, env) {
this.getEnv()

const cacheKey = [name, env ?? this.env].join(':')
if (this.cfg?.[cacheKey]) return this.cfg[cacheKey] // cached

const str = require('fs').readFileSync(`./conf.d/${name}.yml`, 'utf8')
const str = fsSync.readFileSync(`./conf.d/${name}.yml`, 'utf8')
const cfg = YAML.parse(str)

this.cfg[cacheKey] = applyDefaults(cfg[env ?? this.env], cfg.default)
Expand All @@ -36,7 +48,7 @@ class Config {

function applyDefaults(cfg = {}, defaults = {}) {
for (const d in defaults) {
if (cfg[d] === undefined) {
if ([undefined, null].includes(cfg[d])) {
cfg[d] = defaults[d]
} else if (typeof cfg[d] === 'object' && typeof defaults[d] === 'object') {
cfg[d] = applyDefaults(cfg[d], defaults[d])
Expand All @@ -45,4 +57,4 @@ function applyDefaults(cfg = {}, defaults = {}) {
return cfg
}

module.exports = new Config()
export default new Config()
51 changes: 32 additions & 19 deletions lib/config.test.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,59 @@
const assert = require('node:assert/strict')
const { describe, it } = require('node:test')
import assert from 'node:assert/strict'
import { describe, it } from 'node:test'

const config = require('./config')
import Config from './config.js'

describe('config', function () {
describe('get', function () {
it(`loads mysql test config`, async function () {
const cfg = await config.get('mysql', 'test')
describe('config', () => {
describe('get', () => {
it(`loads mysql test config`, async () => {
const cfg = await Config.get('mysql', 'test')
assert.deepEqual(cfg, mysqlTestCfg)
})

it(`loads mysql test config syncronously`, function () {
const cfg = config.getSync('mysql', 'test')
it(`loads mysql test config syncronously`, () => {
const cfg = Config.getSync('mysql', 'test')
assert.deepEqual(cfg, mysqlTestCfg)
})

it(`loads mysql cov config`, async function () {
const cfg = await config.get('mysql', 'cov')
it(`loads mysql cov config`, async () => {
const cfg = await Config.get('mysql', 'cov')
assert.deepEqual(cfg, mysqlTestCfg)
})

it(`loads mysql cov config (from cache)`, async function () {
it(`loads mysql cov config (from cache)`, async () => {
process.env.NODE_DEBUG = 1
const cfg = await config.get('mysql', 'cov')
const cfg = await Config.get('mysql', 'cov')
assert.deepEqual(cfg, mysqlTestCfg)
process.env.NODE_DEBUG = ''
})

it(`loads session test config`, async function () {
const cfg = await config.get('session', 'test')
it(`loads session test config`, async () => {
const cfg = await Config.get('session', 'test')
assert.deepEqual(cfg, sessCfg)
})

it(`loads session test config syncronously`, function () {
const cfg = config.getSync('session', 'test')
it(`loads session test config syncronously`, () => {
const cfg = Config.getSync('session', 'test')
assert.deepEqual(cfg, sessCfg)
})

it(`loads http test config syncronously`, function () {
const cfg = config.getSync('http', 'test')
it(`loads http test config syncronously`, () => {
const cfg = Config.getSync('http', 'test')
assert.deepEqual(cfg, httpCfg)
})

it(`detects NODE_DEBUG env`, async () => {
process.env.NODE_DEBUG = 1
let cfg = await Config.get('mysql', 'test')
assert.equal(Config.debug, true)

process.env.NODE_DEBUG = ''
cfg = await Config.get('mysql', 'test')
assert.equal(Config.debug, false)

cfg = await Config.get('mysql', 'test')
assert.equal(cfg.user, 'root')
})
})
})

Expand Down
Loading
Loading