├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── bin ├── mongo_model.dart ├── mongo_pool.dart ├── ticketing_controller.dart ├── ticketing_model.dart └── webserver.dart ├── lib ├── client │ ├── client.dart │ ├── components │ │ ├── flight_display │ │ │ ├── flight_display.html │ │ │ ├── flight_display.scss │ │ │ └── flight_display_component.dart │ │ ├── landing │ │ │ ├── landing.html │ │ │ └── landing_component.dart │ │ ├── order │ │ │ ├── order.html │ │ │ ├── order.scss │ │ │ ├── order_component.dart │ │ │ ├── recap.html │ │ │ └── recap_component.dart │ │ ├── picker │ │ │ ├── picker.html │ │ │ ├── picker.scss │ │ │ └── picker_component.dart │ │ └── topnav │ │ │ ├── topnav.html │ │ │ └── topnav_component.dart │ ├── composite_views │ │ ├── view_complete_component.dart │ │ ├── view_flights_component.dart │ │ ├── view_landing_component.dart │ │ └── view_order_component.dart │ ├── main_tickets_component.dart │ └── services │ │ ├── environment_variables.dart │ │ ├── flight_formatter.dart │ │ ├── query_service.dart │ │ └── shared_data.dart ├── db │ ├── db_config.dart │ ├── seed.json │ └── seeder.dart ├── shared │ ├── dtos │ │ ├── base_dto.dart │ │ ├── booking_dto.dart │ │ ├── city_dto.dart │ │ ├── purchase_dto.dart │ │ ├── route_dto.dart │ │ ├── time_dto.dart │ │ └── transaction_dto.dart │ └── schemas.dart └── transformer.dart ├── pubspec.yaml ├── test ├── database_test.dart └── ticket_model_test.dart └── web ├── deals.json ├── index.html ├── main.dart └── styles └── main.scss /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .pub 3 | pubspec.lock 4 | .packages 5 | packages 6 | build 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Jack Murphy 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: ./dart-sdk/bin/dart bin/webserver.dart 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # web_apps_dart 2 | Following the language introduction and overview, Jack builds a web application that will provide an easy-to-follow walkthrough of the Dart language and its associated development environments for both the front end and backend programming. 3 | -------------------------------------------------------------------------------- /bin/mongo_model.dart: -------------------------------------------------------------------------------- 1 | library ticket_models; 2 | 3 | import 'dart:async'; 4 | import 'package:tickets/shared/schemas.dart'; 5 | import 'package:mongo_dart/mongo_dart.dart'; 6 | import 'package:connection_pool/connection_pool.dart'; 7 | import 'mongo_pool.dart'; 8 | import "dart:mirrors"; 9 | 10 | class MongoModel { 11 | 12 | MongoPool _dbPool; 13 | 14 | MongoModel(String databaseName, String databaseUrl, int databasePoolSize) { 15 | _dbPool = new MongoPool(databaseUrl + databaseName, databasePoolSize); 16 | } 17 | 18 | Future createByItem(BaseDTO item) { 19 | assert(item.id == null); 20 | item.id = new ObjectId().toString(); 21 | return _dbPool.getConnection().then((ManagedConnection mc) { 22 | Db db = mc.conn; 23 | DbCollection collection = db.collection(item.collection_key); 24 | Map aMap = dtoToMongoMap(item); 25 | return collection.insert(aMap).then((status) { 26 | _dbPool.releaseConnection(mc); 27 | return (status['ok'] == 1) ? item : null; 28 | }); 29 | }); 30 | } 31 | 32 | Future deleteByItem(BaseDTO item) { 33 | assert(item.id != null); 34 | return _dbPool.getConnection().then((ManagedConnection mc) { 35 | Db database = mc.conn; 36 | DbCollection collection = database.collection(item.collection_key); 37 | Map aMap = dtoToMongoMap(item); 38 | return collection.remove(aMap).then((status) { 39 | _dbPool.releaseConnection(mc); 40 | return status; 41 | }); 42 | }); 43 | } 44 | 45 | Future readItemByItem(BaseDTO matcher) { 46 | assert(matcher.id != null); 47 | Map query = {'_id': matcher.id}; 48 | BaseDTO bdto; 49 | return _getCollection(matcher.collection_key, query).then((items) { 50 | bdto = mapToDto(getInstance(matcher.runtimeType), items.first); 51 | return bdto; 52 | }); 53 | } 54 | 55 | Future readCollectionByTypeWhere(t, fieldName, values) { 56 | List list = new List(); 57 | BaseDTO freshInstance = getInstance(t); 58 | return _getCollectionWhere(freshInstance.collection_key, fieldName, values).then((items) { 59 | items.forEach((item) { 60 | list.add(mapToDto(getInstance(t), item)); 61 | }); 62 | return list; 63 | }); 64 | } 65 | 66 | Future readCollectionByType(t, [Map query = null]) { 67 | List list = new List(); 68 | BaseDTO freshInstance = getInstance(t); 69 | return _getCollection(freshInstance.collection_key, query).then((items) { 70 | items.forEach((item) { 71 | list.add(mapToDto(getInstance(t), item)); 72 | }); 73 | return list; 74 | }); 75 | } 76 | 77 | Future updateItem(BaseDTO item) { 78 | assert(item.id != null); 79 | return _dbPool.getConnection().then((ManagedConnection mc) async { 80 | Db database = mc.conn; 81 | DbCollection collection = new DbCollection(database, item.collection_key); 82 | Map selector = {'_id': item.id}; 83 | Map newItem = dtoToMongoMap(item); 84 | return await collection.update(selector, newItem).then((_) { 85 | _dbPool.releaseConnection(mc); 86 | return _; 87 | }); 88 | }); 89 | } 90 | 91 | Future dropDatabase() async { 92 | Db database = await _dbPool.openNewConnection(); 93 | Map status = await database.drop(); 94 | return status; 95 | } 96 | 97 | // Some Abstractions 98 | 99 | Future _getCollectionWhere(String collectionName, fieldName, values) { 100 | return _dbPool.getConnection().then((ManagedConnection mc) async { 101 | Db database = mc.conn; 102 | DbCollection collection = new DbCollection(database, collectionName); 103 | SelectorBuilder builder = where.oneFrom(fieldName, values); 104 | return collection.find( builder ).toList().then((map) { 105 | _dbPool.releaseConnection(mc); 106 | return map; 107 | }); 108 | }); 109 | } 110 | 111 | Future _getCollection(String collectionName, [Map query = null]) { 112 | return _dbPool.getConnection().then((ManagedConnection mc) async { 113 | DbCollection collection = new DbCollection(mc.conn, collectionName); 114 | List list = await collection.find(query).toList(); 115 | _dbPool.releaseConnection(mc); 116 | return list; 117 | }); 118 | } 119 | 120 | dynamic getInstance(Type t) { 121 | MirrorSystem mirrors = currentMirrorSystem(); 122 | LibraryMirror lm = mirrors.libraries.values.firstWhere( 123 | (LibraryMirror lm) => lm.qualifiedName == new Symbol('ticket_schemas')); 124 | ClassMirror cm = lm.declarations[new Symbol(t.toString())]; 125 | InstanceMirror im = cm.newInstance(new Symbol(''), []); 126 | return im.reflectee; 127 | } 128 | 129 | dynamic mapToDto(cleanObject, Map document) { 130 | var reflection = reflect(cleanObject); 131 | document['id'] = document['_id'].toString(); 132 | document.remove('_id'); 133 | document.forEach((k, v) { 134 | reflection.setField(new Symbol(k), v); 135 | }); 136 | return cleanObject; 137 | } 138 | 139 | Map dtoToMap(Object object) { 140 | var reflection = reflect(object); 141 | Map target = new Map(); 142 | var type = reflection.type; 143 | 144 | while (type != null) { 145 | type.declarations.values.forEach((item) { 146 | if (item is VariableMirror) { 147 | VariableMirror value = item; 148 | if (!value.isFinal) { 149 | target[MirrorSystem.getName(value.simpleName)] = reflection.getField(value.simpleName).reflectee; 150 | } 151 | } 152 | }); 153 | type = type.superclass; 154 | // get properties from superclass too! 155 | } 156 | 157 | return target; 158 | } 159 | 160 | Map dtoToMongoMap(object) { 161 | Map item = dtoToMap(object); 162 | 163 | // mongo uses an underscore prefix which would act as a private field in dart 164 | // convert only on write to mongo 165 | 166 | item['_id'] = item['id']; 167 | item.remove('id'); 168 | return item; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /bin/mongo_pool.dart: -------------------------------------------------------------------------------- 1 | import 'package:connection_pool/connection_pool.dart'; 2 | import 'package:mongo_dart/mongo_dart.dart'; 3 | import 'dart:async'; 4 | 5 | class MongoPool extends ConnectionPool { 6 | 7 | String uri; 8 | 9 | MongoPool(String this.uri, int poolSize) : super(poolSize); 10 | 11 | @override 12 | void closeConnection(Db conn) { 13 | conn.close(); 14 | } 15 | 16 | @override 17 | Future openNewConnection() { 18 | var conn = new Db(uri); 19 | return conn.open().then((_) => conn); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /bin/ticketing_controller.dart: -------------------------------------------------------------------------------- 1 | library flight_controller; 2 | 3 | import 'package:shelf/shelf.dart'; 4 | import 'package:shelf_path/shelf_path.dart' as path; 5 | import 'ticketing_model.dart'; 6 | import 'dart:async'; 7 | import 'dart:convert'; 8 | import 'package:dartson/dartson.dart'; 9 | 10 | TicketingModel model = new TicketingModel(); 11 | Dartson converter = new Dartson.JSON(); 12 | 13 | Future handleCities(Request request) { 14 | return _genericJsonHandler(model.getAllCities, request); 15 | } 16 | 17 | Future handleTimesCity(Request request) { 18 | return _genericJsonHandler(model.getTimesByCity, request); 19 | } 20 | 21 | Future handleFlightNumber(Request request) { 22 | return _genericJsonHandler(model.getTimesByFlightNumber, request); 23 | } 24 | 25 | Future handleTimes(Request request) { 26 | return _genericJsonHandler(model.getAllTimes, request); 27 | } 28 | 29 | Future handlePurchase(Request request) { 30 | return _genericJsonHandler(model.createPurchase, request); 31 | } 32 | 33 | Future _genericJsonHandler(Function getter, Request request) { 34 | return getPostParams(request) 35 | .then( ( params ) => getPathParams( request , params ) ) 36 | .then( ( json ) => getter( json ) ) 37 | .then( ( list ) => _dartsonListToJson( list ) ) 38 | .then( makeResponse ); 39 | } 40 | 41 | Future makeResponse( String json ) async { 42 | var response = new Response.ok( json ); 43 | return response; 44 | } 45 | 46 | String _dartsonListToJson(payload) { 47 | dynamic encodable = converter.serialize(payload); 48 | return JSON.encode(encodable); 49 | } 50 | 51 | Map getPathParams(Request request, Map payload) { 52 | Map params = path.getPathParameters(request); 53 | params.forEach( (key, val) { 54 | payload[key] = val; 55 | }); 56 | return payload; 57 | } 58 | 59 | Future getPostParams(Request request) { 60 | return request.readAsString().then( (String body) { 61 | return body.isNotEmpty ? JSON.decode(body) : {}; 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /bin/ticketing_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'mongo_model.dart'; 3 | import 'package:tickets/shared/schemas.dart'; 4 | import 'package:tickets/db/db_config.dart'; 5 | import 'package:dartson/dartson.dart'; 6 | 7 | 8 | class TicketingModel extends Object { 9 | MongoModel _mongo; 10 | 11 | TicketingModel() { 12 | DbConfigValues config = new DbConfigValues(); 13 | _mongo = new MongoModel(config.dbName, config.dbURI, config.dbSize); 14 | } 15 | 16 | final Dartson dson = new Dartson.JSON(); 17 | Future createPurchase(Map params) async { 18 | PurchaseDTO purchaseDTO = dson.map(params, new PurchaseDTO() ); 19 | 20 | TransactionDTO tDTO = new TransactionDTO(); 21 | tDTO.paid = 1000; //we're faking a successful creditcard payment 22 | tDTO.user = purchaseDTO.pEmail; 23 | await _mongo.createByItem(tDTO); 24 | purchaseDTO.transactionId = tDTO.id; 25 | return _mongo.createByItem(purchaseDTO); 26 | } 27 | 28 | Future getAllCities(Map params) { 29 | return _mongo.readCollectionByType(CityDTO); 30 | } 31 | 32 | Future getAllTimes(Map params) { 33 | return _mongo.readCollectionByType(TimeDTO); 34 | } 35 | 36 | Future getTimesByCity(Map params) async { 37 | Map queryTime = {'arrival': params['cityArrival'], 'departure': params['cityDepart']}; 38 | List time_dtos; 39 | time_dtos = await _mongo.readCollectionByType(TimeDTO, queryTime); 40 | Map queryRoutes = {'route': params['cityDepart']+"_"+params['cityArrival'] }; 41 | return _mongo.readCollectionByType(RouteDTO, queryRoutes).then((List rdtos) { 42 | time_dtos.forEach((TimeDTO dto) => dto.route = rdtos.first); 43 | return time_dtos; 44 | }); 45 | } 46 | 47 | Future getTimesByFlightNumber(Map params) async { 48 | List time_dtos; 49 | time_dtos = await _mongo.readCollectionByType(TimeDTO, {'flight': int.parse(params['flight'])} ); 50 | var query = {'route': time_dtos.first.departure + "_" + time_dtos.first.arrival}; 51 | return _mongo.readCollectionByType(RouteDTO, query).then((List rdtos) { 52 | time_dtos.forEach((TimeDTO dto) => dto.route = rdtos.first); 53 | return time_dtos; 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /bin/webserver.dart: -------------------------------------------------------------------------------- 1 | import 'package:shelf/shelf.dart'; 2 | import 'package:shelf/shelf_io.dart' as io; 3 | import 'package:shelf_route/shelf_route.dart'; 4 | 5 | import 'dart:io'; 6 | import 'package:path/path.dart'; 7 | import 'package:shelf_static/shelf_static.dart'; 8 | 9 | import 'ticketing_controller.dart' as controller; 10 | import 'package:logging/logging.dart'; 11 | 12 | main() { 13 | var path = Platform.script.toFilePath(); 14 | var currentDirectory = dirname(path); 15 | 16 | Logger.root.level = Level.ALL; 17 | Logger.root.onRecord.listen((LogRecord rec) { 18 | print('${rec.level.name}: ${rec.time}: ${rec.message}'); 19 | }); 20 | 21 | Router primaryRouter = router(); 22 | Router api = primaryRouter.child('/tickets'); 23 | api.add('/flight/{flight}', ['GET'], controller.handleFlightNumber); 24 | api.add('/cities', ['GET'], controller.handleCities); 25 | api.add('/times', ['POST'], controller.handleTimesCity); 26 | api.add('/purchase', ['POST'], controller.handlePurchase); 27 | 28 | Middleware mw = logRequests(); 29 | Pipeline pl = new Pipeline(); 30 | pl = pl.addMiddleware(corsMiddleWare).addMiddleware(mw); 31 | Handler apiHandler = pl.addHandler(primaryRouter.handler); 32 | 33 | var buildPath = join(currentDirectory, '..', 'build'); 34 | Cascade cc = new Cascade().add(apiHandler); 35 | if(new Directory(buildPath).existsSync() ) 36 | { 37 | var fullPath = join(currentDirectory, '..', 'build/web'); 38 | Handler fHandler = createStaticHandler(fullPath, defaultDocument: 'index.html', serveFilesOutsidePath: true); 39 | cc = cc.add(fHandler); 40 | } 41 | 42 | int http_port = int.parse(Platform.environment['PORT']); 43 | io.serve(cc.handler, '0.0.0.0', http_port) 44 | .then( (HttpServer server) => print( 'http serving on: ' 45 | + server.port.toString() )); 46 | } 47 | 48 | Map CORSHeader = {'content-type': 'text/json', 49 | 'Access-Control-Allow-Origin': '*', 50 | 'Access-Control-Allow-Headers': "Origin, X-Requested-With, Content-Type, Accept", 51 | 'Access-Control-Allow-Methods': "POST, GET, PUT, DELETE, OPTIONS"}; 52 | 53 | Middleware corsMiddleWare = createMiddleware(requestHandler: reqHandler, responseHandler: respHandler); 54 | 55 | Response reqHandler(Request request){ 56 | if(request.method == "OPTIONS") 57 | { 58 | return new Response.ok(null, headers: CORSHeader); 59 | } 60 | return null; // nothing to see here... move along 61 | } 62 | 63 | Response respHandler(Response response) { 64 | return response.change(headers: CORSHeader); 65 | } 66 | -------------------------------------------------------------------------------- /lib/client/client.dart: -------------------------------------------------------------------------------- 1 | library ticket_client; 2 | 3 | import 'dart:async'; 4 | import 'dart:html'; 5 | import 'dart:convert'; 6 | 7 | //angular2 8 | import 'package:angular2/angular2.dart'; 9 | import 'package:angular2/router.dart'; 10 | 11 | //data 12 | import 'package:tickets/shared/schemas.dart'; 13 | import 'package:dartson/dartson.dart'; 14 | import "package:intl/intl.dart"; 15 | 16 | //components 17 | part 'main_tickets_component.dart'; 18 | part 'components/landing/landing_component.dart'; 19 | part 'components/topnav/topnav_component.dart'; 20 | 21 | part 'components/flight_display/flight_display_component.dart'; 22 | part 'components/order/order_component.dart'; 23 | part 'components/order/recap_component.dart'; 24 | part 'components/picker/picker_component.dart'; 25 | 26 | 27 | part 'services/query_service.dart'; 28 | part 'services/shared_data.dart'; 29 | part 'services/flight_formatter.dart'; 30 | part 'services/environment_variables.dart'; 31 | 32 | part 'composite_views/view_complete_component.dart'; 33 | part 'composite_views/view_flights_component.dart'; 34 | part 'composite_views/view_order_component.dart'; 35 | part 'composite_views/view_landing_component.dart'; 36 | 37 | 38 | //injectables 39 | const List client_classes = const [SharedData, FlightQueryService, EnvironmentVariables]; -------------------------------------------------------------------------------- /lib/client/components/flight_display/flight_display.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Please select your flight data above

4 |
5 |
6 | 7 |
8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 36 | 42 | 48 | 51 | 52 | 53 |
Flight #TakeOff TimeLanding TimeDeparture CityArrival CityBest PriceStandardDesperateSelect
{{ time.flight }}{{ time.takeoff }}{{ time.takeoff + time.route.duration }}{{ time.departure }}{{ time.arrival }} 31 | 34 | 35 | 37 | 40 | 41 | 43 | 46 | 47 | 49 | Order 50 |
54 |
55 |
56 | -------------------------------------------------------------------------------- /lib/client/components/flight_display/flight_display.scss: -------------------------------------------------------------------------------- 1 | .table thead { 2 | background-color: #ecf0f1; 3 | } 4 | .order { 5 | text-align: center; 6 | } 7 | input[type="radio"] { 8 | float: left; 9 | margin-right: 8px; 10 | } 11 | -------------------------------------------------------------------------------- /lib/client/components/flight_display/flight_display_component.dart: -------------------------------------------------------------------------------- 1 | part of ticket_client; 2 | 3 | @Component( 4 | selector: 'flight-display' 5 | ) 6 | @View( 7 | styleUrls: const ["package:tickets/client/components/flight_display/flight_display.css"], 8 | templateUrl: "package:tickets/client/components/flight_display/flight_display.html", 9 | directives: const[CORE_DIRECTIVES] 10 | ) 11 | class FlightDisplay extends Object { 12 | 13 | RouteParams routeParams; 14 | Router router; 15 | num service_level; 16 | FlightQueryService queryService; 17 | FlightFormatter params; 18 | List flight_times; 19 | List routes; 20 | TimeDTO selectedDTO; 21 | 22 | FlightDisplay(Router this.router, RouteParams this.routeParams, FlightQueryService this.queryService) { 23 | if(routeParams.params != null && routeParams.params.isEmpty == false) 24 | { 25 | params = new FlightFormatter.FromPost(routeParams.params); 26 | fetchData(params); 27 | } 28 | } 29 | 30 | fetchData(params) { 31 | queryService.fetchFlightTimes(params).then( (List dtos) { 32 | flight_times = dtos; 33 | }); 34 | } 35 | 36 | void select(TimeDTO tdto, level) { 37 | service_level = level; 38 | selectedDTO = tdto; 39 | } 40 | 41 | bool isSelected(TimeDTO value) { 42 | if(selectedDTO != null) 43 | { 44 | return selectedDTO.flight == value.flight; 45 | } 46 | return false; 47 | } 48 | 49 | void onsubmit(TimeDTO time) 50 | { 51 | var post = params.toPostable(); 52 | post['id'] = time.flight; 53 | post['level'] = service_level; 54 | Instruction _navigationInstruction = router.generate(['/Order', post]); 55 | router.navigateByInstruction(_navigationInstruction); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/client/components/landing/landing.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |

{{ aDeal['city_departure'] + " to " + aDeal['city_arrival'] }}

6 |

{{ aDeal['date'] }}

7 |

{{ aDeal['description'] }}

8 |
{{ aDeal['price'] }}
9 | Buy 10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /lib/client/components/landing/landing_component.dart: -------------------------------------------------------------------------------- 1 | part of ticket_client; 2 | 3 | @Component( 4 | selector: "landing" 5 | ) 6 | @View ( 7 | templateUrl: "package:tickets/client/components/landing/landing.html", 8 | directives: const [CORE_DIRECTIVES] 9 | ) 10 | 11 | class Landing{ 12 | Router _router; 13 | List deals; 14 | 15 | Landing(Router this._router) { 16 | print('-- Landing Init --'); 17 | init(); 18 | } 19 | 20 | Future init() async { 21 | String result = await HttpRequest.getString('deals.json'); 22 | var response = JSON.decode(result); 23 | deals = response['deals']; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/client/components/order/order.html: -------------------------------------------------------------------------------- 1 |

Order Page

2 |
3 | 4 |
5 |

Passenger Information

6 | 7 |
8 | 9 | 10 |
11 | 12 |
13 | 14 | 15 |
16 | 17 |
18 | 19 | 20 |
21 | 22 |
23 | 24 | 25 |
26 |
27 | 28 |
29 |

Billing Information

30 |
31 | 32 | 33 |
34 | 35 |
36 | 37 | 38 |
39 | 40 |
41 | 42 | 43 |
44 | 45 |
46 | 47 | 48 |
49 | 50 |
51 | 52 | 53 |
54 | 55 |
56 | 57 | 64 |
65 | 66 |
67 | 68 | 69 |
70 | 71 |
72 | 73 | 74 |
75 |
76 | 77 |
78 |

Credit Card Information

79 | 80 |
81 | 82 | 83 |
84 | 85 |
86 | 87 | 88 |
89 | 90 |
91 | 92 | 93 |
94 | 95 |
96 | 97 | 102 |
103 | 104 |
105 | 106 | 107 |
108 |
109 |
110 | -------------------------------------------------------------------------------- /lib/client/components/order/order.scss: -------------------------------------------------------------------------------- 1 | section { 2 | background: #CCC; 3 | } 4 | -------------------------------------------------------------------------------- /lib/client/components/order/order_component.dart: -------------------------------------------------------------------------------- 1 | part of ticket_client; 2 | 3 | @Component( 4 | selector: 'order-form' 5 | ) 6 | @View( 7 | directives: const[FORM_DIRECTIVES], 8 | styleUrls: const ["package:tickets/client/components/order/order.css"], 9 | templateUrl: "package:tickets/client/components/order/order.html" 10 | ) 11 | class OrderForm extends Object { 12 | 13 | Router _router; 14 | RouteParams _routeParams; 15 | FlightQueryService _queryService; 16 | TimeDTO timeDTO; 17 | SharedData _shared; 18 | PurchaseDTO dto = new PurchaseDTO(); 19 | 20 | OrderForm(Router this._router, RouteParams this._routeParams, FlightQueryService this._queryService, SharedData this._shared) { 21 | fetch(); 22 | } 23 | 24 | Future fetch() async { 25 | if(_routeParams != null && _routeParams.params.isEmpty == false) 26 | { 27 | List dtos = await _queryService.fetchFlightByNumber(_routeParams.params['id'].toString()); 28 | timeDTO = dtos.first; 29 | 30 | dto = new PurchaseDTO(); 31 | dto.flightID = timeDTO.flight; 32 | dto.flightLevel = int.parse( _routeParams.params['level'].toString() ); 33 | 34 | _shared.purchaseDTO = dto; 35 | _shared.timeDTO = timeDTO; 36 | } 37 | } 38 | 39 | Future onSubmit() async { 40 | var dson = new Dartson.JSON(); 41 | String jsonString = dson.encode(dto); 42 | _shared.transaction = await _queryService.purchaseTicket(jsonString); 43 | _router.navigate(['/OrderComplete']).then((item) => print(item) ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/client/components/order/recap.html: -------------------------------------------------------------------------------- 1 |

Recap

2 |
3 |

Flight # {{ shared.timeDTO.flight }}

4 |
{{ shared.timeDTO.departure + ' to ' + shared.timeDTO.arrival }}
5 |
{{ fFlight.dateDepart + ' at ' + shared.timeDTO.takeoff.toString() }}
6 |
7 | -------------------------------------------------------------------------------- /lib/client/components/order/recap_component.dart: -------------------------------------------------------------------------------- 1 | part of ticket_client; 2 | 3 | @Component( 4 | selector: 'recap' 5 | ) 6 | @View( 7 | styles: const ["package:tickets/client/components/order/recap.css"], 8 | templateUrl: "package:tickets/client/components/order/recap.html", 9 | directives: const[CORE_DIRECTIVES] 10 | ) 11 | class Recap extends Object { 12 | Router _router; 13 | RouteParams _routeParams; 14 | 15 | FlightFormatter fFlight; 16 | SharedData shared; 17 | 18 | Recap(Router this._router, RouteParams this._routeParams, SharedData this.shared) 19 | { 20 | fFlight = new FlightFormatter.FromPost(_routeParams.params); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/client/components/picker/picker.html: -------------------------------------------------------------------------------- 1 | Quick Link 2 | 3 |
4 |
5 | 8 | 9 |
10 | 11 |
12 | 18 | 19 |
20 | 21 |
22 | 23 | 24 |
25 | 26 |
27 | 28 |
29 | 30 |
31 | -------------------------------------------------------------------------------- /lib/client/components/picker/picker.scss: -------------------------------------------------------------------------------- 1 | .group { 2 | display: block; 3 | float: left; 4 | height: 100px; 5 | clear:none; 6 | margin-left: 15px; 7 | } 8 | 9 | label { 10 | display:block; 11 | clear:both; 12 | } 13 | -------------------------------------------------------------------------------- /lib/client/components/picker/picker_component.dart: -------------------------------------------------------------------------------- 1 | part of ticket_client; 2 | 3 | @Component( 4 | selector: 'picker' 5 | ) 6 | @View( 7 | styleUrls: const ["package:tickets/client/components/picker/picker.css"], 8 | templateUrl: "package:tickets/client/components/picker/picker.html", 9 | directives: const [CORE_DIRECTIVES, FORM_DIRECTIVES, ROUTER_DIRECTIVES] 10 | ) 11 | class Picker extends Object { 12 | Router router; 13 | RouteParams routeParams; 14 | 15 | FlightFormatter info = new FlightFormatter(); 16 | List cities; 17 | FlightQueryService queryService; 18 | 19 | Picker(Router this.router, FlightQueryService this.queryService, 20 | RouteParams this.routeParams) { 21 | populateCitites(); 22 | populateState(); 23 | } 24 | 25 | void onFind() { 26 | onSubmit(); 27 | } 28 | 29 | void populateCitites() { 30 | queryService.fetchCities().then( (List dtos) { 31 | cities = dtos; 32 | }); 33 | } 34 | 35 | void populateState() { 36 | if(routeParams.params != null && routeParams.params.isNotEmpty ) 37 | { 38 | info = new FlightFormatter.FromPost(routeParams.params); 39 | } 40 | } 41 | 42 | onSubmit() 43 | { 44 | if( info.isSelected() ) { 45 | var linkParams = ['/Picker', info.toPostable() ]; 46 | Instruction _navInst = this.router.generate(linkParams); 47 | this.router.navigateByInstruction(_navInst); 48 | return; 49 | } else { 50 | print("You need to select your flight details!"); 51 | print("You could also add better UI handling..."); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/client/components/topnav/topnav.html: -------------------------------------------------------------------------------- 1 |
2 | 15 |
16 | -------------------------------------------------------------------------------- /lib/client/components/topnav/topnav_component.dart: -------------------------------------------------------------------------------- 1 | part of ticket_client; 2 | 3 | @Component( 4 | selector: "topnav" 5 | ) 6 | 7 | @View ( 8 | templateUrl: "package:tickets/client/components/topnav/topnav.html", 9 | encapsulation: ViewEncapsulation.None, 10 | directives: const [CORE_DIRECTIVES, ROUTER_DIRECTIVES] 11 | ) 12 | 13 | class Topnav{ 14 | List buttons; 15 | Router _router; 16 | RouteParams params; 17 | 18 | Topnav(Router router, RouteParams this.params) { 19 | print('-- Topnav Init --'); 20 | _router = router; 21 | buttons = initbuttons(); 22 | } 23 | 24 | void go(String url) { 25 | this._router.navigateByUrl(url); 26 | } 27 | 28 | List initbuttons() { 29 | List buttons = new List(); 30 | buttons.add( new NavButtonDTO()..route = "/Home"..content="Home"); 31 | buttons.add( new NavButtonDTO()..route = "/Flights"..content="Flights" ); 32 | return buttons; 33 | } 34 | } 35 | 36 | class NavButtonDTO { 37 | String route; 38 | String content; 39 | } 40 | -------------------------------------------------------------------------------- /lib/client/composite_views/view_complete_component.dart: -------------------------------------------------------------------------------- 1 | part of ticket_client; 2 | 3 | @Component( 4 | selector: 'view-complete' 5 | ) 6 | @View( 7 | directives: const [ROUTER_DIRECTIVES, Topnav], 8 | template: 9 | ''' 10 | 11 | 16 |
17 | Return Home 18 |
19 | ''' 20 | ) 21 | class ViewComplete { 22 | ViewComplete(); 23 | } 24 | -------------------------------------------------------------------------------- /lib/client/composite_views/view_flights_component.dart: -------------------------------------------------------------------------------- 1 | part of ticket_client; 2 | 3 | @Component( 4 | selector: 'view-flights' 5 | ) 6 | @View( 7 | directives: const [FlightDisplay, Picker, Topnav], 8 | template: 9 | ''' 10 | 11 | 12 | 13 | ''' 14 | ) 15 | class ViewFlights { 16 | } 17 | -------------------------------------------------------------------------------- /lib/client/composite_views/view_landing_component.dart: -------------------------------------------------------------------------------- 1 | part of ticket_client; 2 | 3 | @Component( 4 | selector: 'view-landing' 5 | ) 6 | @View( 7 | directives: const [Landing, Topnav], 8 | template: 9 | ''' 10 | 11 | 12 | ''' 13 | ) 14 | class ViewLanding { 15 | ViewLanding() { 16 | print('-- ViewLanding Init --'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/client/composite_views/view_order_component.dart: -------------------------------------------------------------------------------- 1 | part of ticket_client; 2 | 3 | @Component( 4 | selector: 'view-order' 5 | ) 6 | @View( 7 | directives: const [OrderForm, Recap, Topnav], 8 | template: 9 | ''' 10 | 11 | 12 | 13 | ''' 14 | ) 15 | class ViewOrder{ } 16 | -------------------------------------------------------------------------------- /lib/client/main_tickets_component.dart: -------------------------------------------------------------------------------- 1 | part of ticket_client; 2 | 3 | @Component( 4 | selector: 'tickets' 5 | ) 6 | @View( 7 | template: '', 8 | directives: const [RouterOutlet] 9 | ) 10 | @RouteConfig(const [ 11 | const Route(path: '/', component: ViewLanding, as: 'Home'), 12 | const Route(path: '/landing', component: ViewLanding, as: 'Landing'), 13 | const Route(path: '/flights', component: ViewFlights, as: 'Flights'), 14 | const Route(path: '/picker/:cityDepart/:cityArrival/:dateDepart/', component: ViewFlights, as: 'Picker'), 15 | const Route(path: '/order/:id/:level/:dateDepart/', component: ViewOrder, as: 'Order'), 16 | const Route(path: '/order/complete', component: ViewComplete, as: 'OrderComplete') 17 | ]) 18 | class Tickets { 19 | String name = 'Jit Ticket Application'; 20 | Router router; 21 | 22 | Tickets(Router this.router) { 23 | print('-- Tickets Init --'); 24 | router.subscribe( (value) { 25 | print("Route changed to: $value"); 26 | }); 27 | } 28 | } -------------------------------------------------------------------------------- /lib/client/services/environment_variables.dart: -------------------------------------------------------------------------------- 1 | part of ticket_client; 2 | 3 | @Injectable() 4 | class EnvironmentVariables { 5 | static int PORT; 6 | static String DART_ENV; 7 | } 8 | -------------------------------------------------------------------------------- /lib/client/services/flight_formatter.dart: -------------------------------------------------------------------------------- 1 | part of ticket_client; 2 | 3 | class FlightFormatter { 4 | String cityDepart; 5 | String cityArrival; 6 | String dateDepart; 7 | DateTime _dateDepart; 8 | 9 | FlightFormatter(); 10 | 11 | factory FlightFormatter.FromPost(Map aMap) { 12 | FlightFormatter instance = new FlightFormatter(); 13 | instance.cityArrival = aMap['cityArrival']; 14 | instance.cityDepart = aMap['cityDepart']; 15 | instance.dateDepart = aMap['dateDepart']; 16 | instance._dateDepart = DateTime.parse(aMap['dateDepart']); 17 | return instance; 18 | } 19 | 20 | Map toPostable() { 21 | DateFormat dtf = new DateFormat('yyyy-MM-dd'); 22 | _dateDepart = DateTime.parse(dateDepart); 23 | return { 24 | 'cityDepart': cityDepart, 25 | 'cityArrival': cityArrival, 26 | 'dateDepart': dtf.format(_dateDepart) 27 | }; 28 | } 29 | 30 | bool isSelected() { 31 | return ( cityArrival != null && cityDepart != null && dateDepart != null); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/client/services/query_service.dart: -------------------------------------------------------------------------------- 1 | part of ticket_client; 2 | 3 | @Injectable() 4 | class FlightQueryService{ 5 | String BASE; 6 | Dartson converter = new Dartson.JSON(); 7 | 8 | FlightQueryService(){ 9 | // port is from environment variables locally 10 | // for production it always uses 80 11 | // see: IndexTransformer 12 | 13 | this.BASE = 'http://' 14 | + window.location.hostname 15 | + ':' + EnvironmentVariables.PORT.toString() 16 | + '/tickets/'; 17 | } 18 | 19 | Future postJson(url, data) { 20 | Completer contract = new Completer(); 21 | HttpRequest request = new HttpRequest(); // create a new XHR 22 | request.open("POST", url); 23 | request.onReadyStateChange.listen((_) { 24 | if (request.readyState == HttpRequest.DONE && (request.status == 200 || request.status == 0)) { 25 | contract.complete(request.responseText); 26 | } 27 | }); 28 | request.send(data); // perform the async POST 29 | return contract.future; 30 | } 31 | 32 | Future fetchFlightTimes(FlightFormatter params) async { 33 | var post = JSON.encode( params.toPostable() ); 34 | return postJson(BASE + 'times', post ).then(handleTimes); 35 | } 36 | Future fetchFlightByNumber(String flightId) async { 37 | return HttpRequest.getString(BASE + 'flight/' + flightId ).then(handleTimes); 38 | } 39 | 40 | Future fetchCities() async { 41 | return HttpRequest.getString(BASE + 'cities').then(handleCities); 42 | } 43 | 44 | Future purchaseTicket(String json) async { 45 | return postJson(BASE + 'purchase', json).then(handlePurchase); 46 | } 47 | 48 | List handleTimes(String resp) { 49 | 50 | List dtos = converter.decode(resp, new TimeDTO(), true); 51 | return dtos; 52 | } 53 | 54 | List handleRoutes(String response) { 55 | List dtos = converter.decode(response, new RouteDTO(), true); 56 | return dtos; 57 | } 58 | 59 | List handleCities(String response) { 60 | List dtos = converter.decode(response, new CityDTO(), true); 61 | return dtos; 62 | } 63 | 64 | TransactionDTO handlePurchase(String response) { 65 | TransactionDTO dtos = converter.decode(response, new TransactionDTO()); 66 | return dtos; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/client/services/shared_data.dart: -------------------------------------------------------------------------------- 1 | part of ticket_client; 2 | 3 | @Injectable() 4 | class SharedData extends Object { 5 | TransactionDTO transaction; 6 | PurchaseDTO purchaseDTO; 7 | TimeDTO timeDTO; 8 | } 9 | -------------------------------------------------------------------------------- /lib/db/db_config.dart: -------------------------------------------------------------------------------- 1 | library database_configuration; 2 | 3 | import 'dart:io'; 4 | 5 | class DbConfigValues { 6 | String dbName = 'rightisleft-dart'; 7 | String dbURI = Platform.environment['TICKET_DB_URI']; 8 | Resource dbSeed = const Resource('package:tickets/db/seed.json'); 9 | int dbSize = 10; 10 | 11 | String get testDbName => dbName + "-test"; 12 | String get testDbURI => Platform.environment['TICKET_DB_URI_TEST']; 13 | Resource get testDbSeed => dbSeed; 14 | int get testDbSize => dbSize; 15 | } 16 | -------------------------------------------------------------------------------- /lib/db/seed.json: -------------------------------------------------------------------------------- 1 | { 2 | "Routes": [ 3 | { 4 | "route": "SAN_LAX", 5 | "duration": 45, 6 | "price1": 29, 7 | "price2": 49, 8 | "price3": 79, 9 | "seats": 7 10 | }, 11 | { 12 | "route": "SAN_SFO", 13 | "duration": 140, 14 | "price1": 49, 15 | "price2": 79, 16 | "price3": 99, 17 | "seats": 7 18 | }, 19 | { 20 | "route": "SAN_OAK", 21 | "duration": 150, 22 | "price1": 49, 23 | "price2": 79, 24 | "price3": 99, 25 | "seats": 7 26 | }, 27 | { 28 | "route": "SAN_SMF", 29 | "duration": 190, 30 | "price1": 59, 31 | "price2": 99, 32 | "price3": 109, 33 | "seats": 7 34 | }, 35 | { 36 | "route": "LAX_SAN", 37 | "duration": 50, 38 | "price1": 29, 39 | "price2": 49, 40 | "price3": 79, 41 | "seats": 7 42 | }, 43 | { 44 | "route": "LAX_SFO", 45 | "duration": 120, 46 | "price1": 29, 47 | "price2": 49, 48 | "price3": 79, 49 | "seats": 7 50 | }, 51 | { 52 | "route": "LAX_OAK", 53 | "duration": 130, 54 | "price1": 29, 55 | "price2": 49, 56 | "price3": 79, 57 | "seats": 7 58 | }, 59 | { 60 | "route": "LAX_SMF", 61 | "duration": 170, 62 | "price1": 39, 63 | "price2": 59, 64 | "price3": 89, 65 | "seats": 7 66 | }, 67 | { 68 | "route": "OAK_SAN", 69 | "duration": 160, 70 | "price1": 49, 71 | "price2": 79, 72 | "price3": 99, 73 | "seats": 7 74 | }, 75 | { 76 | "route": "OAK_LAX", 77 | "duration": 140, 78 | "price1": 39, 79 | "price2": 69, 80 | "price3": 89, 81 | "seats": 7 82 | }, 83 | { 84 | "route": "OAK_SMF", 85 | "duration": 40, 86 | "price1": 19, 87 | "price2": 39, 88 | "price3": 59, 89 | "seats": 7 90 | }, 91 | { 92 | "route": "SFO_SAN", 93 | "duration": 145, 94 | "price1": 49, 95 | "price2": 79, 96 | "price3": 99, 97 | "seats": 7 98 | }, 99 | { 100 | "route": "SFO_LAX", 101 | "duration": 125, 102 | "price1": 29, 103 | "price2": 49, 104 | "price3": 79, 105 | "seats": 7 106 | }, 107 | { 108 | "route": "SFO_SMF", 109 | "duration": 35, 110 | "price1": 19, 111 | "price2": 39, 112 | "price3": 59, 113 | "seats": 7 114 | }, 115 | { 116 | "route": "SMF_SAN", 117 | "duration": 195, 118 | "price1": 59, 119 | "price2": 99, 120 | "price3": 109, 121 | "seats": 7 122 | }, 123 | { 124 | "route": "SMF_SFO", 125 | "duration": 40, 126 | "price1": 19, 127 | "price2": 39, 128 | "price3": 59, 129 | "seats": 7 130 | }, 131 | { 132 | "route": "SMF_OAK", 133 | "duration": 45, 134 | "price1": 19, 135 | "price2": 39, 136 | "price3": 59, 137 | "seats": 7 138 | }, 139 | { 140 | "route": "SMF_LAX", 141 | "duration": 195, 142 | "price1": 39, 143 | "price2": 59, 144 | "price3": 89, 145 | "seats": 7 146 | } 147 | ], 148 | "Times": [ 149 | { 150 | "flight": 1001, 151 | "departure": "SAN", 152 | "arrival": "SFO", 153 | "takeoff": 600 154 | }, 155 | { 156 | "flight": 1002, 157 | "departure": "LAX", 158 | "arrival": "SFO", 159 | "takeoff": 630 160 | }, 161 | { 162 | "flight": 1003, 163 | "departure": "OAK", 164 | "arrival": "SMF", 165 | "takeoff": 700 166 | }, 167 | { 168 | "flight": 1004, 169 | "departure": "LAX", 170 | "arrival": "SAN", 171 | "takeoff": 730 172 | }, 173 | { 174 | "flight": 1005, 175 | "departure": "SMF", 176 | "arrival": "SFO", 177 | "takeoff": 800 178 | }, 179 | { 180 | "flight": 1006, 181 | "departure": "SAN", 182 | "arrival": "SFO", 183 | "takeoff": 830 184 | }, 185 | { 186 | "flight": 1007, 187 | "departure": "SAN", 188 | "arrival": "LAX", 189 | "takeoff": 900 190 | }, 191 | { 192 | "flight": 1008, 193 | "departure": "OAK", 194 | "arrival": "LAX", 195 | "takeoff": 930 196 | }, 197 | { 198 | "flight": 1009, 199 | "departure": "OAK", 200 | "arrival": "SAN", 201 | "takeoff": 1000 202 | }, 203 | { 204 | "flight": 1010, 205 | "departure": "SAN", 206 | "arrival": "SMF", 207 | "takeoff": 1030 208 | }, 209 | { 210 | "flight": 1011, 211 | "departure": "SFO", 212 | "arrival": "LAX", 213 | "takeoff": 1100 214 | }, 215 | { 216 | "flight": 1012, 217 | "departure": "OAK", 218 | "arrival": "SAN", 219 | "takeoff": 1130 220 | }, 221 | { 222 | "flight": 1013, 223 | "departure": "LAX", 224 | "arrival": "SFO", 225 | "takeoff": 1200 226 | }, 227 | { 228 | "flight": 1014, 229 | "departure": "SAN", 230 | "arrival": "LAX", 231 | "takeoff": 1230 232 | }, 233 | { 234 | "flight": 1015, 235 | "departure": "SFO", 236 | "arrival": "SMF", 237 | "takeoff": 1300 238 | }, 239 | { 240 | "flight": 1016, 241 | "departure": "SFO", 242 | "arrival": "SAN", 243 | "takeoff": 1000 244 | }, 245 | { 246 | "flight": 1017, 247 | "departure": "SFO", 248 | "arrival": "LAX", 249 | "takeoff": 1030 250 | }, 251 | { 252 | "flight": 1018, 253 | "departure": "SMF", 254 | "arrival": "OAK", 255 | "takeoff": 1100 256 | }, 257 | { 258 | "flight": 1019, 259 | "departure": "SAN", 260 | "arrival": "LAX", 261 | "takeoff": 1130 262 | }, 263 | { 264 | "flight": 1020, 265 | "departure": "SFO", 266 | "arrival": "SMF", 267 | "takeoff": 1200 268 | }, 269 | { 270 | "flight": 1021, 271 | "departure": "SFO", 272 | "arrival": "SAN", 273 | "takeoff": 1230 274 | }, 275 | { 276 | "flight": 1022, 277 | "departure": "LAX", 278 | "arrival": "SAN", 279 | "takeoff": 1300 280 | }, 281 | { 282 | "flight": 1023, 283 | "departure": "LAX", 284 | "arrival": "OAK", 285 | "takeoff": 1330 286 | }, 287 | { 288 | "flight": 1024, 289 | "departure": "SAN", 290 | "arrival": "OAK", 291 | "takeoff": 1400 292 | }, 293 | { 294 | "flight": 1025, 295 | "departure": "SMF", 296 | "arrival": "SAN", 297 | "takeoff": 1430 298 | }, 299 | { 300 | "flight": 1026, 301 | "departure": "LAX", 302 | "arrival": "SFO", 303 | "takeoff": 1500 304 | }, 305 | { 306 | "flight": 1027, 307 | "departure": "SAN", 308 | "arrival": "OAK", 309 | "takeoff": 1530 310 | }, 311 | { 312 | "flight": 1028, 313 | "departure": "SFO", 314 | "arrival": "LAX", 315 | "takeoff": 1600 316 | }, 317 | { 318 | "flight": 1029, 319 | "departure": "LAX", 320 | "arrival": "SAN", 321 | "takeoff": 1630 322 | }, 323 | { 324 | "flight": 1030, 325 | "departure": "SMF", 326 | "arrival": "SFO", 327 | "takeoff": 1700 328 | } 329 | ], 330 | "Cities": [ 331 | { 332 | "city": "Los Angeles", 333 | "airportcode": "LAX", 334 | "gate": "C32" 335 | }, 336 | { 337 | "city": "San Diego", 338 | "airportcode": "SAN", 339 | "gate": "B21" 340 | }, 341 | { 342 | "city": "San Francisco", 343 | "airportcode": "SFO", 344 | "gate": "A12" 345 | }, 346 | { 347 | "city": "Oakland", 348 | "airportcode": "OAK", 349 | "gate": "B5" 350 | }, 351 | { 352 | "city": "Sacramento", 353 | "airportcode": "SMF", 354 | "gate": "A33" 355 | } 356 | ], 357 | "Bookings": [ 358 | { 359 | "firstname": "Jack", 360 | "lastname": "Murphy", 361 | "email": "jack@rightisleft.com", 362 | "phone": 4155555555, 363 | "address": "111 West 1st St #E, New York, NY, 10010", 364 | "flight": "null", 365 | "transaction": "null" 366 | } 367 | ], 368 | "Transactions": [ 369 | { 370 | "amount": 100, 371 | "user": "jack@rightisleft.com" 372 | } 373 | ] 374 | } 375 | -------------------------------------------------------------------------------- /lib/db/seeder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:json_object/json_object.dart'; 3 | import 'package:mongo_dart/mongo_dart.dart'; 4 | import 'db_config.dart'; 5 | 6 | main() { 7 | DbConfigValues config = new DbConfigValues(); 8 | var importer = new Seeder(config.dbName, config.dbURI, config.dbSeed); 9 | importer.readFile(); 10 | } 11 | 12 | class Seeder { 13 | final String _dbURI; 14 | final String _dbName; 15 | final Resource _dbSeedFile; 16 | 17 | Seeder(String this._dbName, String this._dbURI, Resource this._dbSeedFile); 18 | 19 | Future readFile() { 20 | 21 | return _dbSeedFile.readAsString() 22 | .then((String item) => new JsonObject.fromJsonString(item)) 23 | .then(insertJsonToMongo) 24 | .then(closeDatabase); 25 | } 26 | 27 | JsonObject printJson(JsonObject json) { 28 | json.keys.forEach((String collectionKey) { 29 | print('Collections Name: ' + collectionKey); 30 | var collection = json[collectionKey]; 31 | print('Collection: ' + collection.toString()); 32 | collection.forEach((document) { 33 | print('Document: ' + document.toString()); 34 | }); 35 | }); 36 | return json; 37 | } 38 | 39 | Future insertJsonToMongo(JsonObject json) async 40 | { 41 | Db database = new Db(_dbURI + _dbName); 42 | await database.open(); 43 | await Future.forEach(json.keys, (String collectionName) async { 44 | 45 | //grabs the collection instance 46 | DbCollection collection = new DbCollection(database, collectionName); 47 | 48 | //takes a list of maps and writes to a collection 49 | return collection.insertAll(json[collectionName]); 50 | }); 51 | return database; 52 | } 53 | 54 | Future closeDatabase(Db database) { 55 | return database.close(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/shared/dtos/base_dto.dart: -------------------------------------------------------------------------------- 1 | part of ticket_schemas; 2 | 3 | abstract class BaseDTO 4 | { 5 | String id; 6 | String collection_key; 7 | } 8 | -------------------------------------------------------------------------------- /lib/shared/dtos/booking_dto.dart: -------------------------------------------------------------------------------- 1 | part of ticket_schemas; 2 | 3 | @Entity() 4 | class BookingDTO extends BaseDTO 5 | { 6 | String collection_key = "Bookings"; 7 | 8 | String firstname; 9 | String lastname; 10 | String email; 11 | int phone; 12 | String address; 13 | String flight; 14 | String transaction; 15 | } 16 | -------------------------------------------------------------------------------- /lib/shared/dtos/city_dto.dart: -------------------------------------------------------------------------------- 1 | part of ticket_schemas; 2 | 3 | @Entity() 4 | class CityDTO extends BaseDTO { 5 | String collection_key = "Cities"; 6 | String city; 7 | String airportcode; 8 | String gate; 9 | } -------------------------------------------------------------------------------- /lib/shared/dtos/purchase_dto.dart: -------------------------------------------------------------------------------- 1 | part of ticket_schemas; 2 | 3 | @Entity() 4 | class PurchaseDTO extends BaseDTO 5 | { 6 | String collection_key = "Purchases"; 7 | 8 | int flightID; 9 | int flightLevel; 10 | 11 | String ccn; 12 | String ccv; 13 | String bZip; 14 | 15 | String ccType; 16 | String ccExpiration; 17 | 18 | String pFirstName; 19 | String pMiddleName; 20 | String pLastName; 21 | String pEmail; 22 | 23 | String bFirstName; 24 | String bMiddleName; 25 | String bLastName; 26 | String bAddress; 27 | String bCity; 28 | String bState; 29 | String bCountry; 30 | String transactionId; 31 | } 32 | -------------------------------------------------------------------------------- /lib/shared/dtos/route_dto.dart: -------------------------------------------------------------------------------- 1 | part of ticket_schemas; 2 | 3 | @Entity() 4 | class RouteDTO extends BaseDTO { 5 | String collection_key = "Routes"; 6 | 7 | String route; 8 | num duration; 9 | num price1; 10 | num price2; 11 | num price3; 12 | int seats; 13 | 14 | String getDepartureCity() { 15 | return route.split('_')[0]; 16 | } 17 | 18 | String getArrivalCity() { 19 | return route.split('_')[1]; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/shared/dtos/time_dto.dart: -------------------------------------------------------------------------------- 1 | part of ticket_schemas; 2 | 3 | @Entity() 4 | class TimeDTO extends BaseDTO { 5 | String collection_key = "Times"; 6 | 7 | int flight; 8 | String departure; 9 | String arrival; 10 | int takeoff; 11 | RouteDTO route; 12 | } 13 | -------------------------------------------------------------------------------- /lib/shared/dtos/transaction_dto.dart: -------------------------------------------------------------------------------- 1 | part of ticket_schemas; 2 | 3 | @Entity() 4 | class TransactionDTO extends BaseDTO { 5 | String collection_key = "Transactions"; 6 | int paid; 7 | String user; 8 | } 9 | -------------------------------------------------------------------------------- /lib/shared/schemas.dart: -------------------------------------------------------------------------------- 1 | library ticket_schemas; 2 | 3 | import 'package:dartson/dartson.dart'; 4 | 5 | part 'dtos/base_dto.dart'; 6 | part 'dtos/booking_dto.dart'; 7 | part 'dtos/city_dto.dart'; 8 | part 'dtos/route_dto.dart'; 9 | part 'dtos/purchase_dto.dart'; 10 | part 'dtos/time_dto.dart'; 11 | part 'dtos/transaction_dto.dart'; -------------------------------------------------------------------------------- /lib/transformer.dart: -------------------------------------------------------------------------------- 1 | import 'package:barback/barback.dart'; 2 | import 'dart:async'; 3 | import 'dart:io'; 4 | import 'package:html/parser.dart'; 5 | import 'package:html/dom.dart'; 6 | 7 | class IndexTransformer extends Transformer { 8 | String index; 9 | 10 | IndexTransformer(this.index); 11 | 12 | IndexTransformer.asPlugin(); 13 | 14 | Future isPrimary(AssetId id) async { 15 | return id.path == 'web/index.html'; 16 | } 17 | 18 | Future apply(Transform transform) { 19 | print('IndexTransformer...'); 20 | var input = transform.primaryInput; 21 | return transform.readInputAsString(input.id).then((string){ 22 | var document = parse(string); 23 | print('start ports'); 24 | String port = Platform.environment['PORT']; 25 | String env = Platform.environment['DART_ENV']; 26 | String tag = Platform.environment['INDEX_TRANSFORMER_TAG']; 27 | if(tag != null) 28 | { 29 | List arr = document.getElementsByTagName(tag); 30 | if(arr.length > 0) 31 | { 32 | print('Found Tickets Element...'); 33 | Element tickets = arr.first; 34 | tickets.attributes['environment'] = env; 35 | 36 | //default to 80 if in production 37 | tickets.attributes['port'] = (env == 'production') ? '80' : port; 38 | 39 | print(document.outerHtml); 40 | Asset ast = new Asset.fromString(input.id, document.outerHtml); 41 | transform.addOutput(ast); 42 | } 43 | } 44 | }); 45 | } 46 | } -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: 'tickets' 2 | version: 0.0.1 3 | description: A ticket commerce application 4 | author: Jack Murphy jack@rightisleft.com 5 | homepage: https://www.rightisleft.com 6 | environment: 7 | sdk: '>=1.0.0 <2.0.0' 8 | dependencies: 9 | #Server Dependencies 10 | barback: ">=0.15.2+2 <0.16.0" 11 | json_object: "1.0.19" 12 | mongo_dart: "0.2.5-beta" 13 | connection_pool: "0.1.0+2" 14 | dartson: "0.2.5" 15 | guinness: "0.1.17" 16 | html: "0.12.2" 17 | shelf: '>=0.6.2 <0.7.0' 18 | shelf_static: "0.2.2" 19 | shelf_route: "0.14.0" 20 | #Client Dependencies 21 | bootjack: "0.6.5" 22 | browser: ">=0.10.0+2 <0.11.0" 23 | intl: "0.12.4+2" 24 | sass: "0.4.2" 25 | angular2: "2.0.0-alpha.45" 26 | unittest: "0.11.6+1" 27 | transformers: 28 | - tickets 29 | - dartson 30 | - sass: 31 | executable: sassc # Sass executable to use 32 | - angular2: 33 | entry_points: 34 | - web/main.dart 35 | - $dart2js: 36 | minify: true 37 | commandLineOptions: 38 | - --show-package-warnings 39 | - --trust-type-annotations 40 | - --trust-primitives 41 | - --enable-experimental-mirrors 42 | -------------------------------------------------------------------------------- /test/database_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:guinness/guinness.dart'; 2 | import 'package:tickets/shared/schemas.dart'; 3 | import 'package:tickets/db/seeder.dart'; 4 | import 'package:tickets/db/db_config.dart'; 5 | import '../bin/mongo_model.dart'; 6 | 7 | main() { 8 | 9 | // Logger.root.level = Level.ALL; 10 | // Logger.root.onRecord.listen((LogRecord rec) { 11 | // print('${rec.level.name}: ${rec.time}: ${rec.message}'); 12 | // }); 13 | 14 | DbConfigValues config = new DbConfigValues(); 15 | MongoModel model = new MongoModel(config.testDbName, config.testDbURI, config.testDbSize); 16 | 17 | //A Test DTO 18 | RouteDTO routeDTO = new RouteDTO()..duration=120..price1=90.00..price2=91.00..price3=95.00..seats=7; 19 | 20 | describe("The Ticket MongoModel", () { 21 | 22 | it('Should populate the Test Database', () async { 23 | Seeder seeder = new Seeder(config.testDbName, config.testDbURI, config.testDbSeed); 24 | await seeder.readFile(); 25 | List collection = await model.readCollectionByType( RouteDTO ); 26 | expect(collection.length).toBeGreaterThan(10); 27 | }); 28 | 29 | it("should create a record DTO and write to the db", () { 30 | var originalID = routeDTO.id; 31 | return model.createByItem(routeDTO).then(( var dto ) { 32 | expect(originalID).toBeNull(); 33 | expect(routeDTO.id).toBeNotNull(); 34 | expect(dto.id).toEqual(routeDTO.id); 35 | }); 36 | }); 37 | 38 | // it("should not allows a DTO with an id to be created again db", () { 39 | // var temp = new RouteDTO()..id = '123456'; 40 | // var response; 41 | // try { 42 | // model.createByItem(temp); 43 | // } catch (e) { 44 | // expect(e).toBeAnInstanceOf(AssertionError); 45 | // } 46 | // expect(response).toBeNull(); 47 | // }); 48 | 49 | var action = "update previous db item, retrieve it to make sure its updated"; 50 | // it(action, () { 51 | // routeDTO.price1=10000.10; 52 | // return model.updateItem(routeDTO).then((status) { 53 | // return model.readItemByItem(routeDTO).then((dto){ 54 | // expect(status['ok']).toEqual(1.0); 55 | // expect(dto.price1).toEqual(routeDTO.price1); 56 | // }); 57 | // }); 58 | // }); 59 | 60 | it(action, () async { 61 | routeDTO.price1=10000.10; 62 | var status = await model.updateItem(routeDTO); 63 | var dto = await model.readItemByItem(routeDTO); 64 | expect(status['ok']).toEqual(1.0); 65 | expect(dto.price1).toEqual(routeDTO.price1); 66 | }); 67 | 68 | it("will retrieive the item created in the first step", () { 69 | return model.readItemByItem(routeDTO).then((BaseDTO dto){ 70 | expect(dto.id).toEqual(routeDTO.id); 71 | }); 72 | }); 73 | 74 | it("should retrieve a list of items by the DTO", () { 75 | return model.readCollectionByType( RouteDTO ).then(( List aList ) { 76 | expect(aList.first).toBeAnInstanceOf(RouteDTO); 77 | expect(aList.length).toBeGreaterThan(10); 78 | }); 79 | }); 80 | 81 | it("should delete the route DTO from the DB", () { 82 | return model.deleteByItem(routeDTO).then( (status) { 83 | expect(status['ok']).toEqual(1.0); 84 | }); 85 | }); 86 | 87 | it("should drop the test database", () async { 88 | Map status = await model.dropDatabase(); 89 | expect(status['ok']).toEqual(1.0); 90 | }); 91 | }); 92 | } -------------------------------------------------------------------------------- /test/ticket_model_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:guinness/guinness.dart'; 2 | import 'package:tickets/shared/schemas.dart'; 3 | 4 | import '../bin/ticketing_model.dart'; 5 | 6 | main() { 7 | TicketingModel model = new TicketingModel(); 8 | 9 | Map postPurchase = { "bAddress": "Consequatur amet possimus nulla et consectetur et", 10 | "bCity": "Ad aliqua Possimus dolor est iusto sequi quis officia laboris eos", 11 | "bCountry": "Est numquam nostrum est alias voluptatum corporis numquam voluptas exercitationem consequatur voluptas irure quas commodi est blanditiis", 12 | "bFirstName": "Dean", 13 | "bLastName": "Woodard", 14 | "bMiddleName": "Xaviera Henson", 15 | "bState": "Az", 16 | "bZip": '12', 17 | "ccExpiration": "Sint enim architecto commodi perferendis expedita quisquam quam cupidatat", 18 | "ccType": "visa", 19 | "ccn": '8', 20 | "ccv": '38', 21 | "flightID": 1006, 22 | "flightLevel": 3, 23 | "pEmail": "mohezizova@gmail.com", 24 | "pFirstName": "Mara", 25 | "pLastName": "Zimmerman", 26 | "pMiddleName": "Keegan Lee" }; 27 | 28 | describe('Ticketing Model', () { 29 | 30 | // make sure that createPurchase() converts the ObjectID to a String 31 | // purchaseDTO.transactionId = tDTO.id.toString(); 32 | // this was an editing error in the print version! 33 | 34 | it("should create a purchase object", () async { 35 | return model.createPurchase(postPurchase).then((PurchaseDTO confirmation){ 36 | expect(confirmation.collection_key).toBe('Purchases'); 37 | }); 38 | }); 39 | 40 | it("should get all cities", () async { 41 | return model.getAllCities({}).then((List cities){ 42 | expect(cities.length).toBeGreaterThan(4); 43 | }); 44 | }); 45 | 46 | it("should get all times", () async { 47 | return model.getAllTimes({}).then((List times){ 48 | expect(times.length).toBeGreaterThan(10); 49 | }); 50 | }); 51 | 52 | var timesPost = {"cityDepart":"SFO","cityArrival":"SAN","dateDepart":"2015-12-31"}; 53 | it("Should get times based on arrival and departure city", () { 54 | return model.getTimesByCity(timesPost).then((List dtos){ 55 | expect(dtos.first.flight).toBe(1016); 56 | }); 57 | }); 58 | 59 | it("should return the time for the flight number", () async { 60 | return model.getTimesByFlightNumber({'flight': '1016'}).then((List times){ 61 | expect(times.first.takeoff).toBe(1000); 62 | }); 63 | }); 64 | }); 65 | } 66 | -------------------------------------------------------------------------------- /web/deals.json: -------------------------------------------------------------------------------- 1 | { 2 | "deals": [ 3 | { 4 | "city_departure" : "SAN", 5 | "city_arrival" : "SFO", 6 | "price": "$99.00", 7 | "description": "King Of Sour Dough!", 8 | "date": "10/29/2015", 9 | "image": "http://bit.ly/wiki_sfo", 10 | "url": "/#/picker/SAN/SFO/2015-10-29/" 11 | }, 12 | { 13 | "city_departure" : "SFO", 14 | "city_arrival" : "LAX", 15 | "price": "79.00", 16 | "description": "Surf & Sand!", 17 | "date": "10/30/2015", 18 | "image": "http://bit.ly/wiki_lax", 19 | "url": "/#/picker/SFO/LAX/2015-10-30/" 20 | }, 21 | { 22 | "city_departure" : "SAN", 23 | "city_arrival" : "SAC", 24 | "price": "109.00", 25 | "description": "Visit The State Capital!", 26 | "date": "10/31/2015", 27 | "image": "http://bit.ly/wiki_sac", 28 | "url": "/#/picker/SAN/SMF/2015-10-31/" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JIT Ticket Application 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /web/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:angular2/angular2.dart'; 2 | import 'package:angular2/bootstrap.dart'; 3 | import 'package:angular2/router.dart'; 4 | import 'package:tickets/client/client.dart'; 5 | import 'dart:html'; 6 | 7 | void main() { 8 | 9 | // acquire environment variables applied from transformer 10 | Element parent = querySelector('tickets'); 11 | EnvironmentVariables.PORT = int.parse(parent.attributes['port']); 12 | EnvironmentVariables.DART_ENV = parent.attributes['environment']; 13 | 14 | var appComponent = Tickets; 15 | var inejectableBindings = [ ROUTER_BINDINGS, client_classes, 16 | bind(APP_BASE_HREF).toValue('/'), 17 | bind(LocationStrategy).toClass(HashLocationStrategy)]; 18 | 19 | bootstrap(appComponent, inejectableBindings); 20 | } 21 | -------------------------------------------------------------------------------- /web/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Roboto); 2 | 3 | html, body { 4 | width: 100%; 5 | height: 100%; 6 | margin: 0; 7 | padding: 0; 8 | font-family: 'Roboto', sans-serif; 9 | } 10 | 11 | .container { 12 | width: 1170px; 13 | } 14 | 15 | .deal-box { 16 | h3 { margin-top: 0; }; 17 | img { display: block; width: 100px; height: 66px; float: right; }; 18 | .btn { margin-top: -45px; float: right; }; 19 | } 20 | --------------------------------------------------------------------------------