Compare commits

..

No commits in common. "single_docker" and "dev/prod" have entirely different histories.

18 changed files with 67 additions and 238 deletions

View file

@ -1,106 +1,6 @@
FROM node:12.16.1 as base
ENV APP_HOME=/usr/src/app \
TERM=xterm
# Merge in mongodb to have a single image for Plesk deploy
# Code from: https://github.com/docker-library/mongo/blob/c9d4281a620f1e1ee95aaa084c5b60fc68ba65b9/3.6/Dockerfile
# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN groupadd -r mongodb && useradd -r -g mongodb mongodb
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
ca-certificates \
jq \
numactl \
; \
if ! command -v ps > /dev/null; then \
apt-get install -y --no-install-recommends procps; \
fi; \
rm -rf /var/lib/apt/lists/*
# grab gosu for easy step-down from root (https://github.com/tianon/gosu/releases)
ENV GOSU_VERSION 1.12
# grab "js-yaml" for parsing mongod's YAML config files (https://github.com/nodeca/js-yaml/releases)
ENV JSYAML_VERSION 3.13.1
RUN set -ex; \
\
savedAptMark="$(apt-mark showmanual)"; \
apt-get update; \
apt-get install -y --no-install-recommends \
wget \
; \
if ! command -v gpg > /dev/null; then \
apt-get install -y --no-install-recommends gnupg dirmngr; \
savedAptMark="$savedAptMark gnupg dirmngr"; \
elif gpg --version | grep -q '^gpg (GnuPG) 1\.'; then \
# "This package provides support for HKPS keyservers." (GnuPG 1.x only)
apt-get install -y --no-install-recommends gnupg-curl; \
fi; \
rm -rf /var/lib/apt/lists/*; \
\
dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
export GNUPGHOME="$(mktemp -d)"; \
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
command -v gpgconf && gpgconf --kill all || :; \
rm -r "$GNUPGHOME" /usr/local/bin/gosu.asc; \
\
wget -O /js-yaml.js "https://github.com/nodeca/js-yaml/raw/${JSYAML_VERSION}/dist/js-yaml.js"; \
# TODO some sort of download verification here
\
apt-mark auto '.*' > /dev/null; \
apt-mark manual $savedAptMark > /dev/null; \
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
\
# smoke test
chmod +x /usr/local/bin/gosu; \
gosu --version; \
gosu nobody true
RUN mkdir /docker-entrypoint-initdb.d
ENV GPG_KEYS=2930ADAE8CAF5059EE73BB4B58712A2291FA4AD5
RUN set -ex; export GNUPGHOME="$(mktemp -d)"; for key in $GPG_KEYS; do gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; done; gpg --batch --export $GPG_KEYS > /etc/apt/trusted.gpg.d/mongodb.gpg; command -v gpgconf && gpgconf --kill all || :; rm -r "$GNUPGHOME"; apt-key list
# Allow build-time overrides (eg. to build image with MongoDB Enterprise version)
# Options for MONGO_PACKAGE: mongodb-org OR mongodb-enterprise
# Options for MONGO_REPO: repo.mongodb.org OR repo.mongodb.com
# Example: docker build --build-arg MONGO_PACKAGE=mongodb-enterprise --build-arg MONGO_REPO=repo.mongodb.com .
ARG MONGO_PACKAGE=mongodb-org
ARG MONGO_REPO=repo.mongodb.org
ENV MONGO_PACKAGE=${MONGO_PACKAGE} MONGO_REPO=${MONGO_REPO}
ENV MONGO_MAJOR 3.6
ENV MONGO_VERSION 3.6.21
# bashbrew-architectures:amd64 arm64v8
RUN echo "deb http://$MONGO_REPO/apt/debian stretch/${MONGO_PACKAGE%-unstable}/$MONGO_MAJOR main" | tee "/etc/apt/sources.list.d/${MONGO_PACKAGE%-unstable}.list"
RUN set -x \
# installing "mongodb-enterprise" pulls in "tzdata" which prompts for input
&& export DEBIAN_FRONTEND=noninteractive \
&& apt-get update \
&& apt-get install -y \
${MONGO_PACKAGE}=$MONGO_VERSION \
${MONGO_PACKAGE}-server=$MONGO_VERSION \
${MONGO_PACKAGE}-shell=$MONGO_VERSION \
${MONGO_PACKAGE}-mongos=$MONGO_VERSION \
${MONGO_PACKAGE}-tools=$MONGO_VERSION \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /var/lib/mongodb \
&& mv /etc/mongod.conf /etc/mongod.conf.orig
RUN mkdir -p /data/db /data/configdb \
&& chown -R mongodb:mongodb /data/db /data/configdb
VOLUME /data/db /data/configdb
# End mongo
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
EXPOSE 8000
@ -127,11 +27,5 @@ ENV NODE_ENV=production
COPY package.json package-lock.json index.js ./
RUN npm install --production
RUN npm rebuild node-sass
COPY dist/static/images/* ./dist/static/images/
COPY dist/static/assets ./dist/static/assets
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod 755 /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
# COPY --from=build $APP_HOME/dist ./dist
CMD ["npm", "run", "start:prod"]

View file

@ -10,8 +10,6 @@ There is a hidden link to have an overview of all users registered on the server
# Deployment
## Manual
* Push to git (in case linting fails on commit use `git commit --no-verify`)
* Pull on server.
* `service p5.js-web-editor stop`
@ -19,32 +17,15 @@ There is a hidden link to have an overview of all users registered on the server
* `npm run build:client`
* `service p5.js-web-editor start`
## Docker
Branch `single_docker` has a Frankenstein Dockerfile to embed mongo in the single image for deployment to Plex (which doesn't know docker-compose...)
To build an image called `digitalplayground`:
`docker build -t digitalplayground .`
To run it we provide the port to publish, and need to map the volumes of mongo for persistent storage (don't use the tmp folder from the example if you really want persistent storage):
`docker run -it --env-file .env.production --publish 8001:8000 -v /tmp/dp/db:/data/db -v /tmp/dp/configdb:/data/configdb --name dp_app digitalplayground`
Export the image using:
`docker save digitalplayground > dp_docker.tar`
## s3 storage
Gebruik s3cmd om bucket in te stellen.
### Make bucket:
## Make bucket:
s3cmd mb s3://digitalplayground-p5
### Set the CORS rules
## Set the CORS rules
s3cmd -c .s3cfg setcors CORS.xml s3://digitalplayground-p5
### Delete the CORS rules
## Delete the CORS rules
s3cmd -c .s3cfg delcors s3://digitalplayground-p5
### Get bucket info including CORS rules
## Get bucket info including CORS rules
s3cmd -c .s3cfg info s3://digitalplayground-p5

View file

@ -65,7 +65,7 @@ App.propTypes = {
App.defaultProps = {
children: null,
language: null,
theme: 'dark'
theme: 'light'
};
const mapStateToProps = state => ({

View file

@ -11,8 +11,8 @@ const initialState = {
textOutput: false,
gridOutput: false,
soundOutput: false,
theme: 'dark',
autorefresh: true,
theme: 'light',
autorefresh: false,
language: 'en-US'
};

View file

@ -22,7 +22,7 @@ import FooterTabSwitcher from '../../components/mobile/TabSwitcher';
import FooterTab from '../../components/mobile/Tab';
import Loader from '../App/components/loader';
const EXAMPLE_USERNAME = process.env.EXAMPLE_USERNAME || 'digitalplayground';
const EXAMPLE_USERNAME = process.env.EXAMPLE_USERNAME || 'p5';
// @ghalestrilo 08/13/2020: I'm sorry
const ContentWrapper = styled(Content)`

View file

@ -8,7 +8,7 @@ import Button from '../../../common/Button';
function SignupForm(props) {
const {
fields: {
username, /* email, */ password, confirmPassword
username, email, password, confirmPassword
},
handleSubmit,
submitting,
@ -33,7 +33,7 @@ function SignupForm(props) {
<span className="form-error">{username.error}</span>
)}
</p>
{/* <p className="form__field">
<p className="form__field">
<label htmlFor="email" className="form__label">{props.t('SignupForm.Email')}</label>
<input
className="form__input"
@ -45,7 +45,7 @@ function SignupForm(props) {
{email.touched && email.error && (
<span className="form-error">{email.error}</span>
)}
</p> */}
</p>
<p className="form__field">
<label htmlFor="password" className="form__label">{props.t('SignupForm.Password')}</label>
<input
@ -84,7 +84,7 @@ function SignupForm(props) {
SignupForm.propTypes = {
fields: PropTypes.shape({
username: PropTypes.object.isRequired, // eslint-disable-line
// email: PropTypes.object.isRequired, // eslint-disable-line
email: PropTypes.object.isRequired, // eslint-disable-line
password: PropTypes.object.isRequired, // eslint-disable-line
confirmPassword: PropTypes.object.isRequired, // eslint-disable-line
}).isRequired,

View file

@ -9,9 +9,9 @@ import * as UserActions from '../actions';
import SignupForm from '../components/SignupForm';
import apiClient from '../../../utils/apiClient';
import { validateSignup } from '../../../utils/reduxFormUtils';
// import SocialAuthButton from '../components/SocialAuthButton';
import SocialAuthButton from '../components/SocialAuthButton';
import Nav from '../../../components/Nav';
// import ResponsiveForm from '../components/ResponsiveForm';
import ResponsiveForm from '../components/ResponsiveForm';
class SignupView extends React.Component {
@ -123,9 +123,9 @@ SignupView.defaultProps = {
export default withTranslation()(reduxForm({
form: 'signup',
fields: ['username', /* 'email', */ 'password', 'confirmPassword'],
fields: ['username', 'email', 'password', 'confirmPassword'],
onSubmitFail,
validate: validateSignup,
asyncValidate,
asyncBlurFields: ['username'/* , 'email' */]
asyncBlurFields: ['username', 'email']
}, mapStateToProps, mapDispatchToProps)(SignupView));

View file

@ -1,4 +1,4 @@
$base-font-size: 20;
$base-font-size: 12;
//colors
$p5js-pink: #FFE117; /*DP Colours*/

View file

@ -1,7 +1,6 @@
.CodeMirror {
font-family: Inconsolata, monospace;
height: 100%;
font-size:$base-font-size !important;
}
.CodeMirror-linenumbers {

View file

@ -37,7 +37,7 @@
}
.form__label {
font-size: #{16 / $base-font-size}rem;
font-size: #{12 / $base-font-size}rem;
margin-top: #{25 / $base-font-size}rem;
margin-bottom: #{7 / $base-font-size}rem;
display: block;

View file

@ -158,7 +158,7 @@
color: getThemifyVariable('secondary-text-color');
}
margin-left: #{5 / $base-font-size}rem;
font-size: #{15 / $base-font-size}rem;
font-size: #{12 / $base-font-size}rem;
}
.toolbar__edit-name-button {

View file

@ -28,11 +28,9 @@ function validateNameEmail(formProps, errors) {
errors.username = i18n.t('ReduxFormUtils.errorValidUsername');
}
// if (!formProps.email) {
// errors.email = i18n.t('ReduxFormUtils.errorEmptyEmail');
// } else
if (
formProps.email &&
if (!formProps.email) {
errors.email = i18n.t('ReduxFormUtils.errorEmptyEmail');
} else if (
// eslint-disable-next-line max-len
!formProps.email.match(EMAIL_REGEX)) {
errors.email = i18n.t('ReduxFormUtils.errorInvalidEmail');

View file

@ -1,26 +0,0 @@
#!/bin/sh
set -e
echo "HALLO!"
echo "Starting the mongodb daemon"
/usr/bin/mongod --fork --syslog
# echo "navigating to volume /var/www"
# cd /var/www
# echo "Creating soft link"
# ln -s /opt/mysite mysite
# a2enmod headers
# service apache2 restart
# a2ensite mysite.conf
# a2dissite 000-default.conf
# service apache2 reload
if [ -z "$1" ]
then
exec npm run start:prod
else
exec "$1"
fi

View file

@ -24,7 +24,7 @@ passport.deserializeUser((id, done) => {
* Sign in using Email/Username and Password.
*/
passport.use(new LocalStrategy({ usernameField: 'email' }, (email, password, done) => {
User.findByUsername(email)
User.findByEmailOrUsername(email)
.then((user) => { // eslint-disable-line consistent-return
if (!user) {
return done(null, false, { msg: `Email ${email} not found.` });

View file

@ -12,7 +12,7 @@ export * from './user.controller/apiKey';
export function userResponse(user) {
return {
// email: user.email,
email: user.email,
username: user.username,
preferences: user.preferences,
apiKeys: user.apiKeys,
@ -36,32 +36,31 @@ export function findUserByUsername(username, cb) {
}
export function createUser(req, res, next) {
const { username, _ } = req.body;
const { username, email } = req.body;
const { password } = req.body;
// const emailLowerCase = email ? email.toLowerCase() : null;
const emailLowerCase = email.toLowerCase();
const EMAIL_VERIFY_TOKEN_EXPIRY_TIME = Date.now() + (3600000 * 24); // 24 hours
random((tokenError, token) => {
const user = new User({
username,
// email: emailLowerCase,
email: emailLowerCase,
password,
verified: User.EmailConfirmation.Sent,
verifiedToken: token,
verifiedTokenExpires: EMAIL_VERIFY_TOKEN_EXPIRY_TIME,
});
User.findByUsername(username, (err, existingUser) => {
User.findByEmailAndUsername(email, username, (err, existingUser) => {
if (err) {
res.status(404).send({ error: err });
return;
}
if (existingUser) {
const fieldInUse = 'Username';
const fieldInUse = existingUser.email.toLowerCase() === emailLowerCase ? 'Email' : 'Username';
res.status(422).send({ error: `${fieldInUse} is in use` });
return;
}
user.save((saveErr) => {
if (saveErr) {
next(saveErr);
@ -74,17 +73,17 @@ export function createUser(req, res, next) {
}
const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http';
// const mailOptions = renderEmailConfirmation({
// body: {
// domain: `${protocol}://${req.headers.host}`,
// link: `${protocol}://${req.headers.host}/verify?t=${token}`
// },
// to: req.user.email,
// });
const mailOptions = renderEmailConfirmation({
body: {
domain: `${protocol}://${req.headers.host}`,
link: `${protocol}://${req.headers.host}/verify?t=${token}`
},
to: req.user.email,
});
// mail.send(mailOptions, (mailErr, result) => { // eslint-disable-line no-unused-vars
mail.send(mailOptions, (mailErr, result) => { // eslint-disable-line no-unused-vars
res.json(userResponse(req.user));
// });
});
});
});
});
@ -143,19 +142,18 @@ export function resetPasswordInitiate(req, res) {
async.waterfall([
random,
(token, done) => {
// disable since we don't use email
// User.findByEmail(req.body.email, (err, user) => {
// if (!user) {
// res.json({ success: true, message: 'If the email is registered with the editor, an email has been sent.' });
// return;
// }
// user.resetPasswordToken = token;
// user.resetPasswordExpires = Date.now() + 3600000; // 1 hour
User.findByEmail(req.body.email, (err, user) => {
if (!user) {
res.json({ success: true, message: 'If the email is registered with the editor, an email has been sent.' });
return;
}
user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour
// user.save((saveErr) => {
// done(saveErr, token, user);
// });
// });
user.save((saveErr) => {
done(saveErr, token, user);
});
});
},
(token, user, done) => {
const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http';

View file

@ -50,7 +50,7 @@ const userSchema = new Schema({
verifiedToken: String,
verifiedTokenExpires: Date,
github: { type: String },
email: { type: String },
email: { type: String, unique: true },
tokens: Array,
apiKeys: { type: [apiKeySchema] },
preferences: {
@ -64,8 +64,8 @@ const userSchema = new Schema({
textOutput: { type: Boolean, default: false },
gridOutput: { type: Boolean, default: false },
soundOutput: { type: Boolean, default: false },
theme: { type: String, default: 'dark' },
autorefresh: { type: Boolean, default: true },
theme: { type: String, default: 'light' },
autorefresh: { type: Boolean, default: false },
language: { type: String, default: 'en-US' }
},
totalSize: { type: Number, default: 0 }
@ -192,10 +192,10 @@ userSchema.statics.findByUsername = function findByUsername(username, cb) {
* @return {Promise<Object>} - Returns Promise fulfilled by User document
*/
userSchema.statics.findByEmailOrUsername = function findByEmailOrUsername(value, cb) {
// const isEmail = value.indexOf('@') > -1;
// if (isEmail) {
// return this.findByEmail(value, cb);
// }
const isEmail = value.indexOf('@') > -1;
if (isEmail) {
return this.findByEmail(value, cb);
}
return this.findByUsername(value, cb);
};
@ -210,11 +210,11 @@ userSchema.statics.findByEmailOrUsername = function findByEmailOrUsername(value,
* @return {Promise<Object>} - Returns Promise fulfilled by User document
*/
userSchema.statics.findByEmailAndUsername = function findByEmailAndUsername(email, username, cb) {
const query = { username // override username only as we don't want to register email addresses
// $or: [
// { email },
// { username }
// ]
const query = {
$or: [
{ email },
{ username }
]
};
return this.findOne(query).collation({ locale: 'en', strength: 2 }).exec(cb);
};
@ -224,5 +224,4 @@ userSchema.statics.EmailConfirmation = EmailConfirmationStates;
userSchema.index({ username: 1 }, { collation: { locale: 'en', strength: 2 } });
userSchema.index({ email: 1 }, { collation: { locale: 'en', strength: 2 } });
export default mongoose.model('User', userSchema);

View file

@ -191,26 +191,12 @@ app.use('/api', (error, req, res, next) => {
// overview of users:
app.get('/users', (req, res) => {
// hacky migration here
User.collection.getIndexes(({full: true})).then((indexes) =>{
console.log('indexes', indexes);
// migrate unique
indexes.forEach(function (index) {
if(index.name !=='email_1') return;
if(!index.unique) return;
console.log('Drop index!', index);
User.collection.dropIndex('email_1');
console.log('Recreate index!');
User.collection.createIndex({ email: 1 }, { collation: { locale: 'en', strength: 2 } });
});
})
// let results = [];
User.find({}).sort({ createdAt: -1 }).exec()
.then((users) => {
// const usernames = users.map((user) => user.username);
const usernames = users.map((user) => user.username);
let names = "<ul>";
users.forEach((user) => names += `<li><a href="/${user.username}/sketches">${user.username}</a> - ${user.createdAt.toGMTString()}</li>`);
usernames.forEach((username) => names += `<li><a href="/${username}/sketches">${username}</a></li>`);
names += "</ul>";
res.send(names);
});

View file

@ -29,7 +29,7 @@ export function renderIndex() {
window.process.env.CLIENT = true;
window.process.env.LOGIN_ENABLED = ${process.env.LOGIN_ENABLED === 'false' ? false : true};
window.process.env.EXAMPLES_ENABLED = ${process.env.EXAMPLES_ENABLED === 'false' ? false : true};
window.process.env.EXAMPLE_USERNAME = '${process.env.EXAMPLE_USERNAME || 'digitalplayground'}';
window.process.env.EXAMPLE_USERNAME = '${process.env.EXAMPLE_USERNAME || 'p5'}';
window.process.env.UI_ACCESS_TOKEN_ENABLED = ${process.env.UI_ACCESS_TOKEN_ENABLED === 'false' ? false : true};
window.process.env.UI_COLLECTIONS_ENABLED = ${process.env.UI_COLLECTIONS_ENABLED === 'false' ? false : true};
window.process.env.UPLOAD_LIMIT = ${process.env.UPLOAD_LIMIT ? `${process.env.UPLOAD_LIMIT}` : undefined};