Updated README.md and configure-script.js

This commit is contained in:
2021-03-12 11:03:08 +11:00
parent 9b6c5af09d
commit 9c294ab961
10 changed files with 231 additions and 174 deletions
+62 -81
View File
@@ -1,103 +1,84 @@
//TODO: update this README
# MERN-template # MERN-template
A website template using the MERN stack. A website template using the MERN stack. The primary technology involved is:
# Setup Development * React
* Nodejs
* MariaDB (with Sequelize)
* Docker
To set up this template, please ensure mariadb is running on the host computer, and run `npm install` as normal. This template is designed to support the development of persistent browser based games (PBBGs), but it, and it's component microservices, can be used elsewhere.
1. Run `sql/create_database.sql` This template is released under the zlib license (see LICENSE).
2. Run `cp .envdev .env` and enter your details into the new file
3. Execute `npm run dev`
This should get the template working in development mode.
# Setup Deployment
Eventually, a clean install will be this easy:
```
git clone https://github.com/krgamestudios/MERN-template.git
npm run configure
docker-compose up --build
```
# Microservices # Microservices
There are external components to this template referred to as "microservices". These can be omitted entirely by simply removing the React component that accesses them. There are external components to this template referred to as "microservices". These can be omitted entirely by simply removing the React components that access them. These are also available via [docker hub](https://hub.docker.com/u/krgamestudios).
* News Server: https://github.com/krgamestudios/news-server
* Auth Server: https://github.com/krgamestudios/auth-server * Auth Server: https://github.com/krgamestudios/auth-server
* News Server: https://github.com/krgamestudios/news-server
* Chat Server: https://github.com/krgamestudios/chat-server * Chat Server: https://github.com/krgamestudios/chat-server
# TODO list # Setup Deployment
- Account system A clean install is this easy:
- A separate authentication server
- Administration Panel
- inspect aggregate user data
- Moderation tools for banning, suspending, or chat-banning users
- Chat system (microservice)
- Based on usernames
- Chat logs
- Custom emoji
- Moderation tools
- Global chat channels
- User-created chat channels with an invite/search system
- Private messages
- Icons/roles next to username
- Better compression for client files
- Some form of bot/alt account detection?
- A payment system
- Backend for energy systems
- Backend for trading and leaderboards
- inventory
- stats
- shop
- currency
- random events
- Dcumentation and tutorials
- Server-side actions to allow offline progress or progress on spotty internet connections
- Full tutorial for setting up and using the site
- Start here page
- Security holes
- HTTPS
- Default admin account
- Information about legal requirements of the developers using this template
- Privacy policy & data collection notices
# DONE list ```
git clone https://github.com/krgamestudios/MERN-template.git
node run configure-script.js
docker-compose up --build
```
- Legal Requirements: # Setup Development
- ~~Physical Mailing Address Config (for emails)~~
- ~~Opt-out option (for emails)~~ To set up this template in development mode:
- ~~Privacy policy & data collection notices~~
- ~~LICENSE file~~ 1. Ensure mariadb is running in your development environment
- ~~annoying "This site uses cookies" message~~ 2. Run `mariadb sql/create_database.sql` as the root user
- Account system 3. Run `npm install`
- ~~sign up~~ 4. Run `cp .envdev .env` and enter your details into the `.env` file
- ~~validate email~~ 5. Execute `npm run dev`
- ~~login (with cookies)~~ 6. Navigate to `http://localhost:3001` in your web browser
- ~~logout (with cookies)~~
- ~~account deletion~~ # Features List
- ~~Change passwords~~
- Administration Panel - Fully Featured Account System
- ~~Default admin account~~ - Email validation
- ~~Exclusive to admin accounts~~ - Logging in and out
- News blog system (microservice) - Account deletion
- ~~build the microservice to provide the news feed~~ - Password management
- ~~access an external news feed~~ - Fully Featured Administration Panel
- ~~admin panel for publishing and editing news~~ - A default admin account (if desired)
- ~~"created at" and "updated at" in the response~~ - News Blog
- Configuraton Script: - Optional microservice
- Secure publishing and editing of articles
- Easy To Use Configuraton Script:
- ~~Default UUID keys~~ - ~~Default UUID keys~~
- ~~Docker, docker, docker.~~ - ~~Docker, docker, docker.~~
# Email settings # Coming Soon
Some of the external requirements can be tricky, so let me outline what is needed. If you decide to use gmail as your email provider, then use the following `.env` settings: - Full documentation
- Setup tutorial
- Fully Featured Chat System
- Optional microservice
- Chat logs
- Custom emoji
- Global and room-based chat
- Private messaging?
- Broadcasting to all channels
- Badges next to usernames?
- Moderation tools for banning, suspending, or chat-banning users
# Coming Eventually
- Better compression for client files
- Backend for energy systems
- Backend for leaderboards
- Backend for items, shops, trading and currency
# Gmail Email Settings
If you decide to use gmail as your email provider (as I do), then use the following `.env` settings:
MAIL_SMTP=smtp.gmail.com MAIL_SMTP=smtp.gmail.com
MAIL_USERNAME=you@gmail.com MAIL_USERNAME=you@gmail.com
+1 -1
View File
@@ -7,7 +7,7 @@ import LazyRoute from './lazy-route';
import Markdown from './panels/markdown'; import Markdown from './panels/markdown';
//styling //styling
//TODO: styling import //import a styling template here
//common components //common components
import Header from './panels/header.jsx'; import Header from './panels/header.jsx';
+23 -1
View File
@@ -3,6 +3,8 @@ import { Redirect } from 'react-router-dom';
import { TokenContext } from '../utilities/token-provider'; import { TokenContext } from '../utilities/token-provider';
const validateEmail = require('../../../common/utilities/validate-email');
const LogIn = props => { const LogIn = props => {
//context //context
const authTokens = useContext(TokenContext); const authTokens = useContext(TokenContext);
@@ -55,7 +57,13 @@ const LogIn = props => {
//DOCS: returns two values: err and authTokens //DOCS: returns two values: err and authTokens
const handleSubmit = async (email, password) => { const handleSubmit = async (email, password) => {
email = email.trim(); //TODO: validate email on login email = email.trim();
const err = handleValidation(email, password);
if (err) {
return [err, false];
}
//send to the auth server //send to the auth server
const result = await fetch(`${process.env.AUTH_URI}/login`, { const result = await fetch(`${process.env.AUTH_URI}/login`, {
@@ -82,4 +90,18 @@ const handleSubmit = async (email, password) => {
return [null, newTokens]; return [null, newTokens];
}; };
//returns an error message, or null on success
const handleValidation = (email, password) => {
if (!validateEmail(email)) {
return 'invalid email';
}
if (password.length < 8) {
return 'invalid password (Must be at least 8 characters long)';
}
return null;
};
export default LogIn; export default LogIn;
+2 -2
View File
@@ -4,8 +4,8 @@ import { Redirect } from 'react-router-dom';
import { TokenContext } from '../utilities/token-provider'; import { TokenContext } from '../utilities/token-provider';
//utilities //utilities
const validateEmail = require('../../../common/utilities/validate-email.js'); const validateEmail = require('../../../common/utilities/validate-email');
const validateUsername = require('../../../common/utilities/validate-username.js'); const validateUsername = require('../../../common/utilities/validate-username');
const SignUp = props => { const SignUp = props => {
//context //context
-1
View File
@@ -4,4 +4,3 @@ MERN Template developed by Kayne Ruse, KR Game Studios
[https://github.com/krgamestudios/MERN-template](https://github.com/krgamestudios/MERN-template) [https://github.com/krgamestudios/MERN-template](https://github.com/krgamestudios/MERN-template)
TODO: generate the credits using config script
-1
View File
@@ -1,3 +1,2 @@
# Privacy Policy # Privacy Policy
TODO: generate the privacy policy using config script
+142 -82
View File
@@ -1,5 +1,3 @@
//TODO: update this file
//setup //setup
const readline = require('readline'); const readline = require('readline');
const fs = require('fs'); const fs = require('fs');
@@ -14,52 +12,92 @@ const rl = readline.createInterface({
}); });
//manually promisify this (util didn't work) //manually promisify this (util didn't work)
const question = (prompt, def) => { const question = (prompt, def = null) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
rl.question(`${prompt} (${def}): `, answer => { rl.question(`${prompt}${def ? ` (${def})` : ''}: `, answer => {
resolve(answer || def); //loop on required
if (def === null && !answer) {
return resolve(question(prompt, def));
}
return resolve(answer || def);
}); });
}); });
}; };
//questions //questions
(async () => { (async () => {
console.log(
`This configure script will generate the following files:
* docker-compose.yml
* Dockerfile
* startup.sql
Currently, all microservices are mandatory; you'll have to mess with the result
and the source code if you wish to be more selective. Microservices currently
impelented are:
* auth-server
* news-server
See https://github.com/krgamestudios/MERN-template/wiki for help.
`
);
//project configuration //project configuration
const projectName = await question('Project Name', 'template'); const projectName = await question('Project Name', 'template');
const projectWebAddress = await question('Project Web Address', 'example.com'); const projectWebAddress = await question('Project Web Address', 'example.com');
const projectMailSMTP = await question('Project Mail SMTP', 'smtp.example.com');
const projectMailUser = await question('Project Mail Username', 'foobar@example.com'); const projectDBUser = await question('Project DB Username', projectName);
const projectMailPass = await question('Project Mail Password', 'foobar'); const projectDBPass = await question('Project DB Password', 'pikachu');
const projectMailPhysical = await question('Project Physical Mailing Address', '');
const projectDBUser = await question('Project Database Username', projectName);
const projectDBPass = await question('Project Database Password', 'pikachu');
//news configuration //news configuration
const newsName = await question('News Name', 'news'); const newsName = await question('News Name', 'news');
const newsWebAddress = await question('News Web Address', 'news.example.com'); const newsWebAddress = await question('News Web Address', `${newsName}.${projectWebAddress}`);
const newsDBUser = await question('News Database Username', newsName); const newsDBUser = await question('News DB Username', newsName);
const newsDBPass = await question('News Database Password', 'charizard'); const newsDBPass = await question('News DB Password', 'charizard');
const newsKey = await question('News Query Key', uuid());
//chat configuration //auth configuration
const chatName = await question('Chat Name', 'chat'); const authName = await question('Auth Name', 'auth');
const chatWebAddress = await question('Chat Web Address', 'chat.example.com'); const authWebAddress = await question('Auth Web Address', `${authName}.${projectWebAddress}`);
const chatDBUser = await question('Chat Database Username', chatName); const authDBUser = await question('Auth DB Username', authName);
const chatDBPass = await question('Chat Database Password', 'blastoise'); const authDBPass = await question('Auth DB Password', 'venusaur');
const chatKey = await question('Chat Reservation Key', uuid());
const emailSMTP = await question('Email SMTP', 'smtp.example.com');
const emailUser = await question('Email Address', 'foobar@example.com');
const emailPass = await question('Email Password', 'foobar');
const emailPhysical = await question('Physical Mailing Address', '');
//chat goes here
//database configuration //database configuration
const databaseRootPassword = await question('Database Root Password', 'password'); const dbRootPassword = await question('Database Root Password', 'password');
const databaseTimeZone = await question('Database Timezone', 'Australia/Sydney'); const dbTimeZone = await question('Database Timezone', 'Australia/Sydney');
//joint configuration
const accessToken = await question('Access Token Secret', uuid(32));
const refreshToken = await question('Refresh Token Secret', uuid(32));
console.log('--Leave "Default User" blank if you don\'t want one--');
const defaultUser = await question('Default Admin User', '');
//MUST be at least 8 chars
let tmpPass = '';
while (defaultUser && tmpPass.length < 8) {
console.log('--All passwords must be at least 8 characters long--');
tmpPass = await question('Default Admin Pass', '');
}
const defaultPass = tmpPass;
//traefic configuration //traefic configuration
const supportEmail = await question('Support Email', projectMailUser); const supportEmail = await question('Support Email', emailUser);
//other random values //misc. configuration
const sessionSecret = uuid(); //for session randomness const projectPort = 3000;
const sessionAdmin = uuid(128); //for checking if user is admin const newsPort = 3100;
const authPort = 3200;
//TODO: Implement chat-server as a docker container //const chatPort = 3300;
const ymlfile = ` const ymlfile = `
version: "3.6" version: "3.6"
@@ -67,33 +105,24 @@ services:
${projectName}: ${projectName}:
build: . build: .
ports: ports:
- "3000" - "${projectPort}"
labels: labels:
- "traefik.enable=true" - traefik.enable=true
- "traefik.http.routers.${projectName}router.rule=Host(\`${projectWebAddress}\`)" - traefik.http.routers.${projectName}router.rule=Host(\`${projectWebAddress}\`)
- "traefik.http.routers.${projectName}router.entrypoints=websecure" - traefik.http.routers.${projectName}router.entrypoints=websecure
- "traefik.http.routers.${projectName}router.tls.certresolver=myresolver" - traefik.http.routers.${projectName}router.tls.certresolver=myresolver
- "traefik.http.routers.${projectName}router.service=${projectName}service@docker" - traefik.http.routers.${projectName}router.service=${projectName}service@docker
- "traefik.http.services.${projectName}service.loadbalancer.server.port=3000" - traefik.http.services.${projectName}service.loadbalancer.server.port=${projectPort}
environment: environment:
- WEB_PROTOCOL=https
- WEB_ADDRESS=${projectWebAddress}
- WEB_PORT=3000 - WEB_PORT=3000
- MAIL_SMTP=${projectMailSMTP}
- MAIL_USERNAME=${projectMailUser}
- MAIL_PASSWORD=${projectMailPass}
- MAIL_PHYSICAL=${projectMailPhysical}
- DB_HOSTNAME=database - DB_HOSTNAME=database
- DB_DATABASE=${projectName} - DB_DATABASE=${projectName}
- DB_USERNAME=${projectDBUser} - DB_USERNAME=${projectDBUser}
- DB_PASSWORD=${projectDBPass} - DB_PASSWORD=${projectDBPass}
- DB_TIMEZONE=${databaseTimeZone} - DB_TIMEZONE=${dbTimeZone}
- SESSION_SECRET=${sessionSecret}
- SESSION_ADMIN=${sessionAdmin}
- NEWS_URI=https://${newsWebAddress}/news - NEWS_URI=https://${newsWebAddress}/news
- NEWS_KEY=${newsKey} - AUTH_URI=https://${authWebAddress}/auth
- CHAT_URI=https://${chatWebAddress}/chat - SECRET_ACCESS=${accessToken}
- CHAT_KEY=${chatKey}
networks: networks:
- app-network - app-network
depends_on: depends_on:
@@ -103,23 +132,57 @@ services:
${newsName}: ${newsName}:
image: krgamestudios/news-server:latest image: krgamestudios/news-server:latest
ports: ports:
- "3100" - ${newsPort}
labels: labels:
- "traefik.enable=true" - traefik.enable=true
- "traefik.http.routers.${newsName}router.rule=Host(\`${newsWebAddress}\`)" - traefik.http.routers.${newsName}router.rule=Host(\`${newsWebAddress}\`)
- "traefik.http.routers.${newsName}router.entrypoints=websecure" - traefik.http.routers.${newsName}router.entrypoints=websecure
- "traefik.http.routers.${newsName}router.tls.certresolver=myresolver" - traefik.http.routers.${newsName}router.tls.certresolver=myresolver
- "traefik.http.routers.${newsName}router.service=${newsName}service@docker" - traefik.http.routers.${newsName}router.service=${newsName}service@docker
- "traefik.http.services.${newsName}service.loadbalancer.server.port=3100" - traefik.http.services.${newsName}service.loadbalancer.server.port=3100
environment: environment:
- WEB_PORT=3100 - WEB_PORT=3100
- DB_HOSTNAME=database - DB_HOSTNAME=database
- DB_DATABASE=${newsName} - DB_DATABASE=${newsName}
- DB_USERNAME=${newsDBUser} - DB_USERNAME=${newsDBUser}
- DB_PASSWORD=${newsDBPass} - DB_PASSWORD=${newsDBPass}
- DB_TIMEZONE=${databaseTimeZone} - DB_TIMEZONE=${dbTimeZone}
- QUERY_LIMIT=10 - QUERY_LIMIT=10
- QUERY_KEY=${newsKey} - SECRET_ACCESS=${accessToken}
networks:
- app-network
depends_on:
- database
- traefik
${authName}:
image: krgamestudios/news-server:latest
ports:
- ${authPort}
labels:
- traefik.enable=true
- traefik.http.routers.${authName}router.rule=Host(\`${authWebAddress}\`)
- traefik.http.routers.${authName}router.entrypoints=websecure
- traefik.http.routers.${authName}router.tls.certresolver=myresolver
- traefik.http.routers.${authName}router.service=${authName}service@docker
- traefik.http.services.${authName}service.loadbalancer.server.port=${authPort}
environment:
- WEB_PROTOCOL=https
- WEB_ADDRESS=${authWebAddress}
- WEB_PORT=${authPort}
- DB_HOSTNAME=database
- DB_DATABASE=${authName}
- DB_USERNAME=${authDBUser}
- DB_PASSWORD=${authDBPass}
- DB_TIMEZONE=${dbTimeZone}
- MAIL_SMTP=${emailSMTP}
- MAIL_USERNAME=${emailUser}
- MAIL_PASSWORD=${emailPass}
- MAIL_PHYSICAL=${emailPhysical}
- ADMIN_DEFAULT_USERNAME=${defaultUser}
- ADMIN_DEFAULT_PASSWORD=${defaultPass}
- SECRET_ACCESS=${accessToken}
- SECRET_REFRESH=${refreshToken}
networks: networks:
- app-network - app-network
depends_on: depends_on:
@@ -128,39 +191,37 @@ services:
#chat: #chat:
# image: krgamestudios/chat-server # image: krgamestudios/chat-server
# ports:
# - "3200:3200"
database: database:
image: mariadb image: mariadb
restart: always restart: always
environment: environment:
MYSQL_ROOT_PASSWORD: ${databaseRootPassword} MYSQL_ROOT_PASSWORD: ${dbRootPassword}
volumes: volumes:
- "./mysql:/var/lib/mysql" - ./mysql:/var/lib/mysql
- "./startup.sql:/docker-entrypoint-initdb.d/startup.sql:ro" - ./startup.sql:/docker-entrypoint-initdb.d/startup.sql:ro
networks: networks:
- app-network - app-network
traefik: traefik:
image: "traefik:v2.4" image: traefik:v2.4
container_name: "traefik" container_name: traefik
command: command:
- "--log.level=ERROR" - --log.level=ERROR
- "--api.insecure=false" - --api.insecure=false
- "--providers.docker=true" - --providers.docker=true
- "--providers.docker.exposedbydefault=false" - --providers.docker.exposedbydefault=false
- "--entrypoints.websecure.address=:443" - --entrypoints.websecure.address=:443
- "--certificatesresolvers.myresolver.acme.tlschallenge=true" - --certificatesresolvers.myresolver.acme.tlschallenge=true
- "--certificatesresolvers.myresolver.acme.email=${supportEmail}" - --certificatesresolvers.myresolver.acme.email=${supportEmail}
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json" - --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json
- " traefik.docker.network=app-network" - traefik.docker.network=app-network
ports: ports:
- "80:80" - 80:80
- "443:443" - 443:443
volumes: volumes:
- "./letsencrypt:/letsencrypt" - ./letsencrypt:/letsencrypt
- "/var/run/docker.sock:/var/run/docker.sock:ro" - /var/run/docker.sock:/var/run/docker.sock:ro
networks: networks:
- app-network - app-network
@@ -175,7 +236,7 @@ WORKDIR "/app"
COPY package*.json ./ COPY package*.json ./
RUN npm install RUN npm install
COPY . /app COPY . /app
EXPOSE 3000 EXPOSE ${projectPort}
ENTRYPOINT ["bash", "-c"] ENTRYPOINT ["bash", "-c"]
CMD ["sleep 10 && npm start"] CMD ["sleep 10 && npm start"]
`; `;
@@ -189,9 +250,9 @@ CREATE DATABASE IF NOT EXISTS ${newsName};
CREATE USER IF NOT EXISTS '${newsDBUser}'@'%' IDENTIFIED BY '${newsDBPass}'; CREATE USER IF NOT EXISTS '${newsDBUser}'@'%' IDENTIFIED BY '${newsDBPass}';
GRANT ALL PRIVILEGES ON ${newsName}.* TO '${newsDBUser}'@'%'; GRANT ALL PRIVILEGES ON ${newsName}.* TO '${newsDBUser}'@'%';
CREATE DATABASE IF NOT EXISTS ${chatName}; CREATE DATABASE IF NOT EXISTS ${authName};
CREATE USER IF NOT EXISTS '${chatDBUser}'@'%' IDENTIFIED BY '${chatDBPass}'; CREATE USER IF NOT EXISTS '${authDBUser}'@'%' IDENTIFIED BY '${authDBPass}';
GRANT ALL PRIVILEGES ON ${chatName}.* TO '${chatDBUser}'@'%'; GRANT ALL PRIVILEGES ON ${authName}.* TO '${authDBUser}'@'%';
FLUSH PRIVILEGES; FLUSH PRIVILEGES;
`; `;
@@ -203,4 +264,3 @@ FLUSH PRIVILEGES;
.then(() => rl.close()) .then(() => rl.close())
.catch(e => console.error(e)) .catch(e => console.error(e))
; ;
-2
View File
@@ -1,5 +1,3 @@
//TODO: move this to the wiki?
# Setup Tutorial # Setup Tutorial
Last Updated February 15th 2021 Last Updated February 15th 2021
+1 -1
View File
@@ -1,3 +1,3 @@
module.exports = { module.exports = {
//TODO: models //import models
} }
-2
View File
@@ -1,5 +1,3 @@
#TODO: move this into configure-script.js
#This file only needs to be run once, during initial development setup #This file only needs to be run once, during initial development setup
#This file isnt needed for actual deployment #This file isnt needed for actual deployment