Basic logic

- DB migrations
 - Login/Register flows
 - Dockerfile
 - Layouts, Partials, Components
 - Sass
 - Bulma.io
 - sqlite
This commit is contained in:
Sagi Dayan 2020-01-18 15:46:06 -05:00
parent 05a6705db9
commit d7c9359ef2
37 changed files with 7377 additions and 119 deletions

14
Dockerfile Normal file
View file

@ -0,0 +1,14 @@
FROM node:latest
WORKDIR /app
COPY package*.json ./
RUN npm install --only=prod --no-audit
COPY . .
COPY .env.example .env
# EXPOSE 3333
CMD [ "npm", "start"]

View file

@ -1,3 +1,7 @@
# Seepur # Seepur
#### A shared story time experience #### A shared story time experience
#### For Development
- `adonis serve --dev`
- `npm run css-watch`

View file

@ -0,0 +1,45 @@
'use strict'
const User = use('App/Models/User')
class AuthController {
async registerIndex({view}){
return view.render('register')
}
async loginIndex({view}){
return view.render('login')
}
async register({request, response, view, session, auth}){
const user = await User.create({
email: request.input('email'),
name: request.input('name'),
password: request.input('password')
});
await auth.login(user)
response.redirect('/');
}
async login({request,response, auth, session}){
console.log('login');
const { email, password } = request.all()
try{
const token = await auth.attempt(email, password);
console.log('logged in');
}catch(e){
session.withErrors({ loginError: 'Invalid Credentials' }).flashAll()
return response.redirect('back')
}
response.redirect('/');
}
async logout({auth, response}){
await auth.logout();
response.redirect('/');
}
}
module.exports = AuthController

View file

@ -0,0 +1,17 @@
'use strict'
class IndexController {
async index({auth, view}){
try{
await auth.check();
return view.render('home');
}catch(e){
const bgIndex = Math.ceil((Math.random()*10)%5);
console.log(bgIndex)
return view.render('landing', {bgIndex});
}
}
}
module.exports = IndexController

9
app/Models/Child.js Normal file
View file

@ -0,0 +1,9 @@
'use strict'
/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */
const Model = use('Model')
class Child extends Model {
}
module.exports = Child

16
app/Models/Link.js Normal file
View file

@ -0,0 +1,16 @@
'use strict'
/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */
const Model = use('Model')
class Link extends Model {
users(){
return this.hasMany('App/Models/User')
}
children() {
return this.hasMany('App/Models/Child')
}
}
module.exports = Link

View file

@ -34,6 +34,16 @@ class User extends Model {
tokens () { tokens () {
return this.hasMany('App/Models/Token') return this.hasMany('App/Models/Token')
} }
children() {
return this.hasMany('App/Models/Child')
}
links() {
return this.hasMany('App/Models/Link')
}
} }
module.exports = User module.exports = User

22
app/Validators/Login.js Normal file
View file

@ -0,0 +1,22 @@
'use strict'
class Login {
get rules () {
return {
// validation rules
email: 'required|email',
password: 'required|string',
}
}
get messages() {
return{
'email.required': 'Must Provide an email',
'email.email': 'Email is invalid',
'password.required': 'Invalid password'
}
}
}
module.exports = Login

View file

@ -0,0 +1,23 @@
'use strict'
class Register {
get rules () {
return {
name: 'required|min:1',
email: 'required|email|unique:users',
password: 'required|string|min:6',
// validation rules
}
}
get messages() {
return {
'name.min': 'Must provide a name',
'email.unique': 'This email is already taken',
'required': 'This is required',
'email.unique': 'This email is already taken',
'password.min': 'Password must be at least 6 chars long'
}
}
}
module.exports = Register

View file

@ -16,7 +16,7 @@ module.exports = {
| |
*/ */
name: Env.get('APP_NAME', 'AdonisJs'), name: Env.get('APP_NAME', 'Seepur'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View file

@ -26,7 +26,7 @@ module.exports = {
| are signed and encrypted. | are signed and encrypted.
| |
*/ */
cookieName: 'adonis-session', cookieName: 'seepur-session',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View file

@ -7,9 +7,10 @@ class UserSchema extends Schema {
up () { up () {
this.create('users', (table) => { this.create('users', (table) => {
table.increments() table.increments()
table.string('username', 80).notNullable().unique()
table.string('email', 254).notNullable().unique() table.string('email', 254).notNullable().unique()
table.string('name').notNullable()
table.string('password', 60).notNullable() table.string('password', 60).notNullable()
table.string('avatar')
table.timestamps() table.timestamps()
}) })
} }

View file

@ -0,0 +1,26 @@
'use strict'
/** @type {import('@adonisjs/lucid/src/Schema')} */
const Schema = use('Schema')
class ChildSchema extends Schema {
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'])
})
}
down () {
this.drop('children')
}
}
module.exports = ChildSchema

View file

@ -0,0 +1,23 @@
'use strict'
/** @type {import('@adonisjs/lucid/src/Schema')} */
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'])
})
}
down () {
this.drop('link')
}
}
module.exports = LinkSchema

View file

@ -1,16 +1,19 @@
{ {
"name": "adonis-fullstack-app", "name": "Seepur",
"version": "4.1.0", "version": "0.0.1",
"adonis-version": "4.1.0", "adonis-version": "4.1.0",
"description": "The fullstack application boilerplate for Adonisjs", "description": "A shared story time experience",
"main": "index.js", "main": "server.js",
"scripts": { "scripts": {
"start": "node server.js", "css-build": "node_modules/.bin/node-sass --omit-source-map-url resources/sass/main.scss public/style.css",
"css-watch": "npm run css-build -- --watch",
"migrate": "node_modules/.bin/adonis migration:run -f",
"build": "npm run migrate && npm run css-build",
"start": "npm run migrate && node server.js",
"test": "node ace test" "test": "node ace test"
}, },
"keywords": [ "keywords": [
"adonisjs", "seepur"
"adonis-app"
], ],
"author": "", "author": "",
"license": "UNLICENSED", "license": "UNLICENSED",
@ -19,15 +22,22 @@
"@adonisjs/ace": "^5.0.8", "@adonisjs/ace": "^5.0.8",
"@adonisjs/auth": "^3.0.7", "@adonisjs/auth": "^3.0.7",
"@adonisjs/bodyparser": "^2.0.5", "@adonisjs/bodyparser": "^2.0.5",
"@adonisjs/cli": "^4.0.12",
"@adonisjs/cors": "^1.0.7", "@adonisjs/cors": "^1.0.7",
"@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",
"@adonisjs/lucid": "^6.1.3", "@adonisjs/lucid": "^6.1.3",
"@adonisjs/session": "^1.0.27", "@adonisjs/session": "^1.0.27",
"@adonisjs/shield": "^1.0.8" "@adonisjs/shield": "^1.0.8",
"@adonisjs/validator": "^5.0.6",
"bulma": "^0.8.0",
"fork-awesome": "^1.1.7",
"sqlite3": "^4.1.1"
},
"devDependencies": {
"node-sass": "^4.13.0"
}, },
"devDependencies": {},
"autoload": { "autoload": {
"App": "./app" "App": "./app"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
// Set your brand colors
$purple: #8A4D76;
$pink: #FA7C91;
$brown: #757763;
$beige-light: #D0D1CD;
$beige-lighter: #EFF0EB;

129
resources/sass/main.scss Normal file
View file

@ -0,0 +1,129 @@
@charset "utf-8";
@import './colors.scss';
@import './variables.scss';
@import './mixins.scss';
@import "../../node_modules/bulma/bulma.sass";
.hero-bg-landing01 {
background-image: url('/images/landing-hero01.jpg');
background-size: cover;
background-position: center;
}
.hero-bg-landing02 {
background-image: url('/images/landing-hero02.jpg');
background-size: cover;
background-position: center;
}
.hero-bg-landing03 {
background-image: url('/images/landing-hero03.jpg');
background-size: cover;
background-position: center;
}
.hero-bg-landing04 {
background-image: url('/images/landing-hero04.jpg');
background-size: cover;
background-position: center;
}
.hero-bg-landing05 {
background-image: url('/images/landing-hero05.jpg');
background-size: cover;
background-position: center;
}
.hero {
.navbar.darken {
@include linearGradient(rgba(0, 0, 0, .7), rgba(0, 0, 0, 0));
}
}
// SlideShow
.slideshow {
// max-width: 100%;
// max-height: 100%;
// overflow: hidden;
// position: inherit;
}
.hero {
.slideshow-item {
animation: imageAnimation 30s linear infinite 0s;
backface-visibility: hidden;
background-size: cover;
background-position: center center;
background-repeat: no-repeat;
color: transparent;
height: 77.5vh;
left: 0px;
opacity: 0;
position: absolute;
top: 0px;
width: 100%;
z-index: 0;
}
}
.hero {
.slideshow-item:nth-child(1) {
background-image: url('/images/landing-hero01.jpg');
}
}
.hero {
.slideshow-item:nth-child(2) {
animation-delay: 6s;
background-image: url('/images/landing-hero02.jpg');
}
}
.hero {
.slideshow-item:nth-child(3) {
animation-delay: 12s;
background-image: url('/images/landing-hero03.jpg');
}
}
.hero {
.slideshow-item:nth-child(4) {
animation-delay: 18s;
background-image: url('/images/landing-hero04.jpg');
}
}
.hero {
.slideshow-item:nth-child(5) {
animation-delay: 24s;
background-image: url('/images/landing-hero05.jpg');
}
}
@keyframes imageAnimation {
0% {
animation-timing-function: ease-in;
opacity: 0;
}
8% {
animation-timing-function: ease-out;
opacity: 1;
}
17% {
opacity: 1
}
25% {
opacity: 0
}
100% {
opacity: 0
}
}

View file

@ -0,0 +1,10 @@
@mixin linearGradient($top, $bottom){
background: $top; /* Old browsers */
background: -moz-linear-gradient(top, $top 0%, $bottom 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,$top), color-stop(100%,$bottom)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, $top 0%,$bottom 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, $top 0%,$bottom 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top, $top 0%,$bottom 100%); /* IE10+ */
background: linear-gradient(to bottom, $top 0%,$bottom 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#000000',GradientType=0 ); /* IE6-9 */
}

View file

@ -0,0 +1,8 @@
// Update Bulma's global variables
$family-sans-serif: "Nunito", sans-serif;
$grey-dark: $brown;
$grey-light: $beige-light;
$primary: $purple;
$link: $pink;
$widescreen-enabled: false;
$fullhd-enabled: false;

View file

@ -0,0 +1,57 @@
<nav class="{{ isLanding ? 'darken' : '' }} navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="/">
{{-- <img src="https://bulma.io/images/bulma-logo.png" width="112" height="28"> --}}
<strong>Seepur</strong>
</a>
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item">
Home
</a>
<a class="navbar-item">
About
</a>
</div>
<div class="navbar-end">
@loggedIn
<div class="navbar-item has-dropdown is-hoverable is-dark">
<a class="navbar-link">
{{auth.user.name}}
</a>
<div class="navbar-dropdown">
<a class="navbar-item">
Settings
</a>
<a class="navbar-item" href="/logout">
Logout
</a>
</div>
</div>
@else
<div class="navbar-item">
<div class="buttons">
<a class="button is-primary" href='/register'>
<strong>Sign up</strong>
</a>
<a class="button is-light" href='/login'>
Log in
</a>
</div>
</div>
@endloggedIn
</div>
</div>
</nav>

View file

@ -0,0 +1,9 @@
@layout('layouts.main')
@section('page-title')
Your Title
@endsection
@section('content')
@endsection

10
resources/views/home.edge Normal file
View file

@ -0,0 +1,10 @@
@layout('layouts.main')
@section('page-title')
Seepur | Home
@endsection
@section('content')
<h1 class="title">{{auth.user.name}}</h1>
<h2 class="subtitle">{{auth.user.email}}</h2>
@endsection

View file

@ -0,0 +1,65 @@
@layout('layouts.landing')
@section('page-title')
Seepur | Shared story time
@endsection
@section('hero')
<section class="hero is-large is-primary hero-bg-landing0{{bgIndex}}">
@!component('components.nav.nav', isLanding = true, auth=auth)
<div class="hero-body">
<div class="container">
<h1 class="title">
Seepur
</h1>
<h2 class="subtitle">
A Shared Story Time Experience
</h2>
<div class="buttons">
<a class="button is-primary" href="/register">Sign Up</a>
<a class="button is-link" href='/login'>Log In</a>
</div>
</div>
</div>
</section>
@endsection
@section('content')
<div class="columns">
<div class="column">
<div class="card">
<div class="card-header">
<div class="card-header-title">Open Source</div>
<div class="card-header-icon"><i class="fa fa-code"></i></div>
</div>
<div class="card-content">
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Illo officiis totam architecto odio tenetur aliquam magnam dolores quaerat. Minima, ad.
</div>
</div>
</div>
<div class="column">
<div class="card">
<div class="card-header">
<div class="card-header-title">Easy To Use</div>
<div class="card-header-icon"><div class="fa fa-hand-pointer-o"></div></div>
</div>
<div class="card-content">
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Impedit numquam quas facere saepe aliquid blanditiis dolore, amet totam assumenda in consequatur ipsa optio officia illum excepturi quia reprehenderit quis nihil qui, iste obcaecati aut eius doloremque vero. Saepe, cum accusantium.
</div>
</div>
</div>
<div class="column">
<div class="card">
<div class="card-header">
<div class="card-header-title">Happy Family</div>
<div class="card-header-icon"><div class="fa fa-smile-o"></div></div>
</div>
<div class="card-content">
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Impedit itaque facilis dolore, voluptatibus asperiores magni adipisci repudiandae ex provident nisi.
</div>
</div>
</div>
</div>
@endsection

View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@include('partials.SEO.meta')
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fork-awesome@1.1.7/css/fork-awesome.min.css" integrity="sha256-gsmEoJAws/Kd3CjuOQzLie5Q3yshhvmo7YNtBG7aaEY=" crossorigin="anonymous">
<title>
@!section('page-title')
</title>
{{ style('style') }}
@!section('scripts')
</head>
<body>
@!section('nav')
@!section('hero')
<section class="section">
<div class="container">
@!section('content')
</div>
</section>
</body>
</html>

View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@include('partials.SEO.meta')
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fork-awesome@1.1.7/css/fork-awesome.min.css" integrity="sha256-gsmEoJAws/Kd3CjuOQzLie5Q3yshhvmo7YNtBG7aaEY=" crossorigin="anonymous">
<title>
@!section('page-title')
</title>
{{ style('style') }}
@!section('scripts')
</head>
<body>
@!component('components.nav.nav', isLanding = false, auth=auth)
@!section('hero')
<section class="section">
<div class="container">
@!section('content')
</div>
</section>
</body>
</html>

View file

@ -0,0 +1,66 @@
@layout('layouts.main')
@section('page-title')
Seepur| Login
@endsection
@section('content')
<h1 class="title">
Login
</h1>
<p class="subtitle">
It's Seepur time!
</p>
@if(hasErrorFor('loginError'))
<div class="notification is-danger">
<button class="delete"></button>
{{getErrorFor('loginError')}}
</div>
@endif
<form class="form register" method="POST" action="{{ route('login') }}">
{{ csrfField() }}
<div class="field">
<label class="label">Email</label>
<div class="control has-icons-left">
<input class="input {{ getErrorFor('email') ? 'is-danger' : ''}}" name="email" type="email" placeholder="Email" value="{{ old('email', '') }}">
<span class="icon is-small is-left">
<i class="fa fa-envelope"></i>
</span>
</div>
<p class="help is-danger">{{ getErrorFor('email') ? getErrorFor('email') : '' }}</p>
</div>
<div class="field">
<label class="label">Password</label>
<div class="control has-icons-left">
<input class="input {{ getErrorFor('password') ? 'is-danger' : ''}}" name="password" type="password" placeholder="Password">
<span class="icon is-small is-left">
<i class="fa fa-key"></i>
</span>
</div>
<p class="help is-danger">{{ getErrorFor('password') ? getErrorFor('password') : '' }}</p>
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-link">Submit</button>
</div>
<div class="control">
<a class="button is-link is-light" href='/'>Cancel</a>
</div>
</div>
</form>
<small class='small is-small'>
Want to open a free account? <a href="/register">Sign up here</a>
</small>
<br>
<small class='small is-small'>
<a href="#">Help, I forgot my password!</a>
</small>
@endsection

View file

@ -0,0 +1,7 @@
<meta name="description" content="Seepur, A Realtime shared story time. For babys, parents, family and friends">
<meta name="keywords" content="story+time,video,baby,books">
<meta property="og:title" content="Seepur">
<meta property="og:type" content="website">
<meta property="og:image" content="images/logos/logo.png">
<meta property="og:url" content="/">
<meta property="og:description" content="Seepur, A Realtime shared story time. For babys, parents, family and friends">

View file

@ -0,0 +1,84 @@
@layout('layouts.main')
@section('page-title')
Seepur| Register
@endsection
@section('content')
<h1 class="title">
Register
</h1>
<p class="subtitle">
Yep, It's Free
</p>
<form class="form register" method="POST" action="{{ route('register') }}">
{{ csrfField() }}
<div class="field">
<label class="label">Name</label>
<div class="control has-icons-left">
<input class="input {{ getErrorFor('name') ? 'is-danger' : ''}}" name="name" type="text" placeholder="John Snow" value="{{ old('name', '') }}">
<span class="icon is-small is-left">
<i class="fa fa-id-card"></i>
</span>
</div>
<p class="help is-danger">{{ getErrorFor('name') ? getErrorFor('name') : '' }}</p>
</div>
<div class="field">
<label class="label">Email</label>
<div class="control has-icons-left">
<input class="input {{ getErrorFor('email') ? 'is-danger' : ''}}" name="email" type="email" placeholder="j.snow@thewall.com" value="{{ old('email', '') }}">
<span class="icon is-small is-left">
<i class="fa fa-envelope"></i>
</span>
</div>
<p class="help is-danger">{{ getErrorFor('email') ? getErrorFor('email') : '' }}</p>
</div>
<div class="field">
<label class="label">Password</label>
<div class="control has-icons-left">
<input class="input {{ getErrorFor('password') ? 'is-danger' : ''}}" name="password" type="password" placeholder="password">
<span class="icon is-small is-left">
<i class="fa fa-envelope"></i>
</span>
</div>
<p class="help is-danger">{{ getErrorFor('password') ? getErrorFor('password') : '' }}</p>
</div>
<div class="field">
<label class="label">Confirm Password</label>
<div class="control has-icons-left">
<input class="input" type="password" placeholder="confirm password">
<span class="icon is-small is-left">
<i class="fa fa-envelope"></i>
</span>
</div>
</div>
<div class="field">
<div class="control">
<label class="checkbox">
<input type="checkbox">
I agree to the <a href="#">terms and conditions</a>
</label>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-link">Submit</button>
</div>
<div class="control">
<a class="button is-link is-light" href='/'>Cancel</a>
</div>
</div>
</form>
@endsection

View file

@ -1,20 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Hello Adonis</title>
{{ style('style') }}
</head>
<body>
<section>
<div class="logo"></div>
<div class="title"></div>
<div class="subtitle">
<p>AdonisJs simplicity will make you feel confident about your code</p>
<p>
Don't know where to start? Read the <a href="https://adonisjs.com/docs">documentation</a>.
</p>
</div>
</section>
</body>
</html>

View file

@ -18,7 +18,8 @@ const providers = [
'@adonisjs/cors/providers/CorsProvider', '@adonisjs/cors/providers/CorsProvider',
'@adonisjs/shield/providers/ShieldProvider', '@adonisjs/shield/providers/ShieldProvider',
'@adonisjs/session/providers/SessionProvider', '@adonisjs/session/providers/SessionProvider',
'@adonisjs/auth/providers/AuthProvider' '@adonisjs/auth/providers/AuthProvider',
'@adonisjs/validator/providers/ValidatorProvider',
] ]
/* /*

View file

@ -16,4 +16,14 @@
/** @type {typeof import('@adonisjs/framework/src/Route/Manager')} */ /** @type {typeof import('@adonisjs/framework/src/Route/Manager')} */
const Route = use('Route') const Route = use('Route')
Route.on('/').render('welcome') Route.get('/', 'IndexController.index').as('home');
/*
/ Auth
*/
Route.get('/logout', 'AuthController.logout').as('logout').middleware(['auth']);
Route.get('/register', 'AuthController.registerIndex').as('register');
Route.get('/login', 'AuthController.loginIndex').as('login');
Route.post('/register', 'AuthController.register').validator('Register');
Route.post('/login', 'AuthController.login').validator('Login');