This commit is contained in:
Sagi Dayan 2020-03-17 18:16:34 -04:00
parent 1f49924963
commit 1eaf0a6176
50 changed files with 8541 additions and 203 deletions

3
.gitignore vendored
View file

@ -8,6 +8,9 @@ tmp
# Environment variables, never commit this file
.env
#default Storage for uploads
data
# The development sqlite file
database/*.sqlite

View 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

View file

@ -0,0 +1,10 @@
'use strict'
class AdminController {
index({view}) {
console.log('adminController');
return view.render('admin')
}
}
module.exports = AdminController

View file

@ -13,7 +13,9 @@ class AuthController {
const user = await User.create({
email: request.input('email'),
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) {
user.is_admin = true;
@ -26,10 +28,15 @@ class AuthController {
async login({request, response, auth, session}) {
console.log('login');
const {email, password} = request.all()
console.log({email, password})
try {
const token = await auth.attempt(email, password);
const user = auth.user;
user.last_logged_in = new Date();
await user.save();
console.log('logged in');
} catch (e) {
console.error(e);
session.withErrors({loginError: 'Invalid Credentials'}).flashAll()
return response.redirect('back')
}

View 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

View 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

View 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

View file

@ -4,6 +4,15 @@
const Model = use('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

View file

@ -4,13 +4,12 @@
const Model = use('Model')
class Link extends Model {
users(){
return this.hasMany('App/Models/User')
}
children() {
return this.hasMany('App/Models/Child')
}
user() {
return this.belongsTo('App/Models/User')
}
child() {
return this.belongsTo('App/Models/Child')
}
}
module.exports = Link

View file

@ -25,10 +25,19 @@ class User extends Model {
publicJSON() {
const u = this.toJSON();
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
* work. Since features like `refreshTokens` or
@ -43,10 +52,6 @@ class User extends Model {
return this.hasMany('App/Models/Token')
}
children() {
return this.hasMany('App/Models/Child')
}
links() {
return this.hasMany('App/Models/Link')
}

40
app/Utils/FileUtils.js Normal file
View 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;

View file

@ -32,8 +32,10 @@ module.exports = {
sqlite: {
client: 'sqlite3',
connection: {
filename: Helpers.databasePath(`${Env.get('DB_DATABASE', 'development')}.sqlite`)
filename: Helpers.databasePath(
`${Env.get('DB_DATABASE', 'development')}.sqlite`)
},
// debug: true,
useNullAsDefault: true
},

45
config/drive.js Normal file
View 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')
}
}
}

View file

@ -27,8 +27,7 @@ module.exports = {
| }
|
*/
directives: {
},
directives: {},
/*
|--------------------------------------------------------------------------
| Report only
@ -61,7 +60,8 @@ module.exports = {
| behavior.
|
| 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
@ -75,13 +75,11 @@ module.exports = {
| X-XSS Protection saves applications from XSS attacks. It is adopted
| 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: {
enabled: true,
enableOnOldIE: false
},
xss: {enabled: true, enableOnOldIE: false},
/*
|--------------------------------------------------------------------------
@ -93,7 +91,8 @@ module.exports = {
| @available options
| 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',
@ -106,7 +105,8 @@ module.exports = {
| files with .txt extension containing Javascript code will be executed as
| 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,
@ -134,12 +134,7 @@ module.exports = {
csrf: {
enable: true,
methods: ['POST', 'PUT', 'DELETE'],
filterUris: [],
cookieOptions: {
httpOnly: false,
sameSite: true,
path: '/',
maxAge: 7200
}
filterUris: [/api\/v1\/client\/\w+/], // All Client API routes
cookieOptions: {httpOnly: false, sameSite: true, path: '/', maxAge: 7200}
}
}

View file

@ -12,6 +12,7 @@ class UserSchema extends Schema {
table.string('password', 60).notNullable();
table.string('avatar');
table.boolean('is_admin').defaultTo(false).notNullable();
table.datetime('last_logged_in').defaultTo(null).nullable();
table.timestamps();
});
}

View file

@ -4,21 +4,17 @@
const Schema = use('Schema')
class ChildSchema extends Schema {
up () {
up() {
this.create('children', (table) => {
table.increments()
table.string('name')
table.date('birthday')
table.bigInteger('user_id')
table.string('avatar')
table.string('state')
table.timestamps()
table.index(['user_id'])
table.increments();
table.string('name');
table.date('dob');
table.string('avatar');
table.timestamps();
})
}
down () {
down() {
this.drop('children')
}
}

View file

@ -4,19 +4,20 @@
const Schema = use('Schema')
class LinkSchema extends Schema {
up () {
this.create('link', (table) => {
table.increments()
table.bigInteger('user_id')
table.bigInteger('child_id')
table.timestamps()
table.index(['user_id', 'child_id'])
up() {
this.create('links', (table) => {
table.increments();
table.bigInteger('user_id');
table.bigInteger('child_id');
table.boolean('is_parent');
table.timestamps();
table.index(['user_id']);
table.index(['child_id']);
})
}
down () {
this.drop('link')
down() {
this.drop('links')
}
}

View file

@ -26,6 +26,7 @@
"@adonisjs/bodyparser": "^2.0.5",
"@adonisjs/cli": "^4.0.12",
"@adonisjs/cors": "^1.0.7",
"@adonisjs/drive": "^1.0.4",
"@adonisjs/fold": "^4.0.9",
"@adonisjs/framework": "^5.0.9",
"@adonisjs/ignitor": "^2.0.8",
@ -35,10 +36,12 @@
"@adonisjs/validator": "^5.0.6",
"bulma": "^0.8.0",
"fork-awesome": "^1.1.7",
"moment": "^2.24.0",
"sqlite3": "^4.1.1",
"typescript": "^3.7.5",
"vue": "^2.6.11",
"vue-router": "^3.1.5"
"vue-router": "^3.1.5",
"vuex": "^3.1.2"
},
"devDependencies": {
"@babel/core": "^7.8.3",

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

View 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>

View 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>

View file

@ -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>

View 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>

View 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>

View 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;

View 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>

View 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>&nbsp;Users
</p>
</div>
</div>
<div class="level-right">
<!-- <div class="level-item">
<a href="#" class="button is-success">
<i class="fa fa-plus"></i>&nbsp;Add a new Child
</a>
</div>-->
<div class="level-item">
<a class="button is-primary" @click="showCreateUser=true">
<i class="fa fa-plus"></i>&nbsp;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>

View 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>

View file

@ -16,6 +16,7 @@
<script lang="ts">
import Header from "./components/Header.vue";
import Services from "../services/index";
// import AppRouter from "./router/router.vue";
import {
default as SideBar,
@ -47,6 +48,7 @@ const menu: IMenuItem[] = [
icon: "fa fa-sign-out"
}
];
// Services.ApiService.getConnections();
export default {
name: "App",

View file

@ -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>

View file

@ -1,8 +1,8 @@
<template>
<div class="chiled-avatar has-text-centered">
<div class="child-avatar-image">
<figure class="image is-96x96">
<img :src="child.avatarUrl" alt="Placeholder image" class="is-rounded is-avatar" />
<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>
@ -11,7 +11,7 @@
<script lang="ts">
export interface IChildAvatar {
avatarUrl: string;
url: string;
name: string;
}

View file

@ -1,11 +1,14 @@
<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";
// Vue.use(VueRouter);
Vue.use(Vuex);
const app = new Vue({
router: AppRouter,
store,
render: h => h(App)
}).$mount("#app");

View file

@ -28,6 +28,10 @@ const routes: RouteConfig[] = [
{
path: "/applications",
component: Applications
},
{
path: "*",
redirect: { name: "root" }
}
];

View 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;

View file

@ -21,60 +21,37 @@
</div>
</div>
</nav>
<div class="card">
<div class="card-content">
<div class="columns">
<div class="column" v-for="connection in connections">
<ChildAvatar :child="connection" />
<div class="card m-b-lg">
<div v-for="connection in connections" :key="connection.id">
<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 class="column">
<ChildAvatar />
</div>
<div class="content">
<div class="columns">
<div class="column" v-for="user in connection.linkedUsers" :key="user.id">
<ChildAvatar :child="user" />
</div>
</div>
<div class="column">
<ChildAvatar />
</div>
<div class="column">
<ChildAvatar />
</div>
<div class="column">
<ChildAvatar />
</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>
</template>
<script lang="ts">
import ChildAvatar, { IChildAvatar } from "../components/child_avatar.vue";
const connections: IChildAvatar[] = [
{
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"
}
];
import Services from "../../services/index";
let connections = [];
export default {
name: "Home",
@ -82,7 +59,10 @@ export default {
ChildAvatar
},
beforeCreate() {},
created() {},
async created() {
this.connections = await Services.ApiService.getConnections();
// console.dir(connections);
},
data() {
return {
connections

View file

@ -1,68 +1,176 @@
<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="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">
<input class="input" type="text" placeholder="Text input" />
<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">Email</label>
<div class="control">
<input class="input" type="text" placeholder="Text input" />
<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">
<h3 class="title">Settings</h3>
<h4 class="subtitle">{{user.name}}</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="user.avatar" />
</figure>
<div class="card m-t-lg">
<header class="card-header">
<p class="card-header-title">My Children</p>
</header>
<div class="card-content">
<ChildCard v-for="child in user.children" :key="child.id" :child="child"></ChildCard>
</div>
<footer class="card-footer">
<a
:enabled="childValidation.enableInput"
class="card-footer-item"
@click="enableChildModel=true"
>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
:disabled="!childValidation.enableInput"
class="input"
type="text"
placeholder="Text input"
v-model="user.name"
/>
</div>
</div>
<div class="field">
<label class="label">Email</label>
<div class="control">
<input class="input" v-model="user.email" type="email" placeholder="Text input" />
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
let user: {
avatar?: string;
email?: string;
name: string;
isAdmin: boolean;
} = {
name: "LOADING...",
isAdmin: false
};
import { mapActions, mapGetters } from "vuex";
import Modal from "../../shared/components/Modal/Modal.vue";
import ChildCard from "../components/Child_Card.vue";
import Services from "../../services";
import FileSelect from "../../shared/components/FileSelect/FileSelect.vue";
export default {
components: {
Modal,
FileSelect,
ChildCard
},
name: "Settings",
beforeCreate() {},
async beforeCreate() {
return true;
},
async created() {
const response = await fetch("/users/profile/");
this.user = await response.json();
if (!this.user) {
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() {
return {
user
loading: true,
childValidation: {
enableInput: true,
name: null,
dob: null,
avatar: null
},
enableChildModel: false
};
}
};

View 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;
}
}
}

View file

@ -0,0 +1,6 @@
import ApiService from './api.service';
const Services = {
ApiService
}
export default Services;

View file

@ -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>

View file

@ -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>

View file

@ -0,0 +1,9 @@
@layout('layouts.application');
@section('content')
<div id="app">
Loading...
</div>
{{ script('scripts/applications/admin/app.bundle.js') }}
@endsection

View file

@ -15,7 +15,7 @@
<div id="nav-menu" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item">
<a class="navbar-item" href="/">
Home
</a>
@ -37,7 +37,7 @@
Settings
</a>
@if(auth.user.is_admin)
<a class="navbar-item" href='/admin'>
<a class="navbar-item" href='/admin/'>
Admin Settigns
</a>
@endif

View file

@ -10,44 +10,44 @@
| provider here.
|
*/
const providers = [
'@adonisjs/framework/providers/AppProvider',
'@adonisjs/framework/providers/ViewProvider',
'@adonisjs/lucid/providers/LucidProvider',
'@adonisjs/bodyparser/providers/BodyParserProvider',
'@adonisjs/cors/providers/CorsProvider',
'@adonisjs/shield/providers/ShieldProvider',
'@adonisjs/session/providers/SessionProvider',
'@adonisjs/auth/providers/AuthProvider',
'@adonisjs/validator/providers/ValidatorProvider',
]
const providers =
[
'@adonisjs/framework/providers/AppProvider',
'@adonisjs/framework/providers/ViewProvider',
'@adonisjs/lucid/providers/LucidProvider',
'@adonisjs/bodyparser/providers/BodyParserProvider',
'@adonisjs/cors/providers/CorsProvider',
'@adonisjs/shield/providers/ShieldProvider',
'@adonisjs/session/providers/SessionProvider',
'@adonisjs/auth/providers/AuthProvider',
'@adonisjs/validator/providers/ValidatorProvider',
'@adonisjs/drive/providers/DriveProvider',
]
/*
|--------------------------------------------------------------------------
| Ace Providers
|--------------------------------------------------------------------------
|
| Ace providers are required only when running ace commands. For example
| Providers for migrations, tests etc.
|
*/
const aceProviders = [
'@adonisjs/lucid/providers/MigrationsProvider'
]
/*
|--------------------------------------------------------------------------
| Ace Providers
|--------------------------------------------------------------------------
|
| Ace providers are required only when running ace commands. For example
| Providers for migrations, tests etc.
|
*/
const aceProviders = ['@adonisjs/lucid/providers/MigrationsProvider']
/*
|--------------------------------------------------------------------------
| Aliases
|--------------------------------------------------------------------------
|
| Aliases are short unique names for IoC container bindings. You are free
| to create your own aliases.
|
| For example:
| { Route: 'Adonis/Src/Route' }
|
*/
const aliases = {}
/*
|--------------------------------------------------------------------------
| Aliases
|--------------------------------------------------------------------------
|
| Aliases are short unique names for IoC container bindings. You are free
| to create your own aliases.
|
| For example:
| { Route: 'Adonis/Src/Route' }
|
*/
const aliases = {}
/*
|--------------------------------------------------------------------------
@ -59,4 +59,9 @@ const aliases = {}
*/
const commands = []
module.exports = { providers, aceProviders, aliases, commands }
module.exports = {
providers,
aceProviders,
aliases,
commands
}

View file

@ -40,7 +40,8 @@ const globalMiddleware =
*/
const namedMiddleware = {
auth: 'Adonis/Middleware/Auth',
guest: 'Adonis/Middleware/AllowGuestOnly'
guest: 'Adonis/Middleware/AllowGuestOnly',
adminAuth: 'App/Middleware/AdminAuth',
}
/*

View file

@ -27,26 +27,53 @@ Route.post('/register', 'AuthController.register').validator('Register');
Route.post('/login', 'AuthController.login').validator('Login');
/*
/ Applications
/ Client API
*/
Route
.get(
'/applications/story-time',
({view}) => view.render('applications.story-time.app'))
.group(() => {
Route.get('connections', 'ClientApiController.getConnections');
Route.get('user', 'ClientApiController.getUser');
Route.post('child', 'ClientApiController.createChild');
})
.prefix('api/v1/client')
.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 */
Route
.get(
'/users/profile',
({request, response, auth}) => {
console.log('twergsg');
const u = auth.user.publicJSON();
// Route
// .get(
// '/users/profile',
// ({request, response, auth}) => {
// console.log('twergsg');
// const u = auth.user.publicJSON();
response.send(u);
// return auth.user;
})
.middleware(['auth']);
// response.send(u);
// // return auth.user;
// })
// .middleware(['auth']);
Route.get('/*', 'IndexController.index').as('home');

View file

@ -17,6 +17,7 @@ module.exports = {
// 'applications/story-time':
// './resources/scripts/applications/story-time/main.vue',
'applications/home': './resources/scripts/applications/home/main.vue',
'applications/admin': './resources/scripts/applications/admin/main.vue',
},
output: {
filename: 'scripts/[name]/app.bundle.js',

7363
yarn.lock Normal file

File diff suppressed because it is too large Load diff