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,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
+ }