Skip to content

Commit cd71fb1

Browse files
committedAug 8, 2023
feat: support generation of TypeScript + Express + MySQL applications
Fix #35
1 parent ab5da84 commit cd71fb1

File tree

13 files changed

+600
-2
lines changed

13 files changed

+600
-2
lines changed
 

‎.github/workflows/generate-ts-app.yml

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Generate TypeScript app
2+
3+
on:
4+
push:
5+
6+
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
7+
permissions:
8+
# NOTE: actions/upload-artifact makes no use of permissions
9+
# See https://github.com/actions/upload-artifact/issues/197#issuecomment-832279436
10+
contents: read # for "git clone"
11+
12+
defaults:
13+
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#defaultsrun
14+
run:
15+
# Enable fail-fast behavior using set -eo pipefail
16+
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
17+
shell: bash
18+
19+
jobs:
20+
generate-app:
21+
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idruns-on
22+
runs-on: ubuntu-20.04
23+
steps:
24+
- name: Clone source code
25+
uses: actions/checkout@v3.1.0 # https://github.com/actions/checkout
26+
with:
27+
# Whether to configure the token or SSH key with the local git config. Default: true
28+
persist-credentials: false
29+
- name: Setup NodeJS
30+
uses: actions/setup-node@v3.5.1 # https://github.com/actions/setup-node
31+
with:
32+
node-version: 18
33+
cache: 'npm'
34+
- name: Install project dependencies
35+
run: npm ci --no-audit --no-fund # https://docs.npmjs.com/cli/v8/commands/npm-ci
36+
- name: Generate TypeScript + Express application
37+
run: npm run gen-ts-example
38+
- name: Check whether all modified files have been committed
39+
run: >-
40+
MODIFIED_FILES="$(git status --short)";
41+
if [ -n "$MODIFIED_FILES" ]; then
42+
echo >&2 "ERROR: the following generated files have not been committed:";
43+
echo >&2 "$MODIFIED_FILES";
44+
exit 1;
45+
fi

‎README.md

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Generates the endpoints (or a whole app) from a mapping (SQL query -> URL)
4848
| Language | Command for code generation | Example of generated files | Libraries |
4949
| -----------| ----------------------------| ---------------------------| --------- |
5050
| JavaScript | `npx query2app --lang js` | [`app.js`](examples/js/express/mysql/app.js)<br/>[`routes.js`](examples/js/express/mysql/routes.js)<br/>[`package.json`](examples/js/express/mysql/package.json) | Web: [`express`](https://www.npmjs.com/package/express)<br>Database: [`mysql`](https://www.npmjs.com/package/mysql) |
51+
| TypeScript | `npx query2app --lang ts` | [`app.ts`](examples/ts/express/mysql/app.ts)<br/>[`routes.ts`](examples/ts/express/mysql/routes.js)<br/>[`package.json`](examples/ts/express/mysql/package.json)<br/>[`tsconfig.json`](examples/ts/express/mysql/tsconfig.json) | Web: [`express`](https://www.npmjs.com/package/express)<br>Database: [`mysql`](https://www.npmjs.com/package/mysql) |
5152
| Golang | `npx query2app --lang go` | [`app.go`](examples/go/chi/mysql/app.go)<br/>[`routes.go`](examples/go/chi/mysql/routes.go)<br/>[`go.mod`](examples/go/chi/mysql/go.mod) | Web: [`go-chi/chi`](https://github.com/go-chi/chi)<br/>Database: [`go-sql-driver/mysql`](https://github.com/go-sql-driver/mysql), [`jmoiron/sqlx`](https://github.com/jmoiron/sqlx) |
5253
| Python | `npx query2app --lang python` | [`app.py`](examples/python/fastapi/postgres/app.py)<br/>[`db.py`](examples/python/fastapi/postgres/db.py)<br/>[`routes.py`](examples/python/fastapi/postgres/routes.py)<br/>[`requirements.txt`](examples/python/fastapi/postgres/requirements.txt) | Web: [FastAPI](https://github.com/tiangolo/fastapi), [Uvicorn](https://www.uvicorn.org)<br/>Database: [psycopg2](https://pypi.org/project/psycopg2/) |
5354

‎examples/ts/express/mysql/app.ts

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import express from 'express'
2+
import mysql from 'mysql'
3+
4+
const routes = require('./routes')
5+
6+
const app = express()
7+
app.use(express.json())
8+
9+
const pool = mysql.createPool({
10+
connectionLimit: 2,
11+
host: process.env.DB_HOST || 'localhost',
12+
user: process.env.DB_USER,
13+
password: process.env.DB_PASSWORD,
14+
database: process.env.DB_NAME,
15+
// Support of named placeholders (https://github.com/mysqljs/mysql#custom-format)
16+
queryFormat: function(query, values) {
17+
if (!values) {
18+
return query
19+
}
20+
return query.replace(/\:(\w+)/g, function(txt, key) {
21+
if (values.hasOwnProperty(key)) {
22+
return this.escape(values[key])
23+
}
24+
return txt
25+
}.bind(this))
26+
}
27+
})
28+
29+
routes.register(app, pool)
30+
31+
const port = process.env.PORT || 3000
32+
app.listen(port, () => {
33+
console.log(`Listen on ${port}`)
34+
})
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../js/express/mysql/endpoints.yaml
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "mysql",
3+
"version": "1.0.0",
4+
"scripts": {
5+
"build": "npx tsc",
6+
"start": "node dist/app.js"
7+
},
8+
"dependencies": {
9+
"express": "~4.17.1",
10+
"mysql": "~2.18.1"
11+
},
12+
"devDependencies": {
13+
"@types/express": "~4.17.17",
14+
"@types/mysql": "~2.15.21",
15+
"typescript": "~5.1.6"
16+
}
17+
}

‎examples/ts/express/mysql/routes.ts

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { Express, Request, Response } from 'express'
2+
import { Pool } from 'mysql'
3+
4+
const register = (app: Express, pool: Pool) => {
5+
6+
app.get('/v1/categories/count', (req: Request, res: Response) => {
7+
pool.query(
8+
'SELECT COUNT(*) AS counter FROM categories',
9+
(err, rows, fields) => {
10+
if (err) {
11+
throw err
12+
}
13+
if (rows.length === 0) {
14+
res.status(404).end()
15+
return
16+
}
17+
res.json(rows[0])
18+
}
19+
)
20+
})
21+
22+
app.get('/v1/collections/:collectionId/categories/count', (req: Request, res: Response) => {
23+
pool.query(
24+
'SELECT COUNT(DISTINCT s.category_id) AS counter FROM collections_series cs JOIN series s ON s.id = cs.series_id WHERE cs.collection_id = :collectionId',
25+
{ "collectionId": req.params.collectionId },
26+
(err, rows, fields) => {
27+
if (err) {
28+
throw err
29+
}
30+
if (rows.length === 0) {
31+
res.status(404).end()
32+
return
33+
}
34+
res.json(rows[0])
35+
}
36+
)
37+
})
38+
39+
app.get('/v1/categories', (req: Request, res: Response) => {
40+
pool.query(
41+
'SELECT id , name , name_ru , slug FROM categories LIMIT :limit',
42+
{ "limit": req.query.limit },
43+
(err, rows, fields) => {
44+
if (err) {
45+
throw err
46+
}
47+
res.json(rows)
48+
}
49+
)
50+
})
51+
52+
app.post('/v1/categories', (req: Request, res: Response) => {
53+
pool.query(
54+
'INSERT INTO categories ( name , name_ru , slug , created_at , created_by , updated_at , updated_by ) VALUES ( :name , :name_ru , :slug , NOW() , :user_id , NOW() , :user_id )',
55+
{ "name": req.body.name, "name_ru": req.body.name_ru, "slug": req.body.slug, "user_id": req.body.user_id },
56+
(err, rows, fields) => {
57+
if (err) {
58+
throw err
59+
}
60+
res.sendStatus(204)
61+
}
62+
)
63+
})
64+
65+
app.get('/v1/categories/:categoryId', (req: Request, res: Response) => {
66+
pool.query(
67+
'SELECT id , name , name_ru , slug FROM categories WHERE id = :categoryId',
68+
{ "categoryId": req.params.categoryId },
69+
(err, rows, fields) => {
70+
if (err) {
71+
throw err
72+
}
73+
if (rows.length === 0) {
74+
res.status(404).end()
75+
return
76+
}
77+
res.json(rows[0])
78+
}
79+
)
80+
})
81+
82+
app.put('/v1/categories/:categoryId', (req: Request, res: Response) => {
83+
pool.query(
84+
'UPDATE categories SET name = :name , name_ru = :name_ru , slug = :slug , updated_at = NOW() , updated_by = :user_id WHERE id = :categoryId',
85+
{ "name": req.body.name, "name_ru": req.body.name_ru, "slug": req.body.slug, "user_id": req.body.user_id, "categoryId": req.params.categoryId },
86+
(err, rows, fields) => {
87+
if (err) {
88+
throw err
89+
}
90+
res.sendStatus(204)
91+
}
92+
)
93+
})
94+
95+
app.delete('/v1/categories/:categoryId', (req: Request, res: Response) => {
96+
pool.query(
97+
'DELETE FROM categories WHERE id = :categoryId',
98+
{ "categoryId": req.params.categoryId },
99+
(err, rows, fields) => {
100+
if (err) {
101+
throw err
102+
}
103+
res.sendStatus(204)
104+
}
105+
)
106+
})
107+
108+
}
109+
110+
exports.register = register;

0 commit comments

Comments
 (0)