wip
This commit is contained in:
parent
1f49924963
commit
1eaf0a6176
50 changed files with 8541 additions and 203 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -8,6 +8,9 @@ tmp
|
||||||
# Environment variables, never commit this file
|
# Environment variables, never commit this file
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
#default Storage for uploads
|
||||||
|
data
|
||||||
|
|
||||||
# The development sqlite file
|
# The development sqlite file
|
||||||
database/*.sqlite
|
database/*.sqlite
|
||||||
|
|
||||||
|
|
17
app/Controllers/Http/AdminApiController.js
Normal file
17
app/Controllers/Http/AdminApiController.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
'use strict'
|
||||||
|
const User = use('App/Models/User');
|
||||||
|
const Child = use('App/Models/Child')
|
||||||
|
const Link = use('App/Models/Link')
|
||||||
|
class AdminApiController {
|
||||||
|
async getUsers({response}) {
|
||||||
|
console.log('API');
|
||||||
|
const users = await User.all();
|
||||||
|
// console.log(typeof users);
|
||||||
|
// return users.rows.map(u => {
|
||||||
|
// return u.publicJSON();
|
||||||
|
// });
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AdminApiController
|
10
app/Controllers/Http/AdminController.js
Normal file
10
app/Controllers/Http/AdminController.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
class AdminController {
|
||||||
|
index({view}) {
|
||||||
|
console.log('adminController');
|
||||||
|
return view.render('admin')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AdminController
|
|
@ -13,7 +13,9 @@ class AuthController {
|
||||||
const user = await User.create({
|
const user = await User.create({
|
||||||
email: request.input('email'),
|
email: request.input('email'),
|
||||||
name: request.input('name'),
|
name: request.input('name'),
|
||||||
password: request.input('password')
|
password: request.input('password'),
|
||||||
|
avatar:
|
||||||
|
`https://api.adorable.io/avatars/285/${request.input('email')}.png`
|
||||||
});
|
});
|
||||||
if (user.id == 1) {
|
if (user.id == 1) {
|
||||||
user.is_admin = true;
|
user.is_admin = true;
|
||||||
|
@ -26,10 +28,15 @@ class AuthController {
|
||||||
async login({request, response, auth, session}) {
|
async login({request, response, auth, session}) {
|
||||||
console.log('login');
|
console.log('login');
|
||||||
const {email, password} = request.all()
|
const {email, password} = request.all()
|
||||||
|
console.log({email, password})
|
||||||
try {
|
try {
|
||||||
const token = await auth.attempt(email, password);
|
const token = await auth.attempt(email, password);
|
||||||
|
const user = auth.user;
|
||||||
|
user.last_logged_in = new Date();
|
||||||
|
await user.save();
|
||||||
console.log('logged in');
|
console.log('logged in');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
session.withErrors({loginError: 'Invalid Credentials'}).flashAll()
|
session.withErrors({loginError: 'Invalid Credentials'}).flashAll()
|
||||||
return response.redirect('back')
|
return response.redirect('back')
|
||||||
}
|
}
|
||||||
|
|
16
app/Controllers/Http/CdnController.js
Normal file
16
app/Controllers/Http/CdnController.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
'use strict'
|
||||||
|
const FileUtils = use('App/Utils/FileUtils');
|
||||||
|
|
||||||
|
class CdnController {
|
||||||
|
async publicImages({request, response}) {
|
||||||
|
const file = await FileUtils.getFile(request.params.fileName);
|
||||||
|
if (file)
|
||||||
|
response.send(file);
|
||||||
|
else
|
||||||
|
return {
|
||||||
|
ERROR: 'no file'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = CdnController
|
75
app/Controllers/Http/ClientApiController.js
Normal file
75
app/Controllers/Http/ClientApiController.js
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
'use strict'
|
||||||
|
const {validate, rule} = use('Validator');
|
||||||
|
const User = use('App/Models/User');
|
||||||
|
const Child = use('App/Models/Child')
|
||||||
|
const Link = use('App/Models/Link');
|
||||||
|
// import FileUtils from '../../Utils/FileUtils';
|
||||||
|
const FileUtils = use('App/Utils/FileUtils');
|
||||||
|
|
||||||
|
class ClientApiController {
|
||||||
|
async getConnections({request, auth, response}) {
|
||||||
|
try {
|
||||||
|
const user = auth.user;
|
||||||
|
const userLinks = (await user.links().fetch()).rows;
|
||||||
|
// console.log(userLinks.toJSON());
|
||||||
|
let links = Promise.resolve({});
|
||||||
|
const result = await Promise.all(userLinks.map(async (l) => {
|
||||||
|
const child = await l.child().fetch();
|
||||||
|
const is_parent = !!l.is_parent;
|
||||||
|
const childLinks = (await child.links().fetch())
|
||||||
|
.rows.filter(l => l.user_id != user.id);
|
||||||
|
const linkedUsers = await Promise.all(childLinks.map(async l => {
|
||||||
|
return (await l.user().fetch()).toJSON();
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
...child.toJSON(), linkedUsers, is_parent
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
return result;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
response.send(err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUser({auth}) {
|
||||||
|
const user = auth.user.toJSON();
|
||||||
|
const children = await Promise.all((await auth.user.links().fetch())
|
||||||
|
.rows.filter(l => l.is_parent)
|
||||||
|
.map(async (l) => {
|
||||||
|
return await l.child().fetch();
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
...user, children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createChild({auth, request, response}) {
|
||||||
|
const rules = {
|
||||||
|
name: 'required|string',
|
||||||
|
dob: 'required|date',
|
||||||
|
avatar: [rule('regex', /^(data:image\/\w+;base64).+/)]
|
||||||
|
};
|
||||||
|
const validation = await validate(request.all(), rules);
|
||||||
|
if (validation.fails()) {
|
||||||
|
response.status(400);
|
||||||
|
response.send(validation.messages());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const body = request.body;
|
||||||
|
if (body.avatar) {
|
||||||
|
const file = await FileUtils.saveBase64File(body.avatar);
|
||||||
|
console.log(file);
|
||||||
|
body.avatar = `/u/images/${file.fileName}`;
|
||||||
|
} else {
|
||||||
|
body.avatar =
|
||||||
|
`https://api.adorable.io/avatars/285/${body.name.trim()}.png`;
|
||||||
|
}
|
||||||
|
const child = await Child.create(body);
|
||||||
|
const link = await Link.create(
|
||||||
|
{user_id: auth.user.id, child_id: child.id, is_parent: true});
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ClientApiController
|
21
app/Middleware/AdminAuth.js
Normal file
21
app/Middleware/AdminAuth.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
'use strict'
|
||||||
|
/** @typedef {import('@adonisjs/framework/src/Request')} Request */
|
||||||
|
/** @typedef {import('@adonisjs/framework/src/Response')} Response */
|
||||||
|
/** @typedef {import('@adonisjs/framework/src/View')} View */
|
||||||
|
|
||||||
|
class AdminAuth {
|
||||||
|
/**
|
||||||
|
* @param {object} ctx
|
||||||
|
* @param {Request} ctx.request
|
||||||
|
* @param {Function} next
|
||||||
|
*/
|
||||||
|
async handle({request, auth, response}, next) {
|
||||||
|
// console.log(auth.user);
|
||||||
|
if (!auth.user.is_admin) response.redirect('/');
|
||||||
|
// call next to advance the request
|
||||||
|
else
|
||||||
|
await next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AdminAuth
|
|
@ -4,6 +4,15 @@
|
||||||
const Model = use('Model')
|
const Model = use('Model')
|
||||||
|
|
||||||
class Child extends Model {
|
class Child extends Model {
|
||||||
|
// get table() {
|
||||||
|
// return 'children';
|
||||||
|
// }
|
||||||
|
static get dates() {
|
||||||
|
return super.dates.concat(['dob'])
|
||||||
|
}
|
||||||
|
links() {
|
||||||
|
return this.hasMany('App/Models/Link')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Child
|
module.exports = Child
|
||||||
|
|
|
@ -4,13 +4,12 @@
|
||||||
const Model = use('Model')
|
const Model = use('Model')
|
||||||
|
|
||||||
class Link extends Model {
|
class Link extends Model {
|
||||||
users(){
|
user() {
|
||||||
return this.hasMany('App/Models/User')
|
return this.belongsTo('App/Models/User')
|
||||||
}
|
}
|
||||||
children() {
|
child() {
|
||||||
return this.hasMany('App/Models/Child')
|
return this.belongsTo('App/Models/Child')
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Link
|
module.exports = Link
|
||||||
|
|
|
@ -25,10 +25,19 @@ class User extends Model {
|
||||||
publicJSON() {
|
publicJSON() {
|
||||||
const u = this.toJSON();
|
const u = this.toJSON();
|
||||||
return {
|
return {
|
||||||
avatar: u.avatar, email: u.email, name: u.name, isAdmin: false
|
avatar: `https://api.adorable.io/avatars/285/${u.email}.png`, id: u.id,
|
||||||
|
name: u.name, isAdmin: u.is_admin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get hidden() {
|
||||||
|
return ['password']
|
||||||
|
}
|
||||||
|
|
||||||
|
static get dates() {
|
||||||
|
return super.dates.concat(['last_logged_in'])
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A relationship on tokens is required for auth to
|
* A relationship on tokens is required for auth to
|
||||||
* work. Since features like `refreshTokens` or
|
* work. Since features like `refreshTokens` or
|
||||||
|
@ -43,10 +52,6 @@ class User extends Model {
|
||||||
return this.hasMany('App/Models/Token')
|
return this.hasMany('App/Models/Token')
|
||||||
}
|
}
|
||||||
|
|
||||||
children() {
|
|
||||||
return this.hasMany('App/Models/Child')
|
|
||||||
}
|
|
||||||
|
|
||||||
links() {
|
links() {
|
||||||
return this.hasMany('App/Models/Link')
|
return this.hasMany('App/Models/Link')
|
||||||
}
|
}
|
||||||
|
|
40
app/Utils/FileUtils.js
Normal file
40
app/Utils/FileUtils.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
const Drive = use('Drive');
|
||||||
|
|
||||||
|
|
||||||
|
class FileUtils {
|
||||||
|
static async saveBase64File(base64Str) {
|
||||||
|
console.log(base64Str.length);
|
||||||
|
const parsed = parseBase64(base64Str);
|
||||||
|
const fileName =
|
||||||
|
`${Date.now()}-${Math.random() * 1000}.${parsed.extension}`;
|
||||||
|
const file = await Drive.put(fileName, parsed.data);
|
||||||
|
return {fileName, file};
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getFile(filename) {
|
||||||
|
try {
|
||||||
|
return await Drive.get(filename)
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function parseBase64(dataString) {
|
||||||
|
const matches = dataString.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/);
|
||||||
|
|
||||||
|
if (matches.length !== 3) {
|
||||||
|
return new Error('Invalid input string');
|
||||||
|
}
|
||||||
|
let extension = matches[1].split('/')[1];
|
||||||
|
// if (extension === 'jpeg') extension = 'jpg';
|
||||||
|
let data = matches[2];
|
||||||
|
console.log(data[0], data[1]);
|
||||||
|
return {
|
||||||
|
type: matches[1], extension, data: Buffer.from(data, 'base64'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = FileUtils;
|
|
@ -32,8 +32,10 @@ module.exports = {
|
||||||
sqlite: {
|
sqlite: {
|
||||||
client: 'sqlite3',
|
client: 'sqlite3',
|
||||||
connection: {
|
connection: {
|
||||||
filename: Helpers.databasePath(`${Env.get('DB_DATABASE', 'development')}.sqlite`)
|
filename: Helpers.databasePath(
|
||||||
|
`${Env.get('DB_DATABASE', 'development')}.sqlite`)
|
||||||
},
|
},
|
||||||
|
// debug: true,
|
||||||
useNullAsDefault: true
|
useNullAsDefault: true
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
45
config/drive.js
Normal file
45
config/drive.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const Helpers = use('Helpers')
|
||||||
|
const Env = use('Env')
|
||||||
|
const DRIVE_DISK = Env.get('DRIVE_DISK', './data');
|
||||||
|
module.exports = {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default disk
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The default disk is used when you interact with the file system without
|
||||||
|
| defining a disk name
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
default: 'local',
|
||||||
|
|
||||||
|
disks: {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Local
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Local disk interacts with the a local folder inside your application
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
local: {root: DRIVE_DISK, driver: 'local'},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| S3
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| S3 disk interacts with a bucket on aws s3
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
s3: {
|
||||||
|
driver: 's3',
|
||||||
|
key: Env.get('S3_KEY'),
|
||||||
|
secret: Env.get('S3_SECRET'),
|
||||||
|
bucket: Env.get('S3_BUCKET'),
|
||||||
|
region: Env.get('S3_REGION')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,8 +27,7 @@ module.exports = {
|
||||||
| }
|
| }
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
directives: {
|
directives: {},
|
||||||
},
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Report only
|
| Report only
|
||||||
|
@ -61,7 +60,8 @@ module.exports = {
|
||||||
| behavior.
|
| behavior.
|
||||||
|
|
|
|
||||||
| Here is an issue reported on a different package, but helpful to read
|
| Here is an issue reported on a different package, but helpful to read
|
||||||
| if you want to know the behavior. https://github.com/helmetjs/helmet/pull/82
|
| if you want to know the behavior.
|
||||||
|
https://github.com/helmetjs/helmet/pull/82
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
disableAndroid: true
|
disableAndroid: true
|
||||||
|
@ -75,13 +75,11 @@ module.exports = {
|
||||||
| X-XSS Protection saves applications from XSS attacks. It is adopted
|
| X-XSS Protection saves applications from XSS attacks. It is adopted
|
||||||
| by IE and later followed by some other browsers.
|
| by IE and later followed by some other browsers.
|
||||||
|
|
|
|
||||||
| Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
|
| Learn more at
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
xss: {
|
xss: {enabled: true, enableOnOldIE: false},
|
||||||
enabled: true,
|
|
||||||
enableOnOldIE: false
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
@ -93,7 +91,8 @@ module.exports = {
|
||||||
| @available options
|
| @available options
|
||||||
| DENY, SAMEORIGIN, ALLOW-FROM http://example.com
|
| DENY, SAMEORIGIN, ALLOW-FROM http://example.com
|
||||||
|
|
|
|
||||||
| Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
|
| Learn more at
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
|
||||||
*/
|
*/
|
||||||
xframe: 'DENY',
|
xframe: 'DENY',
|
||||||
|
|
||||||
|
@ -106,7 +105,8 @@ module.exports = {
|
||||||
| files with .txt extension containing Javascript code will be executed as
|
| files with .txt extension containing Javascript code will be executed as
|
||||||
| Javascript. You can disable this behavior by setting nosniff to false.
|
| Javascript. You can disable this behavior by setting nosniff to false.
|
||||||
|
|
|
|
||||||
| Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
|
| Learn more at
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
nosniff: true,
|
nosniff: true,
|
||||||
|
@ -134,12 +134,7 @@ module.exports = {
|
||||||
csrf: {
|
csrf: {
|
||||||
enable: true,
|
enable: true,
|
||||||
methods: ['POST', 'PUT', 'DELETE'],
|
methods: ['POST', 'PUT', 'DELETE'],
|
||||||
filterUris: [],
|
filterUris: [/api\/v1\/client\/\w+/], // All Client API routes
|
||||||
cookieOptions: {
|
cookieOptions: {httpOnly: false, sameSite: true, path: '/', maxAge: 7200}
|
||||||
httpOnly: false,
|
|
||||||
sameSite: true,
|
|
||||||
path: '/',
|
|
||||||
maxAge: 7200
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ class UserSchema extends Schema {
|
||||||
table.string('password', 60).notNullable();
|
table.string('password', 60).notNullable();
|
||||||
table.string('avatar');
|
table.string('avatar');
|
||||||
table.boolean('is_admin').defaultTo(false).notNullable();
|
table.boolean('is_admin').defaultTo(false).notNullable();
|
||||||
|
table.datetime('last_logged_in').defaultTo(null).nullable();
|
||||||
table.timestamps();
|
table.timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,15 +6,11 @@ const Schema = use('Schema')
|
||||||
class ChildSchema extends Schema {
|
class ChildSchema extends Schema {
|
||||||
up() {
|
up() {
|
||||||
this.create('children', (table) => {
|
this.create('children', (table) => {
|
||||||
table.increments()
|
table.increments();
|
||||||
table.string('name')
|
table.string('name');
|
||||||
table.date('birthday')
|
table.date('dob');
|
||||||
table.bigInteger('user_id')
|
table.string('avatar');
|
||||||
table.string('avatar')
|
table.timestamps();
|
||||||
table.string('state')
|
|
||||||
table.timestamps()
|
|
||||||
|
|
||||||
table.index(['user_id'])
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,18 +5,19 @@ const Schema = use('Schema')
|
||||||
|
|
||||||
class LinkSchema extends Schema {
|
class LinkSchema extends Schema {
|
||||||
up() {
|
up() {
|
||||||
this.create('link', (table) => {
|
this.create('links', (table) => {
|
||||||
table.increments()
|
table.increments();
|
||||||
table.bigInteger('user_id')
|
table.bigInteger('user_id');
|
||||||
table.bigInteger('child_id')
|
table.bigInteger('child_id');
|
||||||
table.timestamps()
|
table.boolean('is_parent');
|
||||||
|
table.timestamps();
|
||||||
table.index(['user_id', 'child_id'])
|
table.index(['user_id']);
|
||||||
|
table.index(['child_id']);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
down() {
|
down() {
|
||||||
this.drop('link')
|
this.drop('links')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
"@adonisjs/bodyparser": "^2.0.5",
|
"@adonisjs/bodyparser": "^2.0.5",
|
||||||
"@adonisjs/cli": "^4.0.12",
|
"@adonisjs/cli": "^4.0.12",
|
||||||
"@adonisjs/cors": "^1.0.7",
|
"@adonisjs/cors": "^1.0.7",
|
||||||
|
"@adonisjs/drive": "^1.0.4",
|
||||||
"@adonisjs/fold": "^4.0.9",
|
"@adonisjs/fold": "^4.0.9",
|
||||||
"@adonisjs/framework": "^5.0.9",
|
"@adonisjs/framework": "^5.0.9",
|
||||||
"@adonisjs/ignitor": "^2.0.8",
|
"@adonisjs/ignitor": "^2.0.8",
|
||||||
|
@ -35,10 +36,12 @@
|
||||||
"@adonisjs/validator": "^5.0.6",
|
"@adonisjs/validator": "^5.0.6",
|
||||||
"bulma": "^0.8.0",
|
"bulma": "^0.8.0",
|
||||||
"fork-awesome": "^1.1.7",
|
"fork-awesome": "^1.1.7",
|
||||||
|
"moment": "^2.24.0",
|
||||||
"sqlite3": "^4.1.1",
|
"sqlite3": "^4.1.1",
|
||||||
"typescript": "^3.7.5",
|
"typescript": "^3.7.5",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-router": "^3.1.5"
|
"vue-router": "^3.1.5",
|
||||||
|
"vuex": "^3.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.8.3",
|
"@babel/core": "^7.8.3",
|
||||||
|
|
18
public/scripts/applications/admin/app.bundle.js
Normal file
18
public/scripts/applications/admin/app.bundle.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
70
resources/scripts/applications/admin/app.vue
Normal file
70
resources/scripts/applications/admin/app.vue
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<template>
|
||||||
|
<div class="app">
|
||||||
|
<Header :appName="appName" />
|
||||||
|
<div class="columns m-t-xs is-fullheight">
|
||||||
|
<div class="column sidebar">
|
||||||
|
<SideBar :title="appName" :menu="menu" :appName="appName" />
|
||||||
|
</div>
|
||||||
|
<section class="section column app-content">
|
||||||
|
<div class="container">
|
||||||
|
<router-view></router-view>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Header from "./components/Header.vue";
|
||||||
|
import Services from "../services/index";
|
||||||
|
// import AppRouter from "./router/router.vue";
|
||||||
|
import {
|
||||||
|
default as SideBar,
|
||||||
|
IMenuItem
|
||||||
|
} from "../shared/components/SideBar/SideBar.vue";
|
||||||
|
const menu: IMenuItem[] = [
|
||||||
|
{
|
||||||
|
href: "/",
|
||||||
|
text: "Home",
|
||||||
|
isRouterLink: true,
|
||||||
|
icon: "fa fa-home"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/applications",
|
||||||
|
text: "Applications",
|
||||||
|
isRouterLink: true,
|
||||||
|
icon: "fa fa-puzzle-piece"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/settings",
|
||||||
|
isRouterLink: true,
|
||||||
|
text: "Settings",
|
||||||
|
icon: "fa fa-gears"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isRouterLink: false,
|
||||||
|
href: "/logout",
|
||||||
|
text: "Logout",
|
||||||
|
icon: "fa fa-sign-out"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
// Services.ApiService.getConnections();
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "App",
|
||||||
|
// router: AppRouter,
|
||||||
|
components: {
|
||||||
|
SideBar,
|
||||||
|
Header
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
console.log(this.$store.getters.users);
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
appName: "Admin",
|
||||||
|
menu
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
15
resources/scripts/applications/admin/components/Header.vue
Normal file
15
resources/scripts/applications/admin/components/Header.vue
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<template>
|
||||||
|
<section class="hero is-small is-primary hero-bg-landing01">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title">{{appName}}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: "Header",
|
||||||
|
props: ["appName"]
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<template>
|
||||||
|
<div class="chiled-avatar has-text-centered">
|
||||||
|
<div class="child-avatar-image">
|
||||||
|
<figure class="image is-48x48">
|
||||||
|
<img :src="child.avatar" alt="Placeholder image" class="is-rounded is-avatar" />
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
<div class="chiled-avatar-name">{{child.name}}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export interface IChildAvatar {
|
||||||
|
url: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ChildAvatar",
|
||||||
|
props: ["child"],
|
||||||
|
created() {}
|
||||||
|
};
|
||||||
|
</script>
|
16
resources/scripts/applications/admin/main.vue
Normal file
16
resources/scripts/applications/admin/main.vue
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from "vue";
|
||||||
|
import Vuex from "vuex";
|
||||||
|
import App from "./app.vue";
|
||||||
|
import AppRouter from "./router/router.vue";
|
||||||
|
import store from "./state.vuex";
|
||||||
|
|
||||||
|
const app = new Vue({
|
||||||
|
router: AppRouter,
|
||||||
|
store,
|
||||||
|
render: h => h(App)
|
||||||
|
}).$mount("#app");
|
||||||
|
|
||||||
|
export default app;
|
||||||
|
console.log("ADMIN");
|
||||||
|
</script>
|
41
resources/scripts/applications/admin/router/router.vue
Normal file
41
resources/scripts/applications/admin/router/router.vue
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from "vue";
|
||||||
|
import VueRouter, {
|
||||||
|
RouteConfig,
|
||||||
|
RouterOptions,
|
||||||
|
RouterMode,
|
||||||
|
NavigationGuard
|
||||||
|
} from "vue-router";
|
||||||
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
|
// Views
|
||||||
|
import Home from "../views/home.vue";
|
||||||
|
import Settings from "../views/settings.vue";
|
||||||
|
import Applications from "../views/application.vue";
|
||||||
|
|
||||||
|
const routes: RouteConfig[] = [
|
||||||
|
/** Define Application Routes */
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
component: Home,
|
||||||
|
name: "root"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/settings",
|
||||||
|
component: Settings
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/applications",
|
||||||
|
component: Applications
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const options: RouterOptions = {
|
||||||
|
routes,
|
||||||
|
mode: "history",
|
||||||
|
base: "/admin"
|
||||||
|
};
|
||||||
|
const AppRouter = new VueRouter(options);
|
||||||
|
|
||||||
|
export default AppRouter;
|
||||||
|
</script>
|
28
resources/scripts/applications/admin/state.vuex.ts
Normal file
28
resources/scripts/applications/admin/state.vuex.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import Vuex, { Store } from "vuex";
|
||||||
|
import Services from '../services';
|
||||||
|
Vue.use(Vuex);
|
||||||
|
const store = new Store({
|
||||||
|
strict: true,
|
||||||
|
state: {
|
||||||
|
users: null,
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
users(state) {
|
||||||
|
return state.users;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
setUsers: (state, users) => {
|
||||||
|
state.users = users;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
getUsers: async (ctx) => {
|
||||||
|
console.log('store get users');
|
||||||
|
const users = await Services.ApiService.getAllUsers();
|
||||||
|
ctx.commit('setUsers', users);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
export default store;
|
15
resources/scripts/applications/admin/views/application.vue
Normal file
15
resources/scripts/applications/admin/views/application.vue
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<template>
|
||||||
|
<div class="wrapper">
|
||||||
|
<h1 class="is-1">Applications!!!</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: "Applications",
|
||||||
|
beforeCreate: () => {
|
||||||
|
console.log("before create home vue");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
96
resources/scripts/applications/admin/views/home.vue
Normal file
96
resources/scripts/applications/admin/views/home.vue
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
<template>
|
||||||
|
<div class="wrapper">
|
||||||
|
<Modal title="CreateUser" :isActive="showCreateUser" @close="showCreateUser=false" acceptText="Create" @accept="createUser()">
|
||||||
|
test
|
||||||
|
</Modal>
|
||||||
|
<nav class="level">
|
||||||
|
<div class="level-left">
|
||||||
|
<div class="level-item">
|
||||||
|
<p class="subtitle">
|
||||||
|
<i class="fa fa-users"></i> Users
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-right">
|
||||||
|
<!-- <div class="level-item">
|
||||||
|
<a href="#" class="button is-success">
|
||||||
|
<i class="fa fa-plus"></i> Add a new Child
|
||||||
|
</a>
|
||||||
|
</div>-->
|
||||||
|
<div class="level-item">
|
||||||
|
<a class="button is-primary" @click="showCreateUser=true">
|
||||||
|
<i class="fa fa-plus"></i> Create User
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<table
|
||||||
|
class="table is-striped is-bordered is-narrow is-hoverable is-fullwidth"
|
||||||
|
style="vertical-align: center;"
|
||||||
|
>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>uid</th>
|
||||||
|
<th>avatar</th>
|
||||||
|
<th>name</th>
|
||||||
|
<th>email</th>
|
||||||
|
<th>admin</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tr v-for="user in users" :key="user.id">
|
||||||
|
<td class="m-t-a m-b-a">{{user.id}}</td>
|
||||||
|
<td>
|
||||||
|
<figure class="image is-48x48">
|
||||||
|
<img :src="user.avatar" class="is-avatar is-rounded" />
|
||||||
|
</figure>
|
||||||
|
</td>
|
||||||
|
<td>{{user.name}}</td>
|
||||||
|
<td>{{user.email}}</td>
|
||||||
|
<td>
|
||||||
|
<input class="checkbox" type="checkbox" :checked="user.is_admin" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import ChildAvatar, { IChildAvatar } from "../components/child_avatar.vue";
|
||||||
|
import Services from "../../services/index";
|
||||||
|
import { mapGetters, mapActions } from "vuex";
|
||||||
|
import Modal from "../../shared/components/Modal/Modal.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Home",
|
||||||
|
components: {
|
||||||
|
ChildAvatar,
|
||||||
|
Modal
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
createUser(){
|
||||||
|
alert('created');
|
||||||
|
},
|
||||||
|
...mapActions(["getUsers"])
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
this.loading = true;
|
||||||
|
if (this.users === null) await this.getUsers();
|
||||||
|
this.loading = false;
|
||||||
|
// this.connections = await Services.ApiService.getConnections();
|
||||||
|
// this.users = await Services.ApiService.getAllUsers();
|
||||||
|
// console.dir(connections);
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// async users() {
|
||||||
|
// }
|
||||||
|
...mapGetters(["users"])
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: true,
|
||||||
|
showCreateUser: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
70
resources/scripts/applications/admin/views/settings.vue
Normal file
70
resources/scripts/applications/admin/views/settings.vue
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<template>
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="has-text-centered">
|
||||||
|
<h3 class="title">Settings</h3>
|
||||||
|
<h4 class="subtitle">UserName</h4>
|
||||||
|
</div>
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-one-quarter">
|
||||||
|
<figure class="image is-128x128 m-auto">
|
||||||
|
<img
|
||||||
|
class="is-rounded is-avatar"
|
||||||
|
src="//external-content.duckduckgo.com/iu/?u=http%3A%2F%2F0.gravatar.com%2Favatar%2F3e9dc6179a412b170e6a8d779a84c341.png&f=1"
|
||||||
|
/>
|
||||||
|
</figure>
|
||||||
|
<div class="card m-t-lg">
|
||||||
|
<header class="card-header">
|
||||||
|
<p class="card-header-title">My Children</p>
|
||||||
|
</header>
|
||||||
|
<div class="card-content">bla bla bla</div>
|
||||||
|
<footer class="card-footer">
|
||||||
|
<a href="#" class="card-footer-item">Add a New Child</a>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<form class="form">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Name</label>
|
||||||
|
<div class="control">
|
||||||
|
<input class="input" type="text" placeholder="Text input" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Email</label>
|
||||||
|
<div class="control">
|
||||||
|
<input class="input" type="text" placeholder="Text input" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
let user: {
|
||||||
|
avatar?: string;
|
||||||
|
email?: string;
|
||||||
|
name: string;
|
||||||
|
isAdmin: boolean;
|
||||||
|
} = {
|
||||||
|
name: "LOADING...",
|
||||||
|
isAdmin: false
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Settings",
|
||||||
|
beforeCreate() {},
|
||||||
|
async created() {
|
||||||
|
const response = await fetch("/users/profile/");
|
||||||
|
this.user = await response.json();
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
user
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Header from "./components/Header.vue";
|
import Header from "./components/Header.vue";
|
||||||
|
import Services from "../services/index";
|
||||||
// import AppRouter from "./router/router.vue";
|
// import AppRouter from "./router/router.vue";
|
||||||
import {
|
import {
|
||||||
default as SideBar,
|
default as SideBar,
|
||||||
|
@ -47,6 +48,7 @@ const menu: IMenuItem[] = [
|
||||||
icon: "fa fa-sign-out"
|
icon: "fa fa-sign-out"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
// Services.ApiService.getConnections();
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "App",
|
name: "App",
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<!-- <div class="card"> -->
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="media">
|
||||||
|
<div class="media-left">
|
||||||
|
<figure class="image is-48x48">
|
||||||
|
<img class="is-rounded is-avatar" :src="child.avatar" :alt="child.name" />
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
<div class="media-content">
|
||||||
|
<p class>{{child.name}}</p>
|
||||||
|
<!-- <p class>{{childAge}}</p> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- </div> -->
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import * as Moment from "moment";
|
||||||
|
let childAge;
|
||||||
|
export default {
|
||||||
|
name: "ChildCard",
|
||||||
|
props: ["child"],
|
||||||
|
created() {
|
||||||
|
this.childAge = Moment().diff(this.child.dob, "years");
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
childAge
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="chiled-avatar has-text-centered">
|
<div class="chiled-avatar has-text-centered">
|
||||||
<div class="child-avatar-image">
|
<div class="child-avatar-image">
|
||||||
<figure class="image is-96x96">
|
<figure class="image is-48x48">
|
||||||
<img :src="child.avatarUrl" alt="Placeholder image" class="is-rounded is-avatar" />
|
<img :src="child.avatar" alt="Placeholder image" class="is-rounded is-avatar" />
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
<div class="chiled-avatar-name">{{child.name}}</div>
|
<div class="chiled-avatar-name">{{child.name}}</div>
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export interface IChildAvatar {
|
export interface IChildAvatar {
|
||||||
avatarUrl: string;
|
url: string;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
|
import Vuex from "vuex";
|
||||||
import App from "./app.vue";
|
import App from "./app.vue";
|
||||||
import AppRouter from "./router/router.vue";
|
import AppRouter from "./router/router.vue";
|
||||||
|
import store from "./state.vuex";
|
||||||
|
|
||||||
// Vue.use(VueRouter);
|
Vue.use(Vuex);
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
router: AppRouter,
|
router: AppRouter,
|
||||||
|
store,
|
||||||
render: h => h(App)
|
render: h => h(App)
|
||||||
}).$mount("#app");
|
}).$mount("#app");
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,10 @@ const routes: RouteConfig[] = [
|
||||||
{
|
{
|
||||||
path: "/applications",
|
path: "/applications",
|
||||||
component: Applications
|
component: Applications
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "*",
|
||||||
|
redirect: { name: "root" }
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
27
resources/scripts/applications/home/state.vuex.ts
Normal file
27
resources/scripts/applications/home/state.vuex.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import Vuex, { Store } from "vuex";
|
||||||
|
import Services from '../services';
|
||||||
|
Vue.use(Vuex);
|
||||||
|
const store = new Store({
|
||||||
|
strict: true,
|
||||||
|
state: {
|
||||||
|
user: null,
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
user(state) {
|
||||||
|
return state.user;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
setUser: (state, user) => {
|
||||||
|
state.user = user;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
getUser: async (ctx, userId?: number) => {
|
||||||
|
const user = await Services.ApiService.getUser(userId);
|
||||||
|
ctx.commit('setUser', user);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
export default store;
|
|
@ -21,60 +21,37 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="card">
|
<div class="card m-b-lg">
|
||||||
|
<div v-for="connection in connections" :key="connection.id">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
|
<div class="media">
|
||||||
|
<div class="media-left">
|
||||||
|
<figure class="image is-48x48">
|
||||||
|
<img :src="connection.avatar" alt="Placeholder image" class="is-rounded is-avatar" />
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
<div class="media-content">
|
||||||
|
<p class="title is-4">{{connection.name}}</p>
|
||||||
|
<p class="subtitle is-6">@code</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column" v-for="connection in connections">
|
<div class="column" v-for="user in connection.linkedUsers" :key="user.id">
|
||||||
<ChildAvatar :child="connection" />
|
<ChildAvatar :child="user" />
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="column">
|
</div>
|
||||||
<ChildAvatar />
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
<ChildAvatar />
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
<ChildAvatar />
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
<ChildAvatar />
|
|
||||||
</div>-->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <footer class="card-footer">
|
|
||||||
<p class="card-footer-item">
|
|
||||||
|
|
||||||
</p>
|
|
||||||
<p class="card-footer-item">
|
|
||||||
<span>
|
|
||||||
Connect to a chiled
|
|
||||||
<a href="#">Facebook</a>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</footer>-->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ChildAvatar, { IChildAvatar } from "../components/child_avatar.vue";
|
import ChildAvatar, { IChildAvatar } from "../components/child_avatar.vue";
|
||||||
const connections: IChildAvatar[] = [
|
import Services from "../../services/index";
|
||||||
{
|
let connections = [];
|
||||||
avatarUrl:
|
|
||||||
"http://icons.iconarchive.com/icons/google/noto-emoji-people-face/1024/10122-baby-icon.png",
|
|
||||||
name: "Ayala Dayan"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
avatarUrl:
|
|
||||||
"http://icons.iconarchive.com/icons/google/noto-emoji-people-face/1024/10125-baby-medium-skin-tone-icon.png",
|
|
||||||
name: "Sagi Dayan"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
avatarUrl:
|
|
||||||
"http://icons.iconarchive.com/icons/google/noto-emoji-people-face/1024/10142-girl-medium-light-skin-tone-icon.png",
|
|
||||||
name: "Tamar Choen"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Home",
|
name: "Home",
|
||||||
|
@ -82,7 +59,10 @@ export default {
|
||||||
ChildAvatar
|
ChildAvatar
|
||||||
},
|
},
|
||||||
beforeCreate() {},
|
beforeCreate() {},
|
||||||
created() {},
|
async created() {
|
||||||
|
this.connections = await Services.ApiService.getConnections();
|
||||||
|
// console.dir(connections);
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
connections
|
connections
|
||||||
|
|
|
@ -1,24 +1,77 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
|
<div class="loading" v-if="loading">Loading...</div>
|
||||||
|
<div class v-else>
|
||||||
|
<Modal
|
||||||
|
title="Add A child"
|
||||||
|
:isActive="enableChildModel"
|
||||||
|
acceptText="Add"
|
||||||
|
rejectText="Cancel"
|
||||||
|
@accept="addChild()"
|
||||||
|
@close="enableChildModel=false"
|
||||||
|
>
|
||||||
|
<form class="form register" id="form-register">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Name</label>
|
||||||
|
<div class="control has-icons-left">
|
||||||
|
<input
|
||||||
|
:class="['input']"
|
||||||
|
required="true"
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
placeholder="John Snow"
|
||||||
|
:disabled="!childValidation.enableInput"
|
||||||
|
v-model="childValidation.name"
|
||||||
|
/>
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="fa fa-id-card"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="help is-danger">{{ 'Error' }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Birthday</label>
|
||||||
|
<div class="control has-icons-left">
|
||||||
|
<input
|
||||||
|
:class="['input']"
|
||||||
|
required="true"
|
||||||
|
name="dob"
|
||||||
|
type="date"
|
||||||
|
:disabled="!childValidation.enableInput"
|
||||||
|
v-model="childValidation.dob"
|
||||||
|
/>
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="fa fa-gift"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="help is-danger">{{ 'Error' }}</p>
|
||||||
|
</div>
|
||||||
|
<file-select v-model="childValidation.avatar" accept="image/*" lable="Upload Avatar:"></file-select>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
<div class="has-text-centered">
|
<div class="has-text-centered">
|
||||||
<h3 class="title">Settings</h3>
|
<h3 class="title">Settings</h3>
|
||||||
<h4 class="subtitle">UserName</h4>
|
<h4 class="subtitle">{{user.name}}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-one-quarter">
|
<div class="column is-one-quarter">
|
||||||
<figure class="image is-128x128 m-auto">
|
<figure class="image is-128x128 m-auto">
|
||||||
<img
|
<img class="is-rounded is-avatar" :src="user.avatar" />
|
||||||
class="is-rounded is-avatar"
|
|
||||||
src="//external-content.duckduckgo.com/iu/?u=http%3A%2F%2F0.gravatar.com%2Favatar%2F3e9dc6179a412b170e6a8d779a84c341.png&f=1"
|
|
||||||
/>
|
|
||||||
</figure>
|
</figure>
|
||||||
<div class="card m-t-lg">
|
<div class="card m-t-lg">
|
||||||
<header class="card-header">
|
<header class="card-header">
|
||||||
<p class="card-header-title">My Children</p>
|
<p class="card-header-title">My Children</p>
|
||||||
</header>
|
</header>
|
||||||
<div class="card-content">bla bla bla</div>
|
<div class="card-content">
|
||||||
|
<ChildCard v-for="child in user.children" :key="child.id" :child="child"></ChildCard>
|
||||||
|
</div>
|
||||||
<footer class="card-footer">
|
<footer class="card-footer">
|
||||||
<a href="#" class="card-footer-item">Add a New Child</a>
|
<a
|
||||||
|
:enabled="childValidation.enableInput"
|
||||||
|
class="card-footer-item"
|
||||||
|
@click="enableChildModel=true"
|
||||||
|
>Add a New Child</a>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -27,42 +80,97 @@
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Name</label>
|
<label class="label">Name</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input class="input" type="text" placeholder="Text input" />
|
<input
|
||||||
|
:disabled="!childValidation.enableInput"
|
||||||
|
class="input"
|
||||||
|
type="text"
|
||||||
|
placeholder="Text input"
|
||||||
|
v-model="user.name"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Email</label>
|
<label class="label">Email</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input class="input" type="text" placeholder="Text input" />
|
<input class="input" v-model="user.email" type="email" placeholder="Text input" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
let user: {
|
import { mapActions, mapGetters } from "vuex";
|
||||||
avatar?: string;
|
import Modal from "../../shared/components/Modal/Modal.vue";
|
||||||
email?: string;
|
import ChildCard from "../components/Child_Card.vue";
|
||||||
name: string;
|
import Services from "../../services";
|
||||||
isAdmin: boolean;
|
import FileSelect from "../../shared/components/FileSelect/FileSelect.vue";
|
||||||
} = {
|
|
||||||
name: "LOADING...",
|
|
||||||
isAdmin: false
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
Modal,
|
||||||
|
FileSelect,
|
||||||
|
ChildCard
|
||||||
|
},
|
||||||
name: "Settings",
|
name: "Settings",
|
||||||
beforeCreate() {},
|
async beforeCreate() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
async created() {
|
async created() {
|
||||||
const response = await fetch("/users/profile/");
|
if (!this.user) {
|
||||||
this.user = await response.json();
|
try {
|
||||||
|
await this.getUser();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to fetch user");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async addChild() {
|
||||||
|
this.childValidation.enableInput = false;
|
||||||
|
const childData = {
|
||||||
|
name: this.childValidation.name,
|
||||||
|
dob: this.childValidation.dob,
|
||||||
|
avatar: this.childValidation.avatar
|
||||||
|
};
|
||||||
|
console.log(childData);
|
||||||
|
const child = await Services.ApiService.createChild(
|
||||||
|
childData.name,
|
||||||
|
childData.dob,
|
||||||
|
childData.avatar
|
||||||
|
);
|
||||||
|
if (childData.avatar) console.log(childData.avatar.length);
|
||||||
|
this.childValidation.name = null;
|
||||||
|
this.childValidation.dob = null;
|
||||||
|
this.childValidation.avatar = null;
|
||||||
|
this.childValidation.enableInput = true;
|
||||||
|
|
||||||
|
this.enableChildModel = false;
|
||||||
|
await this.getUser();
|
||||||
|
// console.log(child);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
...mapActions(["getUser"])
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters(["user"])
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
user
|
loading: true,
|
||||||
|
childValidation: {
|
||||||
|
enableInput: true,
|
||||||
|
name: null,
|
||||||
|
dob: null,
|
||||||
|
avatar: null
|
||||||
|
},
|
||||||
|
enableChildModel: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
38
resources/scripts/applications/services/api.service.ts
Normal file
38
resources/scripts/applications/services/api.service.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
export default class ApiService {
|
||||||
|
|
||||||
|
static async getUser(userId?: number) {
|
||||||
|
return (await fetch('/api/v1/client/user/')).json();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getConnections() {
|
||||||
|
return (await fetch('/api/v1/client/connections')).json();
|
||||||
|
}
|
||||||
|
static async getAllUsers() {
|
||||||
|
return (await fetch('/api/v1/admin/users')).json();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async createChild(name: string, dob: Date, avatar: string): Promise<any> {
|
||||||
|
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
name,
|
||||||
|
dob,
|
||||||
|
avatar
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/v1/client/child/', options);
|
||||||
|
console.log(response);
|
||||||
|
return response.json();
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
resources/scripts/applications/services/index.ts
Normal file
6
resources/scripts/applications/services/index.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import ApiService from './api.service';
|
||||||
|
|
||||||
|
const Services = {
|
||||||
|
ApiService
|
||||||
|
}
|
||||||
|
export default Services;
|
|
@ -0,0 +1,47 @@
|
||||||
|
<template>
|
||||||
|
<!--
|
||||||
|
Everything is wrapped in a label, which acts as a clickable wrapper around a form element.
|
||||||
|
In this case, the file input.
|
||||||
|
-->
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">{{lable}}</label>
|
||||||
|
<div class="control">
|
||||||
|
<label class="button">
|
||||||
|
Select File
|
||||||
|
<!-- Now, the file input that we hide. -->
|
||||||
|
<input class="is-hidden" type="file" @change="handleFileChange" :accept="accept" />
|
||||||
|
</label>
|
||||||
|
<span v-if="filename">{{filename.name}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from "vue";
|
||||||
|
|
||||||
|
const toBase64 = file =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.onload = () => resolve(reader.result);
|
||||||
|
reader.onerror = error => reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ["accept", "lable"],
|
||||||
|
data: () => ({
|
||||||
|
filename: null
|
||||||
|
}),
|
||||||
|
created() {
|
||||||
|
this.filename = null;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async handleFileChange(e) {
|
||||||
|
// Whenever the file changes, emit the 'input' event with the file data.
|
||||||
|
this.$emit("input", await toBase64(e.target.files[0]));
|
||||||
|
this.filename = e.target.files[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -0,0 +1,36 @@
|
||||||
|
<template>
|
||||||
|
<div :class="['modal', { 'is-active': !!isActive }]">
|
||||||
|
<div class="modal-background" @click="close()"></div>
|
||||||
|
<div class="modal-card">
|
||||||
|
<header class="modal-card-head" v-if="showTitle">
|
||||||
|
<p class="modal-card-title">{{title}}</p>
|
||||||
|
<button class="delete" aria-label="close" @click="close()"></button>
|
||||||
|
</header>
|
||||||
|
<section class="modal-card-body">
|
||||||
|
<slot></slot>
|
||||||
|
</section>
|
||||||
|
<footer class="modal-card-foot" v-if="showButtons">
|
||||||
|
<button class="button is-success" v-if="!!acceptText" @click="$emit('accept')">{{acceptText}}</button>
|
||||||
|
<button class="button" @click="close()" v-if="!!rejectText">{{rejectText}}</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
|
||||||
|
props: ["title", "isActive", "acceptText", "rejectText"],
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
showTitle: !!this.title,
|
||||||
|
showButtons: this.acceptText || this.rejectText
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.$emit('close');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
9
resources/views/admin.edge
Normal file
9
resources/views/admin.edge
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
@layout('layouts.application');
|
||||||
|
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div id="app">
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
{{ script('scripts/applications/admin/app.bundle.js') }}
|
||||||
|
@endsection
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
<div id="nav-menu" class="navbar-menu">
|
<div id="nav-menu" class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item">
|
<a class="navbar-item" href="/">
|
||||||
Home
|
Home
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
Settings
|
Settings
|
||||||
</a>
|
</a>
|
||||||
@if(auth.user.is_admin)
|
@if(auth.user.is_admin)
|
||||||
<a class="navbar-item" href='/admin'>
|
<a class="navbar-item" href='/admin/'>
|
||||||
Admin Settigns
|
Admin Settigns
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
|
|
15
start/app.js
15
start/app.js
|
@ -10,7 +10,8 @@
|
||||||
| provider here.
|
| provider here.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
const providers = [
|
const providers =
|
||||||
|
[
|
||||||
'@adonisjs/framework/providers/AppProvider',
|
'@adonisjs/framework/providers/AppProvider',
|
||||||
'@adonisjs/framework/providers/ViewProvider',
|
'@adonisjs/framework/providers/ViewProvider',
|
||||||
'@adonisjs/lucid/providers/LucidProvider',
|
'@adonisjs/lucid/providers/LucidProvider',
|
||||||
|
@ -20,6 +21,7 @@ const providers = [
|
||||||
'@adonisjs/session/providers/SessionProvider',
|
'@adonisjs/session/providers/SessionProvider',
|
||||||
'@adonisjs/auth/providers/AuthProvider',
|
'@adonisjs/auth/providers/AuthProvider',
|
||||||
'@adonisjs/validator/providers/ValidatorProvider',
|
'@adonisjs/validator/providers/ValidatorProvider',
|
||||||
|
'@adonisjs/drive/providers/DriveProvider',
|
||||||
]
|
]
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -31,9 +33,7 @@ const providers = [
|
||||||
| Providers for migrations, tests etc.
|
| Providers for migrations, tests etc.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
const aceProviders = [
|
const aceProviders = ['@adonisjs/lucid/providers/MigrationsProvider']
|
||||||
'@adonisjs/lucid/providers/MigrationsProvider'
|
|
||||||
]
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
@ -59,4 +59,9 @@ const aliases = {}
|
||||||
*/
|
*/
|
||||||
const commands = []
|
const commands = []
|
||||||
|
|
||||||
module.exports = { providers, aceProviders, aliases, commands }
|
module.exports = {
|
||||||
|
providers,
|
||||||
|
aceProviders,
|
||||||
|
aliases,
|
||||||
|
commands
|
||||||
|
}
|
||||||
|
|
|
@ -40,7 +40,8 @@ const globalMiddleware =
|
||||||
*/
|
*/
|
||||||
const namedMiddleware = {
|
const namedMiddleware = {
|
||||||
auth: 'Adonis/Middleware/Auth',
|
auth: 'Adonis/Middleware/Auth',
|
||||||
guest: 'Adonis/Middleware/AllowGuestOnly'
|
guest: 'Adonis/Middleware/AllowGuestOnly',
|
||||||
|
adminAuth: 'App/Middleware/AdminAuth',
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -27,26 +27,53 @@ Route.post('/register', 'AuthController.register').validator('Register');
|
||||||
Route.post('/login', 'AuthController.login').validator('Login');
|
Route.post('/login', 'AuthController.login').validator('Login');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
/ Applications
|
/ Client API
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Route
|
Route
|
||||||
.get(
|
.group(() => {
|
||||||
'/applications/story-time',
|
Route.get('connections', 'ClientApiController.getConnections');
|
||||||
({view}) => view.render('applications.story-time.app'))
|
Route.get('user', 'ClientApiController.getUser');
|
||||||
|
Route.post('child', 'ClientApiController.createChild');
|
||||||
|
})
|
||||||
|
.prefix('api/v1/client')
|
||||||
.middleware(['auth']);
|
.middleware(['auth']);
|
||||||
|
|
||||||
|
/*
|
||||||
|
/ Pubic CDN Images
|
||||||
|
*/
|
||||||
|
Route.get('/u/images/:fileName', 'CdnController.publicImages');
|
||||||
|
|
||||||
|
/*
|
||||||
|
/ Admin API
|
||||||
|
*/
|
||||||
|
// API
|
||||||
|
Route
|
||||||
|
.group(() => {
|
||||||
|
Route.get('users', 'AdminApiController.getUsers');
|
||||||
|
})
|
||||||
|
.prefix('/api/v1/admin')
|
||||||
|
.middleware(['auth', 'adminAuth']);
|
||||||
|
Route
|
||||||
|
.group(() => {
|
||||||
|
//
|
||||||
|
Route.get('/*', 'AdminController.index');
|
||||||
|
})
|
||||||
|
.prefix('admin')
|
||||||
|
.middleware(['auth', 'adminAuth']);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** Basic APIs */
|
/** Basic APIs */
|
||||||
Route
|
// Route
|
||||||
.get(
|
// .get(
|
||||||
'/users/profile',
|
// '/users/profile',
|
||||||
({request, response, auth}) => {
|
// ({request, response, auth}) => {
|
||||||
console.log('twergsg');
|
// console.log('twergsg');
|
||||||
const u = auth.user.publicJSON();
|
// const u = auth.user.publicJSON();
|
||||||
|
|
||||||
response.send(u);
|
// response.send(u);
|
||||||
// return auth.user;
|
// // return auth.user;
|
||||||
})
|
// })
|
||||||
.middleware(['auth']);
|
// .middleware(['auth']);
|
||||||
Route.get('/*', 'IndexController.index').as('home');
|
Route.get('/*', 'IndexController.index').as('home');
|
||||||
|
|
|
@ -17,6 +17,7 @@ module.exports = {
|
||||||
// 'applications/story-time':
|
// 'applications/story-time':
|
||||||
// './resources/scripts/applications/story-time/main.vue',
|
// './resources/scripts/applications/story-time/main.vue',
|
||||||
'applications/home': './resources/scripts/applications/home/main.vue',
|
'applications/home': './resources/scripts/applications/home/main.vue',
|
||||||
|
'applications/admin': './resources/scripts/applications/admin/main.vue',
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
filename: 'scripts/[name]/app.bundle.js',
|
filename: 'scripts/[name]/app.bundle.js',
|
||||||
|
|
Reference in a new issue