Merge pull request #19 from gparant/Setup-web-socket-connection

Setup web-socket connection
This commit is contained in:
David Négrier 2020-04-04 22:40:18 +02:00 committed by GitHub
commit dc1a92f086
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 248 additions and 21 deletions

View File

@ -21,9 +21,13 @@
"homepage": "https://github.com/thecodingmachine/workadventure#readme", "homepage": "https://github.com/thecodingmachine/workadventure#readme",
"dependencies": { "dependencies": {
"@types/express": "^4.17.4", "@types/express": "^4.17.4",
"@types/http-status-codes": "^1.2.0",
"@types/jsonwebtoken": "^8.3.8",
"@types/socket.io": "^2.1.4", "@types/socket.io": "^2.1.4",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"express": "^4.17.1", "express": "^4.17.1",
"http-status-codes": "^1.4.0",
"jsonwebtoken": "^8.5.1",
"socket.io": "^2.3.0", "socket.io": "^2.3.0",
"ts-node-dev": "^1.0.0-pre.44", "ts-node-dev": "^1.0.0-pre.44",
"typescript": "^3.8.3" "typescript": "^3.8.3"

View File

@ -1,5 +1,6 @@
// lib/app.ts // lib/app.ts
import {IoSocketController} from "./Controller/IoSocketController"; import {IoSocketController} from "./Controller/IoSocketController"; //TODO fix impot by "_Controller/..."
import {AuthenticateController} from "./Controller/AuthenticateController"; //TODO fix impot by "_Controller/..."
import express from "express"; import express from "express";
import {Application} from 'express'; import {Application} from 'express';
import bodyParser = require('body-parser'); import bodyParser = require('body-parser');
@ -9,14 +10,21 @@ class App {
public app: Application; public app: Application;
public server: http.Server; public server: http.Server;
public ioSocketController: IoSocketController; public ioSocketController: IoSocketController;
public authenticateController: AuthenticateController;
constructor() { constructor() {
this.app = express(); this.app = express();
//config server http
this.config(); this.config();
this.server = http.createServer(this.app); this.server = http.createServer(this.app);
//create controllers
this.ioSocketController = new IoSocketController(this.server); this.ioSocketController = new IoSocketController(this.server);
this.authenticateController = new AuthenticateController(this.app);
} }
// TODO add session user
private config(): void { private config(): void {
this.app.use(bodyParser.json()); this.app.use(bodyParser.json());
this.app.use(bodyParser.urlencoded({extended: false})); this.app.use(bodyParser.urlencoded({extended: false}));

View File

@ -0,0 +1,28 @@
import {Application, Request, Response} from "express";
import Jwt, {JsonWebTokenError} from "jsonwebtoken";
import {BAD_REQUEST, OK} from "http-status-codes";
import {SECRET_KEY} from "../Enum/EnvironmentVariable";
export class AuthenticateController{
App : Application;
constructor(App : Application) {
this.App = App;
this.login();
}
//permit to login on application. Return token to connect on Websocket IO.
login(){
this.App.post("/login", (req: Request, res: Response) => {
let param = req.body;
if(!param.email){
return res.status(BAD_REQUEST).send({
message: "email parameter is empty"
});
}
//TODO check user email for The Coding Machine game
let token = Jwt.sign({email: param.email}, SECRET_KEY, {expiresIn: '24h'});
return res.status(OK).send({token: token});
});
}
}

View File

@ -1,36 +1,80 @@
import socketIO = require('socket.io'); import socketIO = require('socket.io');
import {Socket} from "socket.io"; import {Socket} from "socket.io";
import * as http from "http"; import * as http from "http";
import {MessageUserPosition} from "@Model/Websocket/MessageUserPosition"; import {MessageUserPosition} from "../Model/Websocket/MessageUserPosition"; //TODO fix import by "_Model/.."
import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.."
import Jwt, {JsonWebTokenError} from "jsonwebtoken";
import {SECRET_KEY} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..."
export class IoSocketController{ export class IoSocketController{
Io: socketIO.Server; Io: socketIO.Server;
constructor(server : http.Server) { constructor(server : http.Server) {
this.Io = socketIO(server); this.Io = socketIO(server);
// Authentication with token. it will be decoded and stored in the socket.
this.Io.use( (socket: Socket, next) => {
if (!socket.handshake.query || !socket.handshake.query.token) {
return next(new Error('Authentication error'));
}
Jwt.verify(socket.handshake.query.token, SECRET_KEY, (err: JsonWebTokenError, tokenDecoded: object) => {
if (err) {
return next(new Error('Authentication error'));
}
(socket as ExSocketInterface).token = tokenDecoded;
next();
});
});
this.ioConnection(); this.ioConnection();
} }
ioConnection() { ioConnection() {
this.Io.on('connection', (socket: Socket) => { this.Io.on('connection', (socket: Socket) => {
//TODO check token access
/*join-rom event permit to join one room. /*join-rom event permit to join one room.
message : message :
userId : user identification userId : user identification
roomId: room identification roomId: room identification
positionXUser: user x position map position: position of user in map
positionYUser: user y position on map x: user x position on map
y: user y position on map
*/ */
socket.on('join-room', (message : MessageUserPosition) => {
socket.join(message.roomId); socket.on('join-room', (message : string) => {
let messageUserPosition = this.hydrateMessageReceive(message);
if(messageUserPosition instanceof Error){
return socket.emit("message-error", JSON.stringify({message: messageUserPosition.message}))
}
//join user in room
socket.join(messageUserPosition.roomId);
// sending to all clients in room except sender // sending to all clients in room except sender
socket.to(message.roomId).emit('join-room', message.toString()); this.saveUserPosition((socket as ExSocketInterface), messageUserPosition);
socket.to(messageUserPosition.roomId).emit('join-room', messageUserPosition.toString());
}); });
socket.on('user-position', (message : MessageUserPosition) => { socket.on('user-position', (message : string) => {
let messageUserPosition = this.hydrateMessageReceive(message);
if(messageUserPosition instanceof Error){
return socket.emit("message-error", JSON.stringify({message: messageUserPosition.message}));
}
// sending to all clients in room except sender // sending to all clients in room except sender
socket.to(message.roomId).emit('join-room', message.toString()); this.saveUserPosition((socket as ExSocketInterface), messageUserPosition);
socket.to(messageUserPosition.roomId).emit('join-room', messageUserPosition.toString());
}); });
}); });
} }
//permit to save user position in socket
saveUserPosition(socket : ExSocketInterface, message : MessageUserPosition){
socket.position = message.position;
}
//Hydrate and manage error
hydrateMessageReceive(message : string) : MessageUserPosition | Error{
try {
return new MessageUserPosition(message);
}catch (err) {
//TODO log error
return new Error(err);
}
}
} }

View File

@ -0,0 +1,5 @@
const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY";
export {
SECRET_KEY
}

View File

@ -0,0 +1,7 @@
import {Socket} from "socket.io";
import {PointInterface} from "./PointInterface";
export interface ExSocketInterface extends Socket {
token: object;
position: PointInterface;
}

View File

@ -4,6 +4,9 @@ export class Message {
constructor(message: string) { constructor(message: string) {
let data = JSON.parse(message); let data = JSON.parse(message);
if(!data.userId || !data.roomId){
throw Error("userId and roomId cannot be null");
}
this.userId = data.userId; this.userId = data.userId;
this.roomId = data.roomId; this.roomId = data.roomId;
} }

View File

@ -1,14 +1,33 @@
import {Message} from "./Message"; import {Message} from "./Message";
import {PointInterface} from "./PointInterface";
export class Point implements PointInterface{
x: number;
y: number;
constructor(x : number, y : number) {
if(!x || !y){
throw Error("position x and y cannot be null");
}
this.x = x;
this.y = y;
}
toJson(){
return {
x : this.x,
y: this.y
}
}
}
export class MessageUserPosition extends Message{ export class MessageUserPosition extends Message{
positionXUser: string; position: PointInterface
positionYUser: string;
constructor(message: string) { constructor(message: string) {
super(message); super(message);
let data = JSON.parse(message); let data = JSON.parse(message);
this.positionXUser = data.positionXUser; this.position = new Point(data.position.x, data.position.y);
this.positionYUser = data.positionYUser;
} }
toString() { toString() {
@ -16,8 +35,7 @@ export class MessageUserPosition extends Message{
Object.assign( Object.assign(
super.toJson(), super.toJson(),
{ {
positionXUser: this.positionXUser, position: this.position.toJson()
positionYUser: this.positionYUser
}) })
); );
} }

View File

@ -0,0 +1,5 @@
export interface PointInterface {
x: number;
y: number;
toJson() : object;
}

View File

@ -43,8 +43,8 @@
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"baseUrl": ".", /* Base directory to resolve non-absolute module names. */ "baseUrl": ".", /* Base directory to resolve non-absolute module names. */
"paths": { "paths": {
"@Controller/*": ["src/Controller/*"], "_Controller/*": ["src/Controller/*"],
"@Model/*": ["src/Model/*"] "_Model/*": ["src/Model/*"]
}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */ // "typeRoots": [], /* List of folders to include type definitions from. */

View File

@ -66,11 +66,25 @@
"@types/qs" "*" "@types/qs" "*"
"@types/serve-static" "*" "@types/serve-static" "*"
"@types/http-status-codes@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@types/http-status-codes/-/http-status-codes-1.2.0.tgz#6e5244835aaf7164dd306f1d4d2dfdbb2159d909"
integrity sha512-vjpjevMaxtrtdrrV/TQNIFT7mKL8nvIKG7G/LjMDZdVvqRxRg5SNfGkeuSaowVc0rbK8xDA2d/Etunyb5GyzzA==
dependencies:
http-status-codes "*"
"@types/json-schema@^7.0.3": "@types/json-schema@^7.0.3":
version "7.0.4" version "7.0.4"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339"
integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA== integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==
"@types/jsonwebtoken@^8.3.8":
version "8.3.8"
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.3.8.tgz#b27c9156dde2049ae03e56528a53ef5a8294aa82"
integrity sha512-g2ke5+AR/RKYpQxd+HJ2yisLHGuOV0uourOcPtKlcT5Zqv4wFg9vKhFpXEztN4H/6Y6RSUKioz/2PTFPP30CTA==
dependencies:
"@types/node" "*"
"@types/mime@*": "@types/mime@*":
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d"
@ -317,6 +331,11 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0" balanced-match "^1.0.0"
concat-map "0.0.1" concat-map "0.0.1"
buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
buffer-from@^1.0.0: buffer-from@^1.0.0:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
@ -546,6 +565,13 @@ dynamic-dedupe@^0.3.0:
dependencies: dependencies:
xtend "^4.0.0" xtend "^4.0.0"
ecdsa-sig-formatter@1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
dependencies:
safe-buffer "^5.0.1"
ee-first@1.1.1: ee-first@1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@ -967,6 +993,11 @@ http-errors@~1.7.2:
statuses ">= 1.5.0 < 2" statuses ">= 1.5.0 < 2"
toidentifier "1.0.0" toidentifier "1.0.0"
http-status-codes@*, http-status-codes@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-1.4.0.tgz#6e4c15d16ff3a9e2df03b89f3a55e1aae05fb477"
integrity sha512-JrT3ua+WgH8zBD3HEJYbeEgnuQaAnUeRRko/YojPAJjGmIfGD3KPU/asLdsLwKjfxOmQe5nXMQ0pt/7MyapVbQ==
iconv-lite@0.4.24, iconv-lite@^0.4.24: iconv-lite@0.4.24, iconv-lite@^0.4.24:
version "0.4.24" version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@ -1126,6 +1157,39 @@ json-stable-stringify-without-jsonify@^1.0.1:
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
jsonwebtoken@^8.5.1:
version "8.5.1"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
dependencies:
jws "^3.2.2"
lodash.includes "^4.3.0"
lodash.isboolean "^3.0.3"
lodash.isinteger "^4.0.4"
lodash.isnumber "^3.0.3"
lodash.isplainobject "^4.0.6"
lodash.isstring "^4.0.1"
lodash.once "^4.0.0"
ms "^2.1.1"
semver "^5.6.0"
jwa@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
dependencies:
buffer-equal-constant-time "1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jws@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
dependencies:
jwa "^1.4.1"
safe-buffer "^5.0.1"
levn@^0.3.0, levn@~0.3.0: levn@^0.3.0, levn@~0.3.0:
version "0.3.0" version "0.3.0"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
@ -1145,6 +1209,41 @@ load-json-file@^1.0.0:
pinkie-promise "^2.0.0" pinkie-promise "^2.0.0"
strip-bom "^2.0.0" strip-bom "^2.0.0"
lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
lodash.isboolean@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
lodash.isinteger@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=
lodash.isnumber@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
lodash.isstring@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
lodash.once@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
lodash@^4.17.14, lodash@^4.17.15: lodash@^4.17.14, lodash@^4.17.15:
version "4.17.15" version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
@ -1568,12 +1667,17 @@ safe-buffer@5.1.2:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@^5.0.1:
version "5.2.0"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
"safer-buffer@>= 2.1.2 < 3": "safer-buffer@>= 2.1.2 < 3":
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
"semver@2 || 3 || 4 || 5", semver@^5.5.0: "semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0:
version "5.7.1" version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==

View File

@ -29,6 +29,7 @@ services:
command: yarn dev command: yarn dev
environment: environment:
STARTUP_COMMAND_1: yarn install STARTUP_COMMAND_1: yarn install
SECRET_KEY: yourSecretKey
volumes: volumes:
- ./back:/usr/src/app - ./back:/usr/src/app
labels: labels: