├── .gitignore ├── README.md ├── api ├── app.js ├── app.json ├── datasources.json ├── models.json ├── models │ └── .gitkeep └── package.json └── client ├── .bowerrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── app ├── .buildignore ├── .htaccess ├── 404.html ├── favicon.ico ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── images │ └── yeoman.png ├── index.html ├── robots.txt ├── scripts │ ├── app.js │ ├── controllers │ │ ├── main.js │ │ └── notes.js │ └── factory.js ├── styles │ └── main.css └── views │ ├── main.html │ └── notes │ ├── edit.html │ ├── item.html │ ├── list.html │ └── main.html ├── bower.json ├── package.json └── test ├── .jshintrc ├── runner.html └── spec └── controllers ├── items.js └── main.js /.gitignore: -------------------------------------------------------------------------------- 1 | api/node_modules 2 | client/node_modules 3 | client/dist 4 | client/.tmp 5 | client/.sass-cache 6 | client/app/bower_components 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### If you like this my [new project](https://github.com/beeman/loopback-angular-admin) might be interesting too! 2 | 3 | It uses loopback, angular, restangular and has support for user authentication, file uploads and other stuff :) 4 | 5 | # angular-restangular-crud 6 | 7 | Example of how to use restangular in a way that you DRY too much... 8 | 9 | It uses AngularJS with Restangular at the frontend and loopback (https://www.npmjs.org/package/loopback) as the API. 10 | 11 | To run it you need node/npm/grunt/bower (and maybe even more packages) installed... 12 | 13 | ### Clone the repository 14 | 15 | git clone https://github.com/beeman/angular-restangular-crud 16 | 17 | ### Install the server and leave it running 18 | 19 | cd angular-restangular-crud/api 20 | npm install 21 | node app.js # or nodemon app.js 22 | 23 | ### Install the client 24 | 25 | cd angular-restangular-crud/client 26 | npm install 27 | bower install 28 | grunt 29 | 30 | Grunt might show some errors, most of the time it's jslint testing your JavaScript... Fix the errors or override them with --force. 31 | 32 | ### Serve the page 33 | 34 | grunt serve 35 | 36 | If all went well you can now list/add/view and edit notes. 37 | 38 | You can start working in the client directory and most of the changes will be picked up by autoreload (https://www.npmjs.org/package/livereload). Grunt serves the client on port 9000 and the API serves the same app at port 3000, but without the livereload. 39 | 40 | It is trivial to create some more models and quickly crank out a simple scaffold, as show below. 41 | 42 | ## Adding a new datatype to this example 43 | 44 | We will add an object named 'Host' 45 | 46 | Add menu item somewhere around line 30 in app/index.html: 47 | 48 |
  • Hosts
  • 49 | 50 | Copy app/scripts/controllers/notes.js to app/scripts/controllers/hosts.js 51 | 52 | $ cp app/scripts/controllers/notes.js app/scripts/controllers/hosts.js 53 | 54 | Now edit the new file and search and replace: 55 | 56 | find app/scripts/controllers/hosts.js -type f -exec sed -i '' 's/note/host/g' {} \; 57 | find app/scripts/controllers/hosts.js -type f -exec sed -i '' 's/Note/Host/g' {} \; 58 | 59 | Create the views: 60 | 61 | mkdir app/views/hosts/ 62 | cp app/views/notes/* app/views/hosts 63 | 64 | Now edit these new files and search and replace: 65 | 66 | find app/views/hosts/. -type f -exec sed -i '' 's/note/host/g' {} \; 67 | find app/views/hosts/. -type f -exec sed -i '' 's/Note/Host/g' {} \; 68 | 69 | Last but certainly not least, register the new controller in your template: 70 | 71 | Edit app/index.html and the following line just before the 'endbuild' line 72 | 73 | 74 | 75 | 76 | Update the API to accept this datatype. Edit api/models.json and add the following code to the object array: 77 | 78 | "host": { 79 | "public": true, 80 | "dataSource": "db", 81 | "plural": "hosts" 82 | } 83 | 84 | Now restart the API to start accepting the model abov.. 85 | 86 | # Todo 87 | 88 | * Add easy DB configuration 89 | * You name it? 90 | 91 | # License 92 | 93 | The MIT License (MIT) 94 | 95 | Copyright (c) 2014 Bram Borggreve 96 | 97 | Permission is hereby granted, free of charge, to any person obtaining a copy 98 | of this software and associated documentation files (the "Software"), to deal 99 | in the Software without restriction, including without limitation the rights 100 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 101 | copies of the Software, and to permit persons to whom the Software is 102 | furnished to do so, subject to the following conditions: 103 | 104 | The above copyright notice and this permission notice shall be included in 105 | all copies or substantial portions of the Software. 106 | 107 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 108 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 109 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 110 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 111 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 112 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 113 | THE SOFTWARE. 114 | -------------------------------------------------------------------------------- /api/app.js: -------------------------------------------------------------------------------- 1 | 'use struct'; 2 | 3 | var loopback = require('loopback'); 4 | var path = require('path'); 5 | var app = module.exports = loopback(); 6 | var started = new Date(); 7 | 8 | /* 9 | * 1. Configure LoopBack models and datasources 10 | * 11 | * Read more at http://apidocs.strongloop.com/loopback#appbootoptions 12 | */ 13 | 14 | app.boot(__dirname); 15 | 16 | /* 17 | * 2. Configure request preprocessing 18 | * 19 | * LoopBack support all express-compatible middleware. 20 | */ 21 | 22 | app.use(loopback.favicon()); 23 | app.use(loopback.logger(app.get('env') === 'development' ? 'dev' : 'default')); 24 | app.use(loopback.cookieParser(app.get('cookieSecret'))); 25 | app.use(loopback.token({model: app.models.accessToken})); 26 | app.use(loopback.bodyParser()); 27 | app.use(loopback.methodOverride()); 28 | 29 | /* 30 | * EXTENSION POINT 31 | * Add your custom request-preprocessing middleware here. 32 | * Example: 33 | * app.use(loopback.limit('5.5mb')) 34 | */ 35 | 36 | /* 37 | * 3. Setup request handlers. 38 | */ 39 | 40 | // LoopBack REST interface 41 | app.use(app.get('restApiRoot'), loopback.rest()); 42 | 43 | // API explorer (if present) 44 | try { 45 | var explorer = require('loopback-explorer')(app); 46 | app.use('/explorer', explorer); 47 | app.once('started', function(baseUrl) { 48 | console.log('Browse your REST API at %s%s', baseUrl, explorer.route); 49 | }); 50 | } catch(e){ 51 | console.log( 52 | 'Run `npm install loopback-explorer` to enable the LoopBack explorer' 53 | ); 54 | } 55 | 56 | /* 57 | * EXTENSION POINT 58 | * Add your custom request-handling middleware here. 59 | * Example: 60 | * app.use(function(req, resp, next) { 61 | * if (req.url == '/status') { 62 | * // send status response 63 | * } else { 64 | * next(); 65 | * } 66 | * }); 67 | */ 68 | 69 | // Let express routes handle requests that were not handled 70 | // by any of the middleware registered above. 71 | // This way LoopBack REST and API Explorer take precedence over 72 | // express routes. 73 | app.use(app.router); 74 | 75 | // The static file server should come after all other routes 76 | // Every request that goes through the static middleware hits 77 | // the file system to check if a file exists. 78 | app.use(loopback.static(path.join(__dirname, '../client/app/'))); 79 | 80 | // Requests that get this far won't be handled 81 | // by any middleware. Convert them into a 404 error 82 | // that will be handled later down the chain. 83 | app.use(loopback.urlNotFound()); 84 | 85 | /* 86 | * 4. Setup error handling strategy 87 | */ 88 | 89 | /* 90 | * EXTENSION POINT 91 | * Add your custom error reporting middleware here 92 | * Example: 93 | * app.use(function(err, req, resp, next) { 94 | * console.log(req.url, ' failed: ', err.stack); 95 | * next(err); 96 | * }); 97 | */ 98 | 99 | // The ultimate error handler. 100 | app.use(loopback.errorHandler()); 101 | 102 | 103 | /* 104 | * 5. Add a basic application status route at the root `/`. 105 | * 106 | * (remove this to handle `/` on your own) 107 | */ 108 | 109 | app.get('/status', loopback.status()); 110 | 111 | /* 112 | * 6. Enable access control and token based authentication. 113 | */ 114 | 115 | var swaggerRemote = app.remotes().exports.swagger; 116 | if (swaggerRemote) swaggerRemote.requireToken = false; 117 | 118 | app.enableAuth(); 119 | 120 | /* 121 | * 7. Optionally start the server 122 | * 123 | * (only if this module is the main module) 124 | */ 125 | 126 | app.start = function() { 127 | return app.listen(function() { 128 | var baseUrl = 'http://' + app.get('host') + ':' + app.get('port'); 129 | app.emit('started', baseUrl); 130 | console.log('LoopBack server listening @ %s%s', baseUrl, '/'); 131 | }); 132 | }; 133 | 134 | if(require.main === module) { 135 | app.start(); 136 | } 137 | -------------------------------------------------------------------------------- /api/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 3000, 3 | "host": "0.0.0.0", 4 | "cookieSecret": "499a743d-0ea1-41c0-a8a1-cde6630dd347" 5 | } 6 | -------------------------------------------------------------------------------- /api/datasources.json: -------------------------------------------------------------------------------- 1 | { 2 | "db": { 3 | "defaultForType": "db", 4 | "connector": "memory" 5 | }, 6 | "push": { 7 | "defaultForType": "push", 8 | "connector": "loopback-push-notification", 9 | "installation": "installation", 10 | "notification": "notification", 11 | "application": "application" 12 | }, 13 | "mail": { 14 | "defaultForType": "mail", 15 | "connector": "mail" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /api/models.json: -------------------------------------------------------------------------------- 1 | { 2 | "email": { 3 | "options": { 4 | "base": "Email" 5 | }, 6 | "dataSource": "mail", 7 | "public": false 8 | }, 9 | "user": { 10 | "options": { 11 | "base": "User", 12 | "relations": { 13 | "accessTokens": { 14 | "model": "accessToken", 15 | "type": "hasMany", 16 | "foreignKey": "userId" 17 | } 18 | } 19 | }, 20 | "dataSource": "db", 21 | "public": true 22 | }, 23 | "accessToken": { 24 | "options": { 25 | "base": "AccessToken" 26 | }, 27 | "dataSource": "db", 28 | "public": true 29 | }, 30 | "application": { 31 | "options": { 32 | "base": "Application" 33 | }, 34 | "dataSource": "db", 35 | "public": true 36 | }, 37 | "acl": { 38 | "options": { 39 | "base": "ACL" 40 | }, 41 | "dataSource": "db", 42 | "public": false 43 | }, 44 | "roleMapping": { 45 | "options": { 46 | "base": "RoleMapping" 47 | }, 48 | "dataSource": "db", 49 | "public": false 50 | }, 51 | "role": { 52 | "options": { 53 | "base": "Role", 54 | "relations": { 55 | "principals": { 56 | "type": "hasMany", 57 | "model": "roleMapping", 58 | "foreignKey": "roleId" 59 | } 60 | } 61 | }, 62 | "dataSource": "db", 63 | "public": false 64 | }, 65 | "scope": { 66 | "options": { 67 | "base": "Scope" 68 | }, 69 | "dataSource": "db", 70 | "public": false 71 | }, 72 | "push": { 73 | "options": { 74 | "base": "Push", 75 | "plural": "push" 76 | }, 77 | "dataSource": "push" 78 | }, 79 | "installation": { 80 | "options": { 81 | "base": "Installation" 82 | }, 83 | "dataSource": "db", 84 | "public": true 85 | }, 86 | "notification": { 87 | "options": { 88 | "base": "Notification" 89 | }, 90 | "dataSource": "db", 91 | "public": true 92 | }, 93 | "note": { 94 | "public": true, 95 | "dataSource": "db", 96 | "plural": "notes" 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /api/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beeman/angular-restangular-crud/33704525cb4adf267072906581ce5d3477e6871a/api/models/.gitkeep -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "main": "app.js", 4 | "bin": "server.js", 5 | "scripts": { 6 | "start": "node app.js" 7 | }, 8 | "dependencies": { 9 | "loopback": "1.*", 10 | "loopback-datasource-juggler" : "1.*" 11 | }, 12 | "optionalDependencies": { 13 | "loopback-explorer": "~1.1.0", 14 | "loopback-push-notification": "~1.1.0" 15 | }, 16 | "name": "angular-scaffold-api" 17 | } 18 | -------------------------------------------------------------------------------- /client/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /client/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /client/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .tmp 4 | .sass-cache 5 | app/bower_components 6 | -------------------------------------------------------------------------------- /client/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "angular": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /client/Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on 2014-02-12 using generator-angular 0.7.1 2 | 'use strict'; 3 | 4 | // # Globbing 5 | // for performance reasons we're only matching one level down: 6 | // 'test/spec/{,*/}*.js' 7 | // use this if you want to recursively match all subfolders: 8 | // 'test/spec/**/*.js' 9 | 10 | module.exports = function (grunt) { 11 | 12 | // Load grunt tasks automatically 13 | require('load-grunt-tasks')(grunt); 14 | 15 | // Time how long tasks take. Can help when optimizing build times 16 | require('time-grunt')(grunt); 17 | 18 | // Define the configuration for all the tasks 19 | grunt.initConfig({ 20 | 21 | // Project settings 22 | yeoman: { 23 | // configurable paths 24 | app: require('./bower.json').appPath || 'app', 25 | dist: 'dist' 26 | }, 27 | 28 | // Watches files for changes and runs tasks based on the changed files 29 | watch: { 30 | js: { 31 | files: ['<%= yeoman.app %>/scripts/{,*/}*.js'], 32 | tasks: ['newer:jshint:all'], 33 | options: { 34 | livereload: true 35 | } 36 | }, 37 | jsTest: { 38 | files: ['test/spec/{,*/}*.js'], 39 | tasks: ['newer:jshint:test', 'karma'] 40 | }, 41 | styles: { 42 | files: ['<%= yeoman.app %>/styles/{,*/}*.css'], 43 | tasks: ['newer:copy:styles', 'autoprefixer'] 44 | }, 45 | gruntfile: { 46 | files: ['Gruntfile.js'] 47 | }, 48 | livereload: { 49 | options: { 50 | livereload: '<%= connect.options.livereload %>' 51 | }, 52 | files: [ 53 | '<%= yeoman.app %>/{,*/}*.html', 54 | '.tmp/styles/{,*/}*.css', 55 | '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' 56 | ] 57 | } 58 | }, 59 | 60 | // The actual grunt server settings 61 | connect: { 62 | options: { 63 | port: 9000, 64 | // Change this to '0.0.0.0' to access the server from outside. 65 | hostname: 'localhost', 66 | livereload: 35729 67 | }, 68 | livereload: { 69 | options: { 70 | open: true, 71 | base: [ 72 | '.tmp', 73 | '<%= yeoman.app %>' 74 | ] 75 | } 76 | }, 77 | test: { 78 | options: { 79 | port: 9001, 80 | base: [ 81 | '.tmp', 82 | 'test', 83 | '<%= yeoman.app %>' 84 | ] 85 | } 86 | }, 87 | dist: { 88 | options: { 89 | base: '<%= yeoman.dist %>' 90 | } 91 | } 92 | }, 93 | 94 | // Make sure code styles are up to par and there are no obvious mistakes 95 | jshint: { 96 | options: { 97 | jshintrc: '.jshintrc', 98 | reporter: require('jshint-stylish') 99 | }, 100 | all: [ 101 | 'Gruntfile.js', 102 | '<%= yeoman.app %>/scripts/{,*/}*.js' 103 | ], 104 | test: { 105 | options: { 106 | jshintrc: 'test/.jshintrc' 107 | }, 108 | src: ['test/spec/{,*/}*.js'] 109 | } 110 | }, 111 | 112 | // Empties folders to start fresh 113 | clean: { 114 | dist: { 115 | files: [{ 116 | dot: true, 117 | src: [ 118 | '.tmp', 119 | '<%= yeoman.dist %>/*', 120 | '!<%= yeoman.dist %>/.git*' 121 | ] 122 | }] 123 | }, 124 | server: '.tmp' 125 | }, 126 | 127 | // Add vendor prefixed styles 128 | autoprefixer: { 129 | options: { 130 | browsers: ['last 1 version'] 131 | }, 132 | dist: { 133 | files: [{ 134 | expand: true, 135 | cwd: '.tmp/styles/', 136 | src: '{,*/}*.css', 137 | dest: '.tmp/styles/' 138 | }] 139 | } 140 | }, 141 | 142 | // Automatically inject Bower components into the app 143 | 'bower-install': { 144 | app: { 145 | html: '<%= yeoman.app %>/index.html', 146 | ignorePath: '<%= yeoman.app %>/' 147 | } 148 | }, 149 | 150 | 151 | 152 | 153 | 154 | // Renames files for browser caching purposes 155 | rev: { 156 | dist: { 157 | files: { 158 | src: [ 159 | '<%= yeoman.dist %>/scripts/{,*/}*.js', 160 | '<%= yeoman.dist %>/styles/{,*/}*.css', 161 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', 162 | '<%= yeoman.dist %>/styles/fonts/*' 163 | ] 164 | } 165 | } 166 | }, 167 | 168 | // Reads HTML for usemin blocks to enable smart builds that automatically 169 | // concat, minify and revision files. Creates configurations in memory so 170 | // additional tasks can operate on them 171 | useminPrepare: { 172 | html: '<%= yeoman.app %>/index.html', 173 | options: { 174 | dest: '<%= yeoman.dist %>' 175 | } 176 | }, 177 | 178 | // Performs rewrites based on rev and the useminPrepare configuration 179 | usemin: { 180 | html: ['<%= yeoman.dist %>/{,*/}*.html'], 181 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], 182 | options: { 183 | assetsDirs: ['<%= yeoman.dist %>'] 184 | } 185 | }, 186 | 187 | // The following *-min tasks produce minified files in the dist folder 188 | imagemin: { 189 | dist: { 190 | files: [{ 191 | expand: true, 192 | cwd: '<%= yeoman.app %>/images', 193 | src: '{,*/}*.{png,jpg,jpeg,gif}', 194 | dest: '<%= yeoman.dist %>/images' 195 | }] 196 | } 197 | }, 198 | svgmin: { 199 | dist: { 200 | files: [{ 201 | expand: true, 202 | cwd: '<%= yeoman.app %>/images', 203 | src: '{,*/}*.svg', 204 | dest: '<%= yeoman.dist %>/images' 205 | }] 206 | } 207 | }, 208 | htmlmin: { 209 | dist: { 210 | options: { 211 | collapseWhitespace: true, 212 | collapseBooleanAttributes: true, 213 | removeCommentsFromCDATA: true, 214 | removeOptionalTags: true 215 | }, 216 | files: [{ 217 | expand: true, 218 | cwd: '<%= yeoman.dist %>', 219 | src: ['*.html', 'views/{,*/}*.html'], 220 | dest: '<%= yeoman.dist %>' 221 | }] 222 | } 223 | }, 224 | 225 | // Allow the use of non-minsafe AngularJS files. Automatically makes it 226 | // minsafe compatible so Uglify does not destroy the ng references 227 | ngmin: { 228 | dist: { 229 | files: [{ 230 | expand: true, 231 | cwd: '.tmp/concat/scripts', 232 | src: '*.js', 233 | dest: '.tmp/concat/scripts' 234 | }] 235 | } 236 | }, 237 | 238 | // Replace Google CDN references 239 | cdnify: { 240 | dist: { 241 | html: ['<%= yeoman.dist %>/*.html'] 242 | } 243 | }, 244 | 245 | // Copies remaining files to places other tasks can use 246 | copy: { 247 | dist: { 248 | files: [{ 249 | expand: true, 250 | dot: true, 251 | cwd: '<%= yeoman.app %>', 252 | dest: '<%= yeoman.dist %>', 253 | src: [ 254 | '*.{ico,png,txt}', 255 | '.htaccess', 256 | '*.html', 257 | 'views/{,*/}*.html', 258 | 'bower_components/**/*', 259 | 'images/{,*/}*.{webp}', 260 | 'fonts/*' 261 | ] 262 | }, { 263 | expand: true, 264 | cwd: '.tmp/images', 265 | dest: '<%= yeoman.dist %>/images', 266 | src: ['generated/*'] 267 | }] 268 | }, 269 | styles: { 270 | expand: true, 271 | cwd: '<%= yeoman.app %>/styles', 272 | dest: '.tmp/styles/', 273 | src: '{,*/}*.css' 274 | } 275 | }, 276 | 277 | // Run some tasks in parallel to speed up the build process 278 | concurrent: { 279 | server: [ 280 | 'copy:styles' 281 | ], 282 | test: [ 283 | 'copy:styles' 284 | ], 285 | dist: [ 286 | 'copy:styles', 287 | 'imagemin', 288 | 'svgmin' 289 | ] 290 | }, 291 | 292 | // By default, your `index.html`'s will take care of 293 | // minification. These next options are pre-configured if you do not wish 294 | // to use the Usemin blocks. 295 | // cssmin: { 296 | // dist: { 297 | // files: { 298 | // '<%= yeoman.dist %>/styles/main.css': [ 299 | // '.tmp/styles/{,*/}*.css', 300 | // '<%= yeoman.app %>/styles/{,*/}*.css' 301 | // ] 302 | // } 303 | // } 304 | // }, 305 | // uglify: { 306 | // dist: { 307 | // files: { 308 | // '<%= yeoman.dist %>/scripts/scripts.js': [ 309 | // '<%= yeoman.dist %>/scripts/scripts.js' 310 | // ] 311 | // } 312 | // } 313 | // }, 314 | // concat: { 315 | // dist: {} 316 | // }, 317 | 318 | // Test settings 319 | karma: { 320 | unit: { 321 | configFile: 'karma.conf.js', 322 | singleRun: true 323 | } 324 | } 325 | }); 326 | 327 | 328 | grunt.registerTask('serve', function (target) { 329 | if (target === 'dist') { 330 | return grunt.task.run(['build', 'connect:dist:keepalive']); 331 | } 332 | 333 | grunt.task.run([ 334 | 'clean:server', 335 | 'bower-install', 336 | 'concurrent:server', 337 | 'autoprefixer', 338 | 'connect:livereload', 339 | 'watch' 340 | ]); 341 | }); 342 | 343 | grunt.registerTask('server', function () { 344 | grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); 345 | grunt.task.run(['serve']); 346 | }); 347 | 348 | grunt.registerTask('test', [ 349 | 'clean:server', 350 | 'concurrent:test', 351 | 'autoprefixer', 352 | 'connect:test', 353 | ]); 354 | 355 | grunt.registerTask('build', [ 356 | 'clean:dist', 357 | 'bower-install', 358 | 'useminPrepare', 359 | 'concurrent:dist', 360 | 'autoprefixer', 361 | 'concat', 362 | 'ngmin', 363 | 'copy:dist', 364 | 'cdnify', 365 | 'cssmin', 366 | 'uglify', 367 | 'rev', 368 | 'usemin', 369 | 'htmlmin' 370 | ]); 371 | 372 | grunt.registerTask('default', [ 373 | 'newer:jshint', 374 | 'test', 375 | 'build' 376 | ]); 377 | }; 378 | -------------------------------------------------------------------------------- /client/app/.buildignore: -------------------------------------------------------------------------------- 1 | *.coffee -------------------------------------------------------------------------------- /client/app/.htaccess: -------------------------------------------------------------------------------- 1 | # Apache Configuration File 2 | 3 | # (!) Using `.htaccess` files slows down Apache, therefore, if you have access 4 | # to the main server config file (usually called `httpd.conf`), you should add 5 | # this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html. 6 | 7 | # ############################################################################## 8 | # # CROSS-ORIGIN RESOURCE SHARING (CORS) # 9 | # ############################################################################## 10 | 11 | # ------------------------------------------------------------------------------ 12 | # | Cross-domain AJAX requests | 13 | # ------------------------------------------------------------------------------ 14 | 15 | # Enable cross-origin AJAX requests. 16 | # http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity 17 | # http://enable-cors.org/ 18 | 19 | # 20 | # Header set Access-Control-Allow-Origin "*" 21 | # 22 | 23 | # ------------------------------------------------------------------------------ 24 | # | CORS-enabled images | 25 | # ------------------------------------------------------------------------------ 26 | 27 | # Send the CORS header for images when browsers request it. 28 | # https://developer.mozilla.org/en/CORS_Enabled_Image 29 | # http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html 30 | # http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/ 31 | 32 | 33 | 34 | 35 | SetEnvIf Origin ":" IS_CORS 36 | Header set Access-Control-Allow-Origin "*" env=IS_CORS 37 | 38 | 39 | 40 | 41 | # ------------------------------------------------------------------------------ 42 | # | Web fonts access | 43 | # ------------------------------------------------------------------------------ 44 | 45 | # Allow access from all domains for web fonts 46 | 47 | 48 | 49 | Header set Access-Control-Allow-Origin "*" 50 | 51 | 52 | 53 | 54 | # ############################################################################## 55 | # # ERRORS # 56 | # ############################################################################## 57 | 58 | # ------------------------------------------------------------------------------ 59 | # | 404 error prevention for non-existing redirected folders | 60 | # ------------------------------------------------------------------------------ 61 | 62 | # Prevent Apache from returning a 404 error for a rewrite if a directory 63 | # with the same name does not exist. 64 | # http://httpd.apache.org/docs/current/content-negotiation.html#multiviews 65 | # http://www.webmasterworld.com/apache/3808792.htm 66 | 67 | Options -MultiViews 68 | 69 | # ------------------------------------------------------------------------------ 70 | # | Custom error messages / pages | 71 | # ------------------------------------------------------------------------------ 72 | 73 | # You can customize what Apache returns to the client in case of an error (see 74 | # http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.: 75 | 76 | ErrorDocument 404 /404.html 77 | 78 | 79 | # ############################################################################## 80 | # # INTERNET EXPLORER # 81 | # ############################################################################## 82 | 83 | # ------------------------------------------------------------------------------ 84 | # | Better website experience | 85 | # ------------------------------------------------------------------------------ 86 | 87 | # Force IE to render pages in the highest available mode in the various 88 | # cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf. 89 | 90 | 91 | Header set X-UA-Compatible "IE=edge" 92 | # `mod_headers` can't match based on the content-type, however, we only 93 | # want to send this header for HTML pages and not for the other resources 94 | 95 | Header unset X-UA-Compatible 96 | 97 | 98 | 99 | # ------------------------------------------------------------------------------ 100 | # | Cookie setting from iframes | 101 | # ------------------------------------------------------------------------------ 102 | 103 | # Allow cookies to be set from iframes in IE. 104 | 105 | # 106 | # Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"" 107 | # 108 | 109 | # ------------------------------------------------------------------------------ 110 | # | Screen flicker | 111 | # ------------------------------------------------------------------------------ 112 | 113 | # Stop screen flicker in IE on CSS rollovers (this only works in 114 | # combination with the `ExpiresByType` directives for images from below). 115 | 116 | # BrowserMatch "MSIE" brokenvary=1 117 | # BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1 118 | # BrowserMatch "Opera" !brokenvary 119 | # SetEnvIf brokenvary 1 force-no-vary 120 | 121 | 122 | # ############################################################################## 123 | # # MIME TYPES AND ENCODING # 124 | # ############################################################################## 125 | 126 | # ------------------------------------------------------------------------------ 127 | # | Proper MIME types for all files | 128 | # ------------------------------------------------------------------------------ 129 | 130 | 131 | 132 | # Audio 133 | AddType audio/mp4 m4a f4a f4b 134 | AddType audio/ogg oga ogg 135 | 136 | # JavaScript 137 | # Normalize to standard type (it's sniffed in IE anyways): 138 | # http://tools.ietf.org/html/rfc4329#section-7.2 139 | AddType application/javascript js jsonp 140 | AddType application/json json 141 | 142 | # Video 143 | AddType video/mp4 mp4 m4v f4v f4p 144 | AddType video/ogg ogv 145 | AddType video/webm webm 146 | AddType video/x-flv flv 147 | 148 | # Web fonts 149 | AddType application/font-woff woff 150 | AddType application/vnd.ms-fontobject eot 151 | 152 | # Browsers usually ignore the font MIME types and sniff the content, 153 | # however, Chrome shows a warning if other MIME types are used for the 154 | # following fonts. 155 | AddType application/x-font-ttf ttc ttf 156 | AddType font/opentype otf 157 | 158 | # Make SVGZ fonts work on iPad: 159 | # https://twitter.com/FontSquirrel/status/14855840545 160 | AddType image/svg+xml svg svgz 161 | AddEncoding gzip svgz 162 | 163 | # Other 164 | AddType application/octet-stream safariextz 165 | AddType application/x-chrome-extension crx 166 | AddType application/x-opera-extension oex 167 | AddType application/x-shockwave-flash swf 168 | AddType application/x-web-app-manifest+json webapp 169 | AddType application/x-xpinstall xpi 170 | AddType application/xml atom rdf rss xml 171 | AddType image/webp webp 172 | AddType image/x-icon ico 173 | AddType text/cache-manifest appcache manifest 174 | AddType text/vtt vtt 175 | AddType text/x-component htc 176 | AddType text/x-vcard vcf 177 | 178 | 179 | 180 | # ------------------------------------------------------------------------------ 181 | # | UTF-8 encoding | 182 | # ------------------------------------------------------------------------------ 183 | 184 | # Use UTF-8 encoding for anything served as `text/html` or `text/plain`. 185 | AddDefaultCharset utf-8 186 | 187 | # Force UTF-8 for certain file formats. 188 | 189 | AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml 190 | 191 | 192 | 193 | # ############################################################################## 194 | # # URL REWRITES # 195 | # ############################################################################## 196 | 197 | # ------------------------------------------------------------------------------ 198 | # | Rewrite engine | 199 | # ------------------------------------------------------------------------------ 200 | 201 | # Turning on the rewrite engine and enabling the `FollowSymLinks` option is 202 | # necessary for the following directives to work. 203 | 204 | # If your web host doesn't allow the `FollowSymlinks` option, you may need to 205 | # comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the 206 | # performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks 207 | 208 | # Also, some cloud hosting services require `RewriteBase` to be set: 209 | # http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site 210 | 211 | 212 | Options +FollowSymlinks 213 | # Options +SymLinksIfOwnerMatch 214 | RewriteEngine On 215 | # RewriteBase / 216 | 217 | 218 | # ------------------------------------------------------------------------------ 219 | # | Suppressing / Forcing the "www." at the beginning of URLs | 220 | # ------------------------------------------------------------------------------ 221 | 222 | # The same content should never be available under two different URLs especially 223 | # not with and without "www." at the beginning. This can cause SEO problems 224 | # (duplicate content), therefore, you should choose one of the alternatives and 225 | # redirect the other one. 226 | 227 | # By default option 1 (no "www.") is activated: 228 | # http://no-www.org/faq.php?q=class_b 229 | 230 | # If you'd prefer to use option 2, just comment out all the lines from option 1 231 | # and uncomment the ones from option 2. 232 | 233 | # IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME! 234 | 235 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 236 | 237 | # Option 1: rewrite www.example.com → example.com 238 | 239 | 240 | RewriteCond %{HTTPS} !=on 241 | RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] 242 | RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] 243 | 244 | 245 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 246 | 247 | # Option 2: rewrite example.com → www.example.com 248 | 249 | # Be aware that the following might not be a good idea if you use "real" 250 | # subdomains for certain parts of your website. 251 | 252 | # 253 | # RewriteCond %{HTTPS} !=on 254 | # RewriteCond %{HTTP_HOST} !^www\..+$ [NC] 255 | # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] 256 | # 257 | 258 | 259 | # ############################################################################## 260 | # # SECURITY # 261 | # ############################################################################## 262 | 263 | # ------------------------------------------------------------------------------ 264 | # | Content Security Policy (CSP) | 265 | # ------------------------------------------------------------------------------ 266 | 267 | # You can mitigate the risk of cross-site scripting and other content-injection 268 | # attacks by setting a Content Security Policy which whitelists trusted sources 269 | # of content for your site. 270 | 271 | # The example header below allows ONLY scripts that are loaded from the current 272 | # site's origin (no inline scripts, no CDN, etc). This almost certainly won't 273 | # work as-is for your site! 274 | 275 | # To get all the details you'll need to craft a reasonable policy for your site, 276 | # read: http://html5rocks.com/en/tutorials/security/content-security-policy (or 277 | # see the specification: http://w3.org/TR/CSP). 278 | 279 | # 280 | # Header set Content-Security-Policy "script-src 'self'; object-src 'self'" 281 | # 282 | # Header unset Content-Security-Policy 283 | # 284 | # 285 | 286 | # ------------------------------------------------------------------------------ 287 | # | File access | 288 | # ------------------------------------------------------------------------------ 289 | 290 | # Block access to directories without a default document. 291 | # Usually you should leave this uncommented because you shouldn't allow anyone 292 | # to surf through every directory on your server (which may includes rather 293 | # private places like the CMS's directories). 294 | 295 | 296 | Options -Indexes 297 | 298 | 299 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 300 | 301 | # Block access to hidden files and directories. 302 | # This includes directories used by version control systems such as Git and SVN. 303 | 304 | 305 | RewriteCond %{SCRIPT_FILENAME} -d [OR] 306 | RewriteCond %{SCRIPT_FILENAME} -f 307 | RewriteRule "(^|/)\." - [F] 308 | 309 | 310 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 311 | 312 | # Block access to backup and source files. 313 | # These files may be left by some text editors and can pose a great security 314 | # danger when anyone has access to them. 315 | 316 | 317 | Order allow,deny 318 | Deny from all 319 | Satisfy All 320 | 321 | 322 | # ------------------------------------------------------------------------------ 323 | # | Secure Sockets Layer (SSL) | 324 | # ------------------------------------------------------------------------------ 325 | 326 | # Rewrite secure requests properly to prevent SSL certificate warnings, e.g.: 327 | # prevent `https://www.example.com` when your certificate only allows 328 | # `https://secure.example.com`. 329 | 330 | # 331 | # RewriteCond %{SERVER_PORT} !^443 332 | # RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L] 333 | # 334 | 335 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 336 | 337 | # Force client-side SSL redirection. 338 | 339 | # If a user types "example.com" in his browser, the above rule will redirect him 340 | # to the secure version of the site. That still leaves a window of opportunity 341 | # (the initial HTTP connection) for an attacker to downgrade or redirect the 342 | # request. The following header ensures that browser will ONLY connect to your 343 | # server via HTTPS, regardless of what the users type in the address bar. 344 | # http://www.html5rocks.com/en/tutorials/security/transport-layer-security/ 345 | 346 | # 347 | # Header set Strict-Transport-Security max-age=16070400; 348 | # 349 | 350 | # ------------------------------------------------------------------------------ 351 | # | Server software information | 352 | # ------------------------------------------------------------------------------ 353 | 354 | # Avoid displaying the exact Apache version number, the description of the 355 | # generic OS-type and the information about Apache's compiled-in modules. 356 | 357 | # ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`! 358 | 359 | # ServerTokens Prod 360 | 361 | 362 | # ############################################################################## 363 | # # WEB PERFORMANCE # 364 | # ############################################################################## 365 | 366 | # ------------------------------------------------------------------------------ 367 | # | Compression | 368 | # ------------------------------------------------------------------------------ 369 | 370 | 371 | 372 | # Force compression for mangled headers. 373 | # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping 374 | 375 | 376 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding 377 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding 378 | 379 | 380 | 381 | # Compress all output labeled with one of the following MIME-types 382 | # (for Apache versions below 2.3.7, you don't need to enable `mod_filter` 383 | # and can remove the `` and `` lines 384 | # as `AddOutputFilterByType` is still in the core directives). 385 | 386 | AddOutputFilterByType DEFLATE application/atom+xml \ 387 | application/javascript \ 388 | application/json \ 389 | application/rss+xml \ 390 | application/vnd.ms-fontobject \ 391 | application/x-font-ttf \ 392 | application/x-web-app-manifest+json \ 393 | application/xhtml+xml \ 394 | application/xml \ 395 | font/opentype \ 396 | image/svg+xml \ 397 | image/x-icon \ 398 | text/css \ 399 | text/html \ 400 | text/plain \ 401 | text/x-component \ 402 | text/xml 403 | 404 | 405 | 406 | 407 | # ------------------------------------------------------------------------------ 408 | # | Content transformations | 409 | # ------------------------------------------------------------------------------ 410 | 411 | # Prevent some of the mobile network providers from modifying the content of 412 | # your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5. 413 | 414 | # 415 | # Header set Cache-Control "no-transform" 416 | # 417 | 418 | # ------------------------------------------------------------------------------ 419 | # | ETag removal | 420 | # ------------------------------------------------------------------------------ 421 | 422 | # Since we're sending far-future expires headers (see below), ETags can 423 | # be removed: http://developer.yahoo.com/performance/rules.html#etags. 424 | 425 | # `FileETag None` is not enough for every server. 426 | 427 | Header unset ETag 428 | 429 | 430 | FileETag None 431 | 432 | # ------------------------------------------------------------------------------ 433 | # | Expires headers (for better cache control) | 434 | # ------------------------------------------------------------------------------ 435 | 436 | # The following expires headers are set pretty far in the future. If you don't 437 | # control versioning with filename-based cache busting, consider lowering the 438 | # cache time for resources like CSS and JS to something like 1 week. 439 | 440 | 441 | 442 | ExpiresActive on 443 | ExpiresDefault "access plus 1 month" 444 | 445 | # CSS 446 | ExpiresByType text/css "access plus 1 year" 447 | 448 | # Data interchange 449 | ExpiresByType application/json "access plus 0 seconds" 450 | ExpiresByType application/xml "access plus 0 seconds" 451 | ExpiresByType text/xml "access plus 0 seconds" 452 | 453 | # Favicon (cannot be renamed!) 454 | ExpiresByType image/x-icon "access plus 1 week" 455 | 456 | # HTML components (HTCs) 457 | ExpiresByType text/x-component "access plus 1 month" 458 | 459 | # HTML 460 | ExpiresByType text/html "access plus 0 seconds" 461 | 462 | # JavaScript 463 | ExpiresByType application/javascript "access plus 1 year" 464 | 465 | # Manifest files 466 | ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds" 467 | ExpiresByType text/cache-manifest "access plus 0 seconds" 468 | 469 | # Media 470 | ExpiresByType audio/ogg "access plus 1 month" 471 | ExpiresByType image/gif "access plus 1 month" 472 | ExpiresByType image/jpeg "access plus 1 month" 473 | ExpiresByType image/png "access plus 1 month" 474 | ExpiresByType video/mp4 "access plus 1 month" 475 | ExpiresByType video/ogg "access plus 1 month" 476 | ExpiresByType video/webm "access plus 1 month" 477 | 478 | # Web feeds 479 | ExpiresByType application/atom+xml "access plus 1 hour" 480 | ExpiresByType application/rss+xml "access plus 1 hour" 481 | 482 | # Web fonts 483 | ExpiresByType application/font-woff "access plus 1 month" 484 | ExpiresByType application/vnd.ms-fontobject "access plus 1 month" 485 | ExpiresByType application/x-font-ttf "access plus 1 month" 486 | ExpiresByType font/opentype "access plus 1 month" 487 | ExpiresByType image/svg+xml "access plus 1 month" 488 | 489 | 490 | 491 | # ------------------------------------------------------------------------------ 492 | # | Filename-based cache busting | 493 | # ------------------------------------------------------------------------------ 494 | 495 | # If you're not using a build process to manage your filename version revving, 496 | # you might want to consider enabling the following directives to route all 497 | # requests such as `/css/style.12345.css` to `/css/style.css`. 498 | 499 | # To understand why this is important and a better idea than `*.css?v231`, read: 500 | # http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring 501 | 502 | # 503 | # RewriteCond %{REQUEST_FILENAME} !-f 504 | # RewriteCond %{REQUEST_FILENAME} !-d 505 | # RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L] 506 | # 507 | 508 | # ------------------------------------------------------------------------------ 509 | # | File concatenation | 510 | # ------------------------------------------------------------------------------ 511 | 512 | # Allow concatenation from within specific CSS and JS files, e.g.: 513 | # Inside of `script.combined.js` you could have 514 | # 515 | # 516 | # and they would be included into this single file. 517 | 518 | # 519 | # 520 | # Options +Includes 521 | # AddOutputFilterByType INCLUDES application/javascript application/json 522 | # SetOutputFilter INCLUDES 523 | # 524 | # 525 | # Options +Includes 526 | # AddOutputFilterByType INCLUDES text/css 527 | # SetOutputFilter INCLUDES 528 | # 529 | # 530 | 531 | # ------------------------------------------------------------------------------ 532 | # | Persistent connections | 533 | # ------------------------------------------------------------------------------ 534 | 535 | # Allow multiple requests to be sent over the same TCP connection: 536 | # http://httpd.apache.org/docs/current/en/mod/core.html#keepalive. 537 | 538 | # Enable if you serve a lot of static content but, be aware of the 539 | # possible disadvantages! 540 | 541 | # 542 | # Header set Connection Keep-Alive 543 | # 544 | -------------------------------------------------------------------------------- /client/app/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found :( 6 | 141 | 142 | 143 |
    144 |

    Not found :(

    145 |

    Sorry, but the page you were trying to view does not exist.

    146 |

    It looks like this was the result of either:

    147 | 151 | 154 | 155 |
    156 | 157 | 158 | -------------------------------------------------------------------------------- /client/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beeman/angular-restangular-crud/33704525cb4adf267072906581ce5d3477e6871a/client/app/favicon.ico -------------------------------------------------------------------------------- /client/app/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beeman/angular-restangular-crud/33704525cb4adf267072906581ce5d3477e6871a/client/app/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /client/app/fonts/glyphicons-halflings-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 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 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /client/app/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beeman/angular-restangular-crud/33704525cb4adf267072906581ce5d3477e6871a/client/app/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /client/app/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beeman/angular-restangular-crud/33704525cb4adf267072906581ce5d3477e6871a/client/app/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /client/app/images/yeoman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beeman/angular-restangular-crud/33704525cb4adf267072906581ce5d3477e6871a/client/app/images/yeoman.png -------------------------------------------------------------------------------- /client/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 34 |

    AngularJS + Restangular + UI-Router

    35 |
    36 | 37 | 38 |
    39 |
    40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /client/app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /client/app/scripts/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var app = angular.module('scaffoldApp', [ 4 | 'ngCookies', 5 | 'ngResource', 6 | 'ngSanitize', 7 | 'restangular', 8 | 'ui.router' 9 | ]); 10 | 11 | app.config(function (RestangularProvider) { 12 | RestangularProvider.setBaseUrl('http://localhost:3000/api'); 13 | }); 14 | 15 | 16 | app.config(['$stateProvider', '$urlRouterProvider', 17 | function ($stateProvider, $urlRouterProvider) { 18 | 19 | $urlRouterProvider.otherwise('/'); 20 | 21 | $stateProvider 22 | .state('home', { 23 | url: '/', 24 | templateUrl: 'views/main.html', 25 | controller: 'MainCtrl' 26 | }); 27 | 28 | 29 | } 30 | ]); -------------------------------------------------------------------------------- /client/app/scripts/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('scaffoldApp').controller('MainCtrl', function ($scope) { 4 | $scope.hello = 'from AngularJS'; 5 | }); -------------------------------------------------------------------------------- /client/app/scripts/controllers/notes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var app = angular.module('scaffoldApp'); 4 | 5 | /* Define the Repository that interfaces with Restangular */ 6 | app.factory('NoteRepository', ['Restangular', 'AbstractRepository', 7 | function (restangular, AbstractRepository) { 8 | 9 | function NoteRepository() { 10 | AbstractRepository.call(this, restangular, 'notes'); 11 | } 12 | 13 | AbstractRepository.extend(NoteRepository); 14 | return new NoteRepository(); 15 | } 16 | ]); 17 | 18 | /* Here the controllers are defines */ 19 | app.controller('NotesListCtrl', function ($scope, NoteRepository) { 20 | $scope.notes = NoteRepository.getList(); 21 | $scope.delete = function (data) { 22 | if(window.confirm('Are you sure?')) { 23 | NoteRepository.remove(data).then(function () { 24 | $scope.notes = NoteRepository.getList(); 25 | }); 26 | } 27 | }; 28 | }); 29 | 30 | app.controller('NotesItemCtrl', function ($scope, $stateParams, NoteRepository) { 31 | $scope.note = NoteRepository.get($stateParams.id).then(function (data) { 32 | $scope.note = data; 33 | }); 34 | }); 35 | 36 | app.controller('NotesEditCtrl', function ($scope, $stateParams, $location, NoteRepository) { 37 | $scope.note = NoteRepository.get($stateParams.id).then(function (data) { 38 | $scope.note = data; 39 | }); 40 | $scope.save = function () { 41 | $scope.note.put().then(function () { 42 | $location.path('/notes/' + $stateParams.id); 43 | }); 44 | }; 45 | }); 46 | 47 | app.controller('NotesAddCtrl', function ($scope, $location, NoteRepository) { 48 | $scope.save = function () { 49 | NoteRepository.create($scope.note).then(function () { 50 | $location.path('/notes'); 51 | }); 52 | }; 53 | }); 54 | 55 | /* Below are the states that are used */ 56 | app.config(['$stateProvider', 57 | function ($stateProvider) { 58 | 59 | $stateProvider 60 | .state('notes', { 61 | abstract: true, 62 | url: '/notes', 63 | templateUrl: 'views/notes/main.html' 64 | }) 65 | 66 | .state('notes.list', { 67 | url: '', 68 | templateUrl: 'views/notes/list.html', 69 | controller: 'NotesListCtrl' 70 | }) 71 | 72 | .state('notes.add', { 73 | url: '/add', 74 | templateUrl: 'views/notes/edit.html', 75 | controller: 'NotesAddCtrl' 76 | }) 77 | 78 | .state('notes.edit', { 79 | url: '/edit/{id}', 80 | templateUrl: 'views/notes/edit.html', 81 | controller: 'NotesEditCtrl' 82 | }) 83 | 84 | .state('notes.item', { 85 | url: '/{id}', 86 | templateUrl: 'views/notes/item.html', 87 | controller: 'NotesItemCtrl' 88 | }); 89 | } 90 | ]); -------------------------------------------------------------------------------- /client/app/scripts/factory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var app = angular.module('scaffoldApp'); 4 | 5 | app.factory('AbstractRepository', [ 6 | 7 | function () { 8 | 9 | function AbstractRepository(restangular, route) { 10 | this.restangular = restangular; 11 | this.route = route; 12 | } 13 | 14 | AbstractRepository.prototype = { 15 | getList: function (params) { 16 | return this.restangular.all(this.route).getList(params).$object; 17 | }, 18 | get: function (id) { 19 | return this.restangular.one(this.route, id).get(); 20 | }, 21 | getView: function (id) { 22 | return this.restangular.one(this.route, id).one(this.route + 'view').get(); 23 | }, 24 | update: function (updatedResource) { 25 | return updatedResource.put().$object; 26 | }, 27 | create: function (newResource) { 28 | return this.restangular.all(this.route).post(newResource); 29 | }, 30 | remove: function (object) { 31 | return this.restangular.one(this.route, object.id).remove(); 32 | } 33 | }; 34 | 35 | AbstractRepository.extend = function (repository) { 36 | repository.prototype = Object.create(AbstractRepository.prototype); 37 | repository.prototype.constructor = repository; 38 | }; 39 | 40 | return AbstractRepository; 41 | } 42 | ]); 43 | -------------------------------------------------------------------------------- /client/app/styles/main.css: -------------------------------------------------------------------------------- 1 | /* Space out content a bit */ 2 | body { 3 | padding-top: 20px; 4 | padding-bottom: 20px; 5 | } 6 | 7 | /* Everything but the jumbotron gets side spacing for mobile first views */ 8 | .header, 9 | .marketing, 10 | .footer { 11 | padding-left: 15px; 12 | padding-right: 15px; 13 | } 14 | 15 | /* Custom page header */ 16 | .header { 17 | border-bottom: 1px solid #e5e5e5; 18 | } 19 | /* Make the masthead heading the same height as the navigation */ 20 | .header h3 { 21 | margin-top: 0; 22 | margin-bottom: 0; 23 | line-height: 40px; 24 | padding-bottom: 19px; 25 | } 26 | 27 | /* Custom page footer */ 28 | .footer { 29 | padding-top: 19px; 30 | color: #777; 31 | border-top: 1px solid #e5e5e5; 32 | } 33 | 34 | -------------------------------------------------------------------------------- /client/app/views/main.html: -------------------------------------------------------------------------------- 1 |

    Hello 2 | {{hello}} 3 |

    4 | -------------------------------------------------------------------------------- /client/app/views/notes/edit.html: -------------------------------------------------------------------------------- 1 |

    2 | 3 | 4 | 5 | 6 | 7 | 8 |

    9 | 10 |
    11 | 12 |
    13 |
    14 | 15 |
    16 | 17 |
    18 | 19 |
    20 |
    21 | {{note.name}} 22 |
    23 |
    24 |
    {{note.content}}
    25 |
    26 |
    27 | -------------------------------------------------------------------------------- /client/app/views/notes/item.html: -------------------------------------------------------------------------------- 1 |

    2 | 3 | 4 | 5 | 6 | 7 | 8 |

    9 | 10 |
    11 |
    12 | {{note.name}} 13 |
    14 |
    15 |
    {{note.content}}
    16 |
    17 |
    18 | -------------------------------------------------------------------------------- /client/app/views/notes/list.html: -------------------------------------------------------------------------------- 1 |

    2 | Notes 3 | 4 | 5 | 6 |

    7 | 8 | 9 |
    10 |
    11 |
    12 | 20 | {{note.name}} 21 |
    22 |
    23 |
    {{note.content}}
    24 |
    25 |
    26 |
    27 | 28 |
    29 |

    30 | You have no notes, add some! 31 |

    32 |
    33 | -------------------------------------------------------------------------------- /client/app/views/notes/main.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 |
    5 |
    6 | 7 | -------------------------------------------------------------------------------- /client/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scaffold", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "1.2.6", 6 | "json3": "~3.2.6", 7 | "es5-shim": "~2.1.0", 8 | "jquery": "~1.10.2", 9 | "bootstrap": "~3.0.3", 10 | "angular-resource": "1.2.6", 11 | "angular-cookies": "1.2.6", 12 | "angular-sanitize": "1.2.6", 13 | "angular-route": "1.2.6", 14 | "restangular": "~1.3.1", 15 | "angular-ui-router": "~0.2.10" 16 | }, 17 | "devDependencies": { 18 | "angular-mocks": "1.2.6", 19 | "angular-scenario": "1.2.6" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scaffold", 3 | "version": "0.0.0", 4 | "dependencies": {}, 5 | "devDependencies": { 6 | "grunt": "~0.4.1", 7 | "grunt-autoprefixer": "~0.4.0", 8 | "grunt-bower-install": "~0.7.0", 9 | "grunt-concurrent": "~0.4.1", 10 | "grunt-contrib-clean": "~0.5.0", 11 | "grunt-contrib-coffee": "~0.7.0", 12 | "grunt-contrib-compass": "~0.6.0", 13 | "grunt-contrib-concat": "~0.3.0", 14 | "grunt-contrib-connect": "~0.5.0", 15 | "grunt-contrib-copy": "~0.4.1", 16 | "grunt-contrib-cssmin": "~0.7.0", 17 | "grunt-contrib-htmlmin": "~0.1.3", 18 | "grunt-contrib-imagemin": "~0.3.0", 19 | "grunt-contrib-jshint": "~0.7.1", 20 | "grunt-contrib-uglify": "~0.2.0", 21 | "grunt-contrib-watch": "~0.5.2", 22 | "grunt-google-cdn": "~0.2.0", 23 | "grunt-newer": "~0.5.4", 24 | "grunt-ngmin": "~0.0.2", 25 | "grunt-rev": "~0.1.0", 26 | "grunt-svgmin": "~0.2.0", 27 | "grunt-usemin": "~2.0.0", 28 | "jshint-stylish": "~0.1.3", 29 | "load-grunt-tasks": "~0.2.0", 30 | "time-grunt": "~0.2.1" 31 | }, 32 | "engines": { 33 | "node": ">=0.8.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /client/test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "after": false, 23 | "afterEach": false, 24 | "angular": false, 25 | "before": false, 26 | "beforeEach": false, 27 | "browser": false, 28 | "describe": false, 29 | "expect": false, 30 | "inject": false, 31 | "it": false, 32 | "jasmine": false, 33 | "spyOn": false 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /client/test/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | End2end Test Runner 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /client/test/spec/controllers/items.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: ItemsCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('scaffoldApp')); 7 | 8 | var ItemsCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | ItemsCtrl = $controller('ItemsCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /client/test/spec/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: MainCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('scaffoldApp')); 7 | 8 | var MainCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | MainCtrl = $controller('MainCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | --------------------------------------------------------------------------------