github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/design/api/README.md (about) 1 # Motivation 2 - We need good REST API for Voedger 3 - Old API must still be available until the new one is fully developed, so we can continue with AIR 4 5 # Functional Design 6 7 ## API URL 8 API URL must support versioning ([example IBM MQ](https://www.ibm.com/docs/en/ibm-mq/9.1?topic=api-rest-versions), [example Chargebee](https://apidocs.chargebee.com/docs/api/)): 9 10 - old API is available at `/api/v1/...` (for the period of AIR migration it will be available both on `/api/` and `/api/v1/`) 11 - new API is available at `/api/v2/...` 12 - "v1" is not allowed as an owner name, at least until API "v1" is ready 13 14 TODO: add endpoint for the list of supported versions 15 16 ## REST API Paths 17 18 | Action | REST API Path | 19 |--------------------------------------|------------------------------------------------| 20 | Create CDoc/WDoc/CRecord/WRecord | `POST /api/v2/owner/app/wsid/pkg.table` | 21 | Update CDoc/WDoc/CRecord/WRecord | `PATCH /api/v2/owner/app/wsid/pkg.table/id` | 22 | Deactivate CDoc/WDoc/CRecord/WRecord | `DELETE /api/v2/owner/app/wsid/pkg.table/id` | 23 | Execute Command | `POST /api/v2/owner/app/wsid/pkg.command` | 24 | Read CDoc/WDoc/CRecord/WRecord | `GET /api/v2/owner/app/wsid/pkg.table/id` | 25 | Read from Query Function | `GET /api/v2/owner/app/wsid/pkg.query` | 26 | Read from CDoc Collection | `GET /api/v2/owner/app/wsid/pkg.table` | 27 | Read from View | `GET /api/v2/owner/app/wsid/pkg.view` | 28 29 30 ## Query Processor based on GET 31 Current design of the QueryProcessor based on POST queries. 32 However, according to many resources, using POST for queries in RESTful API is not a good practice: 33 - [Swagger.io: best practices in API design](https://swagger.io/resources/articles/best-practices-in-api-design/) 34 - [MS Azure Architectural Center: Define API operations in terms of HTTP methods](https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design#define-api-operations-in-terms-of-http-methods) 35 - [StackOverflow: REST API using POST instead of GET](https://stackoverflow.com/questions/19637459/rest-api-using-post-instead-of-get) 36 37 Also, using GET and POST allows to distinguish between Query and Command processors clearly: 38 39 | HTTP Method | Processor | 40 |---------------------|-------------------| 41 | GET | Query Processor | 42 | POST, PATCH, DELETE | Command Processor | 43 44 >> Note: according to RESTful API design, queries should not change the state of the system. Current QueryFunction design allows it to execute commands through HTTP bus. 45 46 Another thing is that according to REST best practices, it is not recommended to use verbs in the URL, the resource names should be based on nouns: 47 48 [Example Microsoft](https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design#organize-the-api-design-around-resources): 49 ``` 50 POST https://adventure-works.com/orders // Good 51 POST https://adventure-works.com/create-order // Avoid 52 ``` 53 54 Summary, the following Queries in airs-bp3: 55 ``` 56 POST .../IssueLinkDeviceToken 57 POST .../GetSalesMetrics 58 ``` 59 violate Restful API design: 60 - uses POST for query, without changing the server state 61 - uses verb in the URL 62 63 Should be: 64 ``` 65 GET .../TokenToLinkDevice?args=... 66 GET .../SalesMetrics?args=... 67 ``` 68 69 ### Query Constraints and Query Arguments 70 Every query may have constraints (ex. [IQueryArguments]( https://dev.heeus.io/launchpad/#!12396)) and arguments. 71 72 Constraints are: 73 - order (string) - order by field 74 - limit (int) - limit number of records 75 - skip (int) skip number of records 76 - include (string) - include referenced objects 77 - keys (string) - select only some field(s) 78 - where (object) - filter records 79 80 Arguments are optional and are passed in `&arg=...` GET parameter. 81 82 ### ACL 83 EXECUTE -> SELECT for Queries? 84 85 Currently: 86 ```sql 87 LIMIT AllQueriesLimit EXECUTE ON ALL QUERIES WITH TAG PosTag WITH RATE AppDefaultRate; 88 GRANT EXECUTE ON QUERY Query1 TO LocationUser; 89 ``` 90 91 Should be: 92 ```sql 93 LIMIT AllQueriesLimit SELECT ON ALL QUERIES WITH TAG PosTag WITH RATE AppDefaultRate; 94 GRANT SELECT ON QUERY Query1 TO LocationUser; 95 ``` 96 97 98 ## Paths Detailed 99 100 ### Create CDoc/WDoc/CRecord/WRecord object 101 102 - URL: 103 - `POST /api/v2/owner/app/wsid/pkg.table` 104 - Parameters: 105 - application/json 106 - CDoc/WDoc/CRecord/WRecord 107 - Errors: 108 - 400: Bad Request, e.g. Record requires sys.ParentID 109 - 401: Unauthorized 110 - 403: Forbidden 111 - 404: Table Not Found 112 - 405: Method Not Allowed, table is an ODoc/ORecord 113 114 200 Result: 115 ```json 116 { 117 "CurrentWLogOffset":114, 118 "NewIDs": { 119 "1":322685000131212 120 } 121 } 122 ``` 123 124 ### Read CDoc/WDoc/CRecord/WRecord 125 - URL: 126 - `GET /api/v2/owner/app/wsid/pkg.table/id` 127 - Parameters: none 128 - Result: 129 - CDoc/WDoc/CRecord/WRecord object 130 - Errors: 131 - 401: Unauthorized 132 - 403: Forbidden 133 - 404: Table Not Found 134 - 405: Method Not Allowed, table is an ODoc/ORecord 135 136 ### Update CDoc/WDoc/CRecord/WRecord 137 - URL: 138 - `PATCH /api/v2/owner/app/wsid/pkg.table/id` 139 - Parameters: 140 - application/json 141 - CDoc/WDoc/CRecord/WRecord (fields to be updated) 142 - Errors: 143 - 400: Bad Request, e.g. Record requires sys.ParentID 144 - 401: Unauthorized 145 - 403: Forbidden 146 - 404: Table Not Found 147 - 405: Method Not Allowed, table is an ODoc/ORecord 148 149 200 Result: 150 ```json 151 { 152 "CurrentWLogOffset":114, 153 "NewIDs": { 154 "1":322685000131212 155 } 156 } 157 ``` 158 159 ### Deactivate CDoc/WDoc/CRecord/WRecord 160 - URL: 161 - `DELETE /api/v2/owner/app/wsid/pkg.table/id` 162 - Parameters: none 163 - Errors: 164 - 401: Unauthorized 165 - 403: Forbidden 166 - 404: Table Not Found 167 - 405: Method Not Allowed, table is an ODoc/ORecord 168 169 200 Result: 170 ```json 171 { 172 "CurrentWLogOffset":114, 173 } 174 ``` 175 176 177 ### Read from Query 178 - URL: 179 - `GET /api/v2/owner/app/wsid/pkg.query` 180 - Parameters: 181 - Query [constraints](../queryprocessor/request.md) 182 - Query function argument `&arg=...` 183 - Result: 184 - The return value is a JSON object that contains a results field with a JSON array that lists the objects [example](../queryprocessor/request.md), ref. [Parse API](https://docs.parseplatform.org/rest/guide/#basic-queries) 185 - Errors: 186 - 401: Unauthorized 187 - 403: Forbidden 188 - 404: Query Function Not Found 189 190 - Examples: 191 - Read from WLog 192 - `GET /api/v2/owner/app/wsid/sys.wlog?limit=100&skip=13994` 193 - Read OpenAPI app schema 194 - `GET /api/v2/owner/app/wsid/sys.OpenApi` 195 196 ### Read from CDoc collection 197 - URL: 198 - `GET /api/v2/owner/app/wsid/pkg.table` 199 - Parameters: 200 - Query [constraints](../queryprocessor/request.md) 201 - Result: 202 - The return value is a JSON object that contains a results field with a JSON array that lists the objects [example](../queryprocessor/request.md) 203 - Errors: 204 - 401: Unauthorized 205 - 403: Forbidden 206 - 404: Table Not Found 207 - Examples: 208 - Read articles 209 - `GET /api/v2/untill/airs-bp3/12313123123/untill.articles?limit=20&skip=20` 210 211 ### Read from View 212 - URL: 213 - `GET /api/v2/owner/app/wsid/pkg.view` 214 - Parameters: 215 - Query [constraints](../queryprocessor/request.md) 216 - Constraints 217 - "where" must contain "eq" or "in" condition for PK fields 218 - Result: 219 - The return value is a JSON object that contains a results field with a JSON array that lists the objects [example](../queryprocessor/request.md) 220 - Errors: 221 - 401: Unauthorized 222 - 403: Forbidden 223 - 404: View Not Found 224 - Examples: 225 - `GET /api/v2/untill/airs-bp3/12313123123/air.SalesMetrics?where={"Year":2024, "Month":{"$in":[1,2,3]}}` 226 ### Execute Command 227 - URL 228 - `POST /api/v2/owner/app/wsid/pkg.command` 229 - Parameters: 230 - application/json 231 - Parameter Type / ODoc 232 - Result: 233 - application/json 234 - Return Type 235 - Errors: 236 - 404: Command Not Found 237 - 403: Forbidden 238 - 401: Unauthorized 239 240 ## Errors 241 When HTTP Result code is not OK, then [response](https://docs.parseplatform.org/rest/guide/#response-format) is an object: 242 ```json 243 { 244 "code": 105, 245 "error": "invalid field name: bl!ng" 246 } 247 ``` 248 249 250 # Limitations 251 - sys.CUD function cannot be called directly 252 253 # Technical Design 254 ## Router: 255 - redirects to api v1/v2 256 - for v2, based on HTTP Method: 257 - GET -> QP 258 - Query Function 259 - System functions for: 260 - Collection of CDocs 261 - View 262 - POST, PUT, DELETE -> CP 263 - name is CDoc/WDoc/CRecord/WRecord: exec CUD command 264 - POST && name_is_command: exec this command 265 266 ## Updates to Query Processor 267 [GET params](../queryprocessor/request.md) conversion: 268 - Query constraints (`order`, `limit`, `skip`, `include`, `keys` -> `sys.QueryParams` 269 - Query `arg` -> `sys.QueryArgs` 270 271 Example: 272 ```bash 273 curl -X GET \ 274 -H "AccessToken: ${ACCESS_TOKEN}" 275 --data-urlencode 'arg={"SalesMode":1,"TableNumber":100,"BillPrinter":12312312312,"SalesArea":12312312333}' 276 277 https://air.untill.com/api/rest/untill/airs-bp/140737488486431/air.IssueLinkDeviceToken 278 279 ``` 280 281 ## Migration to GET in Queries 282 Some existing components must be updated: 283 - Air Payouts we use Query Functions for webhooks. In this case, they should be changed to commands + projectors. 284 285 ## `sys.OpenApi` query function 286