api_maker 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +476 -0
  4. data/Rakefile +27 -0
  5. data/app/channels/api_maker/subscriptions_channel.rb +80 -0
  6. data/app/controllers/api_maker/base_controller.rb +32 -0
  7. data/app/controllers/api_maker/commands_controller.rb +26 -0
  8. data/app/controllers/api_maker/devise_controller.rb +60 -0
  9. data/app/controllers/api_maker/session_statuses_controller.rb +33 -0
  10. data/app/services/api_maker/application_service.rb +7 -0
  11. data/app/services/api_maker/collection_command_service.rb +24 -0
  12. data/app/services/api_maker/command_response.rb +67 -0
  13. data/app/services/api_maker/command_service.rb +31 -0
  14. data/app/services/api_maker/create_command.rb +62 -0
  15. data/app/services/api_maker/create_command_service.rb +18 -0
  16. data/app/services/api_maker/destroy_command.rb +39 -0
  17. data/app/services/api_maker/destroy_command_service.rb +22 -0
  18. data/app/services/api_maker/generate_react_native_api_service.rb +61 -0
  19. data/app/services/api_maker/index_command.rb +96 -0
  20. data/app/services/api_maker/index_command_service.rb +22 -0
  21. data/app/services/api_maker/js_method_namer_service.rb +11 -0
  22. data/app/services/api_maker/member_command_service.rb +25 -0
  23. data/app/services/api_maker/model_content_generator_service.rb +108 -0
  24. data/app/services/api_maker/models_finder_service.rb +22 -0
  25. data/app/services/api_maker/models_generator_service.rb +104 -0
  26. data/app/services/api_maker/update_command.rb +43 -0
  27. data/app/services/api_maker/update_command_service.rb +21 -0
  28. data/app/services/api_maker/valid_command.rb +35 -0
  29. data/app/services/api_maker/valid_command_service.rb +21 -0
  30. data/app/views/api_maker/_data.html.erb +15 -0
  31. data/config/rails_best_practices.yml +55 -0
  32. data/config/routes.rb +7 -0
  33. data/lib/api_maker.rb +36 -0
  34. data/lib/api_maker/ability.rb +39 -0
  35. data/lib/api_maker/ability_loader.rb +21 -0
  36. data/lib/api_maker/action_controller_base_extensions.rb +5 -0
  37. data/lib/api_maker/base_command.rb +81 -0
  38. data/lib/api_maker/base_resource.rb +78 -0
  39. data/lib/api_maker/collection_serializer.rb +69 -0
  40. data/lib/api_maker/command_spec_helper.rb +57 -0
  41. data/lib/api_maker/configuration.rb +34 -0
  42. data/lib/api_maker/engine.rb +5 -0
  43. data/lib/api_maker/individual_command.rb +37 -0
  44. data/lib/api_maker/javascript/api.js +92 -0
  45. data/lib/api_maker/javascript/base-model.js +543 -0
  46. data/lib/api_maker/javascript/bootstrap/attribute-row.jsx +16 -0
  47. data/lib/api_maker/javascript/bootstrap/attribute-rows.jsx +47 -0
  48. data/lib/api_maker/javascript/bootstrap/card.jsx +79 -0
  49. data/lib/api_maker/javascript/bootstrap/checkbox.jsx +127 -0
  50. data/lib/api_maker/javascript/bootstrap/checkboxes.jsx +105 -0
  51. data/lib/api_maker/javascript/bootstrap/live-table.jsx +168 -0
  52. data/lib/api_maker/javascript/bootstrap/money-input.jsx +136 -0
  53. data/lib/api_maker/javascript/bootstrap/radio-buttons.jsx +80 -0
  54. data/lib/api_maker/javascript/bootstrap/select.jsx +168 -0
  55. data/lib/api_maker/javascript/bootstrap/string-input.jsx +203 -0
  56. data/lib/api_maker/javascript/cable-connection-pool.js +169 -0
  57. data/lib/api_maker/javascript/cable-subscription-pool.js +111 -0
  58. data/lib/api_maker/javascript/cable-subscription.js +33 -0
  59. data/lib/api_maker/javascript/collection.js +186 -0
  60. data/lib/api_maker/javascript/commands-pool.js +123 -0
  61. data/lib/api_maker/javascript/custom-error.js +14 -0
  62. data/lib/api_maker/javascript/deserializer.js +35 -0
  63. data/lib/api_maker/javascript/devise.js.erb +113 -0
  64. data/lib/api_maker/javascript/error-logger.js +119 -0
  65. data/lib/api_maker/javascript/event-connection.jsx +24 -0
  66. data/lib/api_maker/javascript/event-created.jsx +26 -0
  67. data/lib/api_maker/javascript/event-destroyed.jsx +26 -0
  68. data/lib/api_maker/javascript/event-emitter-listener.jsx +32 -0
  69. data/lib/api_maker/javascript/event-listener.jsx +41 -0
  70. data/lib/api_maker/javascript/event-updated.jsx +26 -0
  71. data/lib/api_maker/javascript/form-data-to-object.js +70 -0
  72. data/lib/api_maker/javascript/included.js +39 -0
  73. data/lib/api_maker/javascript/key-value-store.js +47 -0
  74. data/lib/api_maker/javascript/logger.js +23 -0
  75. data/lib/api_maker/javascript/model-name.js +21 -0
  76. data/lib/api_maker/javascript/model-template.js.erb +110 -0
  77. data/lib/api_maker/javascript/models-response-reader.js +43 -0
  78. data/lib/api_maker/javascript/paginate.jsx +128 -0
  79. data/lib/api_maker/javascript/params.js +68 -0
  80. data/lib/api_maker/javascript/resource-route.jsx +75 -0
  81. data/lib/api_maker/javascript/resource-routes.jsx +36 -0
  82. data/lib/api_maker/javascript/result.js +25 -0
  83. data/lib/api_maker/javascript/session-status-updater.js +113 -0
  84. data/lib/api_maker/javascript/sort-link.jsx +88 -0
  85. data/lib/api_maker/javascript/updated-attribute.jsx +60 -0
  86. data/lib/api_maker/loader.rb +14 -0
  87. data/lib/api_maker/memory_storage.rb +65 -0
  88. data/lib/api_maker/model_extensions.rb +96 -0
  89. data/lib/api_maker/permitted_params_argument.rb +12 -0
  90. data/lib/api_maker/preloader.rb +91 -0
  91. data/lib/api_maker/preloader_belongs_to.rb +58 -0
  92. data/lib/api_maker/preloader_has_many.rb +69 -0
  93. data/lib/api_maker/preloader_has_one.rb +70 -0
  94. data/lib/api_maker/preloader_through.rb +101 -0
  95. data/lib/api_maker/railtie.rb +14 -0
  96. data/lib/api_maker/relationship_includer.rb +42 -0
  97. data/lib/api_maker/resource_routing.rb +8 -0
  98. data/lib/api_maker/result_parser.rb +50 -0
  99. data/lib/api_maker/serializer.rb +86 -0
  100. data/lib/api_maker/spec_helper.rb +100 -0
  101. data/lib/api_maker/version.rb +3 -0
  102. data/lib/tasks/api_maker_tasks.rake +5 -0
  103. metadata +581 -0
@@ -0,0 +1,43 @@
1
+ import Included from "./included"
2
+
3
+ const inflection = require("inflection")
4
+
5
+ export default class ModelsResponseReader {
6
+ static first(response) {
7
+ return ModelsResponseReader.collection(response)[0]
8
+ }
9
+
10
+ static collection(response) {
11
+ var reader = new ModelsResponseReader({response: response})
12
+ return reader.models()
13
+ }
14
+
15
+ constructor(args) {
16
+ this.response = args.response
17
+ }
18
+
19
+ models() {
20
+ var included = new Included(this.response)
21
+ var models = []
22
+
23
+ for(var modelType in this.response.data) {
24
+ var modelClassName = inflection.singularize(modelType)
25
+ var modelClass = require(`api-maker/models/${modelClassName}`).default
26
+ var collectionName = inflection.dasherize(modelClass.modelClassData().collectionName)
27
+
28
+ for(var modelId of this.response.data[modelType]) {
29
+ var modelData = this.response.included[collectionName][modelId]
30
+
31
+ if (!modelData)
32
+ throw new Error(`Couldn't find model data for ${collectionName}(${modelId})`)
33
+
34
+ var model = new modelClass({data: modelData, isNewRecord: false, response: this.response})
35
+
36
+ model._readIncludedRelationships(included)
37
+ models.push(model)
38
+ }
39
+ }
40
+
41
+ return models
42
+ }
43
+ }
@@ -0,0 +1,128 @@
1
+ import { Link } from "react-router-dom"
2
+ import qs from "qs"
3
+ import React from "react"
4
+
5
+ export default class extends React.Component {
6
+ isPageActiveClass(pageNumber) {
7
+ if (this.props.result.currentPage() == pageNumber)
8
+ return "active"
9
+ }
10
+
11
+ pages() {
12
+ var currentPage = this.props.result.currentPage()
13
+ var pages = []
14
+ var totalPages = this.props.result.totalPages()
15
+ var pagesFrom = currentPage - 5
16
+ var pagesTo = currentPage + 5
17
+
18
+ if (pagesFrom < 1)
19
+ pagesFrom = 1
20
+
21
+ if (pagesTo > totalPages)
22
+ pagesTo = totalPages
23
+
24
+ for(var i = pagesFrom; i <= pagesTo; i++) {
25
+ pages.push(i)
26
+ }
27
+
28
+ return pages
29
+ }
30
+
31
+ pagePath(pageNumber) {
32
+ var pageKey = this.props.result.data.collection.queryArgs.pageKey
33
+ if (!pageKey)
34
+ pageKey = "page"
35
+
36
+ var currentParams = qs.parse(window.location.search.substr(1))
37
+ currentParams[pageKey] = pageNumber
38
+ var newParams = qs.stringify(currentParams)
39
+ var newPath = `${location.pathname}?${newParams}`
40
+
41
+ return newPath
42
+ }
43
+
44
+ previousPagePath() {
45
+ var previousPage
46
+
47
+ if (this.props.result.currentPage() > 1) {
48
+ previousPage = this.props.result.currentPage() - 1
49
+ } else {
50
+ previousPage = this.props.result.currentPage()
51
+ }
52
+
53
+ return this.pagePath(previousPage)
54
+ }
55
+
56
+ nextPagePath() {
57
+ var nextPage
58
+
59
+ if (this.props.result.currentPage() < this.props.result.totalPages()) {
60
+ nextPage = this.props.result.currentPage() + 1
61
+ } else {
62
+ nextPage = this.props.result.currentPage()
63
+ }
64
+
65
+ return this.pagePath(nextPage)
66
+ }
67
+
68
+ showBackwardsDots() {
69
+ var currentPage = this.props.result.currentPage()
70
+
71
+ return (currentPage - 5 > 1)
72
+ }
73
+
74
+ showForwardsDots() {
75
+ var currentPage = this.props.result.currentPage()
76
+ var totalPages = this.props.result.totalPages()
77
+
78
+ return (currentPage + 5 < totalPages)
79
+ }
80
+
81
+ render() {
82
+ return (
83
+ <ul className="pagination">
84
+ <li className={`page-item ${this.props.result.currentPage() <= 1 ? "disabled" : ""}`} key="page-first">
85
+ <Link className="page-link" to={this.pagePath(1)}>
86
+
87
+ </Link>
88
+ </li>
89
+ <li className={`page-item ${this.props.result.currentPage() <= 1 ? "disabled" : ""}`} key="page-previous">
90
+ <Link className="page-link" to={this.previousPagePath()}>
91
+
92
+ </Link>
93
+ </li>
94
+ {this.showBackwardsDots() &&
95
+ <li className="page-item">
96
+ <a className="page-link disabled" href="#">
97
+ &hellip;
98
+ </a>
99
+ </li>
100
+ }
101
+ {this.pages().map(page =>
102
+ <li className={`page-item ${this.isPageActiveClass(page)}`} key={`page-${page}`}>
103
+ <Link className="page-link" to={this.pagePath(page)}>
104
+ {page}
105
+ </Link>
106
+ </li>
107
+ )}
108
+ {this.showForwardsDots() &&
109
+ <li className="page-item">
110
+ <a className="page-link disabled" href="#">
111
+ &hellip;
112
+ </a>
113
+ </li>
114
+ }
115
+ <li className={`page-item ${this.props.result.currentPage() >= this.props.result.totalPages() ? "disabled" : ""}`} key="page-next">
116
+ <Link className="page-link" to={this.nextPagePath()}>
117
+
118
+ </Link>
119
+ </li>
120
+ <li className={`page-item ${this.props.result.currentPage() >= this.props.result.totalPages() ? "disabled" : ""}`} key="page-last">
121
+ <Link className="page-link" to={this.pagePath(this.props.result.totalPages())}>
122
+
123
+ </Link>
124
+ </li>
125
+ </ul>
126
+ )
127
+ }
128
+ }
@@ -0,0 +1,68 @@
1
+ import formSerialize from "form-serialize"
2
+ import KeyValueStore from "api-maker/key-value-store"
3
+ import merge from "merge"
4
+ import qs from "qs"
5
+
6
+ export default class Params {
7
+ static parse() {
8
+ return qs.parse(window.location.search.substr(1))
9
+ }
10
+
11
+ static change(given) {
12
+ return merge.recursive(true, Params.parse(), given)
13
+ }
14
+
15
+ static changeParams(given) {
16
+ var params = Params.change(given)
17
+ var newParams = qs.stringify(params)
18
+ var newPath = `${location.pathname}?${newParams}`
19
+
20
+ AppHistory.push(newPath)
21
+ }
22
+
23
+ static async getCachedParams(paramName, args = {}) {
24
+ var oldQuery = await KeyValueStore.get(paramName)
25
+ var params = Params.parse()
26
+
27
+ if (params && paramName in params) {
28
+ return params[paramName]
29
+ } else if (oldQuery) {
30
+ return oldQuery
31
+ } else {
32
+ return args.default || {}
33
+ }
34
+ }
35
+
36
+ static async setCachedParams(paramName, qParams) {
37
+ return await KeyValueStore.set(paramName, qParams)
38
+ }
39
+
40
+ static serializeForm(form) {
41
+ var hash = formSerialize(form, {empty: true, hash: true})
42
+ return Params.setUndefined(hash)
43
+ }
44
+
45
+ // This is used to set all empty values to 'undefined' which makes qs removed those elements from the query string
46
+ static setUndefined(given) {
47
+ if (Array.isArray(given)) {
48
+ if (given.length == 0)
49
+ return undefined
50
+
51
+ return given.map(givenI => Params.setUndefined(givenI))
52
+ } else if (typeof given === "object") {
53
+ if (Object.keys(given).length == 0)
54
+ return undefined
55
+
56
+ var newGiven = {}
57
+ for(var key in given) {
58
+ newGiven[key] = Params.setUndefined(given[key])
59
+ }
60
+
61
+ return newGiven
62
+ } else if (given === "") {
63
+ return undefined
64
+ } else {
65
+ return given
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,75 @@
1
+ const inflection = require("inflection")
2
+
3
+ export default class ResourceRoute {
4
+ constructor(args) {
5
+ this.args = args.args
6
+ this.parsedContext = args.parsedContext
7
+ this.route = args.route
8
+ }
9
+
10
+ routes() {
11
+ if (this.args.locales) {
12
+ return this.withLocale()
13
+ } else {
14
+ return this.withoutLocale()
15
+ }
16
+ }
17
+
18
+ findRouteParams(route) {
19
+ var result = []
20
+ var parts = route.path.split("/")
21
+
22
+ for(var part of parts) {
23
+ if (part.match(/^:([a-z_]+)$/))
24
+ result.push(part)
25
+ }
26
+
27
+ return result
28
+ }
29
+
30
+ requireComponent() {
31
+ var requireResult = this.parsedContext[`components/${this.route.component}.jsx`]
32
+
33
+ if (!requireResult)
34
+ var requireResult = this.parsedContext[`components/${this.route.component}/index.jsx`]
35
+
36
+ if (!requireResult)
37
+ throw new Error(`No such require: ${this.route.component}`)
38
+
39
+ return requireResult.default
40
+ }
41
+
42
+ withLocale() {
43
+ var component = this.requireComponent()
44
+ var Locales = require("shared/locales").default
45
+ var Path = require("shared/path").default
46
+ var routes = []
47
+
48
+ for(var locale of Locales.availableLocales()) {
49
+ var path = Path.localized(inflection.camelize(this.route.name, true), this.findRouteParams(this.route), {locale: locale})
50
+
51
+ routes.push({
52
+ path: path,
53
+ component: component
54
+ })
55
+ }
56
+
57
+ return routes
58
+ }
59
+
60
+ withoutLocale() {
61
+ var routePathName = inflection.camelize(this.route.name, true)
62
+ var routePathMethod = Routes[`${routePathName}Path`]
63
+
64
+ if (!routePathMethod)
65
+ throw new Error(`No such route could be found: ${routePathName}`)
66
+
67
+ var path = routePathMethod.apply(null, this.findRouteParams(this.route))
68
+ var component = this.requireComponent()
69
+
70
+ return [{
71
+ path: path,
72
+ component: component
73
+ }]
74
+ }
75
+ }
@@ -0,0 +1,36 @@
1
+ import React from "react" // This fixes an issue with the Baristo project where it needed it to be loaded
2
+ import ResourceRoute from "./resource-route"
3
+ import { Route } from "react-router-dom"
4
+
5
+ export default class ApiMakerResourceRoutes {
6
+ static readRoutes(args = {}) {
7
+ if (!args.routes)
8
+ throw new Error("Please pass 'routes' to this method")
9
+
10
+ var parsedContext = ApiMakerResourceRoutes.parseContext(args)
11
+ var routesJson = args.routes
12
+ var routes = []
13
+
14
+ for(var route of routesJson.routes) {
15
+ var resourceRoute = new ResourceRoute({args, parsedContext, route})
16
+
17
+ for(var newRoute of resourceRoute.routes()) {
18
+ routes.push(
19
+ <Route exact key={`route-${newRoute.path}`} path={newRoute.path} component={newRoute.component} />
20
+ )
21
+ }
22
+ }
23
+
24
+ return routes
25
+ }
26
+
27
+ static parseContext(args) {
28
+ var result = {}
29
+ args.context.keys().forEach(key => {
30
+ var newKey = `${args.path}/${key.substring(2, key.length)}`
31
+ result[newKey] = args.context(key)
32
+ })
33
+
34
+ return result
35
+ }
36
+ }
@@ -0,0 +1,25 @@
1
+ export default class Result {
2
+ constructor(data) {
3
+ this.data = data
4
+ }
5
+
6
+ currentPage() {
7
+ return this.data.response.meta.currentPage
8
+ }
9
+
10
+ models() {
11
+ return this.data.models
12
+ }
13
+
14
+ modelClass() {
15
+ return this.data.collection.modelClass()
16
+ }
17
+
18
+ totalCount() {
19
+ return this.data.response.meta.totalCount
20
+ }
21
+
22
+ totalPages() {
23
+ return this.data.response.meta.totalPages
24
+ }
25
+ }
@@ -0,0 +1,113 @@
1
+ import Devise from "./devise"
2
+
3
+ var inflection = require("inflection")
4
+ var wakeEvent = require("wake-event")
5
+
6
+ export default class ApiMakerSessionStatusUpdater {
7
+ static current() {
8
+ if (!window.apiMakerSessionStatusUpdater)
9
+ window.apiMakerSessionStatusUpdater = new ApiMakerSessionStatusUpdater()
10
+
11
+ return window.apiMakerSessionStatusUpdater
12
+ }
13
+
14
+ constructor(args = {}) {
15
+ this.debugging = args.debug || false
16
+ this.events = {}
17
+ this.timeout = args.timeout || 600000
18
+
19
+ this.connectOnlineEvent()
20
+ this.connectWakeEvent()
21
+ }
22
+
23
+ connectOnlineEvent() {
24
+ window.addEventListener("online", () => { this.updateSessionStatus() }, false)
25
+ }
26
+
27
+ connectWakeEvent() {
28
+ wakeEvent(() => { this.updateSessionStatus() })
29
+ }
30
+
31
+ debug(message) {
32
+ if (this.debugging)
33
+ console.log(`ApiMakerSessionStatusUpdater: ${message}`)
34
+ }
35
+
36
+ sessionStatus() {
37
+ return new Promise(resolve => {
38
+ var xhr = new XMLHttpRequest()
39
+ xhr.open("POST", "/api_maker/session_statuses", true)
40
+ xhr.onload = () => {
41
+ var response = JSON.parse(xhr.responseText)
42
+ resolve(response)
43
+ }
44
+ xhr.send()
45
+ })
46
+ }
47
+
48
+ onSignedOut(callback) {
49
+ this.addEvent("onSignedOut", callback)
50
+ }
51
+
52
+ startTimeout() {
53
+ this.debug("startTimeout")
54
+
55
+ if (this.updateTimeout)
56
+ clearTimeout(this.updateTimeout)
57
+
58
+ this.updateTimeout = setTimeout(
59
+ () => {
60
+ this.startTimeout()
61
+ this.updateSessionStatus()
62
+ },
63
+ this.timeout
64
+ )
65
+ }
66
+
67
+ stopTimeout() {
68
+ if (this.updateTimeout)
69
+ clearTimeout(this.updateTimeout)
70
+ }
71
+
72
+ updateSessionStatus() {
73
+ this.debug("updateSessionStatus")
74
+
75
+ this.sessionStatus().then(result => {
76
+ this.debug(`Result: ${JSON.stringify(result, null, 2)}`)
77
+ this.updateMetaElementsFromResult(result)
78
+ this.updateUserSessionsFromResult(result)
79
+ })
80
+ }
81
+
82
+ updateMetaElementsFromResult(result) {
83
+ this.debug("updateMetaElementsFromResult")
84
+ var csrfTokenElement = document.querySelector("meta[name='csrf-token']")
85
+
86
+ if (csrfTokenElement) {
87
+ this.debug(`Changing token from "${csrfTokenElement.getAttribute("content")}" to "${result.csrf_token}"`)
88
+ csrfTokenElement.setAttribute("content", result.csrf_token)
89
+ } else {
90
+ this.debug("csrf token element couldn't be found")
91
+ }
92
+ }
93
+
94
+ updateUserSessionsFromResult(result) {
95
+ for(var scopeName in result.scopes) {
96
+ this.updateUserSessionScopeFromResult(scopeName, result.scopes[scopeName])
97
+ }
98
+ }
99
+
100
+ updateUserSessionScopeFromResult(scopeName, scope) {
101
+ var deviseMethodName = `current${inflection.camelize(scopeName)}`
102
+ var deviseIsSignedInMethodName = `is${inflection.camelize(scopeName)}SignedIn`
103
+ var currentlySignedIn = Devise[deviseIsSignedInMethodName]()
104
+ var signedInOnBackend = scope.signed_in
105
+
106
+ if (currentlySignedIn && !signedInOnBackend) {
107
+ this.debug(`${inflection.camelize(scopeName)} signed in on frontend but not in backend!`)
108
+
109
+ Devise.setSignedOut({scope: scopeName})
110
+ Devise.callSignOutEvent({scope: scopeName})
111
+ }
112
+ }
113
+ }