Backend
This commit is contained in:
parent
15d4e2f126
commit
edcd4dcaa0
|
|
@ -3,4 +3,6 @@
|
||||||
.git
|
.git
|
||||||
.gitignore
|
.gitignore
|
||||||
|
|
||||||
|
.env
|
||||||
|
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
|
||||||
1
crabfit-backend/.gitignore
vendored
1
crabfit-backend/.gitignore
vendored
|
|
@ -1,6 +1,7 @@
|
||||||
/node_modules
|
/node_modules
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
.env.development.local
|
.env.development.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
runtime: nodejs10
|
runtime: nodejs
|
||||||
entrypoint: node index.js
|
|
||||||
service: api
|
service: api
|
||||||
|
env: flex
|
||||||
|
|
||||||
|
automatic_scaling:
|
||||||
|
min_num_instances: 1
|
||||||
|
max_num_instances: 4
|
||||||
|
|
||||||
endpoints_api_service:
|
endpoints_api_service:
|
||||||
name: api-dot-crabfit.appspot.com
|
name: api-dot-crabfit.appspot.com
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,30 @@
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
const { Datastore } = require('@google-cloud/datastore');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
|
|
||||||
const package = require('./package.json');
|
const package = require('./package.json');
|
||||||
const app = express();
|
|
||||||
const port = 8080;
|
|
||||||
|
|
||||||
const stats = require('./routes/stats');
|
const stats = require('./routes/stats');
|
||||||
const getEvent = require('./routes/getEvent');
|
const getEvent = require('./routes/getEvent');
|
||||||
const createEvent = require('./routes/createEvent');
|
const createEvent = require('./routes/createEvent');
|
||||||
const getPeople = require('./routes/getPeople');
|
const getPeople = require('./routes/getPeople');
|
||||||
const createPerson = require('./routes/createPerson');
|
const createPerson = require('./routes/createPerson');
|
||||||
|
const login = require('./routes/login');
|
||||||
const updatePerson = require('./routes/updatePerson');
|
const updatePerson = require('./routes/updatePerson');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const port = 8080;
|
||||||
|
|
||||||
|
const datastore = new Datastore({
|
||||||
|
keyFilename: process.env.GOOGLE_APPLICATION_CREDENTIALS,
|
||||||
|
});
|
||||||
|
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
req.datastore = datastore;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/', (req, res) => res.send(`Crabfit API v${package.version}`));
|
app.get('/', (req, res) => res.send(`Crabfit API v${package.version}`));
|
||||||
|
|
||||||
|
|
@ -19,7 +33,8 @@ app.get('/event/:eventId', getEvent);
|
||||||
app.post('/event', createEvent);
|
app.post('/event', createEvent);
|
||||||
app.get('/event/:eventId/people', getPeople);
|
app.get('/event/:eventId/people', getPeople);
|
||||||
app.post('/event/:eventId/people', createPerson);
|
app.post('/event/:eventId/people', createPerson);
|
||||||
app.patch('/event/:eventId/people/:personId', updatePerson);
|
app.get('/event/:eventId/people/:personName', login);
|
||||||
|
app.patch('/event/:eventId/people/:personName', updatePerson);
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Crabfit API listening at http://localhost:${port}`)
|
console.log(`Crabfit API listening at http://localhost:${port}`)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,17 @@
|
||||||
"author": "Ben Grant",
|
"author": "Ben Grant",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "node index.js"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@google-cloud/datastore": "^6.3.1",
|
||||||
|
"bcrypt": "^5.0.1",
|
||||||
|
"dayjs": "^1.10.4",
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
"express": "^4.17.1"
|
"express": "^4.17.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,43 @@
|
||||||
module.exports = (req, res) => {
|
const dayjs = require('dayjs');
|
||||||
|
|
||||||
|
const generateId = (name) => {
|
||||||
|
const id = name.trim().toLowerCase().replace(/[^A-Za-z0-9 ]/g, '').replace(/\s+/g, '-');
|
||||||
|
const number = Math.floor(100000 + Math.random() * 900000);
|
||||||
|
return `${id}-${number}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = async (req, res) => {
|
||||||
const { event } = req.body;
|
const { event } = req.body;
|
||||||
|
|
||||||
if (event) {
|
try {
|
||||||
console.log(event);
|
const eventId = generateId(event.name);
|
||||||
res.sendStatus(201);
|
const currentTime = dayjs().unix();
|
||||||
} else {
|
|
||||||
|
const entity = {
|
||||||
|
key: req.datastore.key(['Event', eventId]),
|
||||||
|
data: {
|
||||||
|
name: event.name.trim(),
|
||||||
|
created: currentTime,
|
||||||
|
timezone: event.timezone,
|
||||||
|
startTime: event.startTime,
|
||||||
|
endTime: event.endTime,
|
||||||
|
dates: event.dates,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await req.datastore.insert(entity);
|
||||||
|
|
||||||
|
res.status(201).send({
|
||||||
|
id: eventId,
|
||||||
|
name: event.name.trim(),
|
||||||
|
created: currentTime,
|
||||||
|
timezone: event.timezone,
|
||||||
|
startTime: event.startTime,
|
||||||
|
endTime: event.endTime,
|
||||||
|
dates: event.dates,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
res.sendStatus(400);
|
res.sendStatus(400);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,45 @@
|
||||||
module.exports = (req, res) => {
|
const dayjs = require('dayjs');
|
||||||
|
const bcrypt = require('bcrypt');
|
||||||
|
|
||||||
|
module.exports = async (req, res) => {
|
||||||
const { eventId } = req.params;
|
const { eventId } = req.params;
|
||||||
const { person } = req.body;
|
const { person } = req.body;
|
||||||
|
|
||||||
if (eventId) {
|
try {
|
||||||
if (person) {
|
const event = (await req.datastore.get(req.datastore.key(['Event', eventId])))[0];
|
||||||
console.log(person);
|
|
||||||
res.sendStatus(201);
|
if (event) {
|
||||||
|
if (person) {
|
||||||
|
const currentTime = dayjs().unix();
|
||||||
|
|
||||||
|
// If password
|
||||||
|
let hash = null;
|
||||||
|
if (person.password) {
|
||||||
|
hash = await bcrypt.hash(person.password, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
const entity = {
|
||||||
|
key: req.datastore.key('Person'),
|
||||||
|
data: {
|
||||||
|
name: person.name.trim(),
|
||||||
|
password: hash,
|
||||||
|
eventId: eventId,
|
||||||
|
created: currentTime,
|
||||||
|
availability: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await req.datastore.insert(entity);
|
||||||
|
|
||||||
|
res.sendStatus(201);
|
||||||
|
} else {
|
||||||
|
res.sendStatus(400);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
res.sendStatus(400);
|
res.sendStatus(404);
|
||||||
}
|
}
|
||||||
} else {
|
} catch (e) {
|
||||||
res.sendStatus(404);
|
console.error(e);
|
||||||
|
res.sendStatus(400);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,19 @@
|
||||||
module.exports = (req, res) => {
|
module.exports = async (req, res) => {
|
||||||
const { eventId } = req.params;
|
const { eventId } = req.params;
|
||||||
|
|
||||||
if (eventId) {
|
try {
|
||||||
res.send({
|
const event = (await req.datastore.get(req.datastore.key(['Event', eventId])))[0];
|
||||||
id: 'event-name-4701240',
|
|
||||||
name: 'Event name',
|
if (event) {
|
||||||
eventCreated: 379642017932,
|
res.send({
|
||||||
timezone: '247',
|
id: eventId,
|
||||||
startTime: 0900,
|
...event,
|
||||||
endTime: 1700,
|
});
|
||||||
dates: [
|
} else {
|
||||||
'26022021',
|
res.sendStatus(404);
|
||||||
'27022021',
|
}
|
||||||
'28022021',
|
} catch (e) {
|
||||||
],
|
console.error(e);
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.sendStatus(404);
|
res.sendStatus(404);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,19 @@
|
||||||
module.exports = (req, res) => {
|
module.exports = async (req, res) => {
|
||||||
const { eventId } = req.params;
|
const { eventId } = req.params;
|
||||||
|
|
||||||
if (eventId) {
|
try {
|
||||||
|
const query = req.datastore.createQuery('Person').filter('eventId', eventId);
|
||||||
|
let people = (await req.datastore.runQuery(query))[0];
|
||||||
|
people = people.map(person => ({
|
||||||
|
name: person.name,
|
||||||
|
availability: person.availability,
|
||||||
|
}));
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
people: [
|
people,
|
||||||
{
|
|
||||||
name: 'Laura',
|
|
||||||
password: null,
|
|
||||||
eventId: 'event-name-4701240',
|
|
||||||
availability: [
|
|
||||||
[
|
|
||||||
'START',
|
|
||||||
'END',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'START',
|
|
||||||
'END',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
} else {
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
res.sendStatus(404);
|
res.sendStatus(404);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
32
crabfit-backend/routes/login.js
Normal file
32
crabfit-backend/routes/login.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
const bcrypt = require('bcrypt');
|
||||||
|
|
||||||
|
module.exports = async (req, res) => {
|
||||||
|
const { eventId, personName } = req.params;
|
||||||
|
const { person } = req.body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query = req.datastore.createQuery('Person')
|
||||||
|
.filter('eventId', eventId)
|
||||||
|
.filter('name', personName);
|
||||||
|
let personResult = (await req.datastore.runQuery(query))[0][0];
|
||||||
|
|
||||||
|
if (personResult) {
|
||||||
|
if (personResult.password) {
|
||||||
|
const passwordsMatch = person && person.password && await bcrypt.compare(person.password, personResult.password);
|
||||||
|
if (!passwordsMatch) {
|
||||||
|
return res.status(401).send('Incorrect password');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
name: personName,
|
||||||
|
availability: personResult.availability,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.sendStatus(404);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
res.sendStatus(404);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,9 +1,21 @@
|
||||||
const package = require('../package.json');
|
const package = require('../package.json');
|
||||||
|
|
||||||
module.exports = (req, res) => {
|
module.exports = async (req, res) => {
|
||||||
|
let eventCount = null;
|
||||||
|
let personCount = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query = req.datastore.createQuery(['__Stat_Kind__']);
|
||||||
|
|
||||||
|
eventCount = (await req.datastore.runQuery(query.filter('kind_name', 'Event')))[0].count;
|
||||||
|
personCount = (await req.datastore.runQuery(query.filter('kind_name', 'Person')))[0].count;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
eventCount: 0,
|
eventCount: eventCount || null,
|
||||||
personCount: 0,
|
personCount: personCount || null,
|
||||||
version: package.version,
|
version: package.version,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,37 @@
|
||||||
module.exports = (req, res) => {
|
const bcrypt = require('bcrypt');
|
||||||
const { eventId, personId } = req.params;
|
|
||||||
|
module.exports = async (req, res) => {
|
||||||
|
const { eventId, personName } = req.params;
|
||||||
const { person } = req.body;
|
const { person } = req.body;
|
||||||
|
|
||||||
if (eventId) {
|
try {
|
||||||
if (personId) {
|
const query = req.datastore.createQuery('Person')
|
||||||
if (person) {
|
.filter('eventId', eventId)
|
||||||
res.send(person);
|
.filter('name', personName);
|
||||||
|
let personResult = (await req.datastore.runQuery(query))[0][0];
|
||||||
|
|
||||||
|
if (personResult) {
|
||||||
|
if (person && person.availability) {
|
||||||
|
if (personResult.password) {
|
||||||
|
const passwordsMatch = person.password && await bcrypt.compare(person.password, personResult.password);
|
||||||
|
if (!passwordsMatch) {
|
||||||
|
return res.status(401).send('Incorrect password');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
personResult.availability = person.availability;
|
||||||
|
|
||||||
|
await req.datastore.upsert(personResult);
|
||||||
|
|
||||||
|
res.sendStatus(200);
|
||||||
} else {
|
} else {
|
||||||
res.sendStatus(400);
|
res.sendStatus(400);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
res.sendStatus(404);
|
res.sendStatus(404);
|
||||||
}
|
}
|
||||||
} else {
|
} catch (e) {
|
||||||
res.sendStatus(404);
|
console.error(e);
|
||||||
|
res.sendStatus(400);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,35 @@ schemes:
|
||||||
- "https"
|
- "https"
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
definitions:
|
||||||
|
Event:
|
||||||
|
type: "object"
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: "string"
|
||||||
|
name:
|
||||||
|
type: "string"
|
||||||
|
created:
|
||||||
|
type: "integer"
|
||||||
|
timezone:
|
||||||
|
type: "string"
|
||||||
|
startTime:
|
||||||
|
type: "string"
|
||||||
|
endTime:
|
||||||
|
type: "string"
|
||||||
|
dates:
|
||||||
|
type: "array"
|
||||||
|
items:
|
||||||
|
type: "string"
|
||||||
|
Person:
|
||||||
|
type: "object"
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: "string"
|
||||||
|
availability:
|
||||||
|
type: "array"
|
||||||
|
items:
|
||||||
|
type: "string"
|
||||||
paths:
|
paths:
|
||||||
"/stats":
|
"/stats":
|
||||||
get:
|
get:
|
||||||
|
|
@ -16,6 +45,15 @@ paths:
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: "OK"
|
description: "OK"
|
||||||
|
schema:
|
||||||
|
type: "object"
|
||||||
|
properties:
|
||||||
|
eventCount:
|
||||||
|
type: "integer"
|
||||||
|
personCount:
|
||||||
|
type: "integer"
|
||||||
|
version:
|
||||||
|
type: "string"
|
||||||
"/event/{eventId}":
|
"/event/{eventId}":
|
||||||
get:
|
get:
|
||||||
summary: "Return an event details"
|
summary: "Return an event details"
|
||||||
|
|
@ -24,11 +62,13 @@ paths:
|
||||||
- in: "path"
|
- in: "path"
|
||||||
name: "eventId"
|
name: "eventId"
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: "string"
|
||||||
description: "The ID of the event"
|
description: "The ID of the event"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: "OK"
|
description: "OK"
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Event'
|
||||||
404:
|
404:
|
||||||
description: "Not found"
|
description: "Not found"
|
||||||
"/event":
|
"/event":
|
||||||
|
|
@ -39,11 +79,27 @@ paths:
|
||||||
- in: "body"
|
- in: "body"
|
||||||
name: "event"
|
name: "event"
|
||||||
required: true
|
required: true
|
||||||
type: object
|
schema:
|
||||||
|
type: "object"
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: "string"
|
||||||
|
timezone:
|
||||||
|
type: "string"
|
||||||
|
startTime:
|
||||||
|
type: "integer"
|
||||||
|
endTime:
|
||||||
|
type: "integer"
|
||||||
|
dates:
|
||||||
|
type: "array"
|
||||||
|
items:
|
||||||
|
type: "string"
|
||||||
description: "New event details"
|
description: "New event details"
|
||||||
responses:
|
responses:
|
||||||
201:
|
201:
|
||||||
description: "Created"
|
description: "Created"
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Event'
|
||||||
400:
|
400:
|
||||||
description: "Invalid data"
|
description: "Invalid data"
|
||||||
"/event/{eventId}/people":
|
"/event/{eventId}/people":
|
||||||
|
|
@ -54,11 +110,18 @@ paths:
|
||||||
- in: "path"
|
- in: "path"
|
||||||
name: "eventId"
|
name: "eventId"
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: "string"
|
||||||
description: "The ID of the event"
|
description: "The ID of the event"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: "OK"
|
description: "OK"
|
||||||
|
schema:
|
||||||
|
type: "object"
|
||||||
|
properties:
|
||||||
|
people:
|
||||||
|
type: "array"
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/Person"
|
||||||
404:
|
404:
|
||||||
description: "Not found"
|
description: "Not found"
|
||||||
post:
|
post:
|
||||||
|
|
@ -68,12 +131,18 @@ paths:
|
||||||
- in: "path"
|
- in: "path"
|
||||||
name: "eventId"
|
name: "eventId"
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: "string"
|
||||||
description: "The ID of the event"
|
description: "The ID of the event"
|
||||||
- in: "body"
|
- in: "body"
|
||||||
name: "person"
|
name: "person"
|
||||||
required: true
|
required: true
|
||||||
type: object
|
schema:
|
||||||
|
type: "object"
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: "string"
|
||||||
|
password:
|
||||||
|
type: "string"
|
||||||
description: "New person details"
|
description: "New person details"
|
||||||
responses:
|
responses:
|
||||||
201:
|
201:
|
||||||
|
|
@ -82,7 +151,39 @@ paths:
|
||||||
description: "Not found"
|
description: "Not found"
|
||||||
400:
|
400:
|
||||||
description: "Invalid data"
|
description: "Invalid data"
|
||||||
"/event/{eventId}/people/{personId}":
|
"/event/{eventId}/people/{personName}":
|
||||||
|
get:
|
||||||
|
summary: "Login as this person"
|
||||||
|
operationId: "getPerson"
|
||||||
|
parameters:
|
||||||
|
- in: "path"
|
||||||
|
name: "eventId"
|
||||||
|
required: true
|
||||||
|
type: "string"
|
||||||
|
description: "The ID of the event"
|
||||||
|
- in: "path"
|
||||||
|
name: "personName"
|
||||||
|
required: true
|
||||||
|
type: "string"
|
||||||
|
description: "The name of the person"
|
||||||
|
- in: "body"
|
||||||
|
name: "person"
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: "object"
|
||||||
|
properties:
|
||||||
|
password:
|
||||||
|
type: "string"
|
||||||
|
description: "Login details"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "OK"
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/Person"
|
||||||
|
401:
|
||||||
|
description: "Incorrect password"
|
||||||
|
404:
|
||||||
|
description: "Not found"
|
||||||
patch:
|
patch:
|
||||||
summary: "Update this person's availabilities"
|
summary: "Update this person's availabilities"
|
||||||
operationId: "patchPerson"
|
operationId: "patchPerson"
|
||||||
|
|
@ -90,21 +191,31 @@ paths:
|
||||||
- in: "path"
|
- in: "path"
|
||||||
name: "eventId"
|
name: "eventId"
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: "string"
|
||||||
description: "The ID of the event"
|
description: "The ID of the event"
|
||||||
- in: "path"
|
- in: "path"
|
||||||
name: "personId"
|
name: "personName"
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: "string"
|
||||||
description: "The ID of the person"
|
description: "The name of the person"
|
||||||
- in: "body"
|
- in: "body"
|
||||||
name: "person"
|
name: "person"
|
||||||
required: true
|
required: true
|
||||||
type: object
|
schema:
|
||||||
|
type: "object"
|
||||||
|
properties:
|
||||||
|
password:
|
||||||
|
type: "string"
|
||||||
|
availability:
|
||||||
|
type: "array"
|
||||||
|
items:
|
||||||
|
type: "string"
|
||||||
description: "Updated person details"
|
description: "Updated person details"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: "OK"
|
description: "OK"
|
||||||
|
401:
|
||||||
|
description: "Incorrect password"
|
||||||
404:
|
404:
|
||||||
description: "Not found"
|
description: "Not found"
|
||||||
400:
|
400:
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
19
crabfit-frontend/deploy.sh
Normal file
19
crabfit-frontend/deploy.sh
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
yarn build
|
||||||
|
|
||||||
|
cd build
|
||||||
|
|
||||||
|
cat > app.yaml << EOF
|
||||||
|
runtime: nodejs12
|
||||||
|
handlers:
|
||||||
|
- url: /(.*\..+)$
|
||||||
|
static_files: \1
|
||||||
|
upload: (.*\..+)$
|
||||||
|
|
||||||
|
- url: /.*
|
||||||
|
static_files: index.html
|
||||||
|
upload: index.html
|
||||||
|
EOF
|
||||||
|
|
||||||
|
gcloud app deploy --project=crabfit
|
||||||
Loading…
Reference in a new issue