├── .editorconfig ├── .gitignore ├── .gitmodules ├── .jscsrc ├── .jshintrc ├── .npmignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── index.js ├── lib ├── adapter.js ├── db.js ├── errors.js ├── mediator.js ├── multiplexer.js ├── transactions.js └── util.js ├── package.json ├── scripts ├── postinstall.sh ├── prepare_testbed.sh └── test.sh └── tests ├── integration ├── app │ ├── .editorconfig │ ├── .gitignore │ ├── .sailsrc │ ├── Gruntfile.js │ ├── README.md │ ├── api │ │ ├── controllers │ │ │ ├── .gitkeep │ │ │ ├── CollectionController.js │ │ │ ├── RequestController.js │ │ │ ├── TeamController.js │ │ │ └── UserController.js │ │ ├── models │ │ │ ├── .gitkeep │ │ │ ├── Collection.js │ │ │ ├── Request.js │ │ │ ├── Team.js │ │ │ └── User.js │ │ ├── policies │ │ │ └── sessionAuth.js │ │ ├── responses │ │ │ ├── badRequest.js │ │ │ ├── forbidden.js │ │ │ ├── notFound.js │ │ │ ├── ok.js │ │ │ └── serverError.js │ │ └── services │ │ │ └── .gitkeep │ ├── app.js │ ├── assets │ │ ├── favicon.ico │ │ ├── images │ │ │ └── .gitkeep │ │ ├── js │ │ │ └── dependencies │ │ │ │ └── sails.io.js │ │ ├── robots.txt │ │ ├── styles │ │ │ └── importer.less │ │ └── templates │ │ │ └── .gitkeep │ ├── config │ │ ├── blueprints.js │ │ ├── bootstrap.js │ │ ├── connections.js │ │ ├── cors.js │ │ ├── csrf.js │ │ ├── db-setup.sql │ │ ├── env │ │ │ ├── development.js │ │ │ └── production.js │ │ ├── globals.js │ │ ├── http.js │ │ ├── i18n.js │ │ ├── local.js │ │ ├── local.js.sample │ │ ├── locales │ │ │ ├── _README.md │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ ├── es.json │ │ │ └── fr.json │ │ ├── log.js │ │ ├── models.js │ │ ├── policies.js │ │ ├── routes.js │ │ ├── session.js │ │ ├── sockets.js │ │ └── views.js │ ├── package.json │ ├── tasks │ │ ├── README.md │ │ ├── config │ │ │ ├── clean.js │ │ │ ├── coffee.js │ │ │ ├── concat.js │ │ │ ├── copy.js │ │ │ ├── cssmin.js │ │ │ ├── jst.js │ │ │ ├── less.js │ │ │ ├── runforever.js │ │ │ ├── sails-linker.js │ │ │ ├── sync.js │ │ │ ├── uglify.js │ │ │ └── watch.js │ │ ├── pipeline.js │ │ └── register │ │ │ ├── build.js │ │ │ ├── buildProd.js │ │ │ ├── compileAssets.js │ │ │ ├── default.js │ │ │ ├── linkAssets.js │ │ │ ├── linkAssetsBuild.js │ │ │ ├── linkAssetsBuildProd.js │ │ │ ├── prod.js │ │ │ └── syncAssets.js │ └── views │ │ ├── 403.ejs │ │ ├── 404.ejs │ │ ├── 500.ejs │ │ ├── homepage.ejs │ │ └── layout.ejs ├── sails-transactions-experiment.postman_environment └── sanity.json.postman_collection └── unit └── repository-spec.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*.js] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### JetBrains template 2 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 3 | 4 | *.iml 5 | 6 | ## Directory-based project format: 7 | .idea/ 8 | # if you remove the above rule, at least ignore the following: 9 | 10 | # User-specific stuff: 11 | # .idea/workspace.xml 12 | # .idea/tasks.xml 13 | # .idea/dictionaries 14 | 15 | # Sensitive or high-churn files: 16 | # .idea/dataSources.ids 17 | # .idea/dataSources.xml 18 | # .idea/sqlDataSources.xml 19 | # .idea/dynamic.xml 20 | # .idea/uiDesigner.xml 21 | 22 | # Gradle: 23 | # .idea/gradle.xml 24 | # .idea/libraries 25 | 26 | # Mongo Explorer plugin: 27 | # .idea/mongoSettings.xml 28 | 29 | ## File-based project format: 30 | *.ipr 31 | *.iws 32 | 33 | ## Plugin-specific files: 34 | 35 | # IntelliJ 36 | out/ 37 | 38 | # mpeltonen/sbt-idea plugin 39 | .idea_modules/ 40 | 41 | # JIRA plugin 42 | atlassian-ide-plugin.xml 43 | 44 | # Crashlytics plugin (for Android Studio and IntelliJ) 45 | com_crashlytics_export_strings.xml 46 | crashlytics.properties 47 | crashlytics-build.properties 48 | 49 | 50 | ### SublimeText template 51 | # cache files for sublime text 52 | *.tmlanguage.cache 53 | *.tmPreferences.cache 54 | *.stTheme.cache 55 | 56 | # workspace files are user-specific 57 | *.sublime-workspace 58 | 59 | # project files should be checked into the repository, unless a significant 60 | # proportion of contributors will probably not be using SublimeText 61 | # *.sublime-project 62 | 63 | # sftp configuration file 64 | sftp-config.json 65 | 66 | 67 | ### Windows template 68 | # Windows image file caches 69 | Thumbs.db 70 | ehthumbs.db 71 | 72 | # Folder config file 73 | Desktop.ini 74 | 75 | # Recycle Bin used on file shares 76 | $RECYCLE.BIN/ 77 | 78 | # Windows Installer files 79 | *.cab 80 | *.msi 81 | *.msm 82 | *.msp 83 | 84 | # Windows shortcuts 85 | *.lnk 86 | 87 | 88 | ### OSX template 89 | .DS_Store 90 | .AppleDouble 91 | .LSOverride 92 | 93 | # Icon must end with two \r 94 | Icon 95 | 96 | # Thumbnails 97 | ._* 98 | 99 | # Files that might appear in the root of a volume 100 | .DocumentRevisions-V100 101 | .fseventsd 102 | .Spotlight-V100 103 | .TemporaryItems 104 | .Trashes 105 | .VolumeIcon.icns 106 | 107 | # Directories potentially created on remote AFP share 108 | .AppleDB 109 | .AppleDesktop 110 | Network Trash Folder 111 | Temporary Items 112 | .apdisk 113 | 114 | 115 | ### Node template 116 | # Logs 117 | logs 118 | *.log 119 | *.log.* 120 | 121 | # Runtime data 122 | pids 123 | *.pid 124 | *.seed 125 | 126 | # Directory for instrumented libs generated by jscoverage/JSCover 127 | lib-cov 128 | 129 | # Coverage directory used by tools like istanbul 130 | coverage 131 | 132 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 133 | .grunt 134 | 135 | # node-waf configuration 136 | .lock-wscript 137 | 138 | # Compiled binary addons (http://nodejs.org/api/addons.html) 139 | build/Release 140 | 141 | # Dependency directory 142 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 143 | node_modules 144 | 145 | ### Linux template 146 | *~ 147 | 148 | # KDE directory preferences 149 | .directory 150 | 151 | # Linux trash folder which might appear on any partition or disk 152 | .Trash-* 153 | 154 | 155 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "waterline"] 2 | path = waterline 3 | url = https://shamasis@github.com/postmanlabs/sails-mysql-transactions-waterline.git 4 | branch = master-postman 5 | [submodule "sails-mysql"] 6 | path = sails-mysql 7 | url = https://github.com/postmanlabs/sails-mysql.git 8 | branch = master-postman 9 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | // "disallowAnonymousFunctions": false, 3 | // "disallowCapitalizedComments": false, 4 | // "disallowCommaBeforeLineBreak": false, 5 | // "disallowDanglingUnderscores": true, 6 | "disallowEmptyBlocks": true, 7 | "disallowFunctionDeclarations": true, 8 | // "disallowImplicitTypeConversion": [], 9 | // "disallowKeywordsOnNewLine": [], 10 | "disallowKeywords": ["with"], 11 | "disallowMixedSpacesAndTabs": true, 12 | "disallowMultipleLineBreaks": true, 13 | "disallowMultipleLineStrings": true, 14 | // "disallowMultipleVarDecl": false, 15 | "disallowNewlineBeforeBlockStatements": true, 16 | // "disallowOperatorBeforeLineBreak": false, 17 | // "disallowPaddingNewlinesBeforeKeywords": false, 18 | "disallowPaddingNewlinesInBlocks": true, 19 | // "disallowPaddingNewLinesInObjects": false, 20 | // "disallowQuotedKeysInObjects": false, 21 | // "disallowSemicolons": false, 22 | // "disallowSpaceAfterBinaryOperators": false, 23 | // "disallowSpaceAfterKeywords": false, 24 | // "disallowSpaceAfterLineComment": false, 25 | // "disallowSpaceAfterObjectKeys": false, 26 | "disallowSpaceAfterPrefixUnaryOperators": true, 27 | // "disallowSpaceBeforeBinaryOperators": false, 28 | // "disallowSpaceBeforeBlockStatements": false, 29 | // "disallowSpaceBeforeKeywords": false, 30 | // "disallowSpaceBeforeObjectValues": false, 31 | "disallowSpaceBeforePostfixUnaryOperators": true, 32 | // "disallowSpaceBetweenArguments": false, 33 | // "disallowSpacesInAnonymousFunctionExpression": { 34 | // "beforeOpeningRoundBrace": false, 35 | // "beforeOpeningCurlyBrace": false 36 | // }, 37 | "disallowSpacesInCallExpression": true, 38 | // "disallowSpacesInConditionalExpression": { 39 | // "afterTest": false, 40 | // "beforeConsequent": false, 41 | // "afterConsequent": false, 42 | // "beforeAlternate": false 43 | // }, 44 | // "disallowSpacesInForStatement": false, 45 | // "disallowSpacesInFunctionDeclaration": { 46 | // "beforeOpeningRoundBrace": false, 47 | // "beforeOpeningCurlyBrace": false 48 | // }, 49 | // "disallowSpacesInFunctionExpression": { 50 | // "beforeOpeningRoundBrace": false, 51 | // "beforeOpeningCurlyBrace": false 52 | // }, 53 | // "disallowSpacesInFunctionExpression": { 54 | // "beforeOpeningRoundBrace": true, 55 | // "beforeOpeningCurlyBrace": true 56 | // }, 57 | // "disallowSpacesInNamedFunctionExpression": { 58 | // "beforeOpeningRoundBrace": false, 59 | // "beforeOpeningCurlyBrace": false 60 | // }, 61 | "disallowSpacesInsideArrayBrackets": "all", 62 | "disallowSpacesInsideObjectBrackets": "all", 63 | "disallowSpacesInsideParentheses": { "only": [ "{", "}" ] }, 64 | "disallowTrailingComma": true, 65 | "disallowTrailingWhitespace": true, 66 | // "disallowYodaConditions": false, 67 | "maximumLineLength": { 68 | "value": 120, 69 | "allowComments": false, 70 | "allowRegex": true 71 | }, 72 | 73 | // "requireAlignedObjectValues": false, 74 | // "requireAnonymousFunctions": false, 75 | "requireBlocksOnNewline": 2, 76 | "requireCamelCaseOrUpperCaseIdentifiers": true, 77 | // "requireCapitalizedComments": true, 78 | "requireCapitalizedConstructors": true, 79 | // "requireCommaBeforeLineBreak": false, 80 | "requireCurlyBraces": true, 81 | "requireDotNotation": "except_snake_case", 82 | // "requireFunctionDeclarations": false, 83 | "requireKeywordsOnNewLine": ["else", "catch", "case", "default", "finally"], 84 | "requireLineBreakAfterVariableAssignment": true, 85 | "requireLineFeedAtFileEnd": true, 86 | "requireMultipleVarDecl": "onevar", 87 | // "requireNewlineBeforeBlockStatements": false, 88 | "requireOperatorBeforeLineBreak": true, 89 | // "requirePaddingNewlinesBeforeKeywords": [], 90 | // "requirePaddingNewlinesInBlocks": false, 91 | // "requirePaddingNewlinesInBlocks": false, 92 | "requireParenthesesAroundIIFE": true, 93 | // "requireQuotedKeysInObjects": false, 94 | "requireSpaceAfterBinaryOperators": true, 95 | "requireSpaceAfterKeywords": true, 96 | "requireSpaceAfterLineComment": { 97 | "allExcept": ["!", "#", "%", "^", "*", "=", "?"] 98 | }, 99 | // "requireSpaceAfterObjectKeys": false, 100 | // "requireSpaceAfterPrefixUnaryOperators": false, 101 | "requireSpaceBeforeBinaryOperators": true, 102 | "requireSpaceBeforeBlockStatements": true, 103 | // "requireSpaceBeforeKeywords": false, 104 | "requireSpaceBeforeObjectValues": true, 105 | // "requireSpaceBeforePostfixUnaryOperators": false, 106 | "requireSpaceBetweenArguments": true, 107 | "requireSpacesInAnonymousFunctionExpression": { 108 | "beforeOpeningRoundBrace": true, 109 | "beforeOpeningCurlyBrace": true 110 | }, 111 | //"requireSpacesInCallExpression": false, 112 | "requireSpacesInConditionalExpression": { 113 | "afterTest": true, 114 | "beforeConsequent": true, 115 | "afterConsequent": true, 116 | "beforeAlternate": true 117 | }, 118 | "requireSpacesInForStatement": true, 119 | "requireSpacesInFunctionDeclaration": { 120 | "beforeOpeningRoundBrace": true, 121 | "beforeOpeningCurlyBrace": true 122 | }, 123 | "requireSpacesInFunctionExpression": { 124 | "beforeOpeningRoundBrace": true, 125 | "beforeOpeningCurlyBrace": true 126 | }, 127 | // "requireSpacesInNamedFunctionExpression": { 128 | // "beforeOpeningRoundBrace": true, 129 | // "beforeOpeningCurlyBrace": true 130 | // }, 131 | // "requireSpacesInsideArrayBrackets": false, 132 | "requireSpacesInsideObjectBrackets": "allButNested", 133 | // "requireSpacesInsideParentheses": false, 134 | // "requireTrailingComma": false, 135 | // "requireYodaConditions": false, 136 | // "safeContextKeyword": [ ], 137 | "validateIndentation": 4, 138 | "validateLineBreaks": "LF", 139 | "validateParameterSeparator": ", ", 140 | "validateQuoteMarks": "'" 141 | } 142 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": false, 3 | "camelcase": true, 4 | "curly": true, 5 | "eqeqeq": false, 6 | "es3": false, 7 | "forin": false, 8 | "freeze": true, 9 | "immed": true, 10 | "indent": 4, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "noempty": true, 15 | "nonew": false, 16 | "plusplus": false, 17 | "quotmark": "single", 18 | "undef": true, 19 | "unused": true, 20 | "trailing": true, 21 | "asi": false, 22 | "boss": true, 23 | "debug": false, 24 | "eqnull": true, 25 | "evil": false, 26 | "expr": true, 27 | "funcscope": false, 28 | "globalstrict": false, 29 | "iterator": false, 30 | "lastsemic": false, 31 | "laxbreak": false, 32 | "laxcomma": false, 33 | "loopfunc": false, 34 | "maxlen": 120, 35 | "multistr": false, 36 | "notypeof": false, 37 | "proto": false, 38 | "scripturl": false, 39 | "smarttabs": false, 40 | "shadow": false, 41 | "sub": false, 42 | "supernew": false, 43 | 44 | "browser": false, 45 | "devel": false, 46 | "node": true, 47 | 48 | "onevar": true, 49 | 50 | "globals": { } 51 | } 52 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | ### NPM Specific: Disregard recursive project files 2 | ### =============================================== 3 | /.jscsrc 4 | /.editorconfig 5 | /.gitmodules 6 | /.jshintrc 7 | /tests 8 | 9 | ### Borrowed from .gitignore 10 | ### ======================== 11 | 12 | ### JetBrains template 13 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 14 | 15 | *.iml 16 | 17 | ## Directory-based project format: 18 | .idea/ 19 | # if you remove the above rule, at least ignore the following: 20 | 21 | # User-specific stuff: 22 | # .idea/workspace.xml 23 | # .idea/tasks.xml 24 | # .idea/dictionaries 25 | 26 | # Sensitive or high-churn files: 27 | # .idea/dataSources.ids 28 | # .idea/dataSources.xml 29 | # .idea/sqlDataSources.xml 30 | # .idea/dynamic.xml 31 | # .idea/uiDesigner.xml 32 | 33 | # Gradle: 34 | # .idea/gradle.xml 35 | # .idea/libraries 36 | 37 | # Mongo Explorer plugin: 38 | # .idea/mongoSettings.xml 39 | 40 | ## File-based project format: 41 | *.ipr 42 | *.iws 43 | 44 | ## Plugin-specific files: 45 | 46 | # IntelliJ 47 | out/ 48 | 49 | # mpeltonen/sbt-idea plugin 50 | .idea_modules/ 51 | 52 | # JIRA plugin 53 | atlassian-ide-plugin.xml 54 | 55 | # Crashlytics plugin (for Android Studio and IntelliJ) 56 | com_crashlytics_export_strings.xml 57 | crashlytics.properties 58 | crashlytics-build.properties 59 | 60 | 61 | ### SublimeText template 62 | # cache files for sublime text 63 | *.tmlanguage.cache 64 | *.tmPreferences.cache 65 | *.stTheme.cache 66 | 67 | # workspace files are user-specific 68 | *.sublime-workspace 69 | 70 | # project files should be checked into the repository, unless a significant 71 | # proportion of contributors will probably not be using SublimeText 72 | # *.sublime-project 73 | 74 | # sftp configuration file 75 | sftp-config.json 76 | 77 | 78 | ### Windows template 79 | # Windows image file caches 80 | Thumbs.db 81 | ehthumbs.db 82 | 83 | # Folder config file 84 | Desktop.ini 85 | 86 | # Recycle Bin used on file shares 87 | $RECYCLE.BIN/ 88 | 89 | # Windows Installer files 90 | *.cab 91 | *.msi 92 | *.msm 93 | *.msp 94 | 95 | # Windows shortcuts 96 | *.lnk 97 | 98 | 99 | ### OSX template 100 | .DS_Store 101 | .AppleDouble 102 | .LSOverride 103 | 104 | # Icon must end with two \r 105 | Icon 106 | 107 | # Thumbnails 108 | ._* 109 | 110 | # Files that might appear in the root of a volume 111 | .DocumentRevisions-V100 112 | .fseventsd 113 | .Spotlight-V100 114 | .TemporaryItems 115 | .Trashes 116 | .VolumeIcon.icns 117 | 118 | # Directories potentially created on remote AFP share 119 | .AppleDB 120 | .AppleDesktop 121 | Network Trash Folder 122 | Temporary Items 123 | .apdisk 124 | 125 | 126 | ### Node template 127 | # Logs 128 | logs 129 | *.log 130 | *.log.* 131 | 132 | # Runtime data 133 | pids 134 | *.pid 135 | *.seed 136 | 137 | # Directory for instrumented libs generated by jscoverage/JSCover 138 | lib-cov 139 | 140 | # Coverage directory used by tools like istanbul 141 | coverage 142 | 143 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 144 | .grunt 145 | 146 | # node-waf configuration 147 | .lock-wscript 148 | 149 | # Compiled binary addons (http://nodejs.org/api/addons.html) 150 | build/Release 151 | 152 | # Dependency directory 153 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 154 | node_modules 155 | 156 | ### Linux template 157 | *~ 158 | 159 | # KDE directory preferences 160 | .directory 161 | 162 | # Linux trash folder which might appear on any partition or disk 163 | .Trash-* 164 | 165 | 166 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - '4' 5 | - '6' 6 | 7 | before_script: 8 | - mysql -e 'create database `sails_transactions`;' 9 | 10 | deploy: 11 | provider: npm 12 | email: help@getpostman.com 13 | api_key: 14 | secure: ZfZzgWuybdOZTtipwwWwr62nQIGo/ZqHDXKuEiNI0Y+WhQ5514uaxhhCYHs1kNtdLTUnUYHY3hmHaugzVI/1+Ep2Gep3EBkWrGa2pcVX5yRZ8HIavY3z+QlDKpM1KKgdGf9uBZyzWghAXSw9brX4ZTcdH9EJA6RQ5bvzCtxvJIk= 15 | on: 16 | repo: postmanlabs/sails-mysql-transactions 17 | branch: master 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Sails MySQL Transactional ORM with replication support 4 | 5 | `sails-mysql-transaction` is a Sails ORM Adapter for MySQL with transaction and replication cluster support. 6 | 7 | This adapter essentially __wraps__ around the popular `sails-mysql` adapter and provides additional API to perform 8 | operations that ties around a database transaction. It also provides to read from a cluster of read-replicas in a 9 | load-balanced fashion. 10 | 11 | ## Installation 12 | 13 | 1. Add `sails-mysql-transactions` to your application’s `package.json`. Do not run install directly if `sails` is not 14 | already installed in your package. 15 | 16 | 17 | 2. If you already have `sails-mysql` installed, it might interfere with operations of this module. Remove it from your 18 | `package.json` and uninstall the same using `npm remove sails-mysql`. 19 | 20 | 3. This package installs successfully only when sails is already installed in the package. If the package is already 21 | installed, then simply run `npm install sails-mysql-transactions --save`, otherwise run `npm install` and it will take 22 | care of rest. 23 | 24 | ## Safe install using postinstall script 25 | 26 | If `npm install` seems erratic to install dependencies in order, you could add the following in your `package.json` as 27 | a [postinstall script of npm](https://docs.npmjs.com/misc/scripts). This would ensure that this module is installed after 28 | sails has been completely installed. Note that in this method, you would not need to add `sails-mysql-transactions` as a 29 | dependency in your package.json 30 | 31 | ``` 32 | { 33 | "scripts": { 34 | "postinstall": "npm install sails-mysql-transactions" 35 | } 36 | } 37 | ``` 38 | 39 | ### Installation Notes: 40 | 41 | This package overwrites the `waterline` module inside Sails with a fork of Waterline maintained by Postman. As such, 42 | if you ever re-install or update sails, ensure you re-install this adapter right after it. 43 | 44 | Do check SailsJS compatibility list before upgrading your Sails version while already using this adapter. 45 | 46 | ## Quick Start 47 | 48 | The integration test Sails App located in `tests/integration/app` directory of this repository has a fully functional 49 | installation. Simply run `npm install` within `test/integration/app` directory. 50 | 51 | ### Sails config/local.js 52 | 53 | ```js 54 | module.exports = { 55 | /* your other config stay as is */ 56 | 57 | connections: { 58 | mySQLT: { 59 | adapter: 'sails-mysql-transactions', 60 | host: '{{your-db-host}}', 61 | user: '{{your-db-username}}', 62 | password: '{{your-db-password}}', 63 | database: '{{your-db-tablename}}', 64 | 65 | transactionConnectionLimit: 10, 66 | rollbackTransactionOnError: true, 67 | queryCaseSensitive: false, 68 | 69 | /* this section is needed only if replication feature is required */ 70 | replication: { 71 | enabled: true, 72 | inheritMaster: true, 73 | canRetry: true, 74 | removeNodeErrorCount: 5, 75 | restoreNodeTimeout: 1000 * 60 * 5, 76 | defaultSelector: 'RR', // 'RANDOM' or 'ORDER' 77 | sources: { 78 | readonly: { 79 | enabled: true, 80 | host: '{{replica-1-host}}', 81 | user: '{{replica-1-user}}', 82 | password: '{{replica-1-password}}' 83 | } 84 | } 85 | } 86 | } 87 | }, 88 | 89 | models: { 90 | connection: 'mySQLT' 91 | } 92 | } 93 | ``` 94 | 95 | ### Use Transaction in your controllers 96 | 97 | ```javascript 98 | var Transaction = require('sails-mysql-transactions').Transaction; 99 | 100 | module.exports = { 101 | create: function (req, res) { 102 | // start a new transaction 103 | Transaction.start(function (err, transaction) { 104 | if (err) { 105 | // the first error might even fail to return a transaction object, so double-check. 106 | transaction && transaction.rollback(); 107 | return res.serverError(err); 108 | } 109 | 110 | OneModel.transact(transaction).create(req.params.all(), function (err, modelInstance) { 111 | if (err) { 112 | transaction.rollback(); 113 | return res.serverError(err); 114 | } 115 | 116 | // using transaction to update another model and using the promises architecture 117 | AnotherModel.transact(transaction).findOne(req.param('id')).exec(function (err, anotherInstance) { 118 | if (err) { 119 | transaction.rollback(); 120 | return res.serverError(err); 121 | } 122 | 123 | // using update and association changes 124 | modelInstance.someAssociatedModel.remove(req.param('remove_id')); 125 | 126 | // standard .save() works when in transaction 127 | modelInstance.save(function (err, savedModel) { 128 | if (err) { 129 | transaction.rollback(); 130 | return res.serverError(err); 131 | } 132 | 133 | // finally commit the transaction before sending response 134 | transaction.commit(); 135 | return res.json({ 136 | one: savedModel, 137 | another: anotherInstance 138 | }); 139 | }); 140 | }); 141 | }); 142 | }); 143 | } 144 | }; 145 | ``` 146 | 147 | #### List of available transactional operations: 148 | 149 | ```javascript 150 | route = function (req, res) { 151 | Transaction.start(function (err, transaction) { 152 | OneModel.transact(transaction).create(/* ... */); 153 | OneModel.transact(transaction).update(/* ... */); 154 | OneModel.transact(transaction).find(/* ... */); 155 | OneModel.transact(transaction).findOrCreate(/* ... */); 156 | OneModel.transact(transaction).findOne(/* ... */); 157 | OneModel.transact(transaction).destroy(/* ... */); 158 | OneModel.transact(transaction).count(/* ... */); 159 | }); 160 | }; 161 | ``` 162 | 163 | Other than those, `update`, `save` and association operations on instance methods work within transaction provided they 164 | were either stemmed from the same transaction or wrapped (`transaction.wrap(instance)`) by a transaction. 165 | 166 | 167 | ### Exceptions where transactions may fail 168 | 169 | In cases where you are performing model instance opertaions such as `save`, `destroy`, etc on instances that has been 170 | stemmed from a `.populate`, transaction might fail. In such scenarios, performing a `transaction.wrap(instance);` before 171 | doing instance operations should fix such errors. 172 | 173 | If you want to selectively intercept errors from this module, compare using `instanceof Transaction.AdapterError`. 174 | 175 | Note that this adapter adds an additional auto column called `transactionId`. If you do not want to use transaction on 176 | a particular model, you can turn off creation of this column by setting `autoTK: false` in your model. 177 | 178 | 179 | ## Support for Read Replicas 180 | 181 | When one or more read replica sources are provded, the following API can be used to access data from one of the defined 182 | replication source databases. This distributes your database workloads across multiple systems. 183 | 184 | Readonly still works without read replica using the normal non-transactional connection set. 185 | 186 | ```javascript 187 | action = function (req, res) { 188 | OneModel.readonly().find(); 189 | OneModel.readonly().findOne(); 190 | OneModel.readonly().count(); 191 | }; 192 | ``` 193 | 194 | ## Support to retrieve changesets during update operations 195 | 196 | Since `sails-mysql` makes a `SELECT` query before every update; it makes sense that the query results can be utilised to 197 | return the changeset when a model is updated. The third parameter of `.update` returns an array having objects that 198 | contain only the fields that have changed and that too with their original values. 199 | 200 | ## Additional Configurations and Features 201 | 202 | 1. `queryCaseSensitive` when set to true, disables the feature where waterline performs case insensitive queries. (Note 203 | that it ises `wlNext` options for waterline-sequel.) 204 | 205 | 2. The bundled waterline adds additional feature to do the following 206 | - `Model.().populateSome(Object);` allows you to populate multiple 207 | associations in one call. It also accepts array of associations as argument 208 | - `.populate` on Models accepts `select: []` as part of criteria parameter. 209 | - Model deletion does not fetch full model data during deletion. 210 | 211 | 3. An additional asynchronous function `fromObject()` which creates a model instance based on the model attributes. 212 | - This function accepts the attributes object and the callback function as the parameter. 213 | - The callback function will receive the error object and the Model Instance object 214 | 215 | ```javascript 216 | OneModel.fromObject(attributesObject, function (err, instance) { 217 | if (err) { return Error; } 218 | 219 | // instance is the required object 220 | }); 221 | ``` 222 | 223 | ## Contributing 224 | 225 | Contribution is accepted in form of Pull Requests that passes Travis CI tests. You should install this repository using 226 | `npm install -d` and run `npm test` locally before sending Pull Request. -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/adapter'); -------------------------------------------------------------------------------- /lib/adapter.js: -------------------------------------------------------------------------------- 1 | var util = require('./util'), 2 | AdapterError = require('./errors'), 3 | _ = require('lodash'), 4 | arrProtoSlice = Array.prototype.slice, 5 | Transaction = require('./transactions'), 6 | Mediator = require('./mediator'), 7 | Multiplexer = require('./multiplexer'), 8 | 9 | sailsmysql = require('../sails-mysql/lib/adapter.js'), 10 | adapter = util.clone(sailsmysql); 11 | 12 | /** 13 | * The adapter override matrix. This object defines how to handle each individual properties of the mySql adapter. 14 | * @type {object} 15 | * 16 | * @note 17 | * Object structure within each override are as follows 18 | * ``` 19 | * 'overrideFunctionName': { 20 | * conn: {number} - the argument index where the adapter expects connection parameter 21 | * criteria: {number=} - the argument index of a criteria object in the original adapter 22 | * probe: {number=} - in case transaction is tied up with values, which argument to probe 23 | * } 24 | * ``` 25 | * 26 | * @note - untapped original adapter functions 27 | * - addAttributes: conn=5 28 | * - describe: conn=3 29 | * - define: conn=4 30 | * - drop: conn=4 31 | */ 32 | util.each({ 33 | create: { 34 | probe: 2, 35 | criteria: 2, // @todo - analyze. criteria works for add member and probe works for create user 36 | conn: 4 37 | }, 38 | count: { 39 | criteria: 2, 40 | conn: 4 41 | }, 42 | createEach: { // unused 43 | criteria: 2, 44 | conn: 4 45 | }, 46 | find: { 47 | criteria: 2, 48 | conn: 4 49 | }, 50 | join: { 51 | criteria: 2, 52 | conn: 4 53 | }, 54 | query: { // unused 55 | criteria: 2, 56 | conn: 5 57 | }, 58 | stream: { // unused 59 | criteria: 2, 60 | conn: 4 61 | }, 62 | update: { 63 | conn: 5, 64 | probe: 3 65 | }, 66 | destroy: { 67 | criteria: 2, 68 | conn: 4 69 | } 70 | }, function (override, prop) { 71 | var _super = adapter[prop]; 72 | 73 | // function that sniffs transactions and puts it in place of connection obj 74 | adapter[prop] = function (connectionName, collectionName) { 75 | var args = arrProtoSlice.call(arguments), 76 | id; 77 | 78 | // if connection is already present in arguments, we do not need to extract anything. 79 | if (!_.isUndefined(args[override.conn])) { 80 | if (_.isString(args[override.conn])) { 81 | id = args[override.conn]; 82 | id = Transaction.retrieveConnection(id) || Multiplexer.retrieveConnection(id); 83 | id && (args[override.conn] = id); 84 | } 85 | return _super.apply(this, args); 86 | } 87 | 88 | if (override.hasOwnProperty('probe')) { 89 | id = util.unwrapquery(args[override.probe]); 90 | // @todo - temporarily remove transactionId from probe having criteria field of joinTables 91 | if (id && override.hasOwnProperty('criteria') && /.+__.+/.test(collectionName)) { 92 | delete args[override.probe].transactionId; 93 | } 94 | } 95 | else if (override.hasOwnProperty('criteria')) { 96 | // @todo: combine connections bucket for a single pass 97 | id = util.unwrapquery(args[override.criteria], Transaction.connections) || 98 | util.unwrapquery(args[override.criteria], Multiplexer.connections); 99 | } 100 | else { 101 | // if no extraction method is provided, use arg beyond last one. 102 | id = util.unwrapquery(args[override.conn + 1]); 103 | } 104 | 105 | // assuming that we have retrieved the id, we pass it on to Transaction system to get the original db 106 | // connection associated with this adapter request. 107 | if (Transaction.retrieveConnection(id)) { 108 | args[override.conn] = Transaction.retrieveConnection(id); 109 | } 110 | else if (Multiplexer.retrieveConnection(id)) { 111 | args[override.conn] = Multiplexer.retrieveConnection(id); 112 | } 113 | 114 | return _super.apply(this, args); 115 | }; 116 | }); 117 | 118 | util.extend(adapter, { 119 | identity: 'sails-mysql-transactions', 120 | AdapterError: AdapterError, 121 | Transaction: Transaction, 122 | 123 | defaults: util.extend(util.clone(adapter), { 124 | pool: true, 125 | waitForConnections: true, 126 | rollbackTransactionOnError: true, 127 | transactionConnectionLimit: 5, 128 | 129 | queryCaseSensitive: false, 130 | 131 | replication: { 132 | enabled: false, 133 | inheritMaster: true, 134 | canRetry: true, 135 | removeNodeErrorCount: 5, 136 | restoreNodeTimeout: (1000 * 60 * 5), 137 | defaultSelector: 'RR', 138 | sources: {} 139 | } 140 | }), 141 | 142 | /** 143 | * Register this adapter when Sails boots up. This additionally initialises the transactional db connection source 144 | * and then goes on to execute the original adapter registration. 145 | * @private 146 | * 147 | * @param config 148 | * @param collections 149 | * @param cb 150 | * @returns {*} 151 | */ 152 | registerConnection: function (config) { 153 | // setup the transactions class with adapter config 154 | Transaction.setup(config); 155 | Multiplexer.setup(config); 156 | 157 | // Call the core registration function. 158 | return sailsmysql.registerConnection.apply(this, arguments); 159 | }, 160 | 161 | /** 162 | * The teardown of this adapter executes teardown of transactions and then goes on to perform whatever original 163 | * adapter teardown was supposed to do. 164 | * @private 165 | */ 166 | teardown: function () { 167 | Transaction.teardown(); 168 | Multiplexer.teardown(); 169 | 170 | return sailsmysql.teardown.apply(this, arguments); 171 | }, 172 | 173 | /** 174 | * This allows one to exercise transaction scoped Model API. 175 | * 176 | * @param {string} connectionName 177 | * @param {string} collectionName 178 | * @param {Transaction} transaction 179 | * @returns {Transaction.Mediator} 180 | */ 181 | transact: function (connectionName, collectionName, transaction) { 182 | return (new Mediator(this, connectionName, transaction)); 183 | }, 184 | 185 | /** 186 | * This allows one to exercise connection scoped Model API. 187 | * 188 | * @returns {Transaction.Multiplexer} 189 | */ 190 | readonly: function (connectionName) { 191 | return (new Multiplexer(this, connectionName)); 192 | }, 193 | 194 | /** 195 | * This allows one to create a model instance. 196 | * 197 | * @param {string} connectionName 198 | * @param {string} collectionName 199 | * @param {Object} obj 200 | * @param {Function} cb 201 | * @returns {Object} Model Instance 202 | */ 203 | fromObject: function (connectionName, collectionName, obj, cb) { 204 | if (_.isObject(obj)) { 205 | try { 206 | var modelInstance = new this._model(obj); 207 | return cb(null, modelInstance); 208 | } 209 | catch (err) { 210 | return cb(err, null); 211 | } 212 | } 213 | } 214 | }); 215 | 216 | module.exports = adapter; 217 | -------------------------------------------------------------------------------- /lib/db.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This module interfaces with mysql and exposes database connectivity for rest of transaction module. This module 3 | * also strives to unify the API that is needed to access pooled vs unpooled db connections. 4 | * 5 | * @module db 6 | */ 7 | var mysql = require('mysql'), 8 | AdapterError = require('./errors'), 9 | db; 10 | 11 | db = { 12 | /** 13 | * this source is designed to not return any connection and instead 14 | * pass error to callback. 15 | * 16 | * @type {object} 17 | */ 18 | oneDumbSource: function () { 19 | return { 20 | getConnection: function (callback) { 21 | callback && callback(new AdapterError(AdapterError.NO_SOURCE)); 22 | }, 23 | end: function () {} 24 | }; 25 | }, 26 | 27 | /** 28 | * Create a cluster of pools 29 | * 30 | * @param {object} config 31 | * @return {mySQL.PoolCluster} 32 | */ 33 | createCluster: function (config) { 34 | var peerNames = Object.keys(config.sources || (config.sources = {})), 35 | poolCluster; 36 | 37 | // return undefined if there is no peer config 38 | if (peerNames.length > 0) { 39 | poolCluster = mysql.createPoolCluster(config); 40 | 41 | peerNames.forEach(function (peerName) { 42 | var peerConfig = config.sources[peerName]; 43 | 44 | // do not add this peer if it has enabled: false marked in config 45 | if (!peerConfig || peerConfig.enabled === false) { 46 | return; 47 | } 48 | 49 | // add this peer to cluster 50 | poolCluster.add(peerName, peerConfig); 51 | }); 52 | } 53 | 54 | return poolCluster || db.oneDumbSource; 55 | }, 56 | /** 57 | * Create db connection source based on configuration parameter provided. 58 | * 59 | * @param {object} config mySQL config 60 | * @returns {mySQL.Source} 61 | */ 62 | createSource: function (config) { 63 | // If the config says pooling is enabled, we simply create a pool and return 64 | if (config.pool) { 65 | return mysql.createPool(config); 66 | } 67 | 68 | // otherwise, we create an object that mimics the api of pool, but returns new connections instead of 69 | // from a pool 70 | return { 71 | getConnection: function (callback) { 72 | var conn, 73 | error; 74 | 75 | try { 76 | conn = mysql.createConnection(config); 77 | // override the `release` function to allow release to act as `end` and as such mimic the pool api. 78 | conn._release = conn.release; 79 | conn.release = conn.end; 80 | } 81 | catch (err) { 82 | error = err; 83 | } 84 | 85 | callback(error, conn); 86 | }, 87 | 88 | // poolless connection source does not require to end, but we still expose the API for parity. 89 | end: function () { } 90 | }; 91 | } 92 | }; 93 | 94 | module.exports = db; 95 | -------------------------------------------------------------------------------- /lib/errors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains all error messages intended to be used by sails-mysql-transactions 3 | * @module transaction-errors 4 | */ 5 | var util = require('./util'), 6 | AdapterError; // constructor 7 | /** 8 | * @constructor 9 | * @inherits Error 10 | */ 11 | AdapterError = function (message) { 12 | if (!(this instanceof AdapterError)) { 13 | return new AdapterError(message); 14 | } 15 | Error.captureStackTrace(this, this.constructor); 16 | arguments.length && (this.message = message); 17 | this.name = 'AdapterError'; 18 | }; 19 | 20 | AdapterError.prototype = new Error(); 21 | AdapterError.prototype.constructor = AdapterError; 22 | 23 | util.extend(AdapterError, { 24 | MEDIATOR_INVALID_MODEL: 'Invalid model forwarded to transaction.', 25 | MEDIATOR_INVALID_TXN: 'Invalid transaction parameters.', 26 | 27 | TRANSACTION_NOT_SETUP: 'Transaction.setup() has not been called. Missing ORM registration?', 28 | TRANSACTION_CONNECTION_OVERLAP: 'Multiple connections got associated with a single transaction. Nasty!', 29 | TRANSACTION_UNINITIATED: 'Transaction was used without doing Transaction.start();', 30 | TRANSACTION_UNINITIATED_COMM: 'Transaction commit failed since transaction has either expired or not started', 31 | TRANSACTION_UNINITIATED_ROLL: 'Transaction rollback failed since transaction has either expired or not started', 32 | TRANSACTION_MULTI_DB_CONNECTION_ATTEMPTED: 'Transaction attempted for multiple databases', 33 | TRANSACTION_NOT_IDENTIFIED: 'Unable to locate the connection for which to transact', 34 | TRANSACTION_CONFIG_NOT_FOUND: 'Database has no transaction configuration', 35 | TRANSACTION_ID_NOT_FOUND: 'No transaction id was associated', 36 | TRANSACTION_NOT_ASSOCIATED: 'No associated transaction found with the connection', 37 | TRANSACTION_ID_INTEGRITY_FAILURE: 'Transaction id integrity mismatch (report issue.)', 38 | 39 | NO_SOURCE: 'Connection source not found', 40 | UNKNOWN_CONNECTION: 'The connection was not found during multiplexing' 41 | }); 42 | 43 | module.exports = AdapterError; 44 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'), 2 | 3 | nonObjectCriteriaTypes = { // object to validate types of wrapper fn 4 | string: true, 5 | number: true, 6 | boolean: true 7 | }, 8 | 9 | E = '', 10 | H = '-', 11 | rnd = Math.random, 12 | 13 | util; // exports 14 | 15 | util = { 16 | /** 17 | * Deep clone an object. 18 | * 19 | * @param {object} obj 20 | * @returns {object} 21 | */ 22 | clone: function (obj) { 23 | return _.cloneDeep(obj); 24 | }, 25 | 26 | /** 27 | * Performs shallow copy of one object into another. 28 | * 29 | * @param {object} recipient 30 | * @param {object} donor 31 | * @returns {object} - returns the seeded recipient parameter 32 | */ 33 | extend: function (recipient, donor) { 34 | for (var prop in donor) { 35 | donor.hasOwnProperty(prop) && (recipient[prop] = donor[prop]); 36 | } 37 | return recipient; 38 | }, 39 | 40 | /** 41 | * Shallow copies all non-existing properties from donor to recipient 42 | * 43 | * @param {object} recipient 44 | * @param {object} donor 45 | * @returns {object} - returns the seeded recipient parameter 46 | */ 47 | fill: function (recipient, donor) { 48 | for (var prop in donor) { 49 | !recipient.hasOwnProperty(prop) && (recipient[prop] = donor[prop]); 50 | } 51 | return recipient; 52 | }, 53 | 54 | /** 55 | * Useful function to iterate on array or object with almost same API. 56 | * 57 | * @param {object|array} obj 58 | * @param {function} iter 59 | * @param {object|function} [scope] 60 | * @returns {object|array} - the original object passed is returned for chaining. 61 | */ 62 | each: function (obj, iter, scope) { 63 | var i, 64 | ii; 65 | 66 | !scope && (scope === obj); 67 | 68 | // proceed only when object passed is truly an object (array too is an object, in case it wasn't clear.) 69 | if (typeof obj !== 'object') { 70 | return obj; 71 | } 72 | 73 | if (_.isArray(obj) || _.isArguments(obj)) { 74 | for (i = 0, ii = obj.length; i < ii; i++) { 75 | if (iter.call(scope, obj[i], i, obj) === false) { 76 | return obj; 77 | } 78 | } 79 | } 80 | else { 81 | for (i in obj) { 82 | if (obj.hasOwnProperty(i)) { 83 | if (iter.call(scope, obj[i], i, obj) === false) { 84 | return obj; 85 | } 86 | } 87 | } 88 | } 89 | return obj; 90 | }, 91 | 92 | /** 93 | * Returns unique GUID on every call as per pseudo-number RFC4122 standards. 94 | * 95 | * @type {function} 96 | * @returns {string} 97 | */ 98 | uid: function () { 99 | var n, r; // r = result , n = numeric variable for positional checks 100 | 101 | // if "n" is not 9 or 14 or 19 or 24 return a random number or 4 102 | // if "n" is not 15 generate a random number from 0 to 15 103 | // `(n ^ 20 ? 16 : 4)` := unless "n" is 20, in which case a random number from 8 to 11 otherwise 4 104 | // 105 | // in other cases (if "n" is 9,14,19,24) insert "-" 106 | /* jshint noempty: false */// jscs:disable 107 | for (r = n = E; n++ < 36; r += n * 51 & 52 ? (n ^ 15 ? 8 ^ rnd() * (n ^ 20 ? 16 : 4) : 4).toString(16) : H) { } 108 | // jscs:enable 109 | return r; 110 | }, 111 | 112 | /** 113 | * Use this function on the new instance of the transaction to mark a query as part of this transaction 114 | * 115 | * @param {*} query 116 | * @param {string} cid 117 | * @returns {*} 118 | * 119 | * @throws {Error} If failed to query the connection to start a transaction 120 | * @throws {Error} If query already has a `transactionId` field. 121 | */ 122 | wrapquery: function (query, id) { 123 | // if query is string or number, we convert it to an object and store transaction id in them. 124 | query = nonObjectCriteriaTypes[typeof query] && { 125 | id: query 126 | } || query; // else inject transactionId. 127 | 128 | // save transaction id to query and return 129 | query && (query.transactionId = id); 130 | 131 | return query; 132 | }, 133 | 134 | /** 135 | * This method allows one to extract transaction id from a critera object. The interesting thing is that it recovers 136 | * transaction id embedded within nested criteria and joins. 137 | * @private 138 | * 139 | * @param {object|array} criteria 140 | * @param {object=} [hash] - object to lookup validity of hash. in its absence simple lookup iis made 141 | * @returns {string} 142 | */ 143 | unwrapquery: function (criteria, hash) { 144 | var transactionId; 145 | 146 | // if hash is absent, we have no option but to look for only the transaction key 147 | if (!hash) { 148 | return criteria && criteria.transactionId; 149 | } 150 | 151 | // if criteria is falsy, we have nothing further to work on. 152 | if (!criteria) { 153 | return; 154 | } 155 | 156 | // we do a crude check to see if transaction id has been sent as part of the criteria object. 157 | if (hash[criteria.transactionId]) { 158 | // @todo this is brought here because there is an extra transactionId coming from somewhere in the where 159 | // clause 160 | if (criteria.where && criteria.where.transactionId) { 161 | delete criteria.where.transactionId; 162 | } 163 | return ((transactionId = criteria.transactionId), (delete criteria.transactionId), transactionId); 164 | } 165 | 166 | // if the criteria object sent is an array, usually this is a result of recusrion from this 167 | // function itself, we then try and process every item in the array for existence of transaction id. 168 | if (Array.isArray(criteria)) { 169 | criteria.some(function (item) { 170 | // during recursion we are sure CRC will always pass as that has been ensured by waterline 171 | // schema. 172 | return (transactionId = util.unwrapquery(item)); 173 | }); 174 | // if trabsaction id was found in recursive criteria, we return the same, else proceed. 175 | if (transactionId) { 176 | return transactionId; 177 | } 178 | } 179 | 180 | // if the criteria object is a schema processed dql, we would possibly get the transaction id within the `where` 181 | // key. 182 | if (criteria.where && hash[criteria.where.transactionId]) { 183 | return ((transactionId = criteria.where.transactionId), (delete criteria.where.transactionId), 184 | transactionId); 185 | } 186 | 187 | // finally, we try to recurse into finding transaction id within joins 188 | Array.isArray(criteria.joins) && (transactionId = util.unwrapquery(criteria.joins)); 189 | 190 | // finally, we return whatever transaction id we have received. could possibly be `undefined` as well. 191 | return transactionId; 192 | }, 193 | 194 | /** 195 | * Converts arguments object to real array 196 | * 197 | * @param {Arguments} args 198 | * @returns {Array} 199 | */ 200 | args2arr: function (args) { 201 | return Array.prototype.slice.call(args); 202 | } 203 | }; 204 | 205 | module.exports = util; 206 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sails-mysql-transactions", 3 | "version": "0.9.1", 4 | "description": "Sails ORM adapter for mySQL with transaction and replication support", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/postmanlabs/sails-mysql-transactions.git" 9 | }, 10 | "keywords": [ 11 | "sails", 12 | "waterline", 13 | "orm", 14 | "mysql", 15 | "transactions", 16 | "replication" 17 | ], 18 | "author": "Postman Labs ", 19 | "license": "Apache-2.0", 20 | "bugs": { 21 | "url": "https://github.com/postmanlabs/sails-mysql-transactions/issues" 22 | }, 23 | "homepage": "https://github.com/postmanlabs/sails-mysql-transactions", 24 | "dependencies": { 25 | "async": "~0.9.0", 26 | "lodash": "~3.8.0", 27 | "mysql": "~2.6.1", 28 | "waterline-cursor": "~0.0.5", 29 | "waterline-errors": "~0.10.0", 30 | "waterline-sequel": "git+https://github.com/postmanlabs/waterline-sequel.git#v0.5.1-postman.3" 31 | }, 32 | "devDependencies": { 33 | "expect.js": "^0.3.1", 34 | "jscs": "^1.11.3", 35 | "jshint": "^2.6.3", 36 | "mocha": "^2.2.1", 37 | "newman": "^3.1.0", 38 | "waterline-adapter-tests": "~0.10.7", 39 | "captains-log": "~0.11.5" 40 | }, 41 | "scripts": { 42 | "postinstall": "scripts/postinstall.sh", 43 | "test": "scripts/test.sh" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /scripts/postinstall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | INFO="sails-mysql-transactions:"; # for console logs 4 | 5 | set -e; 6 | 7 | # If this is an NPM installation, we do not expect `.gitmodules` in the directory 8 | # since it is ignored by `.npmignore`. This is a fairly robust check to test whether 9 | # this script has been run as part of npm install or as part of self install. 10 | if [ -f ".gitmodules" ]; then 11 | echo "${INFO} Not an NPM install, exiting waterline injection."; 12 | exit 0; 13 | fi; 14 | 15 | # Check whether sails has been already installed or not. If not, this is an 16 | # error and we should not proceed. 17 | if [ ! -d "../../node_modules/sails" ] && [ ! -d "../../node_modules/waterline" ]; then 18 | echo -e "\033[1;31m"; 19 | echo "${INFO} Sails and waterline installation not found!"; 20 | echo "${INFO} Ensure your package.json, which has sails-mysql-transaction, also includes sails."; 21 | echo -e "\033[0m\n"; 22 | exit 1; 23 | fi 24 | 25 | if [ ! -d "../../node_modules/waterline" ] && [ -d "../../node_modules/sails-mysql" ]; then 26 | echo -e "\033[1;31m"; 27 | echo "${INFO} WARNING - detected sails-mysql."; 28 | echo "${INFO} You may face unexpected behaviour."; 29 | echo "${INFO} Preferably remove sails-mysql from packages before using this in production."; 30 | echo -e "\033[0m\n"; 31 | fi 32 | 33 | 34 | # most likely npm3 35 | if [ -d "../../node_modules/waterline" ]; then 36 | echo "${INFO} Injecting waterline into sails..."; 37 | pushd "../../" > /dev/null; 38 | npm remove waterline; 39 | npm install "node_modules/sails-mysql-transactions/waterline"; 40 | 41 | if [ -d "node_modules/sails/node_modules/waterline" ]; then 42 | pushd "node_modules/sails" > /dev/null; 43 | npm remove waterline; 44 | popd > /dev/null; 45 | fi 46 | 47 | popd > /dev/null; 48 | 49 | echo 50 | echo "${INFO} Installation successful."; 51 | echo 52 | exit 0; 53 | fi 54 | 55 | if [ -d "../../node_modules/sails" ]; then 56 | echo "${INFO} Injecting waterline into sails..."; 57 | pushd "../../node_modules/sails" > /dev/null; 58 | npm remove waterline; 59 | npm install "../sails-mysql-transactions/waterline"; 60 | popd > /dev/null; 61 | 62 | echo 63 | echo "${INFO} Installation successful."; 64 | echo 65 | exit 0; 66 | fi 67 | -------------------------------------------------------------------------------- /scripts/prepare_testbed.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e; 4 | 5 | pushd tests/integration/app; 6 | # ensure installation has been run once for the testbed app 7 | [ -d node_modules ] && [ -d node_modules/sails ] && [ -d node_modules/sails-mysql-transactions ] || npm install; 8 | 9 | # symlink codebase 10 | pushd node_modules/sails-mysql-transactions; 11 | if [ -d lib ] && [ ! -L lib ]; then 12 | [ ! -d _lib ] && mv lib _lib; 13 | [ -d lib ] && rm -rf lib; 14 | fi 15 | [ ! -L lib ] && ln -fs ../../../../../lib; 16 | popd; 17 | 18 | # symlink waterline submodule 19 | pushd node_modules/sails/node_modules; 20 | if [ -d waterline ] && [ ! -L waterline ]; then 21 | [ ! -d _waterline ] && mv waterline _waterline; 22 | [ -d waterline ] && rm -rf waterline; 23 | fi 24 | [ ! -L waterline ] && ln -fs ../../../../../../waterline; 25 | popd; 26 | 27 | popd; -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e; 4 | 5 | # location of integration test server 6 | INTEGRATION_APP_DIR=tests/integration/app; 7 | 8 | # function to be called on exit 9 | # and ensure cleanup is called before the script exits 10 | function cleanup { 11 | pushd "${INTEGRATION_APP_DIR}" > /dev/null; 12 | npm stop; 13 | popd > /dev/null; 14 | } 15 | 16 | trap cleanup EXIT; 17 | 18 | # =========================================================== 19 | echo "Starting integration test server..."; 20 | pushd "${INTEGRATION_APP_DIR}" > /dev/null; 21 | 22 | if [ "${CI}" = true ]; then 23 | echo 24 | echo "travis_fold:start:integration.npm.install"; 25 | fi; 26 | if [ ! -d 'node_modules' ]; then 27 | npm install -d; 28 | else 29 | if [ -d 'node_modules/sails-mysql-transactions' ]; then 30 | rm -rf 'node_modules/sails-mysql-transactions'; 31 | fi 32 | npm run postinstall; 33 | fi 34 | if [ "${CI}" = true ]; then 35 | echo 36 | echo "travis_fold:end:integration.npm.install"; 37 | fi; 38 | 39 | npm start; 40 | popd > /dev/null; 41 | # =========================================================== 42 | 43 | # Do other tests while giving time for server to start 44 | echo "jscs v`jscs --version`"; 45 | jscs lib tests/unit; 46 | 47 | echo; 48 | 49 | jshint --version; 50 | jshint lib tests/unit; 51 | echo "No code lint issues found."; 52 | 53 | echo 54 | echo "Running unit tests..." 55 | echo "mocha v`mocha --version`"; 56 | 57 | mocha tests/unit/**/*-spec.js 58 | 59 | # =========================================================== 60 | echo 61 | echo "Running integration tests..."; 62 | 63 | # execute newman 64 | # server should be up and running on localhost:1337 65 | newman run tests/integration/sanity.json.postman_collection \ 66 | -e tests/integration/sails-transactions-experiment.postman_environment; 67 | 68 | # =========================================================== 69 | -------------------------------------------------------------------------------- /tests/integration/app/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /tests/integration/app/.gitignore: -------------------------------------------------------------------------------- 1 | ################################################ 2 | ############### .gitignore ################## 3 | ################################################ 4 | # 5 | # This file is only relevant if you are using git. 6 | # 7 | # Files which match the splat patterns below will 8 | # be ignored by git. This keeps random crap and 9 | # and sensitive credentials from being uploaded to 10 | # your repository. It allows you to configure your 11 | # app for your machine without accidentally 12 | # committing settings which will smash the local 13 | # settings of other developers on your team. 14 | # 15 | # Some reasonable defaults are included below, 16 | # but, of course, you should modify/extend/prune 17 | # to fit your needs! 18 | ################################################ 19 | 20 | 21 | 22 | 23 | ################################################ 24 | # Local Configuration 25 | # 26 | # Explicitly ignore files which contain: 27 | # 28 | # 1. Sensitive information you'd rather not push to 29 | # your git repository. 30 | # e.g., your personal API keys or passwords. 31 | # 32 | # 2. Environment-specific configuration 33 | # Basically, anything that would be annoying 34 | # to have to change every time you do a 35 | # `git pull` 36 | # e.g., your local development database, or 37 | # the S3 bucket you're using for file uploads 38 | # development. 39 | # 40 | ################################################ 41 | 42 | # allow for test app 43 | # config/local.js 44 | 45 | 46 | 47 | 48 | 49 | ################################################ 50 | # Dependencies 51 | # 52 | # When releasing a production app, you may 53 | # consider including your node_modules and 54 | # bower_components directory in your git repo, 55 | # but during development, its best to exclude it, 56 | # since different developers may be working on 57 | # different kernels, where dependencies would 58 | # need to be recompiled anyway. 59 | # 60 | # More on that here about node_modules dir: 61 | # http://www.futurealoof.com/posts/nodemodules-in-git.html 62 | # (credit Mikeal Rogers, @mikeal) 63 | # 64 | # About bower_components dir, you can see this: 65 | # http://addyosmani.com/blog/checking-in-front-end-dependencies/ 66 | # (credit Addy Osmani, @addyosmani) 67 | # 68 | ################################################ 69 | 70 | node_modules 71 | !/node_modules/sails 72 | !/node_modules/waterline 73 | !/node_modules/sails-mysql-transactions 74 | bower_components 75 | 76 | 77 | 78 | 79 | ################################################ 80 | # Sails.js / Waterline / Grunt 81 | # 82 | # Files generated by Sails and Grunt, or related 83 | # tasks and adapters. 84 | ################################################ 85 | .tmp 86 | dump.rdb 87 | 88 | 89 | 90 | 91 | 92 | ################################################ 93 | # Node.js / NPM 94 | # 95 | # Common files generated by Node, NPM, and the 96 | # related ecosystem. 97 | ################################################ 98 | lib-cov 99 | *.seed 100 | *.log 101 | *.out 102 | *.pid 103 | npm-debug.log 104 | 105 | 106 | 107 | 108 | 109 | ################################################ 110 | # Miscellaneous 111 | # 112 | # Common files generated by text editors, 113 | # operating systems, file systems, etc. 114 | ################################################ 115 | 116 | *~ 117 | *# 118 | .DS_STORE 119 | .netbeans 120 | nbproject 121 | .idea 122 | .node_history 123 | -------------------------------------------------------------------------------- /tests/integration/app/.sailsrc: -------------------------------------------------------------------------------- 1 | { 2 | "generators": { 3 | "modules": {} 4 | } 5 | } -------------------------------------------------------------------------------- /tests/integration/app/Gruntfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gruntfile 3 | * 4 | * This Node script is executed when you run `grunt` or `sails lift`. 5 | * It's purpose is to load the Grunt tasks in your project's `tasks` 6 | * folder, and allow you to add and remove tasks as you see fit. 7 | * For more information on how this works, check out the `README.md` 8 | * file that was generated in your `tasks` folder. 9 | * 10 | * WARNING: 11 | * Unless you know what you're doing, you shouldn't change this file. 12 | * Check out the `tasks` directory instead. 13 | */ 14 | 15 | module.exports = function(grunt) { 16 | 17 | 18 | // Load the include-all library in order to require all of our grunt 19 | // configurations and task registrations dynamically. 20 | var includeAll; 21 | try { 22 | includeAll = require('include-all'); 23 | } catch (e0) { 24 | try { 25 | includeAll = require('sails/node_modules/include-all'); 26 | } 27 | catch(e1) { 28 | console.error('Could not find `include-all` module.'); 29 | console.error('Skipping grunt tasks...'); 30 | console.error('To fix this, please run:'); 31 | console.error('npm install include-all --save`'); 32 | console.error(); 33 | 34 | grunt.registerTask('default', []); 35 | return; 36 | } 37 | } 38 | 39 | 40 | /** 41 | * Loads Grunt configuration modules from the specified 42 | * relative path. These modules should export a function 43 | * that, when run, should either load/configure or register 44 | * a Grunt task. 45 | */ 46 | function loadTasks(relPath) { 47 | return includeAll({ 48 | dirname: require('path').resolve(__dirname, relPath), 49 | filter: /(.+)\.js$/ 50 | }) || {}; 51 | } 52 | 53 | /** 54 | * Invokes the function from a Grunt configuration module with 55 | * a single argument - the `grunt` object. 56 | */ 57 | function invokeConfigFn(tasks) { 58 | for (var taskName in tasks) { 59 | if (tasks.hasOwnProperty(taskName)) { 60 | tasks[taskName](grunt); 61 | } 62 | } 63 | } 64 | 65 | 66 | 67 | 68 | // Load task functions 69 | var taskConfigurations = loadTasks('./tasks/config'), 70 | registerDefinitions = loadTasks('./tasks/register'); 71 | 72 | // (ensure that a default task exists) 73 | if (!registerDefinitions.default) { 74 | registerDefinitions.default = function (grunt) { grunt.registerTask('default', []); }; 75 | } 76 | 77 | // Run task functions to configure Grunt. 78 | invokeConfigFn(taskConfigurations); 79 | invokeConfigFn(registerDefinitions); 80 | 81 | }; 82 | -------------------------------------------------------------------------------- /tests/integration/app/README.md: -------------------------------------------------------------------------------- 1 | # sails-transactions-experiment 2 | 3 | 1. Install mysql locally - [Mysql install script](http://www.macminivault.com/mysql-yosemite/) 4 | 2. Checkout master branch and npm install 5 | 3. Create a local.js file in the config folder (You can copy local.js.sample, and use your own mysql settings) 6 | 4. sails lift -------------------------------------------------------------------------------- /tests/integration/app/api/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postmanlabs/sails-mysql-transactions/4c8bae4d428afb942860a51d4deb66aa5b9cd405/tests/integration/app/api/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/integration/app/api/controllers/CollectionController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * CollectionController 3 | * 4 | * @description :: Server-side logic for managing collections 5 | * @help :: See http://links.sailsjs.org/docs/controllers 6 | */ 7 | 8 | module.exports = { 9 | retrieve: function (req, res) { 10 | Collection.findOne({ 11 | select: ['name'], 12 | where: {id: req.param('id')} 13 | }) 14 | .populate('user', {select: ['name']}) 15 | .populate('requests', {select: ['name', 'collection', 'id']}) 16 | .exec(function (err, collection) { 17 | if (err) { 18 | return res.serverError(err); 19 | } 20 | res.json(collection); 21 | }); 22 | }, 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /tests/integration/app/api/controllers/RequestController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * RequestController 3 | * 4 | * @description :: Server-side logic for managing requests 5 | * @help :: See http://links.sailsjs.org/docs/controllers 6 | */ 7 | 8 | module.exports = { 9 | 10 | }; 11 | 12 | -------------------------------------------------------------------------------- /tests/integration/app/api/controllers/TeamController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TeamController 3 | * 4 | * @description :: Server-side logic for managing teams. Useful for testing many-to-many associations. 5 | * @help :: See http://links.sailsjs.org/docs/controllers 6 | */ 7 | 8 | var Transaction = require('sails-mysql-transactions').Transaction; 9 | 10 | module.exports = { 11 | create: function (req, res) { 12 | 13 | // start a transaction 14 | Transaction.start(function (err, transaction) { 15 | if (err) { 16 | transaction && transaction.rollback(); 17 | return res.serverError(err); 18 | } 19 | 20 | // create a new team instance in a transactional way. 21 | Team.transact(transaction).findOrCreate(req.param('id'), req.params.all(), function (err, team) { 22 | if (err) { 23 | transaction.rollback(); 24 | return res.serverError(err); 25 | } 26 | 27 | transaction.commit(); 28 | return res.json(team); 29 | }); 30 | }); 31 | 32 | //// Promises are unsupported 33 | //Transaction.start(function (err, transaction) { 34 | // if (err) { 35 | // transaction && transaction.rollback() 36 | // return res.serverError(err); 37 | // } 38 | // 39 | // Team.transact(transaction).create(req.params.all()) 40 | // .then(function (team) { 41 | // transaction.commit(); 42 | // return res.json(team); 43 | // }) 44 | // .catch(function (err) { 45 | // if (err) { 46 | // transaction.rollback(); 47 | // return res.serverError(err); 48 | // } 49 | // }); 50 | //}); 51 | }, 52 | 53 | create_direct: function (req, res) { 54 | Team.create(req.params.all(), function (err, team) { 55 | if (err) { 56 | return res.serverError(err); 57 | } 58 | 59 | return res.json(team); 60 | }); 61 | }, 62 | 63 | 64 | add_member: function (req, res) { 65 | Transaction.start(function (err, transaction) { 66 | if (err) { 67 | transaction && trans.rollback(); 68 | return res.serverError(err); 69 | } 70 | 71 | Team.transact(transaction).findOne(req.param('id')) 72 | .populate('members') 73 | .exec(function (err, team) { 74 | if (err) { 75 | transaction.rollback(); 76 | return res.serverError(err); 77 | } 78 | 79 | team.members.add(req.param('member_id')); 80 | team.transactionId = transaction.connection().transactionId; 81 | 82 | team.save(function (err, team) { 83 | if (err) { 84 | transaction.rollback(); 85 | return res.serverError(err); 86 | } 87 | 88 | transaction.commit(); 89 | return res.json(team); 90 | }); 91 | }); 92 | }); 93 | }, 94 | 95 | remove_member: function (req, res) { 96 | 97 | Transaction.start(function (err, transaction) { 98 | 99 | if (err) { 100 | transaction && transaction.rollback(); 101 | return res.serverError(err); 102 | } 103 | 104 | 105 | Team.transact(transaction) 106 | .findOne(req.param('id')) 107 | .populate('members') 108 | .exec(function (err, team) { 109 | if (err) { 110 | transaction.rollback(); 111 | return res.serverError(err); 112 | } 113 | 114 | team.members.remove(req.param('member_id')); 115 | team.transactionId = transaction.connection().transactionId; 116 | 117 | team.save(function (err, team) { 118 | if (err) { 119 | transaction.rollback(); 120 | return res.serverError(err); 121 | } 122 | 123 | transaction.commit(); 124 | return res.json(team); 125 | }); 126 | }); 127 | }); 128 | }, 129 | 130 | add_member_direct: function (req, res) { 131 | Team.findOne(req.param('id')).populate('members').exec(function (err, team) { 132 | if (err) { 133 | return res.serverError(err); 134 | } 135 | 136 | team.members.add(req.param('member_id')); 137 | team.save(function (err, team) { 138 | if (err) { 139 | return res.serverError(err); 140 | } 141 | 142 | return res.json(team); 143 | }); 144 | }); 145 | }, 146 | 147 | find_selective: function (req, res) { 148 | Team.readonly('set1').findOne({ 149 | select: ['id', 'mascot'], 150 | id: req.param('id') 151 | }).populate('members', {select: ['id', 'name']}).exec(function (err, team) { 152 | if (err) { 153 | return res.serverError(err); 154 | } 155 | 156 | return res.json(team); 157 | }); 158 | } 159 | }; 160 | 161 | -------------------------------------------------------------------------------- /tests/integration/app/api/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postmanlabs/sails-mysql-transactions/4c8bae4d428afb942860a51d4deb66aa5b9cd405/tests/integration/app/api/models/.gitkeep -------------------------------------------------------------------------------- /tests/integration/app/api/models/Collection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Collection.js 3 | * 4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here. 5 | * @docs :: http://sailsjs.org/#!documentation/models 6 | */ 7 | 8 | module.exports = { 9 | schema: true, 10 | autosubscribe: false, 11 | autoTK: true, 12 | attributes: { 13 | name: { 14 | type: 'string' 15 | }, 16 | user: { 17 | model: 'User' 18 | }, 19 | fancy: { 20 | type: 'boolean', 21 | defaultsTo: false 22 | }, 23 | requests: { 24 | collection: 'Request', 25 | via: 'collection' 26 | }, 27 | shared: { 28 | type: 'boolean', 29 | defaultsTo: false 30 | } 31 | }, 32 | 33 | createInstance: function (user, cb) { 34 | Collection.create({ 35 | name: 'collection:' + user.id 36 | }, function (err, collection) { 37 | if (err) { return cb(err); } 38 | 39 | user.collections.add(collection.id); 40 | user.save(function (err, user) { 41 | if (err) { return cb(err); } 42 | 43 | Request.createInstance(user, collection, cb); 44 | }); 45 | }); 46 | } 47 | }; 48 | 49 | -------------------------------------------------------------------------------- /tests/integration/app/api/models/Request.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Request.js 3 | * 4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here. 5 | * @docs :: http://sailsjs.org/#!documentation/models 6 | */ 7 | 8 | module.exports = { 9 | schema: true, 10 | autosubscribe: false, 11 | attributes: { 12 | name: { 13 | type: 'string' 14 | }, 15 | user: { 16 | model: 'User' 17 | }, 18 | collection: { 19 | model: 'Collection' 20 | }, 21 | transactionId: { 22 | type: 'string' 23 | } 24 | }, 25 | 26 | createInstance: function (user, collection, cb) { 27 | Request.create({ 28 | name: 'request:' + user.id + ':' + collection.id, 29 | user: user 30 | }, function (err, request) { 31 | if (err) { return cb(err); } 32 | 33 | collection.requests.add(request.id); 34 | collection.save(function (err, collection) { 35 | if (err) { return cb(err); } 36 | 37 | cb(null, collection); 38 | }); 39 | }); 40 | } 41 | }; 42 | 43 | -------------------------------------------------------------------------------- /tests/integration/app/api/models/Team.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Team.js 3 | * 4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here. 5 | * @docs :: http://sailsjs.org/#!documentation/models 6 | */ 7 | 8 | module.exports = { 9 | schema: true, 10 | autosubscribe: false, 11 | autoPK: false, 12 | attributes: { 13 | id: { 14 | primaryKey: true, 15 | type: 'string' 16 | }, 17 | name: { 18 | type: 'string' 19 | }, 20 | mascot: { 21 | type: 'string' 22 | }, 23 | members: { 24 | collection: 'User', 25 | via: 'teams', 26 | dominant: true 27 | } 28 | } 29 | }; 30 | 31 | -------------------------------------------------------------------------------- /tests/integration/app/api/models/User.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User.js 3 | * 4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here. 5 | * @docs :: http://sailsjs.org/#!documentation/models 6 | */ 7 | 8 | module.exports = { 9 | schema: true, 10 | autosubscribe: false, 11 | autoPK: false, 12 | attributes: { 13 | id: { 14 | primaryKey: true, 15 | type: 'string' 16 | }, 17 | name: { 18 | type: 'string' 19 | }, 20 | admin: { 21 | type: 'boolean', 22 | defaultsTo: false 23 | }, 24 | collections: { 25 | collection: 'Collection', 26 | via: 'user' 27 | }, 28 | teams: { 29 | collection: 'Team', 30 | via: 'members' 31 | } 32 | } 33 | }; 34 | 35 | -------------------------------------------------------------------------------- /tests/integration/app/api/policies/sessionAuth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * sessionAuth 3 | * 4 | * @module :: Policy 5 | * @description :: Simple policy to allow any authenticated user 6 | * Assumes that your login action in one of your controllers sets `req.session.authenticated = true;` 7 | * @docs :: http://sailsjs.org/#!documentation/policies 8 | * 9 | */ 10 | module.exports = function(req, res, next) { 11 | 12 | // User is allowed, proceed to the next policy, 13 | // or if this is the last policy, the controller 14 | if (req.session.authenticated) { 15 | return next(); 16 | } 17 | 18 | // User is not allowed 19 | // (default res.forbidden() behavior can be overridden in `config/403.js`) 20 | return res.forbidden('You are not permitted to perform this action.'); 21 | }; 22 | -------------------------------------------------------------------------------- /tests/integration/app/api/responses/badRequest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 400 (Bad Request) Handler 3 | * 4 | * Usage: 5 | * return res.badRequest(); 6 | * return res.badRequest(data); 7 | * return res.badRequest(data, 'some/specific/badRequest/view'); 8 | * 9 | * e.g.: 10 | * ``` 11 | * return res.badRequest( 12 | * 'Please choose a valid `password` (6-12 characters)', 13 | * 'trial/signup' 14 | * ); 15 | * ``` 16 | */ 17 | 18 | module.exports = function badRequest(data, options) { 19 | 20 | // Get access to `req`, `res`, & `sails` 21 | var req = this.req; 22 | var res = this.res; 23 | var sails = req._sails; 24 | 25 | // Set status code 26 | res.status(400); 27 | 28 | // Log error to console 29 | if (data !== undefined) { 30 | sails.log.verbose('Sending 400 ("Bad Request") response: \n',data); 31 | } 32 | else sails.log.verbose('Sending 400 ("Bad Request") response'); 33 | 34 | // Only include errors in response if application environment 35 | // is not set to 'production'. In production, we shouldn't 36 | // send back any identifying information about errors. 37 | if (sails.config.environment === 'production') { 38 | data = undefined; 39 | } 40 | 41 | // If the user-agent wants JSON, always respond with JSON 42 | if (req.wantsJSON) { 43 | return res.jsonx(data); 44 | } 45 | 46 | // If second argument is a string, we take that to mean it refers to a view. 47 | // If it was omitted, use an empty object (`{}`) 48 | options = (typeof options === 'string') ? { view: options } : options || {}; 49 | 50 | // If a view was provided in options, serve it. 51 | // Otherwise try to guess an appropriate view, or if that doesn't 52 | // work, just send JSON. 53 | if (options.view) { 54 | return res.view(options.view, { data: data }); 55 | } 56 | 57 | // If no second argument provided, try to serve the implied view, 58 | // but fall back to sending JSON(P) if no view can be inferred. 59 | else return res.guessView({ data: data }, function couldNotGuessView () { 60 | return res.jsonx(data); 61 | }); 62 | 63 | }; 64 | 65 | -------------------------------------------------------------------------------- /tests/integration/app/api/responses/forbidden.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 403 (Forbidden) Handler 3 | * 4 | * Usage: 5 | * return res.forbidden(); 6 | * return res.forbidden(err); 7 | * return res.forbidden(err, 'some/specific/forbidden/view'); 8 | * 9 | * e.g.: 10 | * ``` 11 | * return res.forbidden('Access denied.'); 12 | * ``` 13 | */ 14 | 15 | module.exports = function forbidden (data, options) { 16 | 17 | // Get access to `req`, `res`, & `sails` 18 | var req = this.req; 19 | var res = this.res; 20 | var sails = req._sails; 21 | 22 | // Set status code 23 | res.status(403); 24 | 25 | // Log error to console 26 | if (data !== undefined) { 27 | sails.log.verbose('Sending 403 ("Forbidden") response: \n',data); 28 | } 29 | else sails.log.verbose('Sending 403 ("Forbidden") response'); 30 | 31 | // Only include errors in response if application environment 32 | // is not set to 'production'. In production, we shouldn't 33 | // send back any identifying information about errors. 34 | if (sails.config.environment === 'production') { 35 | data = undefined; 36 | } 37 | 38 | // If the user-agent wants JSON, always respond with JSON 39 | if (req.wantsJSON) { 40 | return res.jsonx(data); 41 | } 42 | 43 | // If second argument is a string, we take that to mean it refers to a view. 44 | // If it was omitted, use an empty object (`{}`) 45 | options = (typeof options === 'string') ? { view: options } : options || {}; 46 | 47 | // If a view was provided in options, serve it. 48 | // Otherwise try to guess an appropriate view, or if that doesn't 49 | // work, just send JSON. 50 | if (options.view) { 51 | return res.view(options.view, { data: data }); 52 | } 53 | 54 | // If no second argument provided, try to serve the default view, 55 | // but fall back to sending JSON(P) if any errors occur. 56 | else return res.view('403', { data: data }, function (err, html) { 57 | 58 | // If a view error occured, fall back to JSON(P). 59 | if (err) { 60 | // 61 | // Additionally: 62 | // • If the view was missing, ignore the error but provide a verbose log. 63 | if (err.code === 'E_VIEW_FAILED') { 64 | sails.log.verbose('res.forbidden() :: Could not locate view for error page (sending JSON instead). Details: ',err); 65 | } 66 | // Otherwise, if this was a more serious error, log to the console with the details. 67 | else { 68 | sails.log.warn('res.forbidden() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err); 69 | } 70 | return res.jsonx(data); 71 | } 72 | 73 | return res.send(html); 74 | }); 75 | 76 | }; 77 | 78 | -------------------------------------------------------------------------------- /tests/integration/app/api/responses/notFound.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 404 (Not Found) Handler 3 | * 4 | * Usage: 5 | * return res.notFound(); 6 | * return res.notFound(err); 7 | * return res.notFound(err, 'some/specific/notfound/view'); 8 | * 9 | * e.g.: 10 | * ``` 11 | * return res.notFound(); 12 | * ``` 13 | * 14 | * NOTE: 15 | * If a request doesn't match any explicit routes (i.e. `config/routes.js`) 16 | * or route blueprints (i.e. "shadow routes", Sails will call `res.notFound()` 17 | * automatically. 18 | */ 19 | 20 | module.exports = function notFound (data, options) { 21 | 22 | // Get access to `req`, `res`, & `sails` 23 | var req = this.req; 24 | var res = this.res; 25 | var sails = req._sails; 26 | 27 | // Set status code 28 | res.status(404); 29 | 30 | // Log error to console 31 | if (data !== undefined) { 32 | sails.log.verbose('Sending 404 ("Not Found") response: \n',data); 33 | } 34 | else sails.log.verbose('Sending 404 ("Not Found") response'); 35 | 36 | // Only include errors in response if application environment 37 | // is not set to 'production'. In production, we shouldn't 38 | // send back any identifying information about errors. 39 | if (sails.config.environment === 'production') { 40 | data = undefined; 41 | } 42 | 43 | // If the user-agent wants JSON, always respond with JSON 44 | if (req.wantsJSON) { 45 | return res.jsonx(data); 46 | } 47 | 48 | // If second argument is a string, we take that to mean it refers to a view. 49 | // If it was omitted, use an empty object (`{}`) 50 | options = (typeof options === 'string') ? { view: options } : options || {}; 51 | 52 | // If a view was provided in options, serve it. 53 | // Otherwise try to guess an appropriate view, or if that doesn't 54 | // work, just send JSON. 55 | if (options.view) { 56 | return res.view(options.view, { data: data }); 57 | } 58 | 59 | // If no second argument provided, try to serve the default view, 60 | // but fall back to sending JSON(P) if any errors occur. 61 | else return res.view('404', { data: data }, function (err, html) { 62 | 63 | // If a view error occured, fall back to JSON(P). 64 | if (err) { 65 | // 66 | // Additionally: 67 | // • If the view was missing, ignore the error but provide a verbose log. 68 | if (err.code === 'E_VIEW_FAILED') { 69 | sails.log.verbose('res.notFound() :: Could not locate view for error page (sending JSON instead). Details: ',err); 70 | } 71 | // Otherwise, if this was a more serious error, log to the console with the details. 72 | else { 73 | sails.log.warn('res.notFound() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err); 74 | } 75 | return res.jsonx(data); 76 | } 77 | 78 | return res.send(html); 79 | }); 80 | 81 | }; 82 | 83 | -------------------------------------------------------------------------------- /tests/integration/app/api/responses/ok.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 200 (OK) Response 3 | * 4 | * Usage: 5 | * return res.ok(); 6 | * return res.ok(data); 7 | * return res.ok(data, 'auth/login'); 8 | * 9 | * @param {Object} data 10 | * @param {String|Object} options 11 | * - pass string to render specified view 12 | */ 13 | 14 | module.exports = function sendOK (data, options) { 15 | 16 | // Get access to `req`, `res`, & `sails` 17 | var req = this.req; 18 | var res = this.res; 19 | var sails = req._sails; 20 | 21 | sails.log.silly('res.ok() :: Sending 200 ("OK") response'); 22 | 23 | // Set status code 24 | res.status(200); 25 | 26 | // If appropriate, serve data as JSON(P) 27 | if (req.wantsJSON) { 28 | return res.jsonx(data); 29 | } 30 | 31 | // If second argument is a string, we take that to mean it refers to a view. 32 | // If it was omitted, use an empty object (`{}`) 33 | options = (typeof options === 'string') ? { view: options } : options || {}; 34 | 35 | // If a view was provided in options, serve it. 36 | // Otherwise try to guess an appropriate view, or if that doesn't 37 | // work, just send JSON. 38 | if (options.view) { 39 | return res.view(options.view, { data: data }); 40 | } 41 | 42 | // If no second argument provided, try to serve the implied view, 43 | // but fall back to sending JSON(P) if no view can be inferred. 44 | else return res.guessView({ data: data }, function couldNotGuessView () { 45 | return res.jsonx(data); 46 | }); 47 | 48 | }; 49 | -------------------------------------------------------------------------------- /tests/integration/app/api/responses/serverError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 500 (Server Error) Response 3 | * 4 | * Usage: 5 | * return res.serverError(); 6 | * return res.serverError(err); 7 | * return res.serverError(err, 'some/specific/error/view'); 8 | * 9 | * NOTE: 10 | * If something throws in a policy or controller, or an internal 11 | * error is encountered, Sails will call `res.serverError()` 12 | * automatically. 13 | */ 14 | 15 | module.exports = function serverError (data, options) { 16 | 17 | // Get access to `req`, `res`, & `sails` 18 | var req = this.req; 19 | var res = this.res; 20 | var sails = req._sails; 21 | 22 | // Set status code 23 | res.status(500); 24 | 25 | // Log error to console 26 | if (data !== undefined) { 27 | sails.log.error('Sending 500 ("Server Error") response: \n',data); 28 | } 29 | else sails.log.error('Sending empty 500 ("Server Error") response'); 30 | 31 | // Only include errors in response if application environment 32 | // is not set to 'production'. In production, we shouldn't 33 | // send back any identifying information about errors. 34 | if (sails.config.environment === 'production') { 35 | data = undefined; 36 | } 37 | 38 | // If the user-agent wants JSON, always respond with JSON 39 | if (req.wantsJSON) { 40 | return res.jsonx(data); 41 | } 42 | 43 | // If second argument is a string, we take that to mean it refers to a view. 44 | // If it was omitted, use an empty object (`{}`) 45 | options = (typeof options === 'string') ? { view: options } : options || {}; 46 | 47 | // If a view was provided in options, serve it. 48 | // Otherwise try to guess an appropriate view, or if that doesn't 49 | // work, just send JSON. 50 | if (options.view) { 51 | return res.view(options.view, { data: data }); 52 | } 53 | 54 | // If no second argument provided, try to serve the default view, 55 | // but fall back to sending JSON(P) if any errors occur. 56 | else return res.view('500', { data: data }, function (err, html) { 57 | 58 | // If a view error occured, fall back to JSON(P). 59 | if (err) { 60 | // 61 | // Additionally: 62 | // • If the view was missing, ignore the error but provide a verbose log. 63 | if (err.code === 'E_VIEW_FAILED') { 64 | sails.log.verbose('res.serverError() :: Could not locate view for error page (sending JSON instead). Details: ',err); 65 | } 66 | // Otherwise, if this was a more serious error, log to the console with the details. 67 | else { 68 | sails.log.warn('res.serverError() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err); 69 | } 70 | return res.jsonx(data); 71 | } 72 | 73 | return res.send(html); 74 | }); 75 | 76 | }; 77 | 78 | -------------------------------------------------------------------------------- /tests/integration/app/api/services/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postmanlabs/sails-mysql-transactions/4c8bae4d428afb942860a51d4deb66aa5b9cd405/tests/integration/app/api/services/.gitkeep -------------------------------------------------------------------------------- /tests/integration/app/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * app.js 3 | * 4 | * Use `app.js` to run your app without `sails lift`. 5 | * To start the server, run: `node app.js`. 6 | * 7 | * This is handy in situations where the sails CLI is not relevant or useful. 8 | * 9 | * For example: 10 | * => `node app.js` 11 | * => `forever start app.js` 12 | * => `node debug app.js` 13 | * => `modulus deploy` 14 | * => `heroku scale` 15 | * 16 | * 17 | * The same command-line arguments are supported, e.g.: 18 | * `node app.js --silent --port=80 --prod` 19 | */ 20 | 21 | // Ensure we're in the project directory, so relative paths work as expected 22 | // no matter where we actually lift from. 23 | process.chdir(__dirname); 24 | process.env.MYSQL_LOG_QUERY = true; 25 | 26 | // Ensure a "sails" can be located: 27 | (function() { 28 | var sails; 29 | try { 30 | sails = require('sails'); 31 | } catch (e) { 32 | console.error('To run an app using `node app.js`, you usually need to have a version of `sails` installed in the same directory as your app.'); 33 | console.error('To do that, run `npm install sails`'); 34 | console.error(''); 35 | console.error('Alternatively, if you have sails installed globally (i.e. you did `npm install -g sails`), you can use `sails lift`.'); 36 | console.error('When you run `sails lift`, your app will still use a local `./node_modules/sails` dependency if it exists,'); 37 | console.error('but if it doesn\'t, the app will run with the global sails instead!'); 38 | return; 39 | } 40 | 41 | // Try to get `rc` dependency 42 | var rc; 43 | try { 44 | rc = require('rc'); 45 | } catch (e0) { 46 | try { 47 | rc = require('sails/node_modules/rc'); 48 | } catch (e1) { 49 | console.error('Could not find dependency: `rc`.'); 50 | console.error('Your `.sailsrc` file(s) will be ignored.'); 51 | console.error('To resolve this, run:'); 52 | console.error('npm install rc --save'); 53 | rc = function () { return {}; }; 54 | } 55 | } 56 | 57 | 58 | // Start server 59 | sails.lift(rc('sails')); 60 | })(); 61 | -------------------------------------------------------------------------------- /tests/integration/app/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postmanlabs/sails-mysql-transactions/4c8bae4d428afb942860a51d4deb66aa5b9cd405/tests/integration/app/assets/favicon.ico -------------------------------------------------------------------------------- /tests/integration/app/assets/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postmanlabs/sails-mysql-transactions/4c8bae4d428afb942860a51d4deb66aa5b9cd405/tests/integration/app/assets/images/.gitkeep -------------------------------------------------------------------------------- /tests/integration/app/assets/robots.txt: -------------------------------------------------------------------------------- 1 | # The robots.txt file is used to control how search engines index your live URLs. 2 | # See http://www.robotstxt.org/wc/norobots.html for more information. 3 | 4 | 5 | 6 | # To prevent search engines from seeing the site altogether, uncomment the next two lines: 7 | # User-Agent: * 8 | # Disallow: / 9 | -------------------------------------------------------------------------------- /tests/integration/app/assets/styles/importer.less: -------------------------------------------------------------------------------- 1 | /** 2 | * importer.less 3 | * 4 | * By default, new Sails projects are configured to compile this file 5 | * from LESS to CSS. Unlike CSS files, LESS files are not compiled and 6 | * included automatically unless they are imported below. 7 | * 8 | * The LESS files imported below are compiled and included in the order 9 | * they are listed. Mixins, variables, etc. should be imported first 10 | * so that they can be accessed by subsequent LESS stylesheets. 11 | * 12 | * (Just like the rest of the asset pipeline bundled in Sails, you can 13 | * always omit, customize, or replace this behavior with SASS, SCSS, 14 | * or any other Grunt tasks you like.) 15 | */ 16 | 17 | 18 | 19 | // For example: 20 | // 21 | // @import 'variables/colors.less'; 22 | // @import 'mixins/foo.less'; 23 | // @import 'mixins/bar.less'; 24 | // @import 'mixins/baz.less'; 25 | // 26 | // @import 'styleguide.less'; 27 | // @import 'pages/login.less'; 28 | // @import 'pages/signup.less'; 29 | // 30 | // etc. 31 | -------------------------------------------------------------------------------- /tests/integration/app/assets/templates/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postmanlabs/sails-mysql-transactions/4c8bae4d428afb942860a51d4deb66aa5b9cd405/tests/integration/app/assets/templates/.gitkeep -------------------------------------------------------------------------------- /tests/integration/app/config/blueprints.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Blueprint API Configuration 3 | * (sails.config.blueprints) 4 | * 5 | * These settings are for the global configuration of blueprint routes and 6 | * request options (which impact the behavior of blueprint actions). 7 | * 8 | * You may also override any of these settings on a per-controller basis 9 | * by defining a '_config' key in your controller defintion, and assigning it 10 | * a configuration object with overrides for the settings in this file. 11 | * A lot of the configuration options below affect so-called "CRUD methods", 12 | * or your controllers' `find`, `create`, `update`, and `destroy` actions. 13 | * 14 | * It's important to realize that, even if you haven't defined these yourself, as long as 15 | * a model exists with the same name as the controller, Sails will respond with built-in CRUD 16 | * logic in the form of a JSON API, including support for sort, pagination, and filtering. 17 | * 18 | * For more information on the blueprint API, check out: 19 | * http://sailsjs.org/#/documentation/reference/blueprint-api 20 | * 21 | * For more information on the settings in this file, see: 22 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.blueprints.html 23 | * 24 | */ 25 | 26 | module.exports.blueprints = { 27 | 28 | /*************************************************************************** 29 | * * 30 | * Action routes speed up the backend development workflow by * 31 | * eliminating the need to manually bind routes. When enabled, GET, POST, * 32 | * PUT, and DELETE routes will be generated for every one of a controller's * 33 | * actions. * 34 | * * 35 | * If an `index` action exists, additional naked routes will be created for * 36 | * it. Finally, all `actions` blueprints support an optional path * 37 | * parameter, `id`, for convenience. * 38 | * * 39 | * `actions` are enabled by default, and can be OK for production-- * 40 | * however, if you'd like to continue to use controller/action autorouting * 41 | * in a production deployment, you must take great care not to * 42 | * inadvertently expose unsafe/unintentional controller logic to GET * 43 | * requests. * 44 | * * 45 | ***************************************************************************/ 46 | 47 | // actions: true, 48 | 49 | /*************************************************************************** 50 | * * 51 | * RESTful routes (`sails.config.blueprints.rest`) * 52 | * * 53 | * REST blueprints are the automatically generated routes Sails uses to * 54 | * expose a conventional REST API on top of a controller's `find`, * 55 | * `create`, `update`, and `destroy` actions. * 56 | * * 57 | * For example, a BoatController with `rest` enabled generates the * 58 | * following routes: * 59 | * ::::::::::::::::::::::::::::::::::::::::::::::::::::::: * 60 | * GET /boat -> BoatController.find * 61 | * GET /boat/:id -> BoatController.findOne * 62 | * POST /boat -> BoatController.create * 63 | * PUT /boat/:id -> BoatController.update * 64 | * DELETE /boat/:id -> BoatController.destroy * 65 | * * 66 | * `rest` blueprint routes are enabled by default, and are suitable for use * 67 | * in a production scenario, as long you take standard security precautions * 68 | * (combine w/ policies, etc.) * 69 | * * 70 | ***************************************************************************/ 71 | 72 | // rest: true, 73 | 74 | /*************************************************************************** 75 | * * 76 | * Shortcut routes are simple helpers to provide access to a * 77 | * controller's CRUD methods from your browser's URL bar. When enabled, * 78 | * GET, POST, PUT, and DELETE routes will be generated for the * 79 | * controller's`find`, `create`, `update`, and `destroy` actions. * 80 | * * 81 | * `shortcuts` are enabled by default, but should be disabled in * 82 | * production. * 83 | * * 84 | ***************************************************************************/ 85 | 86 | // shortcuts: true, 87 | 88 | /*************************************************************************** 89 | * * 90 | * An optional mount path for all blueprint routes on a controller, * 91 | * including `rest`, `actions`, and `shortcuts`. This allows you to take * 92 | * advantage of blueprint routing, even if you need to namespace your API * 93 | * methods. * 94 | * * 95 | * (NOTE: This only applies to blueprint autoroutes, not manual routes from * 96 | * `sails.config.routes`) * 97 | * * 98 | ***************************************************************************/ 99 | 100 | // prefix: '', 101 | 102 | /*************************************************************************** 103 | * * 104 | * Whether to pluralize controller names in blueprint routes. * 105 | * * 106 | * (NOTE: This only applies to blueprint autoroutes, not manual routes from * 107 | * `sails.config.routes`) * 108 | * * 109 | * For example, REST blueprints for `FooController` with `pluralize` * 110 | * enabled: * 111 | * GET /foos/:id? * 112 | * POST /foos * 113 | * PUT /foos/:id? * 114 | * DELETE /foos/:id? * 115 | * * 116 | ***************************************************************************/ 117 | 118 | // pluralize: false, 119 | 120 | /*************************************************************************** 121 | * * 122 | * Whether the blueprint controllers should populate model fetches with * 123 | * data from other models which are linked by associations * 124 | * * 125 | * If you have a lot of data in one-to-many associations, leaving this on * 126 | * may result in very heavy api calls * 127 | * * 128 | ***************************************************************************/ 129 | 130 | // populate: true, 131 | 132 | /**************************************************************************** 133 | * * 134 | * Whether to run Model.watch() in the find and findOne blueprint actions. * 135 | * Can be overridden on a per-model basis. * 136 | * * 137 | ****************************************************************************/ 138 | 139 | // autoWatch: true, 140 | 141 | /**************************************************************************** 142 | * * 143 | * The default number of records to show in the response from a "find" * 144 | * action. Doubles as the default size of populated arrays if populate is * 145 | * true. * 146 | * * 147 | ****************************************************************************/ 148 | 149 | // defaultLimit: 30 150 | 151 | }; 152 | -------------------------------------------------------------------------------- /tests/integration/app/config/bootstrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bootstrap 3 | * (sails.config.bootstrap) 4 | * 5 | * An asynchronous bootstrap function that runs before your Sails app gets lifted. 6 | * This gives you an opportunity to set up your data model, run jobs, or perform some special logic. 7 | * 8 | * For more information on bootstrapping your app, check out: 9 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.bootstrap.html 10 | */ 11 | 12 | module.exports.bootstrap = function(cb) { 13 | 14 | // It's very important to trigger this callback method when you are finished 15 | // with the bootstrap! (otherwise your server will never lift, since it's waiting on the bootstrap) 16 | cb(); 17 | }; 18 | -------------------------------------------------------------------------------- /tests/integration/app/config/connections.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Connections 3 | * (sails.config.connections) 4 | * 5 | * `Connections` are like "saved settings" for your adapters. What's the difference between 6 | * a connection and an adapter, you might ask? An adapter (e.g. `sails-mysql`) is generic-- 7 | * it needs some additional information to work (e.g. your database host, password, user, etc.) 8 | * A `connection` is that additional information. 9 | * 10 | * Each model must have a `connection` property (a string) which is references the name of one 11 | * of these connections. If it doesn't, the default `connection` configured in `config/models.js` 12 | * will be applied. Of course, a connection can (and usually is) shared by multiple models. 13 | * . 14 | * Note: If you're using version control, you should put your passwords/api keys 15 | * in `config/local.js`, environment variables, or use another strategy. 16 | * (this is to prevent you inadvertently sensitive credentials up to your repository.) 17 | * 18 | * For more information on configuration, check out: 19 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.connections.html 20 | */ 21 | 22 | module.exports.connections = { 23 | 24 | /*************************************************************************** 25 | * * 26 | * Local disk storage for DEVELOPMENT ONLY * 27 | * * 28 | * Installed by default. * 29 | * * 30 | ***************************************************************************/ 31 | localDiskDb: { 32 | adapter: 'sails-disk' 33 | }, 34 | 35 | /*************************************************************************** 36 | * * 37 | * MySQL is the world's most popular relational database. * 38 | * http://en.wikipedia.org/wiki/MySQL * 39 | * * 40 | * Run: npm install sails-mysql * 41 | * * 42 | ***************************************************************************/ 43 | someMysqlServer: { 44 | adapter: 'sails-mysql', 45 | host: 'YOUR_MYSQL_SERVER_HOSTNAME_OR_IP_ADDRESS', 46 | user: 'YOUR_MYSQL_USER', 47 | password: 'YOUR_MYSQL_PASSWORD', 48 | database: 'YOUR_MYSQL_DB' 49 | }, 50 | 51 | /*************************************************************************** 52 | * * 53 | * MongoDB is the leading NoSQL database. * 54 | * http://en.wikipedia.org/wiki/MongoDB * 55 | * * 56 | * Run: npm install sails-mongo * 57 | * * 58 | ***************************************************************************/ 59 | someMongodbServer: { 60 | adapter: 'sails-mongo', 61 | host: 'localhost', 62 | port: 27017, 63 | // user: 'username', 64 | // password: 'password', 65 | // database: 'your_mongo_db_name_here' 66 | }, 67 | 68 | /*************************************************************************** 69 | * * 70 | * PostgreSQL is another officially supported relational database. * 71 | * http://en.wikipedia.org/wiki/PostgreSQL * 72 | * * 73 | * Run: npm install sails-postgresql * 74 | * * 75 | * * 76 | ***************************************************************************/ 77 | somePostgresqlServer: { 78 | adapter: 'sails-postgresql', 79 | host: 'YOUR_POSTGRES_SERVER_HOSTNAME_OR_IP_ADDRESS', 80 | user: 'YOUR_POSTGRES_USER', 81 | password: 'YOUR_POSTGRES_PASSWORD', 82 | database: 'YOUR_POSTGRES_DB' 83 | } 84 | 85 | 86 | /*************************************************************************** 87 | * * 88 | * More adapters: https://github.com/balderdashy/sails * 89 | * * 90 | ***************************************************************************/ 91 | 92 | }; 93 | -------------------------------------------------------------------------------- /tests/integration/app/config/cors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cross-Origin Resource Sharing (CORS) Settings 3 | * (sails.config.cors) 4 | * 5 | * CORS is like a more modern version of JSONP-- it allows your server/API 6 | * to successfully respond to requests from client-side JavaScript code 7 | * running on some other domain (e.g. google.com) 8 | * Unlike JSONP, it works with POST, PUT, and DELETE requests 9 | * 10 | * For more information on CORS, check out: 11 | * http://en.wikipedia.org/wiki/Cross-origin_resource_sharing 12 | * 13 | * Note that any of these settings (besides 'allRoutes') can be changed on a per-route basis 14 | * by adding a "cors" object to the route configuration: 15 | * 16 | * '/get foo': { 17 | * controller: 'foo', 18 | * action: 'bar', 19 | * cors: { 20 | * origin: 'http://foobar.com,https://owlhoot.com' 21 | * } 22 | * } 23 | * 24 | * For more information on this configuration file, see: 25 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.cors.html 26 | * 27 | */ 28 | 29 | module.exports.cors = { 30 | 31 | /*************************************************************************** 32 | * * 33 | * Allow CORS on all routes by default? If not, you must enable CORS on a * 34 | * per-route basis by either adding a "cors" configuration object to the * 35 | * route config, or setting "cors:true" in the route config to use the * 36 | * default settings below. * 37 | * * 38 | ***************************************************************************/ 39 | 40 | // allRoutes: false, 41 | 42 | /*************************************************************************** 43 | * * 44 | * Which domains which are allowed CORS access? This can be a * 45 | * comma-delimited list of hosts (beginning with http:// or https://) or * 46 | * "*" to allow all domains CORS access. * 47 | * * 48 | ***************************************************************************/ 49 | 50 | // origin: '*', 51 | 52 | /*************************************************************************** 53 | * * 54 | * Allow cookies to be shared for CORS requests? * 55 | * * 56 | ***************************************************************************/ 57 | 58 | // credentials: true, 59 | 60 | /*************************************************************************** 61 | * * 62 | * Which methods should be allowed for CORS requests? This is only used in * 63 | * response to preflight requests (see article linked above for more info) * 64 | * * 65 | ***************************************************************************/ 66 | 67 | // methods: 'GET, POST, PUT, DELETE, OPTIONS, HEAD', 68 | 69 | /*************************************************************************** 70 | * * 71 | * Which headers should be allowed for CORS requests? This is only used in * 72 | * response to preflight requests. * 73 | * * 74 | ***************************************************************************/ 75 | 76 | // headers: 'content-type' 77 | 78 | }; 79 | -------------------------------------------------------------------------------- /tests/integration/app/config/csrf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cross-Site Request Forgery Protection Settings 3 | * (sails.config.csrf) 4 | * 5 | * CSRF tokens are like a tracking chip. While a session tells the server that a user 6 | * "is who they say they are", a csrf token tells the server "you are where you say you are". 7 | * 8 | * When enabled, all non-GET requests to the Sails server must be accompanied by 9 | * a special token, identified as the '_csrf' parameter. 10 | * 11 | * This option protects your Sails app against cross-site request forgery (or CSRF) attacks. 12 | * A would-be attacker needs not only a user's session cookie, but also this timestamped, 13 | * secret CSRF token, which is refreshed/granted when the user visits a URL on your app's domain. 14 | * 15 | * This allows us to have certainty that our users' requests haven't been hijacked, 16 | * and that the requests they're making are intentional and legitimate. 17 | * 18 | * This token has a short-lived expiration timeline, and must be acquired by either: 19 | * 20 | * (a) For traditional view-driven web apps: 21 | * Fetching it from one of your views, where it may be accessed as 22 | * a local variable, e.g.: 23 | *
24 | * 25 | *
26 | * 27 | * or (b) For AJAX/Socket-heavy and/or single-page apps: 28 | * Sending a GET request to the `/csrfToken` route, where it will be returned 29 | * as JSON, e.g.: 30 | * { _csrf: 'ajg4JD(JGdajhLJALHDa' } 31 | * 32 | * 33 | * Enabling this option requires managing the token in your front-end app. 34 | * For traditional web apps, it's as easy as passing the data from a view into a form action. 35 | * In AJAX/Socket-heavy apps, just send a GET request to the /csrfToken route to get a valid token. 36 | * 37 | * For more information on CSRF, check out: 38 | * http://en.wikipedia.org/wiki/Cross-site_request_forgery 39 | * 40 | * For more information on this configuration file, including info on CSRF + CORS, see: 41 | * http://beta.sailsjs.org/#/documentation/reference/sails.config/sails.config.csrf.html 42 | * 43 | */ 44 | 45 | /**************************************************************************** 46 | * * 47 | * Enabled CSRF protection for your site? * 48 | * * 49 | ****************************************************************************/ 50 | 51 | // module.exports.csrf = false; 52 | 53 | /**************************************************************************** 54 | * * 55 | * You may also specify more fine-grained settings for CSRF, including the * 56 | * domains which are allowed to request the CSRF token via AJAX. These * 57 | * settings override the general CORS settings in your config/cors.js file. * 58 | * * 59 | ****************************************************************************/ 60 | 61 | // module.exports.csrf = { 62 | // grantTokenViaAjax: true, 63 | // origin: '' 64 | // } 65 | -------------------------------------------------------------------------------- /tests/integration/app/config/db-setup.sql: -------------------------------------------------------------------------------- 1 | DROP DATABASE IF EXISTS `sails_transactions`; 2 | CREATE DATABASE `sails_transactions`; 3 | GRANT ALL ON *.* TO "root"@"localhost"; 4 | FLUSH PRIVILEGES; 5 | -------------------------------------------------------------------------------- /tests/integration/app/config/env/development.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Development environment settings 3 | * 4 | * This file can include shared settings for a development team, 5 | * such as API keys or remote database passwords. If you're using 6 | * a version control solution for your Sails app, this file will 7 | * be committed to your repository unless you add it to your .gitignore 8 | * file. If your repository will be publicly viewable, don't add 9 | * any private information to this file! 10 | * 11 | */ 12 | 13 | module.exports = { 14 | 15 | /*************************************************************************** 16 | * Set the default database connection for models in the development * 17 | * environment (see config/connections.js and config/models.js ) * 18 | ***************************************************************************/ 19 | 20 | // models: { 21 | // connection: 'someMongodbServer' 22 | // } 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /tests/integration/app/config/env/production.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Production environment settings 3 | * 4 | * This file can include shared settings for a production environment, 5 | * such as API keys or remote database passwords. If you're using 6 | * a version control solution for your Sails app, this file will 7 | * be committed to your repository unless you add it to your .gitignore 8 | * file. If your repository will be publicly viewable, don't add 9 | * any private information to this file! 10 | * 11 | */ 12 | 13 | module.exports = { 14 | 15 | /*************************************************************************** 16 | * Set the default database connection for models in the production * 17 | * environment (see config/connections.js and config/models.js ) * 18 | ***************************************************************************/ 19 | 20 | // models: { 21 | // connection: 'someMysqlServer' 22 | // }, 23 | 24 | /*************************************************************************** 25 | * Set the port in the production environment to 80 * 26 | ***************************************************************************/ 27 | 28 | // port: 80, 29 | 30 | /*************************************************************************** 31 | * Set the log level in production environment to "silent" * 32 | ***************************************************************************/ 33 | 34 | // log: { 35 | // level: "silent" 36 | // } 37 | 38 | }; 39 | -------------------------------------------------------------------------------- /tests/integration/app/config/globals.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Global Variable Configuration 3 | * (sails.config.globals) 4 | * 5 | * Configure which global variables which will be exposed 6 | * automatically by Sails. 7 | * 8 | * For more information on configuration, check out: 9 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.globals.html 10 | */ 11 | module.exports.globals = { 12 | 13 | /**************************************************************************** 14 | * * 15 | * Expose the lodash installed in Sails core as a global variable. If this * 16 | * is disabled, like any other node module you can always run npm install * 17 | * lodash --save, then var _ = require('lodash') at the top of any file. * 18 | * * 19 | ****************************************************************************/ 20 | 21 | // _: true, 22 | 23 | /**************************************************************************** 24 | * * 25 | * Expose the async installed in Sails core as a global variable. If this is * 26 | * disabled, like any other node module you can always run npm install async * 27 | * --save, then var async = require('async') at the top of any file. * 28 | * * 29 | ****************************************************************************/ 30 | 31 | // async: true, 32 | 33 | /**************************************************************************** 34 | * * 35 | * Expose the sails instance representing your app. If this is disabled, you * 36 | * can still get access via req._sails. * 37 | * * 38 | ****************************************************************************/ 39 | 40 | // sails: true, 41 | 42 | /**************************************************************************** 43 | * * 44 | * Expose each of your app's services as global variables (using their * 45 | * "globalId"). E.g. a service defined in api/models/NaturalLanguage.js * 46 | * would have a globalId of NaturalLanguage by default. If this is disabled, * 47 | * you can still access your services via sails.services.* * 48 | * * 49 | ****************************************************************************/ 50 | 51 | // services: true, 52 | 53 | /**************************************************************************** 54 | * * 55 | * Expose each of your app's models as global variables (using their * 56 | * "globalId"). E.g. a model defined in api/models/User.js would have a * 57 | * globalId of User by default. If this is disabled, you can still access * 58 | * your models via sails.models.*. * 59 | * * 60 | ****************************************************************************/ 61 | 62 | // models: true 63 | }; 64 | -------------------------------------------------------------------------------- /tests/integration/app/config/http.js: -------------------------------------------------------------------------------- 1 | /** 2 | * HTTP Server Settings 3 | * (sails.config.http) 4 | * 5 | * Configuration for the underlying HTTP server in Sails. 6 | * Only applies to HTTP requests (not WebSockets) 7 | * 8 | * For more information on configuration, check out: 9 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.http.html 10 | */ 11 | 12 | module.exports.http = { 13 | 14 | /**************************************************************************** 15 | * * 16 | * Express middleware to use for every Sails request. To add custom * 17 | * middleware to the mix, add a function to the middleware config object and * 18 | * add its key to the "order" array. The $custom key is reserved for * 19 | * backwards-compatibility with Sails v0.9.x apps that use the * 20 | * `customMiddleware` config option. * 21 | * * 22 | ****************************************************************************/ 23 | 24 | // middleware: { 25 | 26 | /*************************************************************************** 27 | * * 28 | * The order in which middleware should be run for HTTP request. (the Sails * 29 | * router is invoked by the "router" middleware below.) * 30 | * * 31 | ***************************************************************************/ 32 | 33 | // order: [ 34 | // 'startRequestTimer', 35 | // 'cookieParser', 36 | // 'session', 37 | // 'myRequestLogger', 38 | // 'bodyParser', 39 | // 'handleBodyParserError', 40 | // 'compress', 41 | // 'methodOverride', 42 | // 'poweredBy', 43 | // '$custom', 44 | // 'router', 45 | // 'www', 46 | // 'favicon', 47 | // '404', 48 | // '500' 49 | // ], 50 | 51 | /**************************************************************************** 52 | * * 53 | * Example custom middleware; logs each request to the console. * 54 | * * 55 | ****************************************************************************/ 56 | 57 | // myRequestLogger: function (req, res, next) { 58 | // console.log("Requested :: ", req.method, req.url); 59 | // return next(); 60 | // } 61 | 62 | 63 | /*************************************************************************** 64 | * * 65 | * The body parser that will handle incoming multipart HTTP requests. By * 66 | * default as of v0.10, Sails uses * 67 | * [skipper](http://github.com/balderdashy/skipper). See * 68 | * http://www.senchalabs.org/connect/multipart.html for other options. * 69 | * * 70 | ***************************************************************************/ 71 | 72 | // bodyParser: require('skipper') 73 | 74 | // }, 75 | 76 | /*************************************************************************** 77 | * * 78 | * The number of seconds to cache flat files on disk being served by * 79 | * Express static middleware (by default, these files are in `.tmp/public`) * 80 | * * 81 | * The HTTP static cache is only active in a 'production' environment, * 82 | * since that's the only time Express will cache flat-files. * 83 | * * 84 | ***************************************************************************/ 85 | 86 | // cache: 31557600000 87 | }; 88 | -------------------------------------------------------------------------------- /tests/integration/app/config/i18n.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internationalization / Localization Settings 3 | * (sails.config.i18n) 4 | * 5 | * If your app will touch people from all over the world, i18n (or internationalization) 6 | * may be an important part of your international strategy. 7 | * 8 | * 9 | * For more informationom i18n in Sails, check out: 10 | * http://sailsjs.org/#/documentation/concepts/Internationalization 11 | * 12 | * For a complete list of i18n options, see: 13 | * https://github.com/mashpie/i18n-node#list-of-configuration-options 14 | * 15 | * 16 | */ 17 | 18 | module.exports.i18n = { 19 | 20 | /*************************************************************************** 21 | * * 22 | * Which locales are supported? * 23 | * * 24 | ***************************************************************************/ 25 | 26 | // locales: ['en', 'es', 'fr', 'de'] 27 | 28 | /**************************************************************************** 29 | * * 30 | * What is the default locale for the site? Note that this setting will be * 31 | * overridden for any request that sends an "Accept-Language" header (i.e. * 32 | * most browsers), but it's still useful if you need to localize the * 33 | * response for requests made by non-browser clients (e.g. cURL). * 34 | * * 35 | ****************************************************************************/ 36 | 37 | // defaultLocale: 'en', 38 | 39 | /**************************************************************************** 40 | * * 41 | * Automatically add new keys to locale (translation) files when they are * 42 | * encountered during a request? * 43 | * * 44 | ****************************************************************************/ 45 | 46 | // updateFiles: false, 47 | 48 | /**************************************************************************** 49 | * * 50 | * Path (relative to app root) of directory to store locale (translation) * 51 | * files in. * 52 | * * 53 | ****************************************************************************/ 54 | 55 | // localesDirectory: '/config/locales' 56 | 57 | }; 58 | -------------------------------------------------------------------------------- /tests/integration/app/config/local.js: -------------------------------------------------------------------------------- 1 | var winston = require('winston'); 2 | 3 | module.exports = { 4 | 5 | // Your SSL certificate and key, if you want to be able to serve HTTP responses 6 | // over https:// and/or use websockets over the wss:// protocol 7 | // (recommended for HTTP, strongly encouraged for WebSockets) 8 | // 9 | // In this example, we'll assume you created a folder in your project, `config/ssl` 10 | // and dumped your certificate/key files there: 11 | // ssl: { 12 | // ca: require('fs').readFileSync(__dirname + './ssl/my_apps_ssl_gd_bundle.crt'), 13 | // key: require('fs').readFileSync(__dirname + './ssl/my_apps_ssl.key'), 14 | // cert: require('fs').readFileSync(__dirname + './ssl/my_apps_ssl.crt') 15 | // }, 16 | // ssl: { 17 | // key: require('fs').readFileSync(__dirname + '/ssl/sync_ssl.key'), 18 | // cert: require('fs').readFileSync(__dirname + '/ssl/sync_ssl.crt') 19 | // }, 20 | 21 | // The `port` setting determines which TCP port your app will be deployed on 22 | // Ports are a transport-layer concept designed to allow many different 23 | // networking applications run at the same time on a single computer. 24 | // More about ports: http://en.wikipedia.org/wiki/Port_(computer_networking) 25 | // 26 | // By default, if it's set, Sails uses the `PORT` environment variable. 27 | // Otherwise it falls back to port 1337. 28 | // 29 | // In production, you'll probably want to change this setting 30 | // to 80 (http://) or 443 (https://) if you have an SSL certificate 31 | 32 | port: process.env.PORT || 1337, 33 | 34 | // The runtime "environment" of your Sails app is either 'development' or 'production'. 35 | // 36 | // In development, your Sails app will go out of its way to help you 37 | // (for instance you will receive more descriptive error and debugging output)0 38 | // 39 | // In production, Sails configures itself (and its dependencies) to optimize performance. 40 | // You should always put your app in production mode before you deploy it to a server- 41 | // This helps ensure that your Sails app remains stable, performant, and scalable. 42 | // 43 | // By default, Sails sets its environment using the `NODE_ENV` environment variable. 44 | // If NODE_ENV is not set, Sails will run in the 'development' environment. 45 | 46 | environment: process.env.NODE_ENV || 'development', 47 | 48 | connections: { 49 | mySQLT: { 50 | adapter: 'sails-mysql-transactions', 51 | host: 'localhost', 52 | user: 'root', 53 | password: '', 54 | database: 'sails_transactions', 55 | 56 | queryCaseSensitive: true, 57 | transactionConnectionLimit: 20, 58 | rollbackTransactionOnError: false, 59 | 60 | replication: { 61 | enabled: true, 62 | inheritMaster: true, 63 | 64 | sources: { 65 | set1: { 66 | enabled: false, 67 | host: 'localhost', 68 | user: 'root', 69 | password: '' 70 | }, 71 | set2: { 72 | host: 'localhost', 73 | user: 'root', 74 | password: '' 75 | } 76 | } 77 | } 78 | } 79 | }, 80 | 81 | models: { 82 | connection: 'mySQLT', 83 | migrate: 'drop' 84 | }, 85 | 86 | log: { 87 | 'level': 'info', 88 | 'custom': new (winston.Logger)({ 89 | 'transports': [ 90 | new (winston.transports.Console)({ 91 | 'level': 'verbose', 92 | 'colorize': true, 93 | 'timestamp': true, 94 | 'json': false 95 | }) 96 | ] 97 | }) 98 | } 99 | 100 | }; 101 | -------------------------------------------------------------------------------- /tests/integration/app/config/local.js.sample: -------------------------------------------------------------------------------- 1 | var winston = require('winston'); 2 | 3 | module.exports = { 4 | 5 | // Your SSL certificate and key, if you want to be able to serve HTTP responses 6 | // over https:// and/or use websockets over the wss:// protocol 7 | // (recommended for HTTP, strongly encouraged for WebSockets) 8 | // 9 | // In this example, we'll assume you created a folder in your project, `config/ssl` 10 | // and dumped your certificate/key files there: 11 | // ssl: { 12 | // ca: require('fs').readFileSync(__dirname + './ssl/my_apps_ssl_gd_bundle.crt'), 13 | // key: require('fs').readFileSync(__dirname + './ssl/my_apps_ssl.key'), 14 | // cert: require('fs').readFileSync(__dirname + './ssl/my_apps_ssl.crt') 15 | // }, 16 | // ssl: { 17 | // key: require('fs').readFileSync(__dirname + '/ssl/sync_ssl.key'), 18 | // cert: require('fs').readFileSync(__dirname + '/ssl/sync_ssl.crt') 19 | // }, 20 | 21 | // The `port` setting determines which TCP port your app will be deployed on 22 | // Ports are a transport-layer concept designed to allow many different 23 | // networking applications run at the same time on a single computer. 24 | // More about ports: http://en.wikipedia.org/wiki/Port_(computer_networking) 25 | // 26 | // By default, if it's set, Sails uses the `PORT` environment variable. 27 | // Otherwise it falls back to port 1337. 28 | // 29 | // In production, you'll probably want to change this setting 30 | // to 80 (http://) or 443 (https://) if you have an SSL certificate 31 | 32 | port: process.env.PORT || 1337, 33 | 34 | // The runtime "environment" of your Sails app is either 'development' or 'production'. 35 | // 36 | // In development, your Sails app will go out of its way to help you 37 | // (for instance you will receive more descriptive error and debugging output)0 38 | // 39 | // In production, Sails configures itself (and its dependencies) to optimize performance. 40 | // You should always put your app in production mode before you deploy it to a server- 41 | // This helps ensure that your Sails app remains stable, performant, and scalable. 42 | // 43 | // By default, Sails sets its environment using the `NODE_ENV` environment variable. 44 | // If NODE_ENV is not set, Sails will run in the 'development' environment. 45 | 46 | environment: process.env.NODE_ENV || 'development', 47 | 48 | connections: { 49 | mySQL: { 50 | adapter: 'sails-mysql', 51 | host: 'localhost', 52 | user: 'root', 53 | password: 'zeppelin', 54 | database: 'sails_transactions' 55 | } 56 | }, 57 | 58 | models: { 59 | connection: 'mySQL', 60 | migrate: 'alter' 61 | }, 62 | 63 | log: { 64 | 'level': 'info', 65 | 'custom': new (winston.Logger)({ 66 | 'transports': [ 67 | new (winston.transports.Console)({ 68 | 'level': 'verbose', 69 | 'colorize': true, 70 | 'timestamp': true, 71 | 'json': false 72 | }) 73 | ] 74 | }) 75 | } 76 | 77 | }; 78 | -------------------------------------------------------------------------------- /tests/integration/app/config/locales/_README.md: -------------------------------------------------------------------------------- 1 | # Internationalization / Localization Settings 2 | 3 | > Also see the official docs on internationalization/localization: 4 | > http://links.sailsjs.org/docs/config/locales 5 | 6 | ## Locales 7 | All locale files live under `config/locales`. Here is where you can add translations 8 | as JSON key-value pairs. The name of the file should match the language that you are supporting, which allows for automatic language detection based on request headers. 9 | 10 | Here is an example locale stringfile for the Spanish language (`config/locales/es.json`): 11 | ```json 12 | { 13 | "Hello!": "Hola!", 14 | "Hello %s, how are you today?": "¿Hola %s, como estas?", 15 | } 16 | ``` 17 | ## Usage 18 | Locales can be accessed in controllers/policies through `res.i18n()`, or in views through the `__(key)` or `i18n(key)` functions. 19 | Remember that the keys are case sensitive and require exact key matches, e.g. 20 | 21 | ```ejs 22 |

<%= __('Welcome to PencilPals!') %>

23 |

<%= i18n('Hello %s, how are you today?', 'Pencil Maven') %>

24 |

<%= i18n('That\'s right-- you can use either i18n() or __()') %>

25 | ``` 26 | 27 | ## Configuration 28 | Localization/internationalization config can be found in `config/i18n.js`, from where you can set your supported locales. 29 | -------------------------------------------------------------------------------- /tests/integration/app/config/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Willkommen", 3 | "A brand new app.": "Eine neue App." 4 | } 5 | -------------------------------------------------------------------------------- /tests/integration/app/config/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Welcome", 3 | "A brand new app.": "A brand new app." 4 | } 5 | -------------------------------------------------------------------------------- /tests/integration/app/config/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Bienvenido", 3 | "A brand new app.": "Una aplicación de la nueva marca." 4 | } 5 | -------------------------------------------------------------------------------- /tests/integration/app/config/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Bienvenue", 3 | "A brand new app.": "Une toute nouvelle application." 4 | } 5 | -------------------------------------------------------------------------------- /tests/integration/app/config/log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Built-in Log Configuration 3 | * (sails.config.log) 4 | * 5 | * Configure the log level for your app, as well as the transport 6 | * (Underneath the covers, Sails uses Winston for logging, which 7 | * allows for some pretty neat custom transports/adapters for log messages) 8 | * 9 | * For more information on the Sails logger, check out: 10 | * http://sailsjs.org/#/documentation/concepts/Logging 11 | */ 12 | 13 | module.exports.log = { 14 | 15 | /*************************************************************************** 16 | * * 17 | * Valid `level` configs: i.e. the minimum log level to capture with * 18 | * sails.log.*() * 19 | * * 20 | * The order of precedence for log levels from lowest to highest is: * 21 | * silly, verbose, info, debug, warn, error * 22 | * * 23 | * You may also set the level to "silent" to suppress all logs. * 24 | * * 25 | ***************************************************************************/ 26 | 27 | // level: 'info' 28 | 29 | }; 30 | -------------------------------------------------------------------------------- /tests/integration/app/config/models.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Default model configuration 3 | * (sails.config.models) 4 | * 5 | * Unless you override them, the following properties will be included 6 | * in each of your models. 7 | * 8 | * For more info on Sails models, see: 9 | * http://sailsjs.org/#/documentation/concepts/ORM 10 | */ 11 | 12 | module.exports.models = { 13 | 14 | /*************************************************************************** 15 | * * 16 | * Your app's default connection. i.e. the name of one of your app's * 17 | * connections (see `config/connections.js`) * 18 | * * 19 | ***************************************************************************/ 20 | // connection: 'localDiskDb', 21 | 22 | /*************************************************************************** 23 | * * 24 | * How and whether Sails will attempt to automatically rebuild the * 25 | * tables/collections/etc. in your schema. * 26 | * * 27 | * See http://sailsjs.org/#/documentation/concepts/ORM/model-settings.html * 28 | * * 29 | ***************************************************************************/ 30 | // migrate: 'alter' 31 | 32 | }; 33 | -------------------------------------------------------------------------------- /tests/integration/app/config/policies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Policy Mappings 3 | * (sails.config.policies) 4 | * 5 | * Policies are simple functions which run **before** your controllers. 6 | * You can apply one or more policies to a given controller, or protect 7 | * its actions individually. 8 | * 9 | * Any policy file (e.g. `api/policies/authenticated.js`) can be accessed 10 | * below by its filename, minus the extension, (e.g. "authenticated") 11 | * 12 | * For more information on how policies work, see: 13 | * http://sailsjs.org/#/documentation/concepts/Policies 14 | * 15 | * For more information on configuring policies, check out: 16 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.policies.html 17 | */ 18 | 19 | 20 | module.exports.policies = { 21 | 22 | /*************************************************************************** 23 | * * 24 | * Default policy for all controllers and actions (`true` allows public * 25 | * access) * 26 | * * 27 | ***************************************************************************/ 28 | 29 | // '*': true, 30 | 31 | /*************************************************************************** 32 | * * 33 | * Here's an example of mapping some policies to run before a controller * 34 | * and its actions * 35 | * * 36 | ***************************************************************************/ 37 | // RabbitController: { 38 | 39 | // Apply the `false` policy as the default for all of RabbitController's actions 40 | // (`false` prevents all access, which ensures that nothing bad happens to our rabbits) 41 | // '*': false, 42 | 43 | // For the action `nurture`, apply the 'isRabbitMother' policy 44 | // (this overrides `false` above) 45 | // nurture : 'isRabbitMother', 46 | 47 | // Apply the `isNiceToAnimals` AND `hasRabbitFood` policies 48 | // before letting any users feed our rabbits 49 | // feed : ['isNiceToAnimals', 'hasRabbitFood'] 50 | // } 51 | }; 52 | -------------------------------------------------------------------------------- /tests/integration/app/config/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Route Mappings 3 | * (sails.config.routes) 4 | * 5 | * Your routes map URLs to views and controllers. 6 | * 7 | * If Sails receives a URL that doesn't match any of the routes below, 8 | * it will check for matching files (images, scripts, stylesheets, etc.) 9 | * in your assets directory. e.g. `http://localhost:1337/images/foo.jpg` 10 | * might match an image file: `/assets/images/foo.jpg` 11 | * 12 | * Finally, if those don't match either, the default 404 handler is triggered. 13 | * See `api/responses/notFound.js` to adjust your app's 404 logic. 14 | * 15 | * Note: Sails doesn't ACTUALLY serve stuff from `assets`-- the default Gruntfile in Sails copies 16 | * flat files from `assets` to `.tmp/public`. This allows you to do things like compile LESS or 17 | * CoffeeScript for the front-end. 18 | * 19 | * For more information on configuring custom routes, check out: 20 | * http://sailsjs.org/#/documentation/concepts/Routes/RouteTargetSyntax.html 21 | */ 22 | 23 | module.exports.routes = { 24 | 25 | /*************************************************************************** 26 | * * 27 | * Make the view located at `views/homepage.ejs` (or `views/homepage.jade`, * 28 | * etc. depending on your default view engine) your home page. * 29 | * * 30 | * (Alternatively, remove this and add an `index.html` file in your * 31 | * `assets` directory) * 32 | * * 33 | ***************************************************************************/ 34 | 35 | '/': { 36 | view: 'homepage' 37 | } 38 | 39 | /*************************************************************************** 40 | * * 41 | * Custom routes here... * 42 | * * 43 | * If a request to a URL doesn't match any of the custom routes above, it * 44 | * is matched against Sails route blueprints. See `config/blueprints.js` * 45 | * for configuration options and examples. * 46 | * * 47 | ***************************************************************************/ 48 | 49 | }; 50 | -------------------------------------------------------------------------------- /tests/integration/app/config/session.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Session Configuration 3 | * (sails.config.session) 4 | * 5 | * Sails session integration leans heavily on the great work already done by 6 | * Express, but also unifies Socket.io with the Connect session store. It uses 7 | * Connect's cookie parser to normalize configuration differences between Express 8 | * and Socket.io and hooks into Sails' middleware interpreter to allow you to access 9 | * and auto-save to `req.session` with Socket.io the same way you would with Express. 10 | * 11 | * For more information on configuring the session, check out: 12 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.session.html 13 | */ 14 | 15 | module.exports.session = { 16 | 17 | /*************************************************************************** 18 | * * 19 | * Session secret is automatically generated when your new app is created * 20 | * Replace at your own risk in production-- you will invalidate the cookies * 21 | * of your users, forcing them to log in again. * 22 | * * 23 | ***************************************************************************/ 24 | secret: '200f282943850a87c6568ca740409999', 25 | 26 | 27 | /*************************************************************************** 28 | * * 29 | * Set the session cookie expire time The maxAge is set by milliseconds, * 30 | * the example below is for 24 hours * 31 | * * 32 | ***************************************************************************/ 33 | 34 | // cookie: { 35 | // maxAge: 24 * 60 * 60 * 1000 36 | // }, 37 | 38 | /*************************************************************************** 39 | * * 40 | * In production, uncomment the following lines to set up a shared redis * 41 | * session store that can be shared across multiple Sails.js servers * 42 | ***************************************************************************/ 43 | 44 | // adapter: 'redis', 45 | 46 | /*************************************************************************** 47 | * * 48 | * The following values are optional, if no options are set a redis * 49 | * instance running on localhost is expected. Read more about options at: * 50 | * https://github.com/visionmedia/connect-redis * 51 | * * 52 | * * 53 | ***************************************************************************/ 54 | 55 | // host: 'localhost', 56 | // port: 6379, 57 | // ttl: , 58 | // db: 0, 59 | // pass: , 60 | // prefix: 'sess:', 61 | 62 | 63 | /*************************************************************************** 64 | * * 65 | * Uncomment the following lines to use your Mongo adapter as a session * 66 | * store * 67 | * * 68 | ***************************************************************************/ 69 | 70 | // adapter: 'mongo', 71 | // host: 'localhost', 72 | // port: 27017, 73 | // db: 'sails', 74 | // collection: 'sessions', 75 | 76 | /*************************************************************************** 77 | * * 78 | * Optional Values: * 79 | * * 80 | * # Note: url will override other connection settings url: * 81 | * 'mongodb://user:pass@host:port/database/collection', * 82 | * * 83 | ***************************************************************************/ 84 | 85 | // username: '', 86 | // password: '', 87 | // auto_reconnect: false, 88 | // ssl: false, 89 | // stringify: true 90 | 91 | }; 92 | -------------------------------------------------------------------------------- /tests/integration/app/config/sockets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WebSocket Server Settings 3 | * (sails.config.sockets) 4 | * 5 | * These settings provide transparent access to the options for Sails' 6 | * encapsulated WebSocket server, as well as some additional Sails-specific 7 | * configuration layered on top. 8 | * 9 | * For more information on sockets configuration, including advanced config options, see: 10 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.sockets.html 11 | */ 12 | 13 | module.exports.sockets = { 14 | 15 | 16 | /*************************************************************************** 17 | * * 18 | * Node.js (and consequently Sails.js) apps scale horizontally. It's a * 19 | * powerful, efficient approach, but it involves a tiny bit of planning. At * 20 | * scale, you'll want to be able to copy your app onto multiple Sails.js * 21 | * servers and throw them behind a load balancer. * 22 | * * 23 | * One of the big challenges of scaling an application is that these sorts * 24 | * of clustered deployments cannot share memory, since they are on * 25 | * physically different machines. On top of that, there is no guarantee * 26 | * that a user will "stick" with the same server between requests (whether * 27 | * HTTP or sockets), since the load balancer will route each request to the * 28 | * Sails server with the most available resources. However that means that * 29 | * all room/pubsub/socket processing and shared memory has to be offloaded * 30 | * to a shared, remote messaging queue (usually Redis) * 31 | * * 32 | * Luckily, Socket.io (and consequently Sails.js) apps support Redis for * 33 | * sockets by default. To enable a remote redis pubsub server, uncomment * 34 | * the config below. * 35 | * * 36 | * Worth mentioning is that, if `adapter` config is `redis`, but host/port * 37 | * is left unset, Sails will try to connect to redis running on localhost * 38 | * via port 6379 * 39 | * * 40 | ***************************************************************************/ 41 | // adapter: 'memory', 42 | 43 | // 44 | // -OR- 45 | // 46 | 47 | // adapter: 'redis', 48 | // host: '127.0.0.1', 49 | // port: 6379, 50 | // db: 'sails', 51 | // pass: '' 52 | 53 | 54 | 55 | /*************************************************************************** 56 | * * 57 | * Whether to expose a 'get /__getcookie' route with CORS support that sets * 58 | * a cookie (this is used by the sails.io.js socket client to get access to * 59 | * a 3rd party cookie and to enable sessions). * 60 | * * 61 | * Warning: Currently in this scenario, CORS settings apply to interpreted * 62 | * requests sent via a socket.io connection that used this cookie to * 63 | * connect, even for non-browser clients! (e.g. iOS apps, toasters, node.js * 64 | * unit tests) * 65 | * * 66 | ***************************************************************************/ 67 | 68 | // grant3rdPartyCookie: true, 69 | 70 | 71 | 72 | /*************************************************************************** 73 | * * 74 | * `beforeConnect` * 75 | * * 76 | * This custom beforeConnect function will be run each time BEFORE a new * 77 | * socket is allowed to connect, when the initial socket.io handshake is * 78 | * performed with the server. * 79 | * * 80 | * By default, when a socket tries to connect, Sails allows it, every time. * 81 | * (much in the same way any HTTP request is allowed to reach your routes. * 82 | * If no valid cookie was sent, a temporary session will be created for the * 83 | * connecting socket. * 84 | * * 85 | * If the cookie sent as part of the connetion request doesn't match any * 86 | * known user session, a new user session is created for it. * 87 | * * 88 | * In most cases, the user would already have a cookie since they loaded * 89 | * the socket.io client and the initial HTML pageyou're building. * 90 | * * 91 | * However, in the case of cross-domain requests, it is possible to receive * 92 | * a connection upgrade request WITHOUT A COOKIE (for certain transports) * 93 | * In this case, there is no way to keep track of the requesting user * 94 | * between requests, since there is no identifying information to link * 95 | * him/her with a session. The sails.io.js client solves this by connecting * 96 | * to a CORS/jsonp endpoint first to get a 3rd party cookie(fortunately this* 97 | * works, even in Safari), then opening the connection. * 98 | * * 99 | * You can also pass along a ?cookie query parameter to the upgrade url, * 100 | * which Sails will use in the absense of a proper cookie e.g. (when * 101 | * connecting from the client): * 102 | * io.sails.connect('http://localhost:1337?cookie=smokeybear') * 103 | * * 104 | * Finally note that the user's cookie is NOT (and will never be) accessible* 105 | * from client-side javascript. Using HTTP-only cookies is crucial for your * 106 | * app's security. * 107 | * * 108 | ***************************************************************************/ 109 | // beforeConnect: function(handshake, cb) { 110 | // // `true` allows the connection 111 | // return cb(null, true); 112 | // 113 | // // (`false` would reject the connection) 114 | // }, 115 | 116 | 117 | /*************************************************************************** 118 | * * 119 | * This custom afterDisconnect function will be run each time a socket * 120 | * disconnects * 121 | * * 122 | ***************************************************************************/ 123 | // afterDisconnect: function(session, socket, cb) { 124 | // // By default: do nothing. 125 | // return cb(); 126 | // }, 127 | 128 | 129 | 130 | 131 | 132 | // More configuration options for Sails+Socket.io: 133 | // http://sailsjs.org/#/documentation/reference/sails.config/sails.config.sockets.html 134 | 135 | }; 136 | -------------------------------------------------------------------------------- /tests/integration/app/config/views.js: -------------------------------------------------------------------------------- 1 | /** 2 | * View Engine Configuration 3 | * (sails.config.views) 4 | * 5 | * Server-sent views are a classic and effective way to get your app up 6 | * and running. Views are normally served from controllers. Below, you can 7 | * configure your templating language/framework of choice and configure 8 | * Sails' layout support. 9 | * 10 | * For more information on views and layouts, check out: 11 | * http://sailsjs.org/#/documentation/concepts/Views 12 | */ 13 | 14 | module.exports.views = { 15 | 16 | /**************************************************************************** 17 | * * 18 | * View engine (aka template language) to use for your app's *server-side* * 19 | * views * 20 | * * 21 | * Sails+Express supports all view engines which implement TJ Holowaychuk's * 22 | * `consolidate.js`, including, but not limited to: * 23 | * * 24 | * ejs, jade, handlebars, mustache underscore, hogan, haml, haml-coffee, * 25 | * dust atpl, eco, ect, jazz, jqtpl, JUST, liquor, QEJS, swig, templayed, * 26 | * toffee, walrus, & whiskers * 27 | * * 28 | * For more options, check out the docs: * 29 | * https://github.com/balderdashy/sails-wiki/blob/0.9/config.views.md#engine * 30 | * * 31 | ****************************************************************************/ 32 | 33 | engine: 'ejs', 34 | 35 | 36 | /**************************************************************************** 37 | * * 38 | * Layouts are simply top-level HTML templates you can use as wrappers for * 39 | * your server-side views. If you're using ejs or jade, you can take * 40 | * advantage of Sails' built-in `layout` support. * 41 | * * 42 | * When using a layout, when one of your views is served, it is injected * 43 | * into the `body` partial defined in the layout. This lets you reuse header * 44 | * and footer logic between views. * 45 | * * 46 | * NOTE: Layout support is only implemented for the `ejs` view engine! * 47 | * For most other engines, it is not necessary, since they implement * 48 | * partials/layouts themselves. In those cases, this config will be * 49 | * silently ignored. * 50 | * * 51 | * The `layout` setting may be set to one of the following: * 52 | * * 53 | * If `false`, layouts will be disabled. Otherwise, if a string is * 54 | * specified, it will be interpreted as the relative path to your layout * 55 | * file from `views/` folder. (the file extension, ".ejs", should be * 56 | * omitted) * 57 | * * 58 | ****************************************************************************/ 59 | 60 | /**************************************************************************** 61 | * * 62 | * Using Multiple Layouts * 63 | * * 64 | * If you're using the default `ejs` or `handlebars` Sails supports the use * 65 | * of multiple `layout` files. To take advantage of this, before rendering a * 66 | * view, override the `layout` local in your controller by setting * 67 | * `res.locals.layout`. (this is handy if you parts of your app's UI look * 68 | * completely different from each other) * 69 | * * 70 | * e.g. your default might be * 71 | * layout: 'layouts/public' * 72 | * * 73 | * But you might override that in some of your controllers with: * 74 | * layout: 'layouts/internal' * 75 | * * 76 | ****************************************************************************/ 77 | 78 | layout: 'layout', 79 | 80 | /**************************************************************************** 81 | * * 82 | * Partials are simply top-level snippets you can leverage to reuse template * 83 | * for your server-side views. If you're using handlebars, you can take * 84 | * advantage of Sails' built-in `partials` support. * 85 | * * 86 | * If `false` or empty partials will be located in the same folder as views. * 87 | * Otherwise, if a string is specified, it will be interpreted as the * 88 | * relative path to your partial files from `views/` folder. * 89 | * * 90 | ****************************************************************************/ 91 | 92 | partials: false 93 | 94 | 95 | }; -------------------------------------------------------------------------------- /tests/integration/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sails-mysql-transaction-integration", 3 | "private": true, 4 | "version": "0.0.0", 5 | "description": "a Sails application", 6 | "keywords": [], 7 | "dependencies": { 8 | "ejs": "~0.8.4", 9 | "grunt": "0.4.2", 10 | "grunt-cli": "^0.1.13", 11 | "grunt-contrib-clean": "~0.5.0", 12 | "grunt-contrib-coffee": "~0.10.1", 13 | "grunt-contrib-concat": "~0.3.0", 14 | "grunt-contrib-copy": "~0.5.0", 15 | "grunt-contrib-cssmin": "~0.9.0", 16 | "grunt-contrib-jst": "~0.6.0", 17 | "grunt-contrib-less": "0.11.1", 18 | "grunt-contrib-uglify": "~0.4.0", 19 | "grunt-contrib-watch": "~0.5.3", 20 | "grunt-sails-linker": "~0.9.5", 21 | "grunt-sync": "~0.0.4", 22 | "include-all": "~0.1.3", 23 | "rc": "~1.1.6", 24 | "sails": "0.11.5", 25 | "sails-disk": "~0.10.8", 26 | "winston": "2.1.1" 27 | }, 28 | "scripts": { 29 | "postinstall": "npm install ../../../;", 30 | "start": "pm2 start app.js", 31 | "stop": "pm2 delete app.js", 32 | "debug": "node debug app.js" 33 | }, 34 | "main": "app.js", 35 | "license": "No License", 36 | "devDependencies": { 37 | "pm2": "^1.1.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/README.md: -------------------------------------------------------------------------------- 1 | # About the `tasks` folder 2 | 3 | The `tasks` directory is a suite of Grunt tasks and their configurations, bundled for your convenience. The Grunt integration is mainly useful for bundling front-end assets, (like stylesheets, scripts, & markup templates) but it can also be used to run all kinds of development tasks, from browserify compilation to database migrations. 4 | 5 | If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, read on! 6 | 7 | 8 | ### How does this work? 9 | 10 | The asset pipeline bundled in Sails is a set of Grunt tasks configured with conventional defaults designed to make your project more consistent and productive. 11 | 12 | The entire front-end asset workflow in Sails is completely customizable-- while it provides some suggestions out of the box, Sails makes no pretense that it can anticipate all of the needs you'll encounter building the browser-based/front-end portion of your application. Who's to say you're even building an app for a browser? 13 | 14 | 15 | 16 | ### What tasks does Sails run automatically? 17 | 18 | Sails runs some of these tasks (the ones in the `tasks/register` folder) automatically when you run certain commands. 19 | 20 | ###### `sails lift` 21 | 22 | Runs the `default` task (`tasks/register/default.js`). 23 | 24 | ###### `sails lift --prod` 25 | 26 | Runs the `prod` task (`tasks/register/prod.js`). 27 | 28 | ###### `sails www` 29 | 30 | Runs the `build` task (`tasks/register/build.js`). 31 | 32 | ###### `sails www --prod` (production) 33 | 34 | Runs the `buildProd` task (`tasks/register/buildProd.js`). 35 | 36 | 37 | ### Can I customize this for SASS, Angular, client-side Jade templates, etc? 38 | 39 | You can modify, omit, or replace any of these Grunt tasks to fit your requirements. You can also add your own Grunt tasks- just add a `someTask.js` file in the `grunt/config` directory to configure the new task, then register it with the appropriate parent task(s) (see files in `grunt/register/*.js`). 40 | 41 | 42 | ### Do I have to use Grunt? 43 | 44 | Nope! To disable Grunt integration in Sails, just delete your Gruntfile or disable the Grunt hook. 45 | 46 | 47 | ### What if I'm not building a web frontend? 48 | 49 | That's ok! A core tenant of Sails is client-agnosticism-- it's especially designed for building APIs used by all sorts of clients; native Android/iOS/Cordova, serverside SDKs, etc. 50 | 51 | You can completely disable Grunt by following the instructions above. 52 | 53 | If you still want to use Grunt for other purposes, but don't want any of the default web front-end stuff, just delete your project's `assets` folder and remove the front-end oriented tasks from the `grunt/register` and `grunt/config` folders. You can also run `sails new myCoolApi --no-frontend` to omit the `assets` folder and front-end-oriented Grunt tasks for future projects. You can also replace your `sails-generate-frontend` module with alternative community generators, or create your own. This allows `sails new` to create the boilerplate for native iOS apps, Android apps, Cordova apps, SteroidsJS apps, etc. 54 | 55 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/config/clean.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Clean files and folders. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This grunt task is configured to clean out the contents in the .tmp/public of your 7 | * sails project. 8 | * 9 | * For usage docs see: 10 | * https://github.com/gruntjs/grunt-contrib-clean 11 | */ 12 | module.exports = function(grunt) { 13 | 14 | grunt.config.set('clean', { 15 | dev: ['.tmp/public/**'], 16 | build: ['www'] 17 | }); 18 | 19 | grunt.loadNpmTasks('grunt-contrib-clean'); 20 | }; 21 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/config/coffee.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Compile CoffeeScript files to JavaScript. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Compiles coffeeScript files from `assest/js` into Javascript and places them into 7 | * `.tmp/public/js` directory. 8 | * 9 | * For usage docs see: 10 | * https://github.com/gruntjs/grunt-contrib-coffee 11 | */ 12 | module.exports = function(grunt) { 13 | 14 | grunt.config.set('coffee', { 15 | dev: { 16 | options: { 17 | bare: true, 18 | sourceMap: true, 19 | sourceRoot: './' 20 | }, 21 | files: [{ 22 | expand: true, 23 | cwd: 'assets/js/', 24 | src: ['**/*.coffee'], 25 | dest: '.tmp/public/js/', 26 | ext: '.js' 27 | }, { 28 | expand: true, 29 | cwd: 'assets/js/', 30 | src: ['**/*.coffee'], 31 | dest: '.tmp/public/js/', 32 | ext: '.js' 33 | }] 34 | } 35 | }); 36 | 37 | grunt.loadNpmTasks('grunt-contrib-coffee'); 38 | }; 39 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/config/concat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Concatenate files. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Concatenates files javascript and css from a defined array. Creates concatenated files in 7 | * .tmp/public/contact directory 8 | * [concat](https://github.com/gruntjs/grunt-contrib-concat) 9 | * 10 | * For usage docs see: 11 | * https://github.com/gruntjs/grunt-contrib-concat 12 | */ 13 | module.exports = function(grunt) { 14 | 15 | grunt.config.set('concat', { 16 | js: { 17 | src: require('../pipeline').jsFilesToInject, 18 | dest: '.tmp/public/concat/production.js' 19 | }, 20 | css: { 21 | src: require('../pipeline').cssFilesToInject, 22 | dest: '.tmp/public/concat/production.css' 23 | } 24 | }); 25 | 26 | grunt.loadNpmTasks('grunt-contrib-concat'); 27 | }; 28 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/config/copy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copy files and folders. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * # dev task config 7 | * Copies all directories and files, exept coffescript and less fiels, from the sails 8 | * assets folder into the .tmp/public directory. 9 | * 10 | * # build task config 11 | * Copies all directories nd files from the .tmp/public directory into a www directory. 12 | * 13 | * For usage docs see: 14 | * https://github.com/gruntjs/grunt-contrib-copy 15 | */ 16 | module.exports = function(grunt) { 17 | 18 | grunt.config.set('copy', { 19 | dev: { 20 | files: [{ 21 | expand: true, 22 | cwd: './assets', 23 | src: ['**/*.!(coffee|less)'], 24 | dest: '.tmp/public' 25 | }] 26 | }, 27 | build: { 28 | files: [{ 29 | expand: true, 30 | cwd: '.tmp/public', 31 | src: ['**/*'], 32 | dest: 'www' 33 | }] 34 | } 35 | }); 36 | 37 | grunt.loadNpmTasks('grunt-contrib-copy'); 38 | }; 39 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/config/cssmin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Compress CSS files. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Minifies css files and places them into .tmp/public/min directory. 7 | * 8 | * For usage docs see: 9 | * https://github.com/gruntjs/grunt-contrib-cssmin 10 | */ 11 | module.exports = function(grunt) { 12 | 13 | grunt.config.set('cssmin', { 14 | dist: { 15 | src: ['.tmp/public/concat/production.css'], 16 | dest: '.tmp/public/min/production.min.css' 17 | } 18 | }); 19 | 20 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 21 | }; 22 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/config/jst.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Precompiles Underscore templates to a `.jst` file. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * (i.e. basically it takes HTML files and turns them into tiny little 7 | * javascript functions that you pass data to and return HTML. This can 8 | * speed up template rendering on the client, and reduce bandwidth usage.) 9 | * 10 | * For usage docs see: 11 | * https://github.com/gruntjs/grunt-contrib-jst 12 | * 13 | */ 14 | 15 | module.exports = function(grunt) { 16 | 17 | var templateFilesToInject = [ 18 | 'templates/**/*.html' 19 | ]; 20 | 21 | grunt.config.set('jst', { 22 | dev: { 23 | 24 | // To use other sorts of templates, specify a regexp like the example below: 25 | // options: { 26 | // templateSettings: { 27 | // interpolate: /\{\{(.+?)\}\}/g 28 | // } 29 | // }, 30 | 31 | // Note that the interpolate setting above is simply an example of overwriting lodash's 32 | // default interpolation. If you want to parse templates with the default _.template behavior 33 | // (i.e. using
), there's no need to overwrite `templateSettings.interpolate`. 34 | 35 | 36 | files: { 37 | // e.g. 38 | // 'relative/path/from/gruntfile/to/compiled/template/destination' : ['relative/path/to/sourcefiles/**/*.html'] 39 | '.tmp/public/jst.js': require('../pipeline').templateFilesToInject 40 | } 41 | } 42 | }); 43 | 44 | grunt.loadNpmTasks('grunt-contrib-jst'); 45 | }; 46 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/config/less.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Compiles LESS files into CSS. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Only the `assets/styles/importer.less` is compiled. 7 | * This allows you to control the ordering yourself, i.e. import your 8 | * dependencies, mixins, variables, resets, etc. before other stylesheets) 9 | * 10 | * For usage docs see: 11 | * https://github.com/gruntjs/grunt-contrib-less 12 | */ 13 | module.exports = function(grunt) { 14 | 15 | grunt.config.set('less', { 16 | dev: { 17 | files: [{ 18 | expand: true, 19 | cwd: 'assets/styles/', 20 | src: ['importer.less'], 21 | dest: '.tmp/public/styles/', 22 | ext: '.css' 23 | }] 24 | } 25 | }); 26 | 27 | grunt.loadNpmTasks('grunt-contrib-less'); 28 | }; 29 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/config/runforever.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Run server 3 | * 4 | * --------------------------------------------------------------- 5 | */ 6 | module.exports = function(grunt) { 7 | 8 | grunt.config.set('forever', { 9 | server: { 10 | options: { 11 | index: 'app.js', 12 | logDir: 'logs' 13 | } 14 | } 15 | }); 16 | 17 | grunt.loadNpmTasks('grunt-forever'); 18 | }; 19 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/config/sails-linker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Autoinsert script tags (or other filebased tags) in an html file. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Automatically inject ', 22 | appRoot: '.tmp/public' 23 | }, 24 | files: { 25 | '.tmp/public/**/*.html': require('../pipeline').jsFilesToInject, 26 | 'views/**/*.html': require('../pipeline').jsFilesToInject, 27 | 'views/**/*.ejs': require('../pipeline').jsFilesToInject 28 | } 29 | }, 30 | 31 | devJsRelative: { 32 | options: { 33 | startTag: '', 34 | endTag: '', 35 | fileTmpl: '', 36 | appRoot: '.tmp/public', 37 | relative: true 38 | }, 39 | files: { 40 | '.tmp/public/**/*.html': require('../pipeline').jsFilesToInject, 41 | 'views/**/*.html': require('../pipeline').jsFilesToInject, 42 | 'views/**/*.ejs': require('../pipeline').jsFilesToInject 43 | } 44 | }, 45 | 46 | prodJs: { 47 | options: { 48 | startTag: '', 49 | endTag: '', 50 | fileTmpl: '', 51 | appRoot: '.tmp/public' 52 | }, 53 | files: { 54 | '.tmp/public/**/*.html': ['.tmp/public/min/production.min.js'], 55 | 'views/**/*.html': ['.tmp/public/min/production.min.js'], 56 | 'views/**/*.ejs': ['.tmp/public/min/production.min.js'] 57 | } 58 | }, 59 | 60 | prodJsRelative: { 61 | options: { 62 | startTag: '', 63 | endTag: '', 64 | fileTmpl: '', 65 | appRoot: '.tmp/public', 66 | relative: true 67 | }, 68 | files: { 69 | '.tmp/public/**/*.html': ['.tmp/public/min/production.min.js'], 70 | 'views/**/*.html': ['.tmp/public/min/production.min.js'], 71 | 'views/**/*.ejs': ['.tmp/public/min/production.min.js'] 72 | } 73 | }, 74 | 75 | devStyles: { 76 | options: { 77 | startTag: '', 78 | endTag: '', 79 | fileTmpl: '', 80 | appRoot: '.tmp/public' 81 | }, 82 | 83 | files: { 84 | '.tmp/public/**/*.html': require('../pipeline').cssFilesToInject, 85 | 'views/**/*.html': require('../pipeline').cssFilesToInject, 86 | 'views/**/*.ejs': require('../pipeline').cssFilesToInject 87 | } 88 | }, 89 | 90 | devStylesRelative: { 91 | options: { 92 | startTag: '', 93 | endTag: '', 94 | fileTmpl: '', 95 | appRoot: '.tmp/public', 96 | relative: true 97 | }, 98 | 99 | files: { 100 | '.tmp/public/**/*.html': require('../pipeline').cssFilesToInject, 101 | 'views/**/*.html': require('../pipeline').cssFilesToInject, 102 | 'views/**/*.ejs': require('../pipeline').cssFilesToInject 103 | } 104 | }, 105 | 106 | prodStyles: { 107 | options: { 108 | startTag: '', 109 | endTag: '', 110 | fileTmpl: '', 111 | appRoot: '.tmp/public' 112 | }, 113 | files: { 114 | '.tmp/public/index.html': ['.tmp/public/min/production.min.css'], 115 | 'views/**/*.html': ['.tmp/public/min/production.min.css'], 116 | 'views/**/*.ejs': ['.tmp/public/min/production.min.css'] 117 | } 118 | }, 119 | 120 | prodStylesRelative: { 121 | options: { 122 | startTag: '', 123 | endTag: '', 124 | fileTmpl: '', 125 | appRoot: '.tmp/public', 126 | relative: true 127 | }, 128 | files: { 129 | '.tmp/public/index.html': ['.tmp/public/min/production.min.css'], 130 | 'views/**/*.html': ['.tmp/public/min/production.min.css'], 131 | 'views/**/*.ejs': ['.tmp/public/min/production.min.css'] 132 | } 133 | }, 134 | 135 | // Bring in JST template object 136 | devTpl: { 137 | options: { 138 | startTag: '', 139 | endTag: '', 140 | fileTmpl: '', 141 | appRoot: '.tmp/public' 142 | }, 143 | files: { 144 | '.tmp/public/index.html': ['.tmp/public/jst.js'], 145 | 'views/**/*.html': ['.tmp/public/jst.js'], 146 | 'views/**/*.ejs': ['.tmp/public/jst.js'] 147 | } 148 | }, 149 | 150 | devJsJade: { 151 | options: { 152 | startTag: '// SCRIPTS', 153 | endTag: '// SCRIPTS END', 154 | fileTmpl: 'script(src="%s")', 155 | appRoot: '.tmp/public' 156 | }, 157 | files: { 158 | 'views/**/*.jade': require('../pipeline').jsFilesToInject 159 | } 160 | }, 161 | 162 | devJsRelativeJade: { 163 | options: { 164 | startTag: '// SCRIPTS', 165 | endTag: '// SCRIPTS END', 166 | fileTmpl: 'script(src="%s")', 167 | appRoot: '.tmp/public', 168 | relative: true 169 | }, 170 | files: { 171 | 'views/**/*.jade': require('../pipeline').jsFilesToInject 172 | } 173 | }, 174 | 175 | prodJsJade: { 176 | options: { 177 | startTag: '// SCRIPTS', 178 | endTag: '// SCRIPTS END', 179 | fileTmpl: 'script(src="%s")', 180 | appRoot: '.tmp/public' 181 | }, 182 | files: { 183 | 'views/**/*.jade': ['.tmp/public/min/production.min.js'] 184 | } 185 | }, 186 | 187 | prodJsRelativeJade: { 188 | options: { 189 | startTag: '// SCRIPTS', 190 | endTag: '// SCRIPTS END', 191 | fileTmpl: 'script(src="%s")', 192 | appRoot: '.tmp/public', 193 | relative: true 194 | }, 195 | files: { 196 | 'views/**/*.jade': ['.tmp/public/min/production.min.js'] 197 | } 198 | }, 199 | 200 | devStylesJade: { 201 | options: { 202 | startTag: '// STYLES', 203 | endTag: '// STYLES END', 204 | fileTmpl: 'link(rel="stylesheet", href="%s")', 205 | appRoot: '.tmp/public' 206 | }, 207 | 208 | files: { 209 | 'views/**/*.jade': require('../pipeline').cssFilesToInject 210 | } 211 | }, 212 | 213 | devStylesRelativeJade: { 214 | options: { 215 | startTag: '// STYLES', 216 | endTag: '// STYLES END', 217 | fileTmpl: 'link(rel="stylesheet", href="%s")', 218 | appRoot: '.tmp/public', 219 | relative: true 220 | }, 221 | 222 | files: { 223 | 'views/**/*.jade': require('../pipeline').cssFilesToInject 224 | } 225 | }, 226 | 227 | prodStylesJade: { 228 | options: { 229 | startTag: '// STYLES', 230 | endTag: '// STYLES END', 231 | fileTmpl: 'link(rel="stylesheet", href="%s")', 232 | appRoot: '.tmp/public' 233 | }, 234 | files: { 235 | 'views/**/*.jade': ['.tmp/public/min/production.min.css'] 236 | } 237 | }, 238 | 239 | prodStylesRelativeJade: { 240 | options: { 241 | startTag: '// STYLES', 242 | endTag: '// STYLES END', 243 | fileTmpl: 'link(rel="stylesheet", href="%s")', 244 | appRoot: '.tmp/public', 245 | relative: true 246 | }, 247 | files: { 248 | 'views/**/*.jade': ['.tmp/public/min/production.min.css'] 249 | } 250 | }, 251 | 252 | // Bring in JST template object 253 | devTplJade: { 254 | options: { 255 | startTag: '// TEMPLATES', 256 | endTag: '// TEMPLATES END', 257 | fileTmpl: 'script(type="text/javascript", src="%s")', 258 | appRoot: '.tmp/public' 259 | }, 260 | files: { 261 | 'views/**/*.jade': ['.tmp/public/jst.js'] 262 | } 263 | } 264 | }); 265 | 266 | grunt.loadNpmTasks('grunt-sails-linker'); 267 | }; 268 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/config/sync.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A grunt task to keep directories in sync. It is very similar to grunt-contrib-copy 3 | * but tries to copy only those files that has actually changed. 4 | * 5 | * --------------------------------------------------------------- 6 | * 7 | * Synchronize files from the `assets` folder to `.tmp/public`, 8 | * smashing anything that's already there. 9 | * 10 | * For usage docs see: 11 | * https://github.com/tomusdrw/grunt-sync 12 | * 13 | */ 14 | module.exports = function(grunt) { 15 | 16 | grunt.config.set('sync', { 17 | dev: { 18 | files: [{ 19 | cwd: './assets', 20 | src: ['**/*.!(coffee)'], 21 | dest: '.tmp/public' 22 | }] 23 | } 24 | }); 25 | 26 | grunt.loadNpmTasks('grunt-sync'); 27 | }; 28 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/config/uglify.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Minify files with UglifyJS. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Minifies client-side javascript `assets`. 7 | * 8 | * For usage docs see: 9 | * https://github.com/gruntjs/grunt-contrib-uglify 10 | * 11 | */ 12 | module.exports = function(grunt) { 13 | 14 | grunt.config.set('uglify', { 15 | dist: { 16 | src: ['.tmp/public/concat/production.js'], 17 | dest: '.tmp/public/min/production.min.js' 18 | } 19 | }); 20 | 21 | grunt.loadNpmTasks('grunt-contrib-uglify'); 22 | }; 23 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/config/watch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Run predefined tasks whenever watched file patterns are added, changed or deleted. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Watch for changes on 7 | * - files in the `assets` folder 8 | * - the `tasks/pipeline.js` file 9 | * and re-run the appropriate tasks. 10 | * 11 | * For usage docs see: 12 | * https://github.com/gruntjs/grunt-contrib-watch 13 | * 14 | */ 15 | module.exports = function(grunt) { 16 | 17 | grunt.config.set('watch', { 18 | api: { 19 | 20 | // API files to watch: 21 | files: ['api/**/*'] 22 | }, 23 | assets: { 24 | 25 | // Assets to watch: 26 | files: ['assets/**/*', 'tasks/pipeline.js'], 27 | 28 | // When assets are changed: 29 | tasks: ['syncAssets' , 'linkAssets'] 30 | } 31 | }); 32 | 33 | grunt.loadNpmTasks('grunt-contrib-watch'); 34 | }; 35 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/pipeline.js: -------------------------------------------------------------------------------- 1 | /** 2 | * grunt/pipeline.js 3 | * 4 | * The order in which your css, javascript, and template files should be 5 | * compiled and linked from your views and static HTML files. 6 | * 7 | * (Note that you can take advantage of Grunt-style wildcard/glob/splat expressions 8 | * for matching multiple files.) 9 | */ 10 | 11 | 12 | 13 | // CSS files to inject in order 14 | // 15 | // (if you're using LESS with the built-in default config, you'll want 16 | // to change `assets/styles/importer.less` instead.) 17 | var cssFilesToInject = [ 18 | 'styles/**/*.css' 19 | ]; 20 | 21 | 22 | // Client-side javascript files to inject in order 23 | // (uses Grunt-style wildcard/glob/splat expressions) 24 | var jsFilesToInject = [ 25 | 26 | // Load sails.io before everything else 27 | 'js/dependencies/sails.io.js', 28 | 29 | // Dependencies like jQuery, or Angular are brought in here 30 | 'js/dependencies/**/*.js', 31 | 32 | // All of the rest of your client-side js files 33 | // will be injected here in no particular order. 34 | 'js/**/*.js' 35 | ]; 36 | 37 | 38 | // Client-side HTML templates are injected using the sources below 39 | // The ordering of these templates shouldn't matter. 40 | // (uses Grunt-style wildcard/glob/splat expressions) 41 | // 42 | // By default, Sails uses JST templates and precompiles them into 43 | // functions for you. If you want to use jade, handlebars, dust, etc., 44 | // with the linker, no problem-- you'll just want to make sure the precompiled 45 | // templates get spit out to the same file. Be sure and check out `tasks/README.md` 46 | // for information on customizing and installing new tasks. 47 | var templateFilesToInject = [ 48 | 'templates/**/*.html' 49 | ]; 50 | 51 | 52 | 53 | // Prefix relative paths to source files so they point to the proper locations 54 | // (i.e. where the other Grunt tasks spit them out, or in some cases, where 55 | // they reside in the first place) 56 | module.exports.cssFilesToInject = cssFilesToInject.map(function(path) { 57 | return '.tmp/public/' + path; 58 | }); 59 | module.exports.jsFilesToInject = jsFilesToInject.map(function(path) { 60 | return '.tmp/public/' + path; 61 | }); 62 | module.exports.templateFilesToInject = templateFilesToInject.map(function(path) { 63 | return 'assets/' + path; 64 | }); 65 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/register/build.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.registerTask('build', [ 3 | 'compileAssets', 4 | 'linkAssetsBuild', 5 | 'clean:build', 6 | 'copy:build' 7 | ]); 8 | }; 9 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/register/buildProd.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.registerTask('buildProd', [ 3 | 'compileAssets', 4 | 'concat', 5 | 'uglify', 6 | 'cssmin', 7 | 'linkAssetsBuildProd', 8 | 'clean:build', 9 | 'copy:build' 10 | ]); 11 | }; 12 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/register/compileAssets.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.registerTask('compileAssets', [ 3 | 'clean:dev', 4 | 'jst:dev', 5 | 'less:dev', 6 | 'copy:dev', 7 | 'coffee:dev' 8 | ]); 9 | }; 10 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/register/default.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.registerTask('default', ['compileAssets', 'linkAssets', 'watch']); 3 | }; 4 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/register/linkAssets.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.registerTask('linkAssets', [ 3 | 'sails-linker:devJs', 4 | 'sails-linker:devStyles', 5 | 'sails-linker:devTpl', 6 | 'sails-linker:devJsJade', 7 | 'sails-linker:devStylesJade', 8 | 'sails-linker:devTplJade' 9 | ]); 10 | }; 11 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/register/linkAssetsBuild.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.registerTask('linkAssetsBuild', [ 3 | 'sails-linker:devJsRelative', 4 | 'sails-linker:devStylesRelative', 5 | 'sails-linker:devTpl', 6 | 'sails-linker:devJsRelativeJade', 7 | 'sails-linker:devStylesRelativeJade', 8 | 'sails-linker:devTplJade' 9 | ]); 10 | }; 11 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/register/linkAssetsBuildProd.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.registerTask('linkAssetsBuildProd', [ 3 | 'sails-linker:prodJsRelative', 4 | 'sails-linker:prodStylesRelative', 5 | 'sails-linker:devTpl', 6 | 'sails-linker:prodJsRelativeJade', 7 | 'sails-linker:prodStylesRelativeJade', 8 | 'sails-linker:devTplJade' 9 | ]); 10 | }; 11 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/register/prod.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.registerTask('prod', [ 3 | 'compileAssets', 4 | 'concat', 5 | 'uglify', 6 | 'cssmin', 7 | 'sails-linker:prodJs', 8 | 'sails-linker:prodStyles', 9 | 'sails-linker:devTpl', 10 | 'sails-linker:prodJsJade', 11 | 'sails-linker:prodStylesJade', 12 | 'sails-linker:devTplJade' 13 | ]); 14 | }; 15 | -------------------------------------------------------------------------------- /tests/integration/app/tasks/register/syncAssets.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.registerTask('syncAssets', [ 3 | 'jst:dev', 4 | 'less:dev', 5 | 'sync:dev', 6 | 'coffee:dev' 7 | ]); 8 | }; 9 | -------------------------------------------------------------------------------- /tests/integration/app/views/403.ejs: -------------------------------------------------------------------------------- 1 | 2 | 36 | 37 | 38 | Forbidden 39 | 40 | 44 | 45 | 46 | 47 |
48 |
49 | 50 |
51 | 52 |
53 |

54 | Forbidden 55 |

56 |

57 | <% if (typeof error !== 'undefined') { %> 58 | <%= error %> 59 | <% } else { %> 60 | You don't have permission to see the page you're trying to reach. 61 | <% } %> 62 |

63 |

64 | Why might this be happening? 65 |

66 |
67 | 68 | 73 |
74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /tests/integration/app/views/404.ejs: -------------------------------------------------------------------------------- 1 | 2 | 36 | 37 | 38 | Page Not Found 39 | 40 | 44 | 45 | 46 | 47 |
48 |
49 | 50 |
51 | 52 |
53 |

54 | Something's fishy here. 55 |

56 |

57 | <% if (typeof error!== 'undefined') { %> 58 | <%= error %> 59 | <% } else { %> 60 | The page you were trying to reach doesn't exist. 61 | <% } %> 62 |

63 |

64 | Why might this be happening? 65 |

66 |
67 | 68 | 73 |
74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /tests/integration/app/views/homepage.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 12 | 13 |
14 |
15 |

<%= __('A brand new app.') %>

16 |

You're looking at: <%= view.pathFromApp + '.' +view.ext %>

17 |
18 |
19 | 21 |
    22 |
  • 23 |
    24 |
    25 |

    Generate a REST API.

    26 |

    27 | Run sails generate api user. This will create two files: a model api/models/User.js and a controllerapi/controllers/UserController.js. 28 |

    29 |
    30 |
  • 31 |
  • 32 |
    33 |
    34 |

    35 | Lift your app. 36 |

    37 |

    38 | Run sails lift to start up your app server. If you visit http://localhost:1337/user in your browser, you'll see a WebSocket-compatible user API. 39 |

    40 |
    41 |
  • 42 |
  • 43 |
    44 |
    45 |

    46 | Dive in. 47 |

    48 |

    Blueprints are just the beginning. You'll probably also want to learn how to customize your app's routes, set up security policies, configure your data sources, and build custom controller actions. For more help getting started, check out the links on this page.

    49 | 50 |
    51 |
  • 52 |
53 | 73 |
74 |
75 | -------------------------------------------------------------------------------- /tests/integration/app/views/layout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | New Sails App 5 | 6 | 7 | 8 | 9 | 10 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | <%- body %> 38 | 39 | 40 | 41 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /tests/integration/sails-transactions-experiment.postman_environment: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ac6e2ce1-fe1e-713c-55b1-57350e59983c", 3 | "name": "sails-transactions-experiment", 4 | "values": [ 5 | { 6 | "key": "url", 7 | "value": "http://localhost:1337", 8 | "type": "text", 9 | "name": "url", 10 | "enabled": true 11 | }, 12 | { 13 | "key": "last_created_blob", 14 | "type": "text", 15 | "value": "54e652d8e4b03671eae999fe" 16 | } 17 | ], 18 | "timestamp": 1426158097034, 19 | "synced": false, 20 | "syncedFilename": "" 21 | } -------------------------------------------------------------------------------- /tests/integration/sanity.json.postman_collection: -------------------------------------------------------------------------------- 1 | { 2 | "id": "15db846a-e78f-bddf-cf86-03d0bc78cc9c", 3 | "name": "Sails MySQL Transactions", 4 | "description": "", 5 | "order": [ 6 | "25104f0d-8fc8-8daf-e2c0-157ad4ceca6c", 7 | "bcf3da1a-0727-f8c3-37c1-ff5e7dba282d", 8 | "b3ed28e3-d50a-518b-4656-d363f9e3abb1", 9 | "70cdb552-7444-bf1a-5928-73c3285198a7", 10 | "4cd9853d-035a-3154-2b04-a14a95a17d8d", 11 | "89b740df-ffb5-6f9e-703d-06eb59db8001" 12 | ], 13 | "folders": [], 14 | "timestamp": 1427187196133, 15 | "synced": false, 16 | "owner": "108", 17 | "sharedWithTeam": true, 18 | "subscribed": false, 19 | "remoteLink": "http://sync-staging.getpostman.com/collections/b401547cb75e940cba09", 20 | "public": false, 21 | "write": true, 22 | "requests": [ 23 | { 24 | "id": "25104f0d-8fc8-8daf-e2c0-157ad4ceca6c", 25 | "headers": "", 26 | "url": "{{url}}/team/create", 27 | "preRequestScript": "", 28 | "pathVariables": {}, 29 | "method": "POST", 30 | "data": [ 31 | { 32 | "key": "name", 33 | "value": "team-create", 34 | "type": "text", 35 | "enabled": true 36 | }, 37 | { 38 | "key": "id", 39 | "value": "1", 40 | "type": "text", 41 | "enabled": true 42 | } 43 | ], 44 | "dataMode": "params", 45 | "version": 2, 46 | "tests": "var data = JSON.parse(responseBody);\n\ntests[\"Expect Name\"] = data.name === \"team-create\";\ntests[\"Expect Id\"] = (data.id === \"1\");\ntests[\"Non Null Transaction\"] = (data.transactionId !== null && data.transactionId !== undefined);\n\ntests[\"Status code is 200\"] = responseCode.code === 200;", 47 | "currentHelper": "normal", 48 | "helperAttributes": {}, 49 | "time": 1427276401967, 50 | "name": "Create Team", 51 | "description": "", 52 | "collectionId": "15db846a-e78f-bddf-cf86-03d0bc78cc9c", 53 | "synced": false 54 | }, 55 | { 56 | "owner": "108", 57 | "lastUpdatedBy": "108", 58 | "folder": "2ddbb0b5-7863-fd27-91e0-33e5128834b7", 59 | "collection": "0aa5be11-543b-87a4-37a4-e5c9c1ac7f1f", 60 | "id": "4cd9853d-035a-3154-2b04-a14a95a17d8d", 61 | "name": "Update User", 62 | "dataMode": "params", 63 | "data": [ 64 | { 65 | "key": "name", 66 | "value": "user-update", 67 | "type": "text", 68 | "enabled": true 69 | } 70 | ], 71 | "rawModeData": null, 72 | "descriptionFormat": "html", 73 | "description": "", 74 | "headers": "", 75 | "method": "PUT", 76 | "pathVariables": {}, 77 | "url": "{{url}}/user/update/1", 78 | "preRequestScript": "", 79 | "tests": "", 80 | "version": 2, 81 | "currentHelper": "normal", 82 | "helperAttributes": "{}", 83 | "collectionId": "15db846a-e78f-bddf-cf86-03d0bc78cc9c", 84 | "write": true, 85 | "synced": false, 86 | "isFromCollection": true, 87 | "collectionRequestId": "b11a44d5-023f-4402-c57a-4921a88207fa" 88 | }, 89 | { 90 | "id": "70cdb552-7444-bf1a-5928-73c3285198a7", 91 | "headers": "", 92 | "url": "{{url}}/team/remove_member/1", 93 | "preRequestScript": "", 94 | "pathVariables": {}, 95 | "method": "POST", 96 | "data": [ 97 | { 98 | "key": "member_id", 99 | "value": "1", 100 | "type": "text", 101 | "enabled": true 102 | } 103 | ], 104 | "dataMode": "params", 105 | "version": 2, 106 | "tests": "var data = JSON.parse(responseBody);\n\ntests[\"Expect Empty Member Array\"] = data.members.length === 0;\n\ntests[\"Non Null Member Transaction\"] = (data.transactionId !== null && data.transactionId !== undefined);\n\ntests[\"Status code is 200\"] = responseCode.code === 200;", 107 | "currentHelper": "normal", 108 | "helperAttributes": {}, 109 | "time": 1427191359121, 110 | "name": "Remove member from Team", 111 | "description": "", 112 | "collectionId": "15db846a-e78f-bddf-cf86-03d0bc78cc9c", 113 | "synced": false 114 | }, 115 | { 116 | "owner": "108", 117 | "lastUpdatedBy": "108", 118 | "collection": "0aa5be11-543b-87a4-37a4-e5c9c1ac7f1f", 119 | "id": "89b740df-ffb5-6f9e-703d-06eb59db8001", 120 | "name": "Delete User", 121 | "dataMode": "params", 122 | "data": [], 123 | "rawModeData": null, 124 | "descriptionFormat": "html", 125 | "description": "", 126 | "headers": "", 127 | "method": "DELETE", 128 | "pathVariables": {}, 129 | "url": "{{url}}/user/destroy/1", 130 | "preRequestScript": "", 131 | "tests": "", 132 | "version": 2, 133 | "currentHelper": "normal", 134 | "helperAttributes": "{}", 135 | "collectionId": "15db846a-e78f-bddf-cf86-03d0bc78cc9c", 136 | "write": true, 137 | "synced": false, 138 | "isFromCollection": true, 139 | "collectionRequestId": "cf608799-52cb-3a88-6d37-07bcd8d42538" 140 | }, 141 | { 142 | "id": "b3ed28e3-d50a-518b-4656-d363f9e3abb1", 143 | "headers": "", 144 | "url": "{{url}}/team/add_member/1", 145 | "preRequestScript": "", 146 | "pathVariables": {}, 147 | "method": "POST", 148 | "data": [ 149 | { 150 | "key": "member_id", 151 | "value": "1", 152 | "type": "text", 153 | "enabled": true 154 | } 155 | ], 156 | "dataMode": "params", 157 | "version": 2, 158 | "tests": "var data = JSON.parse(responseBody);\n\ntests[\"Expect Member Name\"] = data.members[0].name === \"user-create\";\ntests[\"Expect Member Id\"] = (data.members[0].id == \"1\");\n\n\ntests[\"Non Null Member Transaction\"] = (data.members[0].transactionId !== null && data.members[0].transactionId !== undefined);\n\ntests[\"Status code is 200\"] = responseCode.code === 200;", 159 | "currentHelper": "normal", 160 | "helperAttributes": {}, 161 | "time": 1427191212808, 162 | "name": "Add member to Team", 163 | "description": "", 164 | "collectionId": "15db846a-e78f-bddf-cf86-03d0bc78cc9c", 165 | "synced": false 166 | }, 167 | { 168 | "id": "bcf3da1a-0727-f8c3-37c1-ff5e7dba282d", 169 | "headers": "", 170 | "url": "{{url}}/user/create", 171 | "preRequestScript": "", 172 | "pathVariables": {}, 173 | "method": "POST", 174 | "data": [ 175 | { 176 | "key": "name", 177 | "value": "user-create", 178 | "type": "text", 179 | "enabled": true 180 | }, 181 | { 182 | "key": "id", 183 | "value": "1", 184 | "type": "text", 185 | "enabled": true 186 | } 187 | ], 188 | "dataMode": "params", 189 | "version": 2, 190 | "tests": "var data = JSON.parse(responseBody);\n\ntests[\"Expect User Name\"] = data.user.name === \"user-create\";\ntests[\"Expect User Id\"] = (data.user.id == \"1\");\n\ntests[\"Expect Request Name\"] = data.request.name === \"request: 1 - 1\";\ntests[\"Expect Request Id\"] = (data.request.id == \"1\");\n\n\ntests[\"Non Null User Transaction\"] = (data.user.transactionId !== null && data.user.transactionId !== undefined);\ntests[\"Expect unified transaction\"] = (data.request.transactionId === data.request.transactionId) && (data.collection.requests[0].transactionId === data.request.transactionId);\n\ntests[\"Status code is 200\"] = responseCode.code === 200;", 191 | "currentHelper": "normal", 192 | "helperAttributes": {}, 193 | "time": 1428311380882, 194 | "name": "Create User", 195 | "description": "", 196 | "collectionId": "15db846a-e78f-bddf-cf86-03d0bc78cc9c", 197 | "synced": false 198 | } 199 | ] 200 | } -------------------------------------------------------------------------------- /tests/unit/repository-spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview This test specs runs tests on the package.json file of repository. It has a set of strict tests on the 3 | * content of the file as well. Any change to package.json must be accompanied by valid test case in this spec-sheet. 4 | */ 5 | var expect = require('expect.js'); 6 | 7 | /* global describe, it */ 8 | describe('repository', function () { 9 | var fs = require('fs'); 10 | 11 | describe('package.json', function () { 12 | var content, 13 | json; 14 | 15 | it('must exist', function () { 16 | expect(fs.existsSync('./package.json')).to.be.ok(); 17 | }); 18 | 19 | it('must have readable content', function () { 20 | expect(content = fs.readFileSync('./package.json').toString()).to.be.ok(); 21 | }); 22 | 23 | it('content must be valid JSON', function () { 24 | expect(json = JSON.parse(content)).to.be.ok(); 25 | }); 26 | 27 | describe('package.json JSON data', function () { 28 | it('must have valid name, description and author', function () { 29 | expect(json.name).to.equal('sails-mysql-transactions'); 30 | expect(json.description) 31 | .to.equal('Sails ORM adapter for mySQL with transaction and replication support'); 32 | expect(json.author).to.equal('Postman Labs '); 33 | expect(json.license).to.equal('Apache-2.0'); 34 | }); 35 | 36 | it('must have a valid version string in form of ..', function () { 37 | /* jshint ignore:start */ 38 | expect(json.version).to.match(/^((\d+)\.(\d+)\.(\d+))(?:-([\dA-Za-z\-]+(?:\.[\dA-Za-z\-]+)*))?(?:\+([\dA-Za-z\-]+(?:\.[\dA-Za-z\-]+)*))?$/); 39 | /* jshint ignore:end */ 40 | }); 41 | }); 42 | 43 | describe('script definitions', function () { 44 | var props = {}; 45 | 46 | it('files must exist', function () { 47 | var prop, 48 | propBase; 49 | 50 | expect(json.scripts).to.be.ok(); 51 | 52 | for (prop in json.scripts) { 53 | props[prop] = { 54 | base: (propBase = prop.substr(0, prop.indexOf('-') > -1 ? 55 | prop.indexOf('-') : undefined)), 56 | path: 'scripts/' + propBase + '.sh' 57 | }; 58 | expect(fs.existsSync(props[prop].path)).to.be.ok(); 59 | } 60 | }); 61 | 62 | it('postinstall script must exist', function () { 63 | expect(props.postinstall).to.be.ok(); 64 | expect(fs.existsSync(props.postinstall.path)).to.be.ok(); 65 | }); 66 | 67 | it('must be defined as per standards `[script]: scripts/[name].sh`', function () { 68 | for (var prop in json.scripts) { 69 | expect(json.scripts[prop]).to.match(new RegExp(props[prop].path, 'g')); 70 | } 71 | }); 72 | 73 | it('must have the hashbang defined', function () { 74 | var fileContent, 75 | prop; 76 | 77 | for (prop in json.scripts) { 78 | fileContent = fs.readFileSync(props[prop].path).toString(); 79 | expect(/^#!\/(bin\/bash|usr\/bin\/env\s(node|bash))[\r\n][\W\w]*$/g.test(fileContent)).to.be.ok(); 80 | } 81 | }); 82 | }); 83 | 84 | describe('devDependencies', function () { 85 | it('must exist', function () { 86 | expect(json.devDependencies).to.be.a('object'); 87 | }); 88 | 89 | it('must have specified version for dependencies ', function () { 90 | for (var item in json.devDependencies) { 91 | expect(json.devDependencies[item]).to.be.ok(); 92 | } 93 | }); 94 | 95 | it('must point to specific package version; (*) not expected', function () { 96 | for (var item in json.devDependencies) { 97 | expect(json.devDependencies[item]).not.to.equal('*'); 98 | } 99 | }); 100 | }); 101 | 102 | describe('main entry script', function () { 103 | it('must point to a valid file', function () { 104 | expect(json.main).to.equal('index.js'); 105 | expect(fs.existsSync(json.main)).to.be.ok(); 106 | }); 107 | }); 108 | }); 109 | 110 | describe('README.md', function () { 111 | it('must exist', function () { 112 | expect(fs.existsSync('./README.md')).to.be.ok(); 113 | }); 114 | 115 | it('must have readable content', function () { 116 | expect(fs.readFileSync('./README.md').toString()).to.be.ok(); 117 | }); 118 | }); 119 | 120 | describe('LICENSE.md', function () { 121 | it('must exist', function () { 122 | expect(fs.existsSync('./LICENSE.md')).to.be.ok(); 123 | }); 124 | 125 | it('must have readable content', function () { 126 | expect(fs.readFileSync('./LICENSE.md').toString()).to.be.ok(); 127 | }); 128 | }); 129 | 130 | describe('.gitignore file', function () { 131 | it('must exist', function () { 132 | expect(fs.existsSync('./.gitignore')).to.be.ok(); 133 | }); 134 | 135 | it('must have readable content', function () { 136 | expect(fs.readFileSync('./.gitignore').toString()).to.be.ok(); 137 | }); 138 | }); 139 | 140 | describe('.npmignore file', function () { 141 | it('must exist', function () { 142 | expect(fs.existsSync('./.npmignore')).to.be.ok(); 143 | }); 144 | 145 | it('must be a superset of .gitignore (.npmi = .npmi + .gi)', function () { 146 | // normalise the ignore file text contents 147 | var gi = fs.readFileSync('./.gitignore').toString().replace(/#.*\n/g, '\n').replace(/\n+/g, '\n'), 148 | npmi = fs.readFileSync('./.npmignore').toString().replace(/#.*\n/g, '\n').replace(/\n+/g, '\n'); 149 | 150 | expect(npmi.substr(-gi.length)).to.eql(gi); 151 | }); 152 | }); 153 | 154 | describe('waterline submodule', function () { 155 | it('must exist in repo root', function () { 156 | expect(fs.existsSync('./waterline')).to.be.ok(); 157 | expect(fs.existsSync('./waterline/.git')).to.be.ok(); 158 | expect(fs.existsSync('./waterline/package.json')).to.be.ok(); 159 | }); 160 | 161 | it('must not be ignored by any .*ignore file', function () { 162 | expect(/\n\/?waterline/.test(fs.readFileSync('./.gitignore'))).to.not.be.ok(); 163 | expect(/\n\/?waterline/.test(fs.readFileSync('./.npmignore'))).to.not.be.ok(); 164 | }); 165 | }); 166 | }); 167 | --------------------------------------------------------------------------------