mirror of
https://github.com/sasjs/server.git
synced 2025-12-10 19:34:34 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ae8f35e9a | ||
|
|
667e26b080 | ||
|
|
d09876c05f | ||
|
|
fb8e18be75 | ||
|
|
7ac7a4e083 | ||
|
|
8e23786dd4 | ||
|
|
4bd01bcf29 | ||
|
|
51f6aa34a1 | ||
|
|
486207128d | ||
|
|
1e4b0b9171 | ||
|
|
1ff820605a | ||
|
|
9c1a781b3a | ||
| 36628551ae | |||
| 23cf8fa06f | |||
| 84ee743eae | |||
|
|
19e5bd7d2d | ||
|
|
e251747302 | ||
|
|
7e7558d4cf | ||
|
|
f02996facf |
73
.github/CONTRIBUTING.md
vendored
73
.github/CONTRIBUTING.md
vendored
@@ -2,25 +2,22 @@
|
|||||||
|
|
||||||
Contributions are very welcome! Feel free to raise an issue or start a discussion, for help in getting started.
|
Contributions are very welcome! Feel free to raise an issue or start a discussion, for help in getting started.
|
||||||
|
|
||||||
|
The app can be deployed using Docker or NodeJS.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Configuration is made in the `configuration` section of `package.json`:
|
Configuration is made using `.env` files (per [README.md](https://github.com/sasjs/server#env-var-configuration) settings), _except_ for one case, when running in NodeJS in production - in which case the path to the SAS executable is made in the `configuration` section of `package.json`.
|
||||||
|
|
||||||
- Provide path to SAS9 executable.
|
The `.env` file should be created in the location(s) below. Each folder contains a `.env.example` file that may be adjusted and renamed.
|
||||||
|
|
||||||
|
* `.env` - the root .env file is used only for Docker deploys.
|
||||||
|
* `api/.env` - this is the primary file used in NodeJS deploys
|
||||||
|
* `web/.env` - this file is only necessary in NodeJS when running `web` and `api` seperately (on different ports).
|
||||||
|
|
||||||
|
|
||||||
### Using dockers:
|
## Using Docker
|
||||||
|
|
||||||
There is `.env.example` file present at root of the project. [for Production]
|
### Docker Development Mode
|
||||||
|
|
||||||
There is `.env.example` file present at `./api` of the project. [for Development]
|
|
||||||
|
|
||||||
There is `.env.example` file present at `./web` of the project. [for Development]
|
|
||||||
|
|
||||||
Remember to provide enviornment variables.
|
|
||||||
|
|
||||||
#### Development
|
|
||||||
|
|
||||||
Command to run docker for development:
|
Command to run docker for development:
|
||||||
|
|
||||||
@@ -38,7 +35,7 @@ It will build following images if running first time:
|
|||||||
- `mongo-seed-clients` - will be populating client data specified in _./mongo-seed/clients/client.json_
|
- `mongo-seed-clients` - will be populating client data specified in _./mongo-seed/clients/client.json_
|
||||||
|
|
||||||
|
|
||||||
#### Production
|
### Docker Production Mode
|
||||||
|
|
||||||
Command to run docker for production:
|
Command to run docker for production:
|
||||||
|
|
||||||
@@ -54,47 +51,45 @@ It will build following images if running first time:
|
|||||||
- `mongo-seed-users` - will be populating user data specified in _./mongo-seed/users/user.json_
|
- `mongo-seed-users` - will be populating user data specified in _./mongo-seed/users/user.json_
|
||||||
- `mongo-seed-clients` - will be populating client data specified in _./mongo-seed/clients/client.json_
|
- `mongo-seed-clients` - will be populating client data specified in _./mongo-seed/clients/client.json_
|
||||||
|
|
||||||
### Using node:
|
## Using NodeJS:
|
||||||
|
|
||||||
#### Development (running api and web seperately):
|
Be sure to use v16 or above, and to set your environment variables in the relevant `.env` file(s) - else defaults will be used.
|
||||||
|
|
||||||
##### API
|
### NodeJS Development Mode
|
||||||
|
|
||||||
Navigate to `./api`
|
SASjs Server is split between an API server (serving REST requests) and a WEB Server (everything else). These can be run together, or on seperate ports.
|
||||||
There is `.env.example` file present at `./api` directory. Remember to provide enviornment variables else default values will be used mentioned in `.env.example` files
|
|
||||||
Command to install and run api server.
|
### NodeJS Dev - Single Port
|
||||||
|
|
||||||
|
Here the environment variables should be configured under `api.env`. Then:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
cd ./web && npm i && npm build
|
||||||
|
cd ../api && npm i && npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
### NodeJS Dev - Seperate Ports
|
||||||
|
|
||||||
|
Set the backend variables in `api/.env` and the frontend variables in `web/.env`. Then:
|
||||||
|
|
||||||
|
#### API server
|
||||||
|
```
|
||||||
|
cd api
|
||||||
npm install
|
npm install
|
||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Web
|
#### Web Server
|
||||||
|
|
||||||
Navigate to `./web`
|
|
||||||
There is `.env.example` file present at `./web` directory. Remember to provide enviornment variables else default values will be used mentioned in `.env.example` files
|
|
||||||
Command to install and run api server.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
cd web
|
||||||
npm install
|
npm install
|
||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Development (running only api server and have web build served):
|
#### NodeJS Production Mode
|
||||||
|
|
||||||
##### API server also serving Web build files
|
Update the `.env` file in the *api* folder. Then:
|
||||||
|
|
||||||
There is `.env.example` file present at `./api` directory. Remember to provide enviornment variables else default values will be used mentioned in `.env.example` files
|
|
||||||
Command to install and run api server.
|
|
||||||
|
|
||||||
```
|
|
||||||
cd ./web && npm i && npm build && cd ../
|
|
||||||
cd ./api && npm i && npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Production
|
|
||||||
|
|
||||||
##### API & WEB
|
|
||||||
|
|
||||||
```
|
```
|
||||||
npm run server
|
npm run server
|
||||||
@@ -105,7 +100,7 @@ This will install/build `web` and install `api`, then start prod server.
|
|||||||
|
|
||||||
## Executables
|
## Executables
|
||||||
|
|
||||||
Command to generate executables
|
In order to generate the final executables:
|
||||||
|
|
||||||
```
|
```
|
||||||
cd ./web && npm i && npm build && cd ../
|
cd ./web && npm i && npm build && cd ../
|
||||||
|
|||||||
22
CHANGELOG.md
22
CHANGELOG.md
@@ -2,6 +2,28 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
|
### [0.0.58](https://github.com/sasjs/server/compare/v0.0.57...v0.0.58) (2022-04-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* bumping core library to get latest user management macros ([4862071](https://github.com/sasjs/server/commit/486207128da58fc4866bd0919c1bed2bd98097ea))
|
||||||
|
* missing dependency ([d09876c](https://github.com/sasjs/server/commit/d09876c05f89166eec20064f7aa7ed5b867be081))
|
||||||
|
|
||||||
|
### [0.0.57](https://github.com/sasjs/server/compare/v0.0.56...v0.0.57) (2022-04-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* create AppContext ([84ee743](https://github.com/sasjs/server/commit/84ee743eae16e87eaa91969393bebf01e2d15a44))
|
||||||
|
|
||||||
|
### [0.0.56](https://github.com/sasjs/server/compare/v0.0.55...v0.0.56) (2022-04-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* shortening min length of username. Closes [#61](https://github.com/sasjs/server/issues/61) ([f02996f](https://github.com/sasjs/server/commit/f02996facf1019ec4022ccfbc99c1d0137074e1b))
|
||||||
|
|
||||||
### [0.0.55](https://github.com/sasjs/server/compare/v0.0.53...v0.0.55) (2022-04-20)
|
### [0.0.55](https://github.com/sasjs/server/compare/v0.0.53...v0.0.55) (2022-04-20)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -98,7 +98,20 @@ SASV9_OPTIONS= -NOXCMD
|
|||||||
|
|
||||||
## Persisting the Session
|
## Persisting the Session
|
||||||
|
|
||||||
Normally the server process will stop when your terminal dies. To keep it going you can use the npm package [pm2](https://www.npmjs.com/package/pm2) (`npm install pm2@latest -g`) as follows:
|
Normally the server process will stop when your terminal dies. To keep it going you can use the following suggested approaches:
|
||||||
|
|
||||||
|
1. Linux Background Job
|
||||||
|
2. NPM package `pm2`
|
||||||
|
|
||||||
|
### Background Job
|
||||||
|
|
||||||
|
Trigger the command using NOHUP, redirecting the output commands, eg `nohup ./api-linux > server.log 2>&1 &`.
|
||||||
|
|
||||||
|
You can now see the job running using the `jobs` command. To ensure that it will still run when your terminal is closed, execute the `disown` command. To kill it later, use the `kill -9 <pid>` command. You can see your sessions using `top -u <userid>`. Type `c` to see the commands being run against each pid.
|
||||||
|
|
||||||
|
### PM2
|
||||||
|
|
||||||
|
Install the npm package [pm2](https://www.npmjs.com/package/pm2) (`npm install pm2@latest -g`) and execute, eg as follows:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export SAS_PATH=/opt/sas9/SASHome/SASFoundation/9.4/sasexe/sas
|
export SAS_PATH=/opt/sas9/SASHome/SASFoundation/9.4/sasexe/sas
|
||||||
@@ -132,7 +145,7 @@ Instead of `app_name` you can pass:
|
|||||||
|
|
||||||
## Server Version
|
## Server Version
|
||||||
|
|
||||||
The following credentials can be used for the initial connection to SASjs/server. It is recommended to change these on first use.
|
The following credentials can be used for the initial connection to SASjs/server. It is highly recommended to change these on first use.
|
||||||
|
|
||||||
- CLIENTID: `clientID1`
|
- CLIENTID: `clientID1`
|
||||||
- USERNAME: `secretuser`
|
- USERNAME: `secretuser`
|
||||||
|
|||||||
26
api/package-lock.json
generated
26
api/package-lock.json
generated
@@ -8,7 +8,7 @@
|
|||||||
"name": "api",
|
"name": "api",
|
||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/core": "4.9.0",
|
"@sasjs/core": "^4.19.0",
|
||||||
"@sasjs/utils": "2.42.1",
|
"@sasjs/utils": "2.42.1",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
@@ -1379,9 +1379,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sasjs/core": {
|
"node_modules/@sasjs/core": {
|
||||||
"version": "4.9.0",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.19.0.tgz",
|
||||||
"integrity": "sha512-zc1Ey0ylHt/eRZAfK0mVG3EqNyq//wLxbiguiK0R6FhVqwYFEkprs3IiLGZ5M9ttKs2rHRIjOe/ckklHm+6HNQ=="
|
"integrity": "sha512-vG2YHJveQUQqN0YBhapXb8y+Qp4OniHzRedlqKRxyL0Pc+kwXx5co4Vo+dcOI5/MX0p+8oERP2aCR77s4FEUJg=="
|
||||||
},
|
},
|
||||||
"node_modules/@sasjs/utils": {
|
"node_modules/@sasjs/utils": {
|
||||||
"version": "2.42.1",
|
"version": "2.42.1",
|
||||||
@@ -2448,9 +2448,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/async": {
|
"node_modules/async": {
|
||||||
"version": "2.6.3",
|
"version": "2.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash": "^4.17.14"
|
"lodash": "^4.17.14"
|
||||||
}
|
}
|
||||||
@@ -11127,9 +11127,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@sasjs/core": {
|
"@sasjs/core": {
|
||||||
"version": "4.9.0",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.19.0.tgz",
|
||||||
"integrity": "sha512-zc1Ey0ylHt/eRZAfK0mVG3EqNyq//wLxbiguiK0R6FhVqwYFEkprs3IiLGZ5M9ttKs2rHRIjOe/ckklHm+6HNQ=="
|
"integrity": "sha512-vG2YHJveQUQqN0YBhapXb8y+Qp4OniHzRedlqKRxyL0Pc+kwXx5co4Vo+dcOI5/MX0p+8oERP2aCR77s4FEUJg=="
|
||||||
},
|
},
|
||||||
"@sasjs/utils": {
|
"@sasjs/utils": {
|
||||||
"version": "2.42.1",
|
"version": "2.42.1",
|
||||||
@@ -12060,9 +12060,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"async": {
|
"async": {
|
||||||
"version": "2.6.3",
|
"version": "2.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"lodash": "^4.17.14"
|
"lodash": "^4.17.14"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"prestart": "npm run initial",
|
"prestart": "npm run initial",
|
||||||
"prebuild": "npm run initial",
|
"prebuild": "npm run initial",
|
||||||
"start": "nodemon ./src/server.ts",
|
"start": "nodemon ./src/server.ts",
|
||||||
|
"start:prod": "node ./build/src/server.js",
|
||||||
"build": "rimraf build && tsc",
|
"build": "rimraf build && tsc",
|
||||||
"postbuild": "npm run copy:files",
|
"postbuild": "npm run copy:files",
|
||||||
"swagger": "tsoa spec",
|
"swagger": "tsoa spec",
|
||||||
@@ -46,7 +47,7 @@
|
|||||||
},
|
},
|
||||||
"author": "4GL Ltd",
|
"author": "4GL Ltd",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/core": "4.9.0",
|
"@sasjs/core": "^4.19.0",
|
||||||
"@sasjs/utils": "2.42.1",
|
"@sasjs/utils": "2.42.1",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
|
|||||||
@@ -5,23 +5,12 @@
|
|||||||
_before_ any user-provided content.
|
_before_ any user-provided content.
|
||||||
|
|
||||||
A number of useful CORE macros are also compiled below, so that they can be
|
A number of useful CORE macros are also compiled below, so that they can be
|
||||||
available "out of the box".
|
available by default for Stored Programs.
|
||||||
|
|
||||||
|
Note that the full CORE library is available to sessions in SASjs Studio.
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mcf_stpsrv_header.sas
|
|
||||||
@li mf_getuser.sas
|
|
||||||
@li mf_getvarlist.sas
|
|
||||||
@li mf_mkdir.sas
|
|
||||||
@li mf_nobs.sas
|
|
||||||
@li mf_uid.sas
|
|
||||||
@li mfs_httpheader.sas
|
@li mfs_httpheader.sas
|
||||||
@li mp_dirlist.sas
|
@li ms_webout.sas
|
||||||
@li mp_ds2ddl.sas
|
|
||||||
@li mp_ds2md.sas
|
|
||||||
@li mp_getdbml.sas
|
|
||||||
@li mp_init.sas
|
|
||||||
@li mp_makedata.sas
|
|
||||||
@li mp_zip.sas
|
|
||||||
|
|
||||||
**/
|
**/
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Joi from 'joi'
|
import Joi from 'joi'
|
||||||
|
|
||||||
const usernameSchema = Joi.string().alphanum().min(6).max(20)
|
const usernameSchema = Joi.string().alphanum().min(3).max(16)
|
||||||
const passwordSchema = Joi.string().min(6).max(1024)
|
const passwordSchema = Joi.string().min(6).max(1024)
|
||||||
|
|
||||||
export const blockFileRegex = /\.(exe|sh|htaccess)$/i
|
export const blockFileRegex = /\.(exe|sh|htaccess)$/i
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.0.55",
|
"version": "0.0.58",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.0.55",
|
"version": "0.0.58",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "^2.3.1",
|
"prettier": "^2.3.1",
|
||||||
"standard-version": "^9.3.2"
|
"standard-version": "^9.3.2"
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.0.55",
|
"version": "0.0.58",
|
||||||
"description": "NodeJS wrapper for calling the SAS binary executable",
|
"description": "NodeJS wrapper for calling the SAS binary executable",
|
||||||
"repository": "https://github.com/sasjs/server",
|
"repository": "https://github.com/sasjs/server",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"server": "npm run server:prepare && npm run server:start",
|
"server": "npm run server:prepare && npm run server:start",
|
||||||
"server:prepare": "cd web && npm ci && npm run build && cd ../api && npm ci && cd ..",
|
"server:prepare": "cd web && npm ci && npm run build && cd ../api && npm ci && npm run build && cd ..",
|
||||||
"server:start": "cd api && npm run start",
|
"server:start": "cd api && npm run start:prod",
|
||||||
"release": "standard-version",
|
"release": "standard-version",
|
||||||
"lint-api:fix": "npx prettier --write \"api/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
|
"lint-api:fix": "npx prettier --write \"api/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
|
||||||
"lint-api": "npx prettier --check \"api/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
|
"lint-api": "npx prettier --check \"api/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
|
||||||
|
|||||||
12
web/package-lock.json
generated
12
web/package-lock.json
generated
@@ -4191,9 +4191,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/async": {
|
"node_modules/async": {
|
||||||
"version": "2.6.3",
|
"version": "2.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash": "^4.17.14"
|
"lodash": "^4.17.14"
|
||||||
@@ -14381,9 +14381,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"async": {
|
"async": {
|
||||||
"version": "2.6.3",
|
"version": "2.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"lodash": "^4.17.14"
|
"lodash": "^4.17.14"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { Route, HashRouter, Switch } from 'react-router-dom'
|
import { Route, HashRouter, Switch } from 'react-router-dom'
|
||||||
import { ThemeProvider } from '@mui/material/styles'
|
import { ThemeProvider } from '@mui/material/styles'
|
||||||
import { theme } from './theme'
|
import { theme } from './theme'
|
||||||
@@ -9,12 +9,12 @@ import Home from './components/home'
|
|||||||
import Drive from './containers/Drive'
|
import Drive from './containers/Drive'
|
||||||
import Studio from './containers/Studio'
|
import Studio from './containers/Studio'
|
||||||
|
|
||||||
import useTokens from './components/useTokens'
|
import { AppContext } from './context/appContext'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { tokens, setTokens } = useTokens()
|
const appContext = useContext(AppContext)
|
||||||
|
|
||||||
if (!tokens) {
|
if (!appContext.tokens) {
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
@@ -24,7 +24,7 @@ function App() {
|
|||||||
<Login getCodeOnly />
|
<Login getCodeOnly />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/">
|
<Route path="/">
|
||||||
<Login setTokens={setTokens} />
|
<Login />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState, useContext } from 'react'
|
||||||
import { Link, useHistory, useLocation } from 'react-router-dom'
|
import { Link, useHistory, useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
import AppBar from '@mui/material/AppBar'
|
import {
|
||||||
import Toolbar from '@mui/material/Toolbar'
|
AppBar,
|
||||||
import Tabs from '@mui/material/Tabs'
|
Toolbar,
|
||||||
import Tab from '@mui/material/Tab'
|
Tabs,
|
||||||
import Button from '@mui/material/Button'
|
Tab,
|
||||||
|
Button,
|
||||||
|
Menu,
|
||||||
|
MenuItem
|
||||||
|
} from '@mui/material'
|
||||||
import OpenInNewIcon from '@mui/icons-material/OpenInNew'
|
import OpenInNewIcon from '@mui/icons-material/OpenInNew'
|
||||||
|
|
||||||
|
import UserName from './userName'
|
||||||
|
import { AppContext } from '../context/appContext'
|
||||||
|
|
||||||
const NODE_ENV = process.env.NODE_ENV
|
const NODE_ENV = process.env.NODE_ENV
|
||||||
const PORT_API = process.env.PORT_API
|
const PORT_API = process.env.PORT_API
|
||||||
const baseUrl =
|
const baseUrl =
|
||||||
@@ -16,11 +23,29 @@ const baseUrl =
|
|||||||
const Header = (props: any) => {
|
const Header = (props: any) => {
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
|
const appContext = useContext(AppContext)
|
||||||
const [tabValue, setTabValue] = useState(pathname)
|
const [tabValue, setTabValue] = useState(pathname)
|
||||||
|
const [anchorEl, setAnchorEl] = useState<
|
||||||
|
(EventTarget & HTMLButtonElement) | null
|
||||||
|
>(null)
|
||||||
|
|
||||||
|
const handleMenu = (
|
||||||
|
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
||||||
|
) => {
|
||||||
|
setAnchorEl(event.currentTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setAnchorEl(null)
|
||||||
|
}
|
||||||
|
|
||||||
const handleTabChange = (event: React.SyntheticEvent, value: string) => {
|
const handleTabChange = (event: React.SyntheticEvent, value: string) => {
|
||||||
setTabValue(value)
|
setTabValue(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
if (appContext.logout) appContext.logout()
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<AppBar
|
<AppBar
|
||||||
position="fixed"
|
position="fixed"
|
||||||
@@ -81,6 +106,39 @@ const Header = (props: any) => {
|
|||||||
>
|
>
|
||||||
App Stream
|
App Stream
|
||||||
</Button>
|
</Button>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexGrow: 1,
|
||||||
|
justifyContent: 'flex-end'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<UserName
|
||||||
|
userName={appContext.userName}
|
||||||
|
onClickHandler={handleMenu}
|
||||||
|
/>
|
||||||
|
<Menu
|
||||||
|
id="menu-appbar"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'center'
|
||||||
|
}}
|
||||||
|
keepMounted
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'center'
|
||||||
|
}}
|
||||||
|
open={!!anchorEl}
|
||||||
|
onClose={handleClose}
|
||||||
|
>
|
||||||
|
<MenuItem onClick={handleLogout} sx={{ justifyContent: 'center' }}>
|
||||||
|
<Button variant="contained" color="primary">
|
||||||
|
Logout
|
||||||
|
</Button>
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState, useContext } from 'react'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import { CssBaseline, Box, TextField, Button, Typography } from '@mui/material'
|
import { CssBaseline, Box, TextField, Button, Typography } from '@mui/material'
|
||||||
|
import { AppContext } from '../context/appContext'
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
@@ -33,8 +34,9 @@ const getTokens = async (payload: any) => {
|
|||||||
}).then((data) => data.json())
|
}).then((data) => data.json())
|
||||||
}
|
}
|
||||||
|
|
||||||
const Login = ({ setTokens, getCodeOnly }: any) => {
|
const Login = ({ getCodeOnly }: any) => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const appContext = useContext(AppContext)
|
||||||
const [username, setUserName] = useState('')
|
const [username, setUserName] = useState('')
|
||||||
const [password, setPassword] = useState('')
|
const [password, setPassword] = useState('')
|
||||||
const [errorMessage, setErrorMessage] = useState('')
|
const [errorMessage, setErrorMessage] = useState('')
|
||||||
@@ -71,7 +73,8 @@ const Login = ({ setTokens, getCodeOnly }: any) => {
|
|||||||
code
|
code
|
||||||
})
|
})
|
||||||
|
|
||||||
setTokens(accessToken, refreshToken)
|
if (appContext.setTokens) appContext.setTokens(accessToken, refreshToken)
|
||||||
|
if (appContext.setUserName) appContext.setUserName(username)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +129,7 @@ const Login = ({ setTokens, getCodeOnly }: any) => {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{errorMessage && <span>{errorMessage}</span>}
|
{errorMessage && <span>{errorMessage}</span>}
|
||||||
<Button type="submit" variant="outlined">
|
<Button type="submit" variant="outlined" disabled={!appContext.setTokens}>
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -134,7 +137,6 @@ const Login = ({ setTokens, getCodeOnly }: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Login.propTypes = {
|
Login.propTypes = {
|
||||||
setTokens: PropTypes.func,
|
|
||||||
getCodeOnly: PropTypes.bool
|
getCodeOnly: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
30
web/src/components/userName.tsx
Normal file
30
web/src/components/userName.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Typography, IconButton } from '@mui/material'
|
||||||
|
import AccountCircle from '@mui/icons-material/AccountCircle'
|
||||||
|
|
||||||
|
const UserName = (props: any) => {
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
aria-label="account of current user"
|
||||||
|
aria-controls="menu-appbar"
|
||||||
|
aria-haspopup="true"
|
||||||
|
onClick={props.onClickHandler}
|
||||||
|
color="inherit"
|
||||||
|
>
|
||||||
|
{props.avatarContent ? (
|
||||||
|
<img
|
||||||
|
src={props.avatarContent}
|
||||||
|
alt="user-avatar"
|
||||||
|
style={{ width: '25px' }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<AccountCircle></AccountCircle>
|
||||||
|
)}
|
||||||
|
<Typography variant="h6" sx={{ color: 'white', padding: '0 8px' }}>
|
||||||
|
{props.userName}
|
||||||
|
</Typography>
|
||||||
|
</IconButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserName
|
||||||
139
web/src/context/appContext.tsx
Normal file
139
web/src/context/appContext.tsx
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import React, {
|
||||||
|
createContext,
|
||||||
|
Dispatch,
|
||||||
|
SetStateAction,
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useCallback,
|
||||||
|
ReactNode
|
||||||
|
} from 'react'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
const NODE_ENV = process.env.NODE_ENV
|
||||||
|
const PORT_API = process.env.PORT_API
|
||||||
|
const baseUrl =
|
||||||
|
NODE_ENV === 'development' ? `http://localhost:${PORT_API ?? 5000}` : ''
|
||||||
|
|
||||||
|
const isAbsoluteURLRegex = /^(?:\w+:)\/\//
|
||||||
|
|
||||||
|
const setAxiosRequestHeader = (accessToken: string) => {
|
||||||
|
axios.interceptors.request.use(function (config) {
|
||||||
|
if (baseUrl && !isAbsoluteURLRegex.test(config.url as string)) {
|
||||||
|
config.url = baseUrl + config.url
|
||||||
|
}
|
||||||
|
console.log('axios.interceptors.request.use', accessToken)
|
||||||
|
config.headers!['Authorization'] = `Bearer ${accessToken}`
|
||||||
|
config.withCredentials = true
|
||||||
|
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const setAxiosResponse = (setTokens: Function) => {
|
||||||
|
// Add a response interceptor
|
||||||
|
axios.interceptors.response.use(
|
||||||
|
function (response) {
|
||||||
|
// Any status code that lie within the range of 2xx cause this function to trigger
|
||||||
|
return response
|
||||||
|
},
|
||||||
|
async function (error) {
|
||||||
|
if (error.response?.status === 401) {
|
||||||
|
// refresh token
|
||||||
|
// const { accessToken, refreshToken: newRefresh } = await refreshMyToken(
|
||||||
|
// refreshToken
|
||||||
|
// )
|
||||||
|
|
||||||
|
// if (accessToken && newRefresh) {
|
||||||
|
// setTokens(accessToken, newRefresh)
|
||||||
|
// error.config.headers['Authorization'] = 'Bearer ' + accessToken
|
||||||
|
// error.config.baseURL = undefined
|
||||||
|
|
||||||
|
// return axios.request(error.config)
|
||||||
|
// }
|
||||||
|
console.log(53)
|
||||||
|
setTokens(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTokens = () => {
|
||||||
|
const accessToken = localStorage.getItem('accessToken')
|
||||||
|
const refreshToken = localStorage.getItem('refreshToken')
|
||||||
|
|
||||||
|
if (accessToken && refreshToken) {
|
||||||
|
setAxiosRequestHeader(accessToken)
|
||||||
|
return { accessToken, refreshToken }
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AppContextProps {
|
||||||
|
userName: string
|
||||||
|
setUserName: Dispatch<SetStateAction<string>> | null
|
||||||
|
tokens?: { accessToken: string; refreshToken: string }
|
||||||
|
setTokens: ((accessToken: string, refreshToken: string) => void) | null
|
||||||
|
logout: (() => void) | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AppContext = createContext<AppContextProps>({
|
||||||
|
userName: '',
|
||||||
|
tokens: getTokens(),
|
||||||
|
setUserName: null,
|
||||||
|
setTokens: null,
|
||||||
|
logout: null
|
||||||
|
})
|
||||||
|
|
||||||
|
const AppContextProvider = (props: { children: ReactNode }) => {
|
||||||
|
const { children } = props
|
||||||
|
const [userName, setUserName] = useState('')
|
||||||
|
const [tokens, setTokens] = useState(getTokens())
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setAxiosResponse(setTokens)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(97)
|
||||||
|
if (tokens === undefined) {
|
||||||
|
console.log(99)
|
||||||
|
localStorage.removeItem('accessToken')
|
||||||
|
localStorage.removeItem('refreshToken')
|
||||||
|
}
|
||||||
|
}, [tokens])
|
||||||
|
|
||||||
|
const saveTokens = useCallback(
|
||||||
|
(accessToken: string, refreshToken: string) => {
|
||||||
|
localStorage.setItem('accessToken', accessToken)
|
||||||
|
localStorage.setItem('refreshToken', refreshToken)
|
||||||
|
console.log(accessToken)
|
||||||
|
setAxiosRequestHeader(accessToken)
|
||||||
|
setTokens({ accessToken, refreshToken })
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const logout = useCallback(() => {
|
||||||
|
setUserName('')
|
||||||
|
setTokens(undefined)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppContext.Provider
|
||||||
|
value={{
|
||||||
|
userName,
|
||||||
|
setUserName,
|
||||||
|
tokens,
|
||||||
|
setTokens: saveTokens,
|
||||||
|
logout
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AppContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AppContextProvider
|
||||||
@@ -2,10 +2,13 @@ import React from 'react'
|
|||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import App from './App'
|
import App from './App'
|
||||||
|
import AppContextProvider from './context/appContext'
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<AppContextProvider>
|
||||||
|
<App />
|
||||||
|
</AppContextProvider>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user