api_maker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+ }