Commit 9196acfe authored by Bertrand PINEL's avatar Bertrand PINEL

add sample application with todo + handling of S3 storage

parent 79c5f91b
......@@ -6,6 +6,8 @@ It also guide you to set up a JSON-API server on top of AWS API Gateway, Lambda
It could also be seen as a starter for setting up Mirage, allowing the developper to build a full working application (using mock data) entirely running on the client. Once the development is over, the persistance can be delegated to an AWS API Gateway using a lambda function storing objects in DynamoDB.
Complete code is available at https://gitlab.ippon.fr/bpinel/ember-aws-ehipster
Installation
------------------------------------------------------------------------------
......
export { default } from 'ember-aws-ehipster/adapters/application';
......@@ -51,7 +51,7 @@ module.exports = {
'\t\t\t\tlabel: "col-form-label",\n\t\t\t\thelp: "small form-text text-danger",'+
'\n\t\t\t\thint: "small form-text text-muted",\n\t\t\t\tcheckbox: "checkbox",\n\t\t\t\tbutton: "btn btn-default",'+
'\n\t\t\t\tsubmit: "btn btn-primary",\n\t\t\t\tloading: "loading",\n\t\t\t\tvalid: "is-valid",'+
'\n\t\t\t\terror: "is-invalid"\n\t\t\t}\n\t\t},\n';
'\n\t\t\t\terror: "is-invalid"\n\t\t\t}\n\t\t},\n\t\tgatewayURL: "",\n';
addLineToFile(this, configPath, /let ENV = {/, validatedFormConfig);
// Add import of ember_aws_ehipster.css to apps.css
......
import DS from 'ember-data';
import config from '../config/environment';
export default DS.JSONAPIAdapter.extend({
headers: {
"Content-Type": "application/json; charset=utf-8"
}
});
},
host: config.gatewayURL
});
\ No newline at end of file
entity User {
login String required minlength(4),
passwordHash String required minlength(8),
firstName String,
lastName String,
email String,
imageUrl String,
activated Boolean,
createdBy String,
createdDate LocalDate,
lastModifiedBy String,
lastModifiedDate Instant
}
entity Authority {
name String required
}
entity Todo {
title String required,
completed Boolean
}
relationship ManyToMany {
User{user(login)} to Authority{authority(name)}
}
relationship OneToMany {
User{user(login)} to Todo{todo}
}
entity User {
login String required,
passwordHash String required minlength(8),
firstName String,
lastName String,
email String,
imageUrl String,
activated Boolean,
createdBy String,
createdDate LocalDate,
lastModifiedBy String,
lastModifiedDate Instant
}
entity Authority {
name String required
}
relationship ManyToMany {
User{user(id)} to Authority{authorityName}
}
export default function(server) {
}
{
"name": "ember-aws-ehipster",
"version": "0.3.26",
"version": "0.4.3",
"description": "Attempt to build a complete web application using serverless architecture on AWS",
"keywords": [
"ember-addon",
......
import Ember from 'ember';
export default Ember.Component.extend({
store: Ember.inject.service(),
tagName: 'li',
editing: false,
classNameBindings: ['todo.completed', 'editing'],
actions: {
startEditing() {
this.get('onStartEdit')();
this.set('editing', true);
Ember.run.scheduleOnce('afterRender', this, 'focusInput');
},
doneEditing(todoTitle) {
if (!this.get('editing')) { return; }
if (Ember.isBlank(todoTitle)) {
this.send('removeTodo');
} else {
this.set('todo.title', todoTitle.trim());
this.set('editing', false);
this.get('onEndEdit')();
this.get('todo').save();
}
},
handleKeydown(e) {
if (e.keyCode === 13) {
e.target.blur();
} else if (e.keyCode === 27) {
this.set('editing', false);
}
},
toggleCompleted(e) {
let todo = this.get('todo');
Ember.set(todo, 'completed', e.target.checked);
todo.save();
},
removeTodo() {
this.get('store').deleteRecord(this.get('todo'));
}
},
focusInput() {
this.element.querySelector('input.edit').focus();
}
});
import Ember from 'ember';
export default Ember.Component.extend({
store: Ember.inject.service(),
tagName: 'section',
elementId: 'main',
canToggle: true,
allCompleted: Ember.computed('todos.@each.completed', function () {
return this.get('todos').isEvery('completed');
}),
actions: {
enableToggle() {
this.set('canToggle', true);
},
disableToggle() {
this.set('canToggle', false);
},
toggleAll() {
let allCompleted = this.get('allCompleted');
this.get('todos').forEach(todo => Ember.set(todo, 'completed', !allCompleted));
this.get('store').save();
}
}
});
import Controller from '@ember/controller';
import { computed } from '@ember/object';
export default Controller.extend({
todos: computed.filterBy('model', 'completed', false)
});
import Controller from '@ember/controller';
import { computed } from '@ember/object';
import { isBlank } from '@ember/utils';
export default Controller.extend({
remaining: computed.filterBy('model', 'completed', false),
completed: computed.filterBy('model', 'completed'),
actions: {
createTodo(e) {
if (e.keyCode === 13 && !isBlank(e.target.value)) {
let newEntity = this.store.createRecord('todo',{ title: e.target.value.trim(), completed: false });
newEntity.save().then(() => { e.target.value = ''; });
}
},
clearCompleted() {
this.get('completed').forEach((record) => record.destroyRecord());
}
}
});
import Controller from '@ember/controller';
import { computed } from '@ember/object';
export default Controller.extend({
todos: computed.filterBy('model', 'completed', true)
});
import Controller from '@ember/controller';
export default Controller.extend({
entities: [
'todo',
'authority',
'user',
],
entity: '',
actions: {
selectEntity(entity) {
this.set('entity', entity);
this.transitionToRoute('entity-factory.'+entity);
}
}
});
\ No newline at end of file
import Controller from '@ember/controller';
import { computed } from '@ember/object';
import { A } from '@ember/array';
import EmberObject from '@ember/object';
import AuthorityValidations from "../../validations/authority";
import { task } from "ember-concurrency";
export default Controller.extend({
isAddingEntry: false,
newEntry: EmberObject.create({name: '',users: []}),
AuthorityValidations,
userList: computed('users', function() {
this.store.findAll('user');
}),
name: null,
users: null,
authorityTableColumns: computed(function() {
var col = A([
EmberObject.create({
propertyName: "name",
title: "name"
})]);
col.pushObject({
title: 'Delete',
component: 'delete-row'
});
return col; }),
authorityTableContent: computed(function() {
return this.get('authorities');
}),
actions: {
submit() {
console.log("action submit");
},
deleteRecord (record) {
console.log('record is '+ record);
record.destroyRecord();
}
},
submitEntry: task(function*(model) {
yield model.save();
let newEntity = this.store.createRecord('authority',yield model.get('data'));
yield newEntity.save();
console.log("new entry of id "+newEntity.get('id')+" created");
this.set('addEntryModal', false);
})
});
\ No newline at end of file
import Controller from '@ember/controller';
import { computed } from '@ember/object';
import { A } from '@ember/array';
import EmberObject from '@ember/object';
import TodoValidations from "../../validations/todo";
import { task } from "ember-concurrency";
export default Controller.extend({
isAddingEntry: false,
newEntry: EmberObject.create({title: '',completed: 'false',user: []}),
TodoValidations,
userList: computed('user', function() {
this.store.findAll('user');
}),
title: null,
completed: null,
user: null,
todoTableColumns: computed(function() {
var col = A([
EmberObject.create({
propertyName: "title",
title: "title"
}),
EmberObject.create({
propertyName: "completed",
title: "completed"
})]);
col.pushObject({
title: 'Delete',
component: 'delete-row'
});
return col; }),
todoTableContent: computed(function() {
return this.get('todos');
}),
actions: {
submit() {
console.log("action submit");
},
deleteRecord (record) {
console.log('record is '+ record);
record.destroyRecord();
}
},
submitEntry: task(function*(model) {
yield model.save();
let newEntity = this.store.createRecord('todo',yield model.get('data'));
yield newEntity.save();
console.log("new entry of id "+newEntity.get('id')+" created");
this.set('addEntryModal', false);
})
});
\ No newline at end of file
import Controller from '@ember/controller';
import { computed } from '@ember/object';
import { A } from '@ember/array';
import EmberObject from '@ember/object';
import UserValidations from "../../validations/user";
import { task } from "ember-concurrency";
export default Controller.extend({
isAddingEntry: false,
newEntry: EmberObject.create({login: '',passwordHash: '',firstName: '',lastName: '',email: '',imageUrl: '',activated: 'false',createdBy: '',createdDate: new Date(),lastModifiedBy: '',lastModifiedDate: new Date(),authorities: [],todos: []}),
UserValidations,
authorityList: computed('authorities', function() {
this.store.findAll('authority');
}),
todoList: computed('todos', function() {
this.store.findAll('todo');
}),
login: null,
passwordHash: null,
firstName: null,
lastName: null,
email: null,
imageUrl: null,
activated: null,
createdBy: null,
createdDate: null,
lastModifiedBy: null,
lastModifiedDate: null,
authorities: null,
todos: null,
userTableColumns: computed(function() {
var col = A([
EmberObject.create({
propertyName: "login",
title: "login"
}),
EmberObject.create({
propertyName: "passwordHash",
title: "passwordHash"
}),
EmberObject.create({
propertyName: "firstName",
title: "firstName"
}),
EmberObject.create({
propertyName: "lastName",
title: "lastName"
}),
EmberObject.create({
propertyName: "email",
title: "email"
}),
EmberObject.create({
propertyName: "imageUrl",
title: "imageUrl"
}),
EmberObject.create({
propertyName: "activated",
title: "activated"
}),
EmberObject.create({
propertyName: "createdBy",
title: "createdBy"
}),
EmberObject.create({
propertyName: "createdDate",
title: "createdDate"
}),
EmberObject.create({
propertyName: "lastModifiedBy",
title: "lastModifiedBy"
}),
EmberObject.create({
propertyName: "lastModifiedDate",
title: "lastModifiedDate"
})]);
col.pushObject({
title: 'Delete',
component: 'delete-row'
});
return col; }),
userTableContent: computed(function() {
return this.get('users');
}),
actions: {
submit() {
console.log("action submit");
},
deleteRecord (record) {
console.log('record is '+ record);
record.destroyRecord();
}
},
submitEntry: task(function*(model) {
yield model.save();
let newEntity = this.store.createRecord('user',yield model.get('data'));
yield newEntity.save();
console.log("new entry of id "+newEntity.get('id')+" created");
this.set('addEntryModal', false);
})
});
\ No newline at end of file
import Ember from 'ember';
import { pluralize } from 'ember-inflector';
export function pluralizeHelper([singular, count]/*, hash*/) {
return count === 1 ? singular : pluralize(singular);
}
export default Ember.Helper.helper(pluralizeHelper);
\ No newline at end of file
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
users: DS.hasMany('user')
});
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
completed: DS.attr('boolean'),
user: DS.hasMany('user')
});
import DS from 'ember-data';
export default DS.Model.extend({
login: DS.attr('string'),
passwordHash: DS.attr('string'),
firstName: DS.attr('string'),
lastName: DS.attr('string'),
email: DS.attr('string'),
imageUrl: DS.attr('string'),
activated: DS.attr('boolean'),
createdBy: DS.attr('string'),
createdDate: DS.attr('date'),
lastModifiedBy: DS.attr('string'),
lastModifiedDate: DS.attr('date'),
authorities: DS.hasMany('authority'),
todos: DS.hasMany('todo')
});
......@@ -7,7 +7,13 @@ const Router = EmberRouter.extend({
});
Router.map(function() {
this.route('entity-factory', function() {
this.route('todo');
this.route('authority');
this.route('user');
});
this.route('active');
this.route('completed');
});
export default Router;
import Route from '@ember/routing/route';
export default Route.extend({
model() {
return this.store.findAll("todo");
}
});
import Route from '@ember/routing/route';
export default Route.extend({
model() {
return Ember.RSVP.hash({
authorities: this.store.findAll('authority'),
users: this.store.findAll('user')
});
},
setupController(controller, models) {
controller.setProperties(models);
}
});
\ No newline at end of file
import Route from '@ember/routing/route';
export default Route.extend({
model() {
return Ember.RSVP.hash({
todos: this.store.findAll('todo'),
users: this.store.findAll('user')
});
},
setupController(controller, models) {
controller.setProperties(models);
}
});
\ No newline at end of file
import Route from '@ember/routing/route';
export default Route.extend({
model() {
return Ember.RSVP.hash({
users: this.store.findAll('user'),
authorities: this.store.findAll('authority'),
todos: this.store.findAll('todo')
});
},
setupController(controller, models) {
controller.setProperties(models);
}
});
\ No newline at end of file
@import 'ember-aws-ehipster.css';
@import 'todo-base.css';
@import 'todo-index.css';
footer p {
font-size: 25px;
}
#new-todo {
background-color: #ffffff;
}
\ No newline at end of file
a:hover {
text-decoration: none;
}
a.active {
font-weight: 700;
}
pre {
text-align: left;
white-space: pre-line;
}
.table-footer {
border: 1px solid #ddd;
padding: 5px 0;
}
.models-table-wrapper {
margin-bottom: 20px;
}
.btn-default {
background-image: none !important;
}
.columns-dropdown {
margin-bottom: 20px;
}
.table-column-options.table > tbody > tr > td {
border-top-width: 0;
}
tr.selected-row>td:not(.grouping-cell), tr.selected-expand>td:not(.grouping-cell) {
background: #C6E746;
}
.navbar-text.gh {
margin-top: 13px !important;
margin-bottom: 11px !important;
}
\ No newline at end of file
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #c5c5c5;
border-bottom: 1px dashed #f7f7f7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
#issue-count {
display: none;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}