Skip to content

Commit 3a2dfab

Browse files
authored
convert from commonjs to es6/ecma modules
- mysql: make a singleton, so process exits after tests - mysql: add .delete and .whereConditions - mysql.update added - deps: updated vers - doc(README): expand with curl session usage (#5) - group.put added with tests - test(group): use the test case - user.put added - permission: added with tests - add config.getEnv - ci: tests do not depend on lint - ci: require node v20 - ci: fix test running on windows - ci: only test on node v20 - eslint: switch to babel-eslint parser, b/c eslint doesn't yet support import..from..with syntax
1 parent 8bda2c2 commit 3a2dfab

39 files changed

Lines changed: 1149 additions & 582 deletions

.eslintrc.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
env:
22
node: true
3+
es6: true
34
es2021: true
5+
es2024: true
46
extends: eslint:recommended
7+
parser: '@babel/eslint-parser'
58
parserOptions:
9+
babelOptions:
10+
configFile: false
11+
plugins: [ "@babel/plugin-syntax-import-attributes" ]
12+
requireConfigFile: false
613
ecmaVersion: latest
714
sourceType: module
815
rules: {}

.github/workflows/ci.yml

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ jobs:
1818
- name: Start MySQL
1919
run: sudo /etc/init.d/mysql start
2020
- uses: actions/setup-node@v4
21+
with:
22+
node-version: 20
2123
- uses: actions/checkout@v4
2224
- run: npm install
2325
- name: Initialize MySQL
@@ -43,12 +45,13 @@ jobs:
4345
active: ${{ steps.get.outputs.active }}
4446

4547
test:
46-
needs: [ lint, get-lts ]
48+
needs: [ get-lts ]
4749
runs-on: ${{ matrix.os }}
4850
strategy:
4951
matrix:
5052
os: [ubuntu-latest]
51-
node-version: ${{ fromJson(needs.get-lts.outputs.active) }}
53+
# node-version: ${{ fromJson(needs.get-lts.outputs.active) }}
54+
node-version: [ 20 ]
5255
fail-fast: false
5356
steps:
5457
- run: sudo /etc/init.d/mysql start
@@ -61,11 +64,12 @@ jobs:
6164
- run: npm test
6265

6366
test-mac:
64-
needs: [ lint, get-lts ]
67+
needs: [ get-lts ]
6568
runs-on: macos-latest
6669
strategy:
6770
matrix:
68-
node-version: ${{ fromJson(needs.get-lts.outputs.active) }}
71+
# node-version: ${{ fromJson(needs.get-lts.outputs.active) }}
72+
node-version: [ 20 ]
6973
fail-fast: false
7074
steps:
7175
- name: Install & Start MySQL
@@ -84,11 +88,12 @@ jobs:
8488

8589
test-win:
8690
# if: false
87-
needs: [ lint, get-lts ]
91+
needs: [ get-lts ]
8892
runs-on: windows-latest
8993
strategy:
9094
matrix:
91-
node-version: ${{ fromJson(needs.get-lts.outputs.active) }}
95+
node-version: [ 20 ]
96+
# node-version: ${{ fromJson(needs.get-lts.outputs.active) }}
9297
experimental: [true]
9398
fail-fast: false
9499
steps:
@@ -102,4 +107,4 @@ jobs:
102107
node-version: ${{ matrix.node-version }}
103108
- run: sh sql/init-mysql.sh
104109
- run: npm install
105-
- run: npm test
110+
- run: sh test.sh

README.md

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ npm install
2121

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

24-
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.
24+
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.
2525

2626
## Start the service
2727

@@ -33,5 +33,113 @@ or
3333

3434
`npm run develop (development)`
3535

36-
will start up the HTTP service on the port specified in conf.d/http.yml.
36+
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).
37+
38+
39+
## Using the API service
40+
41+
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:
42+
43+
### Start a New Session
44+
45+
`curl -X POST http://localhost:3000/session`
46+
47+
```json
48+
{"statusCode":400,"error":"Bad Request","message":"Invalid request payload input"}
49+
```
50+
51+
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:
52+
53+
`curl -X POST http://localhost:3000/session --header "Content-Type: application/json" -d @nt-auth.json`
54+
55+
```json
56+
{"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"}
57+
```
58+
59+
That's not the easiest to read so lets pipe it through `json_pp`:
60+
61+
`curl -X POST http://localhost:3000/session --header "Content-Type: application/json" -d @nt-auth.json | json_pp`
62+
63+
```json
64+
{
65+
"group" : {
66+
"id" : 4096,
67+
"name" : "example.com"
68+
},
69+
"meta" : {
70+
"api" : {
71+
"version" : "3.0.0"
72+
},
73+
"msg" : "you are logged in"
74+
},
75+
"session" : {
76+
"id" : 162
77+
},
78+
"user" : {
79+
"email" : "unit-test@example.com",
80+
"first_name" : "Unit",
81+
"id" : 4096,
82+
"last_name" : "Test",
83+
"username" : "unit-test"
84+
}
85+
}
86+
```
87+
88+
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.
89+
90+
```
91+
~ curl -i -X POST http://localhost:3000/session --header "Content-Type: application/json" -d @nt-auth.json
92+
HTTP/1.1 200 OK
93+
content-type: application/json; charset=utf-8
94+
cache-control: no-cache
95+
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=/
96+
content-length: 237
97+
Date: Sun, 25 Feb 2024 20:51:20 GMT
98+
Connection: keep-alive
99+
Keep-Alive: timeout=5
100+
101+
{"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"}}
102+
```
103+
104+
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:
105+
106+
```
107+
curl --cookie-jar nt-session -X POST http://localhost:3000/session --header "Content-Type: application/json" -d @nt-auth.json
108+
{"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"}}
109+
```
110+
111+
and if we peek inside the cookie jar:
112+
113+
```sh
114+
➜ ~ cat nt-session
115+
# Netscape HTTP Cookie File
116+
# https://curl.se/docs/http-cookies.html
117+
# This file was generated by libcurl! Edit at your own risk.
118+
119+
#HttpOnly_localhost FALSE / FALSE 1708898204 sid-nictool Fe26.2**7a4db1aa0d250c5ba5dda0560ef6cb2c33652f412ee385ebe022313f4fd206f1*g8kgix2HyZUvCKdc60ITMA*Pk3tlc4lYvDAs2J_ZyVHOhYyKWAsGZzbkMdHleLxNPQ55EDmO0vfZWTSILzhceQn**46883c6f21a76dddc10d7c1b0bc3a82302b989057bed459fe61f00eba7d7cacd*bBpV_eKE8VJEz-IDDobcI0nmJT54IndUmoWfE1Eu4fM
120+
```
121+
122+
We can see that our session cookie has been saved. Now we can make other requests to the API using that session cookie:
123+
124+
```
125+
curl -b nt-session -X GET http://localhost:3000/user/4096 --header "Content-Type: application/json" | json_pp
126+
{
127+
"group" : {
128+
"id" : 4096
129+
},
130+
"meta" : {
131+
"api" : {
132+
"version" : "3.0.0"
133+
},
134+
"msg" : "here's your user"
135+
},
136+
"user" : {
137+
"email" : "unit-test@example.com",
138+
"first_name" : "Unit",
139+
"id" : 4096,
140+
"last_name" : "Test",
141+
"username" : "unit-test"
142+
}
143+
}
144+
```
37145

lib/config.js

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
1-
const fs = require('fs/promises')
1+
import fs from 'node:fs/promises'
2+
import fsSync from 'node:fs'
23

3-
const YAML = require('yaml')
4+
import YAML from 'yaml'
5+
6+
import { setEnv } from './util.js'
7+
setEnv()
48

59
class Config {
610
constructor(opts = {}) {
711
this.cfg = {}
8-
this.debug = process.env.NODE_DEBUG ? true : false
9-
this.env = process.env.NODE_ENV ?? opts.env
12+
this.getEnv(opts)
13+
}
14+
15+
async getEnv(opts = {}) {
16+
this.env = process.env.NODE_ENV ?? opts.env ?? ''
17+
this.debug = Boolean(process.env.NODE_DEBUG)
1018
if (this.debug) console.log(`debug: true, env: ${this.env}`)
1119
}
1220

1321
async get(name, env) {
22+
this.getEnv()
23+
1424
const cacheKey = [name, env ?? this.env].join(':')
1525
if (this.cfg?.[cacheKey]) return this.cfg[cacheKey] // cached
1626

@@ -23,10 +33,12 @@ class Config {
2333
}
2434

2535
getSync(name, env) {
36+
this.getEnv()
37+
2638
const cacheKey = [name, env ?? this.env].join(':')
2739
if (this.cfg?.[cacheKey]) return this.cfg[cacheKey] // cached
2840

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

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

3749
function applyDefaults(cfg = {}, defaults = {}) {
3850
for (const d in defaults) {
39-
if (cfg[d] === undefined) {
51+
if ([undefined, null].includes(cfg[d])) {
4052
cfg[d] = defaults[d]
4153
} else if (typeof cfg[d] === 'object' && typeof defaults[d] === 'object') {
4254
cfg[d] = applyDefaults(cfg[d], defaults[d])
@@ -45,4 +57,4 @@ function applyDefaults(cfg = {}, defaults = {}) {
4557
return cfg
4658
}
4759

48-
module.exports = new Config()
60+
export default new Config()

lib/config.test.js

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,59 @@
1-
const assert = require('node:assert/strict')
2-
const { describe, it } = require('node:test')
1+
import assert from 'node:assert/strict'
2+
import { describe, it } from 'node:test'
33

4-
const config = require('./config')
4+
import Config from './config.js'
55

6-
describe('config', function () {
7-
describe('get', function () {
8-
it(`loads mysql test config`, async function () {
9-
const cfg = await config.get('mysql', 'test')
6+
describe('config', () => {
7+
describe('get', () => {
8+
it(`loads mysql test config`, async () => {
9+
const cfg = await Config.get('mysql', 'test')
1010
assert.deepEqual(cfg, mysqlTestCfg)
1111
})
1212

13-
it(`loads mysql test config syncronously`, function () {
14-
const cfg = config.getSync('mysql', 'test')
13+
it(`loads mysql test config syncronously`, () => {
14+
const cfg = Config.getSync('mysql', 'test')
1515
assert.deepEqual(cfg, mysqlTestCfg)
1616
})
1717

18-
it(`loads mysql cov config`, async function () {
19-
const cfg = await config.get('mysql', 'cov')
18+
it(`loads mysql cov config`, async () => {
19+
const cfg = await Config.get('mysql', 'cov')
2020
assert.deepEqual(cfg, mysqlTestCfg)
2121
})
2222

23-
it(`loads mysql cov config (from cache)`, async function () {
23+
it(`loads mysql cov config (from cache)`, async () => {
2424
process.env.NODE_DEBUG = 1
25-
const cfg = await config.get('mysql', 'cov')
25+
const cfg = await Config.get('mysql', 'cov')
2626
assert.deepEqual(cfg, mysqlTestCfg)
2727
process.env.NODE_DEBUG = ''
2828
})
2929

30-
it(`loads session test config`, async function () {
31-
const cfg = await config.get('session', 'test')
30+
it(`loads session test config`, async () => {
31+
const cfg = await Config.get('session', 'test')
3232
assert.deepEqual(cfg, sessCfg)
3333
})
3434

35-
it(`loads session test config syncronously`, function () {
36-
const cfg = config.getSync('session', 'test')
35+
it(`loads session test config syncronously`, () => {
36+
const cfg = Config.getSync('session', 'test')
3737
assert.deepEqual(cfg, sessCfg)
3838
})
3939

40-
it(`loads http test config syncronously`, function () {
41-
const cfg = config.getSync('http', 'test')
40+
it(`loads http test config syncronously`, () => {
41+
const cfg = Config.getSync('http', 'test')
4242
assert.deepEqual(cfg, httpCfg)
4343
})
44+
45+
it(`detects NODE_DEBUG env`, async () => {
46+
process.env.NODE_DEBUG = 1
47+
let cfg = await Config.get('mysql', 'test')
48+
assert.equal(Config.debug, true)
49+
50+
process.env.NODE_DEBUG = ''
51+
cfg = await Config.get('mysql', 'test')
52+
assert.equal(Config.debug, false)
53+
54+
cfg = await Config.get('mysql', 'test')
55+
assert.equal(cfg.user, 'root')
56+
})
4457
})
4558
})
4659

0 commit comments

Comments
 (0)