Selaa lähdekoodia

feat: added mongo module with accountSchema and way to import an accountSchema

Kristian Vos 5 vuotta sitten
vanhempi
sitoutus
327dcd2833

+ 3 - 1
.gitignore

@@ -1,3 +1,5 @@
 node_modules
 log/
-default.json
+default.json
+.database
+scripts/

+ 1 - 0
backend/index.js

@@ -160,6 +160,7 @@ module.exports = moduleManager;
 
 moduleManager.addModule("logger");
 moduleManager.addModule("io");
+moduleManager.addModule("mongo");
 
 moduleManager.initialize();
 

+ 31 - 7
backend/logic/io.js

@@ -11,14 +11,12 @@ const express = require("express");
 const http = require("http");
 const socketio = require('socket.io');
 
-const accountSchema = require("../schemas/account.js");
-
 module.exports = class extends coreClass {
-	/*constructor(name, moduleManager) {
+	constructor(name, moduleManager) {
 		super(name, moduleManager);
 
-		//this.dependsOn = ["app", "db", "cache", "utils"];
-	}*/
+		this.dependsOn = ["mongo"];
+	}
 
 	initialize() {
 		return new Promise(resolve => {
@@ -28,6 +26,8 @@ module.exports = class extends coreClass {
 			const server = http.createServer(app);
 			const io = socketio(server);
 
+			this.mongo = this.moduleManager.modules["mongo"];
+
 			this.handlers = {
 				"getAccounts": cb => {
 					cb({
@@ -36,8 +36,32 @@ module.exports = class extends coreClass {
 				},
 
 				"getAccountSchema": cb => {
-					cb({
-						schema: accountSchema
+					this.mongo.models.accountSchema.find({}, null, { sort: ["version"], limit: 1 }, (err, res) => {
+						if (err || !res || res.length !== 1)
+							return cb({
+								status: "failure",
+								message: "Something went wrong."
+							});
+						else
+							return cb({
+								status: "success",
+								schema: res[0]
+							});
+					});
+				},
+
+				"importAccountSchema": (cb, name) => {
+					let schema = require(`../schemas/${name}`);
+					this.mongo.models.accountSchema.create(schema, (err) => {
+						if (err)
+							return cb({
+								status: "failure",
+								err: err
+							});
+						else
+							return cb({
+								status: "success"
+							});
 					});
 				}
 			}

+ 208 - 0
backend/logic/mongo/index.js

@@ -0,0 +1,208 @@
+'use strict';
+
+const coreClass = require("../../core");
+
+const mongoose = require('mongoose');
+const config = require('config');
+
+/*const bluebird = require('bluebird');
+
+mongoose.Promise = bluebird;*/
+
+module.exports = class extends coreClass {
+	initialize() {
+		return new Promise((resolve, reject) => {
+			this.setStage(1);
+
+			this.schemas = {};
+			this.models = {};
+
+			const mongoUrl = config.get("mongo").url;
+
+			mongoose.connect(mongoUrl, {
+				useNewUrlParser: true,
+				useCreateIndex: true,
+				reconnectInterval: 3000,
+				reconnectTries: 10
+			})
+				.then(() => {
+					this.schemas = {
+						accountSchema: new mongoose.Schema(require(`./schemas/accountSchema`))
+					};
+		
+					this.models = {
+						accountSchema: mongoose.model('accountSchema', this.schemas.accountSchema)
+					};
+
+					mongoose.connection.on('error', err => {
+						this.logger.error("DB_MODULE", err);
+					});
+
+					mongoose.connection.on('disconnected', () => {
+						this.logger.error("DB_MODULE", "Disconnected, going to try to reconnect...");
+						this.setState("RECONNECTING");
+					});
+
+					mongoose.connection.on('reconnected', () => {
+						this.logger.success("DB_MODULE", "Reconnected.");
+						this.setState("INITIALIZED");
+					});
+
+					mongoose.connection.on('reconnectFailed', () => {
+						this.logger.error("DB_MODULE", "Reconnect failed, stopping reconnecting.");
+						this.failed = true;
+						this._lockdown();
+					});
+		
+					/*// User
+					this.schemas.user.path('username').validate((username) => {
+						return (isLength(username, 2, 32) && regex.custom("a-zA-Z0-9_-").test(username));
+					}, 'Invalid username.');
+		
+					this.schemas.user.path('email.address').validate((email) => {
+						if (!isLength(email, 3, 254)) return false;
+						if (email.indexOf('@') !== email.lastIndexOf('@')) return false;
+						return regex.emailSimple.test(email) && regex.ascii.test(email);
+					}, 'Invalid email.');
+
+					// Station
+					this.schemas.station.path('name').validate((id) => {
+						return (isLength(id, 2, 16) && regex.az09_.test(id));
+					}, 'Invalid station name.');
+		
+					this.schemas.station.path('displayName').validate((displayName) => {
+						return (isLength(displayName, 2, 32) && regex.ascii.test(displayName));
+					}, 'Invalid display name.');
+		
+					this.schemas.station.path('description').validate((description) => {
+						if (!isLength(description, 2, 200)) return false;
+						let characters = description.split("");
+						return characters.filter((character) => {
+							return character.charCodeAt(0) === 21328;
+						}).length === 0;
+					}, 'Invalid display name.');
+		
+					this.schemas.station.path('owner').validate({
+						validator: (owner) => {
+							return new Promise((resolve, reject) => {
+								this.models.station.countDocuments({ owner: owner }, (err, c) => {
+									if (err) reject(new Error("A mongo error happened."));
+									else if (c >= 3) reject(new Error("User already has 3 stations."));
+									else resolve();
+								});
+							});
+						},
+						message: 'User already has 3 stations.'
+					});
+		
+					/*
+					this.schemas.station.path('queue').validate((queue, callback) => { //Callback no longer works, see station max count
+						let totalDuration = 0;
+						queue.forEach((song) => {
+							totalDuration += song.duration;
+						});
+						return callback(totalDuration <= 3600 * 3);
+					}, 'The max length of the queue is 3 hours.');
+		
+					this.schemas.station.path('queue').validate((queue, callback) => { //Callback no longer works, see station max count
+						if (queue.length === 0) return callback(true);
+						let totalDuration = 0;
+						const userId = queue[queue.length - 1].requestedBy;
+						queue.forEach((song) => {
+							if (userId === song.requestedBy) {
+								totalDuration += song.duration;
+							}
+						});
+						return callback(totalDuration <= 900);
+					}, 'The max length of songs per user is 15 minutes.');
+		
+					this.schemas.station.path('queue').validate((queue, callback) => { //Callback no longer works, see station max count
+						if (queue.length === 0) return callback(true);
+						let totalSongs = 0;
+						const userId = queue[queue.length - 1].requestedBy;
+						queue.forEach((song) => {
+							if (userId === song.requestedBy) {
+								totalSongs++;
+							}
+						});
+						if (totalSongs <= 2) return callback(true);
+						if (totalSongs > 3) return callback(false);
+						if (queue[queue.length - 2].requestedBy !== userId || queue[queue.length - 3] !== userId) return callback(true);
+						return callback(false);
+					}, 'The max amount of songs per user is 3, and only 2 in a row is allowed.');
+					*/
+
+
+					// Song
+					/*let songTitle = (title) => {
+						return isLength(title, 1, 100);
+					};
+					this.schemas.song.path('title').validate(songTitle, 'Invalid title.');
+					this.schemas.queueSong.path('title').validate(songTitle, 'Invalid title.');
+		
+					this.schemas.song.path('artists').validate((artists) => {
+						return !(artists.length < 1 || artists.length > 10);
+					}, 'Invalid artists.');
+					this.schemas.queueSong.path('artists').validate((artists) => {
+						return !(artists.length < 0 || artists.length > 10);
+					}, 'Invalid artists.');
+		
+					let songArtists = (artists) => {
+						return artists.filter((artist) => {
+								return (isLength(artist, 1, 64) && artist !== "NONE");
+							}).length === artists.length;
+					};
+					this.schemas.song.path('artists').validate(songArtists, 'Invalid artists.');
+					this.schemas.queueSong.path('artists').validate(songArtists, 'Invalid artists.');
+		
+					let songGenres = (genres) => {
+						if (genres.length < 1 || genres.length > 16) return false;
+						return genres.filter((genre) => {
+								return (isLength(genre, 1, 32) && regex.ascii.test(genre));
+							}).length === genres.length;
+					};
+					this.schemas.song.path('genres').validate(songGenres, 'Invalid genres.');
+					this.schemas.queueSong.path('genres').validate(songGenres, 'Invalid genres.');
+		
+					let songThumbnail = (thumbnail) => {
+						if (!isLength(thumbnail, 1, 256)) return false;
+						if (config.get("cookie.secure") === true) return thumbnail.startsWith("https://");
+						else return thumbnail.startsWith("http://") || thumbnail.startsWith("https://");
+					};
+					this.schemas.song.path('thumbnail').validate(songThumbnail, 'Invalid thumbnail.');
+					this.schemas.queueSong.path('thumbnail').validate(songThumbnail, 'Invalid thumbnail.');
+
+					// Playlist
+					this.schemas.playlist.path('displayName').validate((displayName) => {
+						return (isLength(displayName, 1, 32) && regex.ascii.test(displayName));
+					}, 'Invalid display name.');
+		
+					this.schemas.playlist.path('createdBy').validate((createdBy) => {
+						this.models.playlist.countDocuments({ createdBy: createdBy }, (err, c) => {
+							return !(err || c >= 10);
+						});
+					}, 'Max 10 playlists per user.');
+		
+					this.schemas.playlist.path('songs').validate((songs) => {
+						return songs.length <= 5000;
+					}, 'Max 5000 songs per playlist.');
+		
+					this.schemas.playlist.path('songs').validate((songs) => {
+						if (songs.length === 0) return true;
+						return songs[0].duration <= 10800;
+					}, 'Max 3 hours per song.');
+		
+					// Report
+					this.schemas.report.path('description').validate((description) => {
+						return (!description || (isLength(description, 0, 400) && regex.ascii.test(description)));
+					}, 'Invalid description.');*/
+
+					resolve();
+				})
+				.catch(err => {
+					this.logger.error("DB_MODULE", err);
+					reject(err);
+				});
+		})
+	}
+}

+ 6 - 0
backend/logic/mongo/schemas/accountSchema.js

@@ -0,0 +1,6 @@
+module.exports = {
+	name: { type: String, required: true },
+	description: { type: String, required: true },
+	version: { type: Number, required: true, unique: true },
+	fields: [{ type: Object }]
+};

+ 4 - 0
backend/package.json

@@ -3,6 +3,10 @@
     "async": "^3.1.0",
     "config": "^3.2.2",
     "express": "^4.17.1",
+    "mongoose": "^5.7.3",
     "socket.io": "^2.3.0"
+  },
+  "scripts": {
+    "dev": "nodemon ."
   }
 }

+ 0 - 512
backend/schemas/account.js

@@ -1,512 +0,0 @@
-module.exports = {
-	name: "Account",
-	description: "",
-	versions: [
-		{
-			version: "1",
-			fields: [
-				{
-					name: "Name",
-					fieldId: "name",
-					fieldTypes: [
-						{
-							type: "text",
-							fill: true,
-							fieldTypeId: "name"
-						}
-					],
-					minEntries: 1,
-					maxEntries: 1
-				},
-				{
-					name: "Domain",
-					fieldId: "domain",
-					fieldTypes: [
-						{
-							type: "text",
-							fill: true,
-							fieldTypeId: "domain"
-						}
-					],
-					minEntries: 0,
-					maxEntries: 10
-				},
-				{
-					name: "App",
-					fieldId: "app",
-					fieldTypes: [
-						{
-							type: "select",
-							options: [
-								{
-									value: "android",
-									text: "Android"
-								},
-								{
-									value: "ios",
-									text: "iOS"
-								},
-								{
-									value: "windows",
-									text: "Windows"
-								}
-							],
-							fieldTypeId: "appType"
-						},
-						{
-							type: "text",
-							fill: true,
-							fieldTypeId: "appName"
-						}
-					],
-					minEntries: 0,
-					maxEntries: 10
-				},
-				{
-					name: "Account exists",
-					fieldId: "accountExists",
-					fieldTypes: [
-						{
-							type: "checkbox",
-							fieldTypeId: "accountExists"
-						}
-					],
-					minEntries: 1,
-					maxEntries: 1
-				},
-				{
-					name: "E-mail",
-					fieldId: "email",
-					fieldTypes: [
-						{
-							type: "text",
-							fieldTypeId: "email",
-							fill: true
-						}
-					],
-					minEntries: 0,
-					maxEntries: 10
-				},
-				{
-					name: "Username",
-					fieldId: "username",
-					fieldTypes: [
-						{
-							type: "text",
-							fieldTypeId: "username",
-							fill: true
-						}
-					],
-					minEntries: 0,
-					maxEntries: 10
-				},
-				{
-					name: "Login name",
-					fieldId: "loginName",
-					fieldTypes: [
-						{
-							type: "text",
-							fieldTypeId: "loginName",
-							fill: true
-						}
-					],
-					minEntries: 0,
-					maxEntries: 10
-				},
-				{
-					name: "Category",
-					fieldId: "category",
-					fieldTypes: [
-						{
-							type: "text",
-							fieldTypeId: "category",
-							fill: true
-						}
-					],
-					minEntries: 0,
-					maxEntries: 10
-				},
-				{
-					name: "Uses password",
-					fieldId: "usesPassword",
-					fieldTypes: [
-						{
-							type: "checkbox",
-							fieldTypeId: "usesPassword"
-						}
-					],
-					minEntries: 1,
-					maxEntries: 1
-				},
-				{
-					name: "Password last changed",
-					fieldId: "passwordLastChanged",
-					fieldTypes: [
-						{
-							type: "text",
-							fieldTypeId: "passwordLastChanged",
-							fill: true
-						}
-					],
-					minEntries: 0,
-					maxEntries: 1
-				},
-				{
-					name: "2FA possible",
-					fieldId: "twofaPossible",
-					fieldTypes: [
-						{
-							type: "select",
-							options: [
-								{
-									value: "otp",
-									text: "OTP"
-								},
-								{
-									value: "sms",
-									text: "SMS"
-								}
-							],
-							fieldTypeId: "twofaPossibleType",
-							fill: true
-						}
-					],
-					minEntries: 0,
-					maxEntries: 10
-				},
-				{
-					name: "2FA used",
-					fieldId: "twofaUsed",
-					fieldTypes: [
-						{
-							type: "select",
-							options: [
-								{
-									value: "otp",
-									text: "OTP"
-								},
-								{
-									value: "sms",
-									text: "SMS"
-								}
-							],
-							fieldTypeId: "twofaUsedType"
-						},
-						{
-							type: "text",
-							fieldTypeId: "twofaUsedValue",
-							fill: true
-						}
-					],
-					minEntries: 0,
-					maxEntries: 10
-				},
-				{
-					name: "2FA recovery method",
-					fieldId: "twofaRecovery",
-					fieldTypes: [
-						{
-							type: "select",
-							options: [
-								{
-									value: "backupCodes",
-									text: "Backup codes"
-								}
-							],
-							fieldTypeId: "twofaRecoveryMethod"
-						},
-						{
-							type: "text",
-							fieldTypeId: "twofaRecoveryValue",
-							fill: true
-						}
-					],
-					minEntries: 0,
-					maxEntries: 10
-				},
-				{
-					name: "Login service",
-					fieldId: "loginService",
-					fieldTypes: [
-						{
-							type: "text",
-							fieldTypeId: "loginService",
-							fill: true
-						}
-					],
-					minEntries: 0,
-					maxEntries: 10
-				},
-				{
-					name: "Service linked",
-					fieldId: "serviceLinked",
-					fieldTypes: [
-						{
-							type: "text",
-							fieldTypeId: "serviceLinked",
-							fill: true
-						}
-					],
-					minEntries: 0,
-					maxEntries: 10
-				},
-				{
-					name: "Uses security questions",
-					fieldId: "usesSecurityQuestions",
-					fieldTypes: [
-						{
-							type: "checkbox",
-							fieldTypeId: "usesSecurityQuestions",
-							fill: true
-						}
-					],
-					minEntries: 1,
-					maxEntries: 1
-				},
-				{
-					name: "Recovery e-mail",
-					fieldId: "recoveryEmail",
-					fieldTypes: [
-						{
-							type: "text",
-							fieldTypeId: "recoveryEmail",
-							fill: true
-						}
-					],
-					minEntries: 0,
-					maxEntries: 10
-				},
-				{
-					name: "Recovery phone number",
-					fieldId: "recoveryPhoneNumber",
-					fieldTypes: [
-						{
-							type: "text",
-							fieldTypeId: "recoveryPhoneNumber",
-							fill: true
-						}
-					],
-					minEntries: 0,
-					maxEntries: 10
-				},
-				{
-					name: "Comments",
-					fieldId: "comments",
-					fieldTypes: [
-						{
-							type: "text",
-							fieldTypeId: "comments",
-							fill: true
-						}
-					],
-					minEntries: 0,
-					maxEntries: 1
-				},
-				{
-					name: "In 1password",
-					fieldId: "in1password",
-					fieldTypes: [
-						{
-							type: "checkbox",
-							fieldTypeId: "in1password"
-						}
-					],
-					minEntries: 1,
-					maxEntries: 1
-				},
-				{
-					name: "Deleted",
-					fieldId: "deleted",
-					fieldTypes: [
-						{
-							type: "checkbox",
-							fieldTypeId: "deleted"
-						}
-					],
-					minEntries: 1,
-					maxEntries: 1
-				},
-				{
-					name: "Deleted at",
-					fieldId: "deletedAt",
-					fieldTypes: [
-						{
-							type: "text",
-							fieldTypeId: "deletedAt",
-							fill: true
-						}
-					],
-					minEntries: 1,
-					maxEntries: 1
-				},
-				{
-					name: "Service accessible",
-					fieldId: "serviceAccessible",
-					fieldTypes: [
-						{
-							type: "checkbox",
-							fieldTypeId: "serviceAccessible"
-						}
-					],
-					minEntries: 1,
-					maxEntries: 1
-				},
-				{
-					name: "Requested deletion",
-					fieldId: "requestedDeletion",
-					fieldTypes: [
-						{
-							type: "checkbox",
-							fieldTypeId: "requestedDeletion"
-						}
-					],
-					minEntries: 1,
-					maxEntries: 1
-				},
-				{
-					name: "Requested deletion at",
-					fieldId: "requestedDeletionAt",
-					fieldTypes: [
-						{
-							type: "text",
-							fieldTypeId: "requestedDeletionAt",
-							fill: true
-						}
-					],
-					minEntries: 1,
-					maxEntries: 1
-				},
-				{
-					name: "To delete",
-					fieldId: "toDelete",
-					fieldTypes: [
-						{
-							type: "checkbox",
-							fieldTypeId: "toDelete"
-						}
-					],
-					minEntries: 1,
-					maxEntries: 1
-				},
-				{
-					name: "To delete",
-					fieldId: "toDelete",
-					fieldTypes: [
-						{
-							type: "checkbox",
-							fieldTypeId: "toDelete"
-						}
-					],
-					minEntries: 1,
-					maxEntries: 1
-				},
-				{
-					name: "Created at",
-					fieldId: "createdAt",
-					fieldTypes: [
-						{
-							type: "text",
-							fieldTypeId: "createdAt",
-							fill: true
-						}
-					],
-					minEntries: 1,
-					maxEntries: 1
-				}
-			]
-		}
-	]
-};
-
-/*
-[
-	{
-		name: "Domain",
-		fieldTypes: [
-			{
-				type: "checkbox",
-				extraButtons: []
-			},
-			{
-				type: "select",
-				options: [
-					{
-						value: "option1",
-						text: "Option 1"
-					},
-					{
-						value: "option2",
-						text: "Option 2"
-					},
-					{
-						value: "option3",
-						text: "Option 3"
-					}
-				],
-				extraButtons: [
-					{
-						icon: "~",
-						style: "red"
-					}
-				]
-			},
-			{
-				type: "text",
-				extraButtons: [],
-				fill: true
-			}
-		],
-		minEntries: 0,
-		maxEntries: 3,
-		initialEntries: [
-			[
-				true,
-				"option1",
-				"Hahaha value"
-			]
-		]
-	},
-	{
-		name: "Apps",
-		fieldTypes: [
-			{
-				type: "select",
-				options: [
-					{
-						value: "option1",
-						text: "Option 1"
-					},
-					{
-						value: "option2",
-						text: "Option 2"
-					},
-					{
-						value: "option3",
-						text: "Option 3"
-					}
-				],
-				extraButtons: [
-					{
-						icon: "~",
-						style: "red"
-					}
-				]
-			},
-			{
-				type: "text",
-				extraButtons: [],
-				fill: true
-			}
-		],
-		minEntries: 0,
-		maxEntries: 3,
-		initialEntries: [
-			[
-				true,
-				"option1",
-				"Hahaha value"
-			]
-		]
-	}
-]
-*/

+ 508 - 0
backend/schemas/accountSchemaV1.js

@@ -0,0 +1,508 @@
+module.exports = {
+	name: "Account",
+	description: "Account schema",
+	version: "1",
+	fields: [
+		{
+			name: "Name",
+			fieldId: "name",
+			fieldTypes: [
+				{
+					type: "text",
+					fill: true,
+					fieldTypeId: "name"
+				}
+			],
+			minEntries: 1,
+			maxEntries: 1
+		},
+		{
+			name: "Domain",
+			fieldId: "domain",
+			fieldTypes: [
+				{
+					type: "text",
+					fill: true,
+					fieldTypeId: "domain"
+				}
+			],
+			minEntries: 0,
+			maxEntries: 10
+		},
+		{
+			name: "App",
+			fieldId: "app",
+			fieldTypes: [
+				{
+					type: "select",
+					options: [
+						{
+							value: "android",
+							text: "Android"
+						},
+						{
+							value: "ios",
+							text: "iOS"
+						},
+						{
+							value: "windows",
+							text: "Windows"
+						}
+					],
+					fieldTypeId: "appType"
+				},
+				{
+					type: "text",
+					fill: true,
+					fieldTypeId: "appName"
+				}
+			],
+			minEntries: 0,
+			maxEntries: 10
+		},
+		{
+			name: "Account exists",
+			fieldId: "accountExists",
+			fieldTypes: [
+				{
+					type: "checkbox",
+					fieldTypeId: "accountExists"
+				}
+			],
+			minEntries: 1,
+			maxEntries: 1
+		},
+		{
+			name: "E-mail",
+			fieldId: "email",
+			fieldTypes: [
+				{
+					type: "text",
+					fieldTypeId: "email",
+					fill: true
+				}
+			],
+			minEntries: 0,
+			maxEntries: 10
+		},
+		{
+			name: "Username",
+			fieldId: "username",
+			fieldTypes: [
+				{
+					type: "text",
+					fieldTypeId: "username",
+					fill: true
+				}
+			],
+			minEntries: 0,
+			maxEntries: 10
+		},
+		{
+			name: "Login name",
+			fieldId: "loginName",
+			fieldTypes: [
+				{
+					type: "text",
+					fieldTypeId: "loginName",
+					fill: true
+				}
+			],
+			minEntries: 0,
+			maxEntries: 10
+		},
+		{
+			name: "Category",
+			fieldId: "category",
+			fieldTypes: [
+				{
+					type: "text",
+					fieldTypeId: "category",
+					fill: true
+				}
+			],
+			minEntries: 0,
+			maxEntries: 10
+		},
+		{
+			name: "Uses password",
+			fieldId: "usesPassword",
+			fieldTypes: [
+				{
+					type: "checkbox",
+					fieldTypeId: "usesPassword"
+				}
+			],
+			minEntries: 1,
+			maxEntries: 1
+		},
+		{
+			name: "Password last changed",
+			fieldId: "passwordLastChanged",
+			fieldTypes: [
+				{
+					type: "text",
+					fieldTypeId: "passwordLastChanged",
+					fill: true
+				}
+			],
+			minEntries: 0,
+			maxEntries: 1
+		},
+		{
+			name: "2FA possible",
+			fieldId: "twofaPossible",
+			fieldTypes: [
+				{
+					type: "select",
+					options: [
+						{
+							value: "otp",
+							text: "OTP"
+						},
+						{
+							value: "sms",
+							text: "SMS"
+						}
+					],
+					fieldTypeId: "twofaPossibleType",
+					fill: true
+				}
+			],
+			minEntries: 0,
+			maxEntries: 10
+		},
+		{
+			name: "2FA used",
+			fieldId: "twofaUsed",
+			fieldTypes: [
+				{
+					type: "select",
+					options: [
+						{
+							value: "otp",
+							text: "OTP"
+						},
+						{
+							value: "sms",
+							text: "SMS"
+						}
+					],
+					fieldTypeId: "twofaUsedType"
+				},
+				{
+					type: "text",
+					fieldTypeId: "twofaUsedValue",
+					fill: true
+				}
+			],
+			minEntries: 0,
+			maxEntries: 10
+		},
+		{
+			name: "2FA recovery method",
+			fieldId: "twofaRecovery",
+			fieldTypes: [
+				{
+					type: "select",
+					options: [
+						{
+							value: "backupCodes",
+							text: "Backup codes"
+						}
+					],
+					fieldTypeId: "twofaRecoveryMethod"
+				},
+				{
+					type: "text",
+					fieldTypeId: "twofaRecoveryValue",
+					fill: true
+				}
+			],
+			minEntries: 0,
+			maxEntries: 10
+		},
+		{
+			name: "Login service",
+			fieldId: "loginService",
+			fieldTypes: [
+				{
+					type: "text",
+					fieldTypeId: "loginService",
+					fill: true
+				}
+			],
+			minEntries: 0,
+			maxEntries: 10
+		},
+		{
+			name: "Service linked",
+			fieldId: "serviceLinked",
+			fieldTypes: [
+				{
+					type: "text",
+					fieldTypeId: "serviceLinked",
+					fill: true
+				}
+			],
+			minEntries: 0,
+			maxEntries: 10
+		},
+		{
+			name: "Uses security questions",
+			fieldId: "usesSecurityQuestions",
+			fieldTypes: [
+				{
+					type: "checkbox",
+					fieldTypeId: "usesSecurityQuestions",
+					fill: true
+				}
+			],
+			minEntries: 1,
+			maxEntries: 1
+		},
+		{
+			name: "Recovery e-mail",
+			fieldId: "recoveryEmail",
+			fieldTypes: [
+				{
+					type: "text",
+					fieldTypeId: "recoveryEmail",
+					fill: true
+				}
+			],
+			minEntries: 0,
+			maxEntries: 10
+		},
+		{
+			name: "Recovery phone number",
+			fieldId: "recoveryPhoneNumber",
+			fieldTypes: [
+				{
+					type: "text",
+					fieldTypeId: "recoveryPhoneNumber",
+					fill: true
+				}
+			],
+			minEntries: 0,
+			maxEntries: 10
+		},
+		{
+			name: "Comments",
+			fieldId: "comments",
+			fieldTypes: [
+				{
+					type: "text",
+					fieldTypeId: "comments",
+					fill: true
+				}
+			],
+			minEntries: 0,
+			maxEntries: 1
+		},
+		{
+			name: "In 1password",
+			fieldId: "in1password",
+			fieldTypes: [
+				{
+					type: "checkbox",
+					fieldTypeId: "in1password"
+				}
+			],
+			minEntries: 1,
+			maxEntries: 1
+		},
+		{
+			name: "Deleted",
+			fieldId: "deleted",
+			fieldTypes: [
+				{
+					type: "checkbox",
+					fieldTypeId: "deleted"
+				}
+			],
+			minEntries: 1,
+			maxEntries: 1
+		},
+		{
+			name: "Deleted at",
+			fieldId: "deletedAt",
+			fieldTypes: [
+				{
+					type: "text",
+					fieldTypeId: "deletedAt",
+					fill: true
+				}
+			],
+			minEntries: 1,
+			maxEntries: 1
+		},
+		{
+			name: "Service accessible",
+			fieldId: "serviceAccessible",
+			fieldTypes: [
+				{
+					type: "checkbox",
+					fieldTypeId: "serviceAccessible"
+				}
+			],
+			minEntries: 1,
+			maxEntries: 1
+		},
+		{
+			name: "Requested deletion",
+			fieldId: "requestedDeletion",
+			fieldTypes: [
+				{
+					type: "checkbox",
+					fieldTypeId: "requestedDeletion"
+				}
+			],
+			minEntries: 1,
+			maxEntries: 1
+		},
+		{
+			name: "Requested deletion at",
+			fieldId: "requestedDeletionAt",
+			fieldTypes: [
+				{
+					type: "text",
+					fieldTypeId: "requestedDeletionAt",
+					fill: true
+				}
+			],
+			minEntries: 1,
+			maxEntries: 1
+		},
+		{
+			name: "To delete",
+			fieldId: "toDelete",
+			fieldTypes: [
+				{
+					type: "checkbox",
+					fieldTypeId: "toDelete"
+				}
+			],
+			minEntries: 1,
+			maxEntries: 1
+		},
+		{
+			name: "To delete",
+			fieldId: "toDelete",
+			fieldTypes: [
+				{
+					type: "checkbox",
+					fieldTypeId: "toDelete"
+				}
+			],
+			minEntries: 1,
+			maxEntries: 1
+		},
+		{
+			name: "Created at",
+			fieldId: "createdAt",
+			fieldTypes: [
+				{
+					type: "text",
+					fieldTypeId: "createdAt",
+					fill: true
+				}
+			],
+			minEntries: 1,
+			maxEntries: 1
+		}
+	]
+};
+
+/*
+[
+	{
+		name: "Domain",
+		fieldTypes: [
+			{
+				type: "checkbox",
+				extraButtons: []
+			},
+			{
+				type: "select",
+				options: [
+					{
+						value: "option1",
+						text: "Option 1"
+					},
+					{
+						value: "option2",
+						text: "Option 2"
+					},
+					{
+						value: "option3",
+						text: "Option 3"
+					}
+				],
+				extraButtons: [
+					{
+						icon: "~",
+						style: "red"
+					}
+				]
+			},
+			{
+				type: "text",
+				extraButtons: [],
+				fill: true
+			}
+		],
+		minEntries: 0,
+		maxEntries: 3,
+		initialEntries: [
+			[
+				true,
+				"option1",
+				"Hahaha value"
+			]
+		]
+	},
+	{
+		name: "Apps",
+		fieldTypes: [
+			{
+				type: "select",
+				options: [
+					{
+						value: "option1",
+						text: "Option 1"
+					},
+					{
+						value: "option2",
+						text: "Option 2"
+					},
+					{
+						value: "option3",
+						text: "Option 3"
+					}
+				],
+				extraButtons: [
+					{
+						icon: "~",
+						style: "red"
+					}
+				]
+			},
+			{
+				type: "text",
+				extraButtons: [],
+				fill: true
+			}
+		],
+		minEntries: 0,
+		maxEntries: 3,
+		initialEntries: [
+			[
+				true,
+				"option1",
+				"Hahaha value"
+			]
+		]
+	}
+]
+*/

+ 102 - 2
backend/yarn.lock

@@ -62,6 +62,11 @@ blob@0.0.5:
   resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
   integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==
 
+bluebird@3.5.1:
+  version "3.5.1"
+  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
+  integrity sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==
+
 body-parser@1.19.0:
   version "1.19.0"
   resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
@@ -78,6 +83,11 @@ body-parser@1.19.0:
     raw-body "2.4.0"
     type-is "~1.6.17"
 
+bson@^1.1.1, bson@~1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.1.tgz#4330f5e99104c4e751e7351859e2d408279f2f13"
+  integrity sha512-jCGVYLoYMHDkOsbwJZBCqwMHyH4c+wzgI9hG7Z6SZJRXWr+x58pdIbm2i9a/jFGCkRJqRUr8eoI7lDWa0hTkxg==
+
 bytes@3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
@@ -144,7 +154,7 @@ debug@2.6.9:
   dependencies:
     ms "2.0.0"
 
-debug@~3.1.0:
+debug@3.1.0, debug@~3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
   integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
@@ -360,6 +370,11 @@ json5@^1.0.1:
   dependencies:
     minimist "^1.2.0"
 
+kareem@2.3.1:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.1.tgz#def12d9c941017fabfb00f873af95e9c99e1be87"
+  integrity sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==
+
 media-typer@0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -397,6 +412,53 @@ minimist@^1.2.0:
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
   integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
 
+mongodb@3.3.2:
+  version "3.3.2"
+  resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.3.2.tgz#ff086b5f552cf07e24ce098694210f3d42d668b2"
+  integrity sha512-fqJt3iywelk4yKu/lfwQg163Bjpo5zDKhXiohycvon4iQHbrfflSAz9AIlRE6496Pm/dQKQK5bMigdVo2s6gBg==
+  dependencies:
+    bson "^1.1.1"
+    require_optional "^1.0.1"
+    safe-buffer "^5.1.2"
+
+mongoose-legacy-pluralize@1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz#3ba9f91fa507b5186d399fb40854bff18fb563e4"
+  integrity sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==
+
+mongoose@^5.7.3:
+  version "5.7.3"
+  resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.7.3.tgz#8bd46b561eaed1a4d4a6cdd81d5b23c2e30da846"
+  integrity sha512-CKCCCAhFnJRtmdmver8Ud9/NZ9m7D2H/xLgmrcL6cb9D4nril/idL8lsWWpBsJI81AOCVsktiZJ4X4vfo2S0fw==
+  dependencies:
+    bson "~1.1.1"
+    kareem "2.3.1"
+    mongodb "3.3.2"
+    mongoose-legacy-pluralize "1.0.2"
+    mpath "0.6.0"
+    mquery "3.2.2"
+    ms "2.1.2"
+    regexp-clone "1.0.0"
+    safe-buffer "5.1.2"
+    sift "7.0.1"
+    sliced "1.0.1"
+
+mpath@0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.6.0.tgz#aa922029fca4f0f641f360e74c5c1b6a4c47078e"
+  integrity sha512-i75qh79MJ5Xo/sbhxrDrPSEG0H/mr1kcZXJ8dH6URU5jD/knFxCVqVC/gVSW7GIXL/9hHWlT9haLbCXWOll3qw==
+
+mquery@3.2.2:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/mquery/-/mquery-3.2.2.tgz#e1383a3951852ce23e37f619a9b350f1fb3664e7"
+  integrity sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==
+  dependencies:
+    bluebird "3.5.1"
+    debug "3.1.0"
+    regexp-clone "^1.0.0"
+    safe-buffer "5.1.2"
+    sliced "1.0.1"
+
 ms@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@@ -407,7 +469,7 @@ ms@2.1.1:
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
   integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
 
-ms@^2.1.1:
+ms@2.1.2, ms@^2.1.1:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
   integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
@@ -481,16 +543,44 @@ raw-body@2.4.0:
     iconv-lite "0.4.24"
     unpipe "1.0.0"
 
+regexp-clone@1.0.0, regexp-clone@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-1.0.0.tgz#222db967623277056260b992626354a04ce9bf63"
+  integrity sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==
+
+require_optional@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.1.tgz#4cf35a4247f64ca3df8c2ef208cc494b1ca8fc2e"
+  integrity sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==
+  dependencies:
+    resolve-from "^2.0.0"
+    semver "^5.1.0"
+
+resolve-from@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57"
+  integrity sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=
+
 safe-buffer@5.1.2:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
 
+safe-buffer@^5.1.2:
+  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":
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
   integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
 
+semver@^5.1.0:
+  version "5.7.1"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+  integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+
 send@0.17.1:
   version "0.17.1"
   resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
@@ -525,6 +615,16 @@ setprototypeof@1.1.1:
   resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
   integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
 
+sift@7.0.1:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/sift/-/sift-7.0.1.tgz#47d62c50b159d316f1372f8b53f9c10cd21a4b08"
+  integrity sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==
+
+sliced@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41"
+  integrity sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=
+
 socket.io-adapter@~1.1.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b"

+ 13 - 3
frontend/vue/pages/Homepage.vue

@@ -1,5 +1,8 @@
 <template>
 	<div>
+		<input v-model="importAccountSchemaName"/>
+		<button @click="importAccountSchema()">Import account schema</button>
+		<hr />
 		<h1>Sites</h1>
 		<form>
 			<field
@@ -23,18 +26,25 @@ export default {
 	data: () => {
 		return {
 			fields: [],
-			accounts: []
+			accounts: [],
+			importAccountSchemaName: ""
 		}
 	},
 	methods: {
-		
+		importAccountSchema() {
+			this.socket.emit("importAccountSchema", this.importAccountSchemaName, (res) => {
+				console.log(res);
+				alert(res.status);
+			});
+		}
 	},
 	mounted() {
 		io.getSocket(socket => {
 			this.socket = socket;
 
 			socket.emit("getAccountSchema", res => {
-				this.fields = res.schema.versions[0].fields;
+				console.log(res);
+				this.fields = res.schema.fields;
 			});
 
 			socket.emit("getAccounts", res => {