Backend
This commit is contained in:
parent
15d4e2f126
commit
edcd4dcaa0
|
|
@ -3,4 +3,6 @@
|
|||
.git
|
||||
.gitignore
|
||||
|
||||
.env
|
||||
|
||||
node_modules/
|
||||
|
|
|
|||
1
crabfit-backend/.gitignore
vendored
1
crabfit-backend/.gitignore
vendored
|
|
@ -1,6 +1,7 @@
|
|||
/node_modules
|
||||
|
||||
.DS_Store
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
runtime: nodejs10
|
||||
entrypoint: node index.js
|
||||
runtime: nodejs
|
||||
service: api
|
||||
env: flex
|
||||
|
||||
automatic_scaling:
|
||||
min_num_instances: 1
|
||||
max_num_instances: 4
|
||||
|
||||
endpoints_api_service:
|
||||
name: api-dot-crabfit.appspot.com
|
||||
|
|
|
|||
|
|
@ -1,16 +1,30 @@
|
|||
require('dotenv').config();
|
||||
|
||||
const { Datastore } = require('@google-cloud/datastore');
|
||||
const express = require('express');
|
||||
|
||||
const package = require('./package.json');
|
||||
const app = express();
|
||||
const port = 8080;
|
||||
|
||||
const stats = require('./routes/stats');
|
||||
const getEvent = require('./routes/getEvent');
|
||||
const createEvent = require('./routes/createEvent');
|
||||
const getPeople = require('./routes/getPeople');
|
||||
const createPerson = require('./routes/createPerson');
|
||||
const login = require('./routes/login');
|
||||
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((req, res, next) => {
|
||||
req.datastore = datastore;
|
||||
next();
|
||||
});
|
||||
|
||||
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.get('/event/:eventId/people', getPeople);
|
||||
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, () => {
|
||||
console.log(`Crabfit API listening at http://localhost:${port}`)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,17 @@
|
|||
"author": "Ben Grant",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google-cloud/datastore": "^6.3.1",
|
||||
"bcrypt": "^5.0.1",
|
||||
"dayjs": "^1.10.4",
|
||||
"dotenv": "^8.2.0",
|
||||
"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;
|
||||
|
||||
if (event) {
|
||||
console.log(event);
|
||||
res.sendStatus(201);
|
||||
} else {
|
||||
try {
|
||||
const eventId = generateId(event.name);
|
||||
const currentTime = dayjs().unix();
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 { person } = req.body;
|
||||
|
||||
if (eventId) {
|
||||
if (person) {
|
||||
console.log(person);
|
||||
res.sendStatus(201);
|
||||
try {
|
||||
const event = (await req.datastore.get(req.datastore.key(['Event', eventId])))[0];
|
||||
|
||||
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 {
|
||||
res.sendStatus(400);
|
||||
res.sendStatus(404);
|
||||
}
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.sendStatus(400);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,21 +1,19 @@
|
|||
module.exports = (req, res) => {
|
||||
module.exports = async (req, res) => {
|
||||
const { eventId } = req.params;
|
||||
|
||||
if (eventId) {
|
||||
res.send({
|
||||
id: 'event-name-4701240',
|
||||
name: 'Event name',
|
||||
eventCreated: 379642017932,
|
||||
timezone: '247',
|
||||
startTime: 0900,
|
||||
endTime: 1700,
|
||||
dates: [
|
||||
'26022021',
|
||||
'27022021',
|
||||
'28022021',
|
||||
],
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
const event = (await req.datastore.get(req.datastore.key(['Event', eventId])))[0];
|
||||
|
||||
if (event) {
|
||||
res.send({
|
||||
id: eventId,
|
||||
...event,
|
||||
});
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.sendStatus(404);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,27 +1,19 @@
|
|||
module.exports = (req, res) => {
|
||||
module.exports = async (req, res) => {
|
||||
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({
|
||||
people: [
|
||||
{
|
||||
name: 'Laura',
|
||||
password: null,
|
||||
eventId: 'event-name-4701240',
|
||||
availability: [
|
||||
[
|
||||
'START',
|
||||
'END',
|
||||
],
|
||||
[
|
||||
'START',
|
||||
'END',
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
people,
|
||||
});
|
||||
} else {
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
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');
|
||||
|
||||
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({
|
||||
eventCount: 0,
|
||||
personCount: 0,
|
||||
eventCount: eventCount || null,
|
||||
personCount: personCount || null,
|
||||
version: package.version,
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,18 +1,37 @@
|
|||
module.exports = (req, res) => {
|
||||
const { eventId, personId } = req.params;
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
module.exports = async (req, res) => {
|
||||
const { eventId, personName } = req.params;
|
||||
const { person } = req.body;
|
||||
|
||||
if (eventId) {
|
||||
if (personId) {
|
||||
if (person) {
|
||||
res.send(person);
|
||||
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 (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 {
|
||||
res.sendStatus(400);
|
||||
}
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.sendStatus(400);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,6 +8,35 @@ schemes:
|
|||
- "https"
|
||||
produces:
|
||||
- "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:
|
||||
"/stats":
|
||||
get:
|
||||
|
|
@ -16,6 +45,15 @@ paths:
|
|||
responses:
|
||||
200:
|
||||
description: "OK"
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
eventCount:
|
||||
type: "integer"
|
||||
personCount:
|
||||
type: "integer"
|
||||
version:
|
||||
type: "string"
|
||||
"/event/{eventId}":
|
||||
get:
|
||||
summary: "Return an event details"
|
||||
|
|
@ -24,11 +62,13 @@ paths:
|
|||
- in: "path"
|
||||
name: "eventId"
|
||||
required: true
|
||||
type: string
|
||||
type: "string"
|
||||
description: "The ID of the event"
|
||||
responses:
|
||||
200:
|
||||
description: "OK"
|
||||
schema:
|
||||
$ref: '#/definitions/Event'
|
||||
404:
|
||||
description: "Not found"
|
||||
"/event":
|
||||
|
|
@ -39,11 +79,27 @@ paths:
|
|||
- in: "body"
|
||||
name: "event"
|
||||
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"
|
||||
responses:
|
||||
201:
|
||||
description: "Created"
|
||||
schema:
|
||||
$ref: '#/definitions/Event'
|
||||
400:
|
||||
description: "Invalid data"
|
||||
"/event/{eventId}/people":
|
||||
|
|
@ -54,11 +110,18 @@ paths:
|
|||
- in: "path"
|
||||
name: "eventId"
|
||||
required: true
|
||||
type: string
|
||||
type: "string"
|
||||
description: "The ID of the event"
|
||||
responses:
|
||||
200:
|
||||
description: "OK"
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
people:
|
||||
type: "array"
|
||||
items:
|
||||
$ref: "#/definitions/Person"
|
||||
404:
|
||||
description: "Not found"
|
||||
post:
|
||||
|
|
@ -68,12 +131,18 @@ paths:
|
|||
- in: "path"
|
||||
name: "eventId"
|
||||
required: true
|
||||
type: string
|
||||
type: "string"
|
||||
description: "The ID of the event"
|
||||
- in: "body"
|
||||
name: "person"
|
||||
required: true
|
||||
type: object
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
name:
|
||||
type: "string"
|
||||
password:
|
||||
type: "string"
|
||||
description: "New person details"
|
||||
responses:
|
||||
201:
|
||||
|
|
@ -82,7 +151,39 @@ paths:
|
|||
description: "Not found"
|
||||
400:
|
||||
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:
|
||||
summary: "Update this person's availabilities"
|
||||
operationId: "patchPerson"
|
||||
|
|
@ -90,21 +191,31 @@ paths:
|
|||
- in: "path"
|
||||
name: "eventId"
|
||||
required: true
|
||||
type: string
|
||||
type: "string"
|
||||
description: "The ID of the event"
|
||||
- in: "path"
|
||||
name: "personId"
|
||||
name: "personName"
|
||||
required: true
|
||||
type: string
|
||||
description: "The ID of the person"
|
||||
type: "string"
|
||||
description: "The name of the person"
|
||||
- in: "body"
|
||||
name: "person"
|
||||
required: true
|
||||
type: object
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
password:
|
||||
type: "string"
|
||||
availability:
|
||||
type: "array"
|
||||
items:
|
||||
type: "string"
|
||||
description: "Updated person details"
|
||||
responses:
|
||||
200:
|
||||
description: "OK"
|
||||
401:
|
||||
description: "Incorrect password"
|
||||
404:
|
||||
description: "Not found"
|
||||
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