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,123 @@
1
+ import Api from "./api"
2
+ import CustomError from "./custom-error"
3
+ import Deserializer from "./deserializer"
4
+ import FormDataToObject from "./form-data-to-object"
5
+ import objectToFormData from "object-to-formdata"
6
+
7
+ export default class ApiMakerCommandsPool {
8
+ static addCommand(data, args = {}) {
9
+ if (args.instant) {
10
+ var pool = new ApiMakerCommandsPool()
11
+ } else {
12
+ var pool = ApiMakerCommandsPool.current()
13
+ }
14
+
15
+ var promiseResult = pool.addCommand(data)
16
+
17
+ if (args.instant) {
18
+ pool.flush()
19
+ } else {
20
+ pool.setFlushTimeout()
21
+ }
22
+
23
+ return promiseResult
24
+ }
25
+
26
+ static current() {
27
+ if (!window.currentApiMakerCommandsPool)
28
+ window.currentApiMakerCommandsPool = new ApiMakerCommandsPool()
29
+
30
+ return window.currentApiMakerCommandsPool
31
+ }
32
+
33
+ static flush() {
34
+ ApiMakerCommandsPool.current().flush()
35
+ }
36
+
37
+ constructor() {
38
+ this.pool = {}
39
+ this.poolData = {}
40
+ this.currentId = 1
41
+ this.globalRequestData = null
42
+ }
43
+
44
+ addCommand(data) {
45
+ return new Promise((resolve, reject) => {
46
+ var id = this.currentId
47
+ this.currentId += 1
48
+
49
+ var commandType = data.type
50
+ var commandName = data.command
51
+ var collectionName = data.collectionName
52
+
53
+ this.pool[id] = {resolve: resolve, reject: reject}
54
+
55
+ if (!this.poolData[commandType])
56
+ this.poolData[commandType] = {}
57
+
58
+ if (!this.poolData[commandType][collectionName])
59
+ this.poolData[commandType][collectionName] = {}
60
+
61
+ if (!this.poolData[commandType][collectionName][commandName])
62
+ this.poolData[commandType][collectionName][commandName] = {}
63
+
64
+ if (data.args instanceof FormData) {
65
+ var args = FormDataToObject.toObject(data.args)
66
+ } else {
67
+ var args = data.args
68
+ }
69
+
70
+ this.poolData[commandType][collectionName][commandName][id] = {
71
+ args: args,
72
+ primary_key: data.primaryKey,
73
+ id: id
74
+ }
75
+ })
76
+ }
77
+
78
+ async flush() {
79
+ if (Object.keys(this.pool) == 0)
80
+ return
81
+
82
+ this.clearTimeout()
83
+
84
+ var currentPool = this.pool
85
+ var currentPoolData = this.poolData
86
+
87
+ this.pool = {}
88
+ this.poolData = {}
89
+
90
+ var objectForFormData = {pool: currentPoolData}
91
+
92
+ if (this.globalRequestData)
93
+ objectForFormData.global = this.globalRequestData
94
+
95
+ var formData = objectToFormData(objectForFormData)
96
+ var url = `/api_maker/commands`
97
+ var response = await Api.requestLocal({path: url, method: "POST", rawData: formData})
98
+
99
+ for(var commandId in response.responses) {
100
+ var commandResponse = response.responses[commandId]
101
+ var commandResponseData = Deserializer.parse(commandResponse.data)
102
+ var commandData = currentPool[parseInt(commandId)]
103
+
104
+ if (commandResponse.type == "success") {
105
+ commandData.resolve(commandResponseData)
106
+ } else if (commandResponse.type == "error") {
107
+ commandData.reject(new CustomError("Command error", {response: commandResponseData}))
108
+ } else {
109
+ commandData.reject(new CustomError("Command failed", {response: commandResponseData}))
110
+ }
111
+ }
112
+ }
113
+
114
+ clearTimeout() {
115
+ if (this.flushTimeout)
116
+ clearTimeout(this.flushTimeout)
117
+ }
118
+
119
+ setFlushTimeout() {
120
+ this.clearTimeout()
121
+ this.flushTimeout = setTimeout(() => this.flush(), 0)
122
+ }
123
+ }
@@ -0,0 +1,14 @@
1
+ export default class ApiMakerCustomError extends Error {
2
+ constructor(message, args = {}) {
3
+ if (args.response && args.response.errors)
4
+ message = `${message}: ${args.response.errors.join(". ")}`
5
+
6
+ super(message)
7
+
8
+ // Maintains proper stack trace for where our error was thrown (only available on V8)
9
+ if (Error.captureStackTrace)
10
+ Error.captureStackTrace(this, ApiMakerCustomError)
11
+
12
+ this.args = args
13
+ }
14
+ }
@@ -0,0 +1,35 @@
1
+ import Money from "js-money"
2
+
3
+ const inflection = require("inflection")
4
+
5
+ export default class ApiMakerDeserializer {
6
+ static parse(object) {
7
+ if (Array.isArray(object)) {
8
+ return object.map(value => ApiMakerDeserializer.parse(value))
9
+ } else if (object && typeof object == "object") {
10
+ if (object.api_maker_type == "money") {
11
+ var cents = object.amount
12
+ var currency = object.currency
13
+
14
+ return Money.fromInteger(cents, currency)
15
+ } else if (object.api_maker_type == "model") {
16
+ var modelClassName = inflection.singularize(object.model_name)
17
+ var modelClass = require(`api-maker/models/${modelClassName}`).default
18
+ var model = new modelClass({data: object.serialized, isNewRecord: false})
19
+
20
+ return model
21
+ } else {
22
+ var newObject = {}
23
+
24
+ for(var key in object) {
25
+ var value = object[key]
26
+ newObject[key] = ApiMakerDeserializer.parse(value)
27
+ }
28
+
29
+ return newObject
30
+ }
31
+ } else {
32
+ return object
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,113 @@
1
+ import Api from "./api"
2
+ import CustomError from "./custom-error"
3
+ import EventEmitter from "events"
4
+ const inflection = require("inflection")
5
+
6
+ export default class Devise {
7
+ static callSignOutEvent(args) {
8
+ Devise.events().emit("onDeviseSignOut", {args: args})
9
+ }
10
+
11
+ static current() {
12
+ if (!window.currentApiMakerDevise)
13
+ window.currentApiMakerDevise = new Devise()
14
+
15
+ return window.currentApiMakerDevise
16
+ }
17
+
18
+ static events() {
19
+ if (!window.apiMakerDeviseEvents)
20
+ window.apiMakerDeviseEvents = new EventEmitter()
21
+
22
+ return window.apiMakerDeviseEvents
23
+ }
24
+
25
+ <% Devise.mappings.each do |scope| %>
26
+ <%
27
+ klass = scope[1].class_name.safe_constantize
28
+ resource = ApiMaker::Serializer.resource_for(klass)
29
+ %>
30
+ <% if resource %>
31
+ static <%= ApiMaker::JsMethodNamerService.execute!(name: "is_#{scope[0]}_signed_in") %>() {
32
+ if (Devise.current().getCurrentScope("<%= scope[1].class_name %>"))
33
+ return true
34
+
35
+ return false
36
+ }
37
+
38
+ static current<%= scope[1].class_name %>() {
39
+ return Devise.current().getCurrentScope("<%= scope[1].class_name %>")
40
+ }
41
+ <% end %>
42
+ <% end %>
43
+
44
+ static async signIn(username, password, args = {}) {
45
+ if (!args.scope)
46
+ args.scope = "user"
47
+
48
+ var postData = {"username": username, "password": password, "args": args}
49
+ var response = await Api.post("/api_maker/devise/do_sign_in", postData)
50
+
51
+ if (response.success) {
52
+ var modelClass = require(`api-maker/models/${inflection.dasherize(args.scope)}`).default
53
+ var modelInstance = new modelClass(response.model_data)
54
+
55
+ Devise.updateSession(modelInstance)
56
+ Devise.events().emit("onDeviseSignIn", Object.assign({username: username}, args))
57
+
58
+ return {model: modelInstance, response: response}
59
+ } else {
60
+ throw new CustomError("Sign in failed", {response: response})
61
+ }
62
+ }
63
+
64
+ static updateSession(model) {
65
+ var scope = model.modelClassData().name
66
+ Devise.current().currents[scope] = model
67
+ }
68
+
69
+ static setSignedOut(args) {
70
+ Devise.current().currents[inflection.camelize(args.scope)] = null
71
+ }
72
+
73
+ static async signOut(args = {}) {
74
+ if (!args.scope)
75
+ args.scope = "user"
76
+
77
+ var postData = {"args": args}
78
+ var response = await Api.post("/api_maker/devise/do_sign_out", postData)
79
+
80
+ if (response.success) {
81
+ Devise.setSignedOut(args)
82
+ Devise.callSignOutEvent(args)
83
+ return response
84
+ } else {
85
+ throw new CustomError("Sign out failed", {response: response})
86
+ }
87
+ }
88
+
89
+ constructor() {
90
+ this.currents = {}
91
+ }
92
+
93
+ getCurrentScope(scope) {
94
+ if (!(scope in this.currents))
95
+ this.currents[scope] = this.loadCurrentScope(scope)
96
+
97
+ return this.currents[scope]
98
+ }
99
+
100
+ loadCurrentScope(scope) {
101
+ var apiMakerDataElement = document.querySelector(".api-maker-data")
102
+ var keyName = `current${inflection.camelize(scope)}`
103
+ var scopeData = apiMakerDataElement.dataset[keyName]
104
+
105
+ if (!scopeData)
106
+ return null
107
+
108
+ var modelClass = require(`api-maker/models/${inflection.dasherize(inflection.underscore(scope))}`).default
109
+ var modelInstance = new modelClass({data: JSON.parse(scopeData)})
110
+
111
+ return modelInstance
112
+ }
113
+ }
@@ -0,0 +1,119 @@
1
+ import retrace from "retrace"
2
+
3
+ export default class ErrorLogger {
4
+ constructor() {
5
+ this.errors = []
6
+ }
7
+
8
+ loadSourceMaps() {
9
+ return new Promise(resolve => {
10
+ var scripts = document.querySelectorAll("script")
11
+ var promises = []
12
+
13
+ for(var script of scripts) {
14
+ var src = script.getAttribute("src")
15
+ var type = script.getAttribute("type")
16
+
17
+ if (src && src.includes("/packs/") && (type == "text/javascript" || !type)) {
18
+ var promise = this.loadSourceMapForScript(script)
19
+ promises.push(promise)
20
+ }
21
+ }
22
+
23
+ Promise.all(promises).then(() => { resolve() })
24
+ })
25
+ }
26
+
27
+ loadSourceMapForScript(script) {
28
+ var src = script.getAttribute("src")
29
+ var url = this.loadUrl(src)
30
+ var originalUrl = `${url.origin}${url.pathname}`
31
+ var mapUrl = `${url.origin}${url.pathname}.map`
32
+
33
+ return new Promise(resolve => {
34
+ var xhr = new XMLHttpRequest()
35
+ xhr.open("GET", mapUrl, true)
36
+ xhr.onload = () => {
37
+ retrace.register(originalUrl, xhr.responseText)
38
+ resolve()
39
+ }
40
+ xhr.send()
41
+ })
42
+ }
43
+
44
+ loadUrl(url) {
45
+ var parser = document.createElement("a")
46
+ parser.href = url
47
+
48
+ return parser
49
+ }
50
+
51
+ enable() {
52
+ this.connectOnError()
53
+ this.connectUnhandledRejection()
54
+ }
55
+
56
+ getErrors() {
57
+ return this.errors
58
+ }
59
+
60
+ connectOnError() {
61
+ window.addEventListener("error", (event) => {
62
+ if (!this.isHandlingError) {
63
+ try {
64
+ this.isHandlingError = true
65
+ this.errors.push({
66
+ errorClass: event.error ? event.error.name : "No error class",
67
+ file: event.filename,
68
+ message: event.message || "Unknown error",
69
+ url: window.location.href,
70
+ line: event.lineno,
71
+ error: event.error
72
+ })
73
+ } finally {
74
+ this.isHandlingError = false
75
+ }
76
+ }
77
+ })
78
+ }
79
+
80
+ connectUnhandledRejection() {
81
+ window.addEventListener("unhandledrejection", (event, test) => {
82
+ if (!this.isHandlingError) {
83
+ this.isHandlingError = true
84
+
85
+ try {
86
+ if (event.reason.stack) {
87
+ retrace.map(event.reason.stack).then(mappedStackTrace => {
88
+ this.errors.push({
89
+ errorClass: "UnhandledRejection",
90
+ file: null,
91
+ message: event.reason.message || "Unhandled promise rejection",
92
+ url: window.location.href,
93
+ line: null,
94
+ backtrace: mappedStackTrace.split("\n")
95
+ })
96
+ })
97
+ } else {
98
+ this.errors.push({
99
+ errorClass: "UnhandledRejection",
100
+ file: null,
101
+ message: event.reason.message || "Unhandled promise rejection",
102
+ url: window.location.href,
103
+ line: null,
104
+ backtrace: null
105
+ })
106
+ }
107
+ } finally {
108
+ this.isHandlingError = false
109
+ }
110
+ }
111
+ })
112
+ }
113
+
114
+ testPromiseError() {
115
+ return new Promise(resolve => {
116
+ throw new Error("testPromiseError")
117
+ })
118
+ }
119
+ }
@@ -0,0 +1,24 @@
1
+ import PropTypes from "prop-types"
2
+ import PropTypesExact from "prop-types-exact"
3
+ import React from "react"
4
+
5
+ export default class ApiMakerEventConnection extends React.Component {
6
+ static propTypes = PropTypesExact({
7
+ model: PropTypes.object.isRequired,
8
+ event: PropTypes.string.isRequired,
9
+ onCall: PropTypes.func.isRequired
10
+ })
11
+
12
+ componentDidMount() {
13
+ this.subscription = this.props.model.connect(this.props.event, this.props.onCall)
14
+ }
15
+
16
+ componentWillUnmount() {
17
+ if (this.subscription)
18
+ this.subscription.unsubscribe()
19
+ }
20
+
21
+ render() {
22
+ return ""
23
+ }
24
+ }
@@ -0,0 +1,26 @@
1
+ import PropTypes from "prop-types"
2
+ import PropTypesExact from "prop-types-exact"
3
+ import React from "react"
4
+
5
+ export default class ApiMakerEventCreated extends React.Component {
6
+ static propTypes = PropTypesExact({
7
+ modelClass: PropTypes.func.isRequired,
8
+ onCreated: PropTypes.func.isRequired
9
+ })
10
+
11
+ componentDidMount() {
12
+ this.connect()
13
+ }
14
+
15
+ componentWillUnmount() {
16
+ this.connectCreated.unsubscribe()
17
+ }
18
+
19
+ connect() {
20
+ this.connectCreated = this.props.modelClass.connectCreated(this.props.onCreated)
21
+ }
22
+
23
+ render() {
24
+ return ""
25
+ }
26
+ }