@hablala/api-contract 0.7.0 → 0.8.0

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.
package/dist/index.d.ts CHANGED
@@ -1335,6 +1335,26 @@ interface paths {
1335
1335
  patch?: never;
1336
1336
  trace?: never;
1337
1337
  };
1338
+ "/v1/objects/{object_slug}/uploads": {
1339
+ parameters: {
1340
+ query?: never;
1341
+ header?: never;
1342
+ path?: never;
1343
+ cookie?: never;
1344
+ };
1345
+ get?: never;
1346
+ put?: never;
1347
+ /**
1348
+ * Pedir una URL de subida para un attribute file (staged upload)
1349
+ * @description Fase 1 del flujo de archivos de un record: valida el archivo declarado contra el config del attribute `file` (`accepted_content_types` con comodines `image/*`, `max_size_bytes`) y devuelve el destino de subida prefirmado. Luego: (2) `PUT` del blob crudo a `uploadUrl` con `headers`; (3) confirmar escribiendo el record con `fileUrl` en el envelope del attribute (`{"url": fileUrl, …}`) — el servidor verifica el blob contra el storage (tamaño/tipo REALES) y lo promueve a su ubicación definitiva. Un blob nunca confirmado expira solo. Gate `records:create`.
1350
+ */
1351
+ post: operations["HablalaApiWeb.Machine.UploadController.create"];
1352
+ delete?: never;
1353
+ options?: never;
1354
+ head?: never;
1355
+ patch?: never;
1356
+ trace?: never;
1357
+ };
1338
1358
  "/v1/organizations": {
1339
1359
  parameters: {
1340
1360
  query?: never;
@@ -3617,6 +3637,26 @@ interface paths {
3617
3637
  patch: operations["HablalaApiWeb.PublicApi.RecordController.update (2)"];
3618
3638
  trace?: never;
3619
3639
  };
3640
+ "/v1/public/me/objects/{object_slug}/uploads": {
3641
+ parameters: {
3642
+ query?: never;
3643
+ header?: never;
3644
+ path?: never;
3645
+ cookie?: never;
3646
+ };
3647
+ get?: never;
3648
+ put?: never;
3649
+ /**
3650
+ * Pedir una URL de subida para un attribute file (canal público)
3651
+ * @description Como el staged upload del canal de máquina (`POST /v1/objects/{object_slug}/uploads`), pero gobernado por el ABAC default-deny del canal público: exige una policy `allow` de `create` o `update` para el principal (`public` con la API key; `end_user` con el session token en la variante `/me`) sobre el object — sin policy, 403. Flujo: presign → `PUT` del blob a `uploadUrl` → confirmar escribiendo el record con `fileUrl` en el envelope del attribute `file`.
3652
+ */
3653
+ post: operations["HablalaApiWeb.PublicApi.UploadController.create"];
3654
+ delete?: never;
3655
+ options?: never;
3656
+ head?: never;
3657
+ patch?: never;
3658
+ trace?: never;
3659
+ };
3620
3660
  "/v1/public/me/query": {
3621
3661
  parameters: {
3622
3662
  query?: never;
@@ -3673,6 +3713,26 @@ interface paths {
3673
3713
  patch: operations["HablalaApiWeb.PublicApi.RecordController.update (4)"];
3674
3714
  trace?: never;
3675
3715
  };
3716
+ "/v1/public/objects/{object_slug}/uploads": {
3717
+ parameters: {
3718
+ query?: never;
3719
+ header?: never;
3720
+ path?: never;
3721
+ cookie?: never;
3722
+ };
3723
+ get?: never;
3724
+ put?: never;
3725
+ /**
3726
+ * Pedir una URL de subida para un attribute file (canal público)
3727
+ * @description Como el staged upload del canal de máquina (`POST /v1/objects/{object_slug}/uploads`), pero gobernado por el ABAC default-deny del canal público: exige una policy `allow` de `create` o `update` para el principal (`public` con la API key; `end_user` con el session token en la variante `/me`) sobre el object — sin policy, 403. Flujo: presign → `PUT` del blob a `uploadUrl` → confirmar escribiendo el record con `fileUrl` en el envelope del attribute `file`.
3728
+ */
3729
+ post: operations["HablalaApiWeb.PublicApi.UploadController.create (2)"];
3730
+ delete?: never;
3731
+ options?: never;
3732
+ head?: never;
3733
+ patch?: never;
3734
+ trace?: never;
3735
+ };
3676
3736
  "/v1/public/query": {
3677
3737
  parameters: {
3678
3738
  query?: never;
@@ -7185,6 +7245,7 @@ interface components {
7185
7245
  Policy: {
7186
7246
  /** @enum {string} */
7187
7247
  action: "read" | "create" | "update" | "delete";
7248
+ /** @description Predicado por fila: hoja `{attr, op, value|value_from}` o árbol `and`/`or`. `attr` admite un attribute path de UN nivel por un attribute `reference` (`pet.owner`: condiciona sobre el record destino; fail-closed si la referencia es nula). */
7188
7249
  condition?: Record<string, never>;
7189
7250
  /** @enum {string} */
7190
7251
  effect: "allow" | "deny";
@@ -7206,6 +7267,7 @@ interface components {
7206
7267
  PolicyInput: {
7207
7268
  /** @enum {string} */
7208
7269
  action: "read" | "create" | "update" | "delete";
7270
+ /** @description Predicado por fila: hoja `{attr, op, value|value_from}` o árbol `and`/`or`. `attr` admite un attribute path de UN nivel por un attribute `reference` (`pet.owner`: condiciona sobre el record destino; fail-closed si la referencia es nula). */
7209
7271
  condition?: Record<string, never>;
7210
7272
  /** @enum {string} */
7211
7273
  effect: "allow" | "deny";
@@ -7503,6 +7565,38 @@ interface components {
7503
7565
  RecordResponse: {
7504
7566
  data: components["schemas"]["Record"];
7505
7567
  };
7568
+ /**
7569
+ * RecordUploadRequest
7570
+ * @description Archivo a adjuntar a un attribute `file` de un record: para qué attribute es y qué se va a subir. El servidor lo valida contra el config del attribute (`accepted_content_types` con comodines `image/*`, `max_size_bytes`) antes de emitir la URL de subida.
7571
+ */
7572
+ RecordUploadRequest: {
7573
+ /** @description Slug del attribute `data_type: file` del object al que se adjuntará. */
7574
+ attribute: string;
7575
+ /** @description MIME type del archivo (p. ej. `image/jpeg`). Se valida contra `accepted_content_types` del attribute. */
7576
+ contentType: string;
7577
+ /** @description Nombre del archivo (p. ej. `firulais.jpg`). */
7578
+ filename: string;
7579
+ /** @description Tamaño declarado en bytes (opcional). Si excede `max_size_bytes` el presign se rechaza aquí (fail-fast); la verdad final la impone el storage al confirmar el record (el tamaño REAL se relee del blob). */
7580
+ size?: number | null;
7581
+ };
7582
+ /** RecordUploadResponse */
7583
+ RecordUploadResponse: {
7584
+ data: components["schemas"]["RecordUploadTarget"];
7585
+ };
7586
+ /**
7587
+ * RecordUploadTarget
7588
+ * @description Destino de subida prefirmado (*staged upload target*, patrón Shopify `stagedUploadsCreate` / S3 presigned upload): el cliente hace `PUT` del blob crudo a `uploadUrl` con `headers`, y luego CONFIRMA escribiendo el record con `fileUrl` en el envelope del attribute `file` (`{"url": fileUrl, "filename": …, "content_type": …}`). Un blob nunca confirmado expira solo en el staging.
7589
+ */
7590
+ RecordUploadTarget: {
7591
+ /** @description URL del blob (en staging `pending/…`): va como `url` en el envelope del attribute `file` al crear/actualizar el record. Al confirmarse, el servidor la reescribe a la definitiva. */
7592
+ fileUrl: string;
7593
+ /** @description Cabeceras que el cliente debe enviar en el `PUT` (p. ej. `content-type`). */
7594
+ headers?: {
7595
+ [key: string]: string;
7596
+ };
7597
+ /** @description Destino del `PUT` del blob (URL prefirmada, expira). */
7598
+ uploadUrl: string;
7599
+ };
7506
7600
  /** RegisterCredentials */
7507
7601
  RegisterCredentials: {
7508
7602
  /** Format: email */
@@ -11008,6 +11102,51 @@ interface operations {
11008
11102
  };
11009
11103
  };
11010
11104
  };
11105
+ "HablalaApiWeb.Machine.UploadController.create": {
11106
+ parameters: {
11107
+ query?: never;
11108
+ header?: never;
11109
+ path: {
11110
+ object_slug: string;
11111
+ };
11112
+ cookie?: never;
11113
+ };
11114
+ /** @description Archivo a subir */
11115
+ requestBody?: {
11116
+ content: {
11117
+ "application/json": components["schemas"]["RecordUploadRequest"];
11118
+ };
11119
+ };
11120
+ responses: {
11121
+ /** @description Destino de subida prefirmado */
11122
+ 200: {
11123
+ headers: {
11124
+ [name: string]: unknown;
11125
+ };
11126
+ content: {
11127
+ "application/json": components["schemas"]["RecordUploadResponse"];
11128
+ };
11129
+ };
11130
+ /** @description Objeto desconocido */
11131
+ 404: {
11132
+ headers: {
11133
+ [name: string]: unknown;
11134
+ };
11135
+ content: {
11136
+ "application/json": components["schemas"]["Error"];
11137
+ };
11138
+ };
11139
+ /** @description Attribute inexistente/no-file, MIME no admitido o tamaño excedido */
11140
+ 422: {
11141
+ headers: {
11142
+ [name: string]: unknown;
11143
+ };
11144
+ content: {
11145
+ "application/json": components["schemas"]["Error"];
11146
+ };
11147
+ };
11148
+ };
11149
+ };
11011
11150
  "HablalaApiWeb.Orgs.OrganizationController.index": {
11012
11151
  parameters: {
11013
11152
  query?: never;
@@ -17763,6 +17902,60 @@ interface operations {
17763
17902
  };
17764
17903
  };
17765
17904
  };
17905
+ "HablalaApiWeb.PublicApi.UploadController.create": {
17906
+ parameters: {
17907
+ query?: never;
17908
+ header?: never;
17909
+ path: {
17910
+ object_slug: string;
17911
+ };
17912
+ cookie?: never;
17913
+ };
17914
+ /** @description Archivo a subir */
17915
+ requestBody?: {
17916
+ content: {
17917
+ "application/json": components["schemas"]["RecordUploadRequest"];
17918
+ };
17919
+ };
17920
+ responses: {
17921
+ /** @description Destino de subida prefirmado */
17922
+ 200: {
17923
+ headers: {
17924
+ [name: string]: unknown;
17925
+ };
17926
+ content: {
17927
+ "application/json": components["schemas"]["RecordUploadResponse"];
17928
+ };
17929
+ };
17930
+ /** @description Sin policy allow de create/update para este principal */
17931
+ 403: {
17932
+ headers: {
17933
+ [name: string]: unknown;
17934
+ };
17935
+ content: {
17936
+ "application/json": components["schemas"]["Error"];
17937
+ };
17938
+ };
17939
+ /** @description Objeto desconocido */
17940
+ 404: {
17941
+ headers: {
17942
+ [name: string]: unknown;
17943
+ };
17944
+ content: {
17945
+ "application/json": components["schemas"]["Error"];
17946
+ };
17947
+ };
17948
+ /** @description Attribute inexistente/no-file, MIME no admitido o tamaño excedido */
17949
+ 422: {
17950
+ headers: {
17951
+ [name: string]: unknown;
17952
+ };
17953
+ content: {
17954
+ "application/json": components["schemas"]["Error"];
17955
+ };
17956
+ };
17957
+ };
17958
+ };
17766
17959
  "HablalaApiWeb.PublicApi.QueryController.create (2)": {
17767
17960
  parameters: {
17768
17961
  query?: never;
@@ -18018,6 +18211,60 @@ interface operations {
18018
18211
  };
18019
18212
  };
18020
18213
  };
18214
+ "HablalaApiWeb.PublicApi.UploadController.create (2)": {
18215
+ parameters: {
18216
+ query?: never;
18217
+ header?: never;
18218
+ path: {
18219
+ object_slug: string;
18220
+ };
18221
+ cookie?: never;
18222
+ };
18223
+ /** @description Archivo a subir */
18224
+ requestBody?: {
18225
+ content: {
18226
+ "application/json": components["schemas"]["RecordUploadRequest"];
18227
+ };
18228
+ };
18229
+ responses: {
18230
+ /** @description Destino de subida prefirmado */
18231
+ 200: {
18232
+ headers: {
18233
+ [name: string]: unknown;
18234
+ };
18235
+ content: {
18236
+ "application/json": components["schemas"]["RecordUploadResponse"];
18237
+ };
18238
+ };
18239
+ /** @description Sin policy allow de create/update para este principal */
18240
+ 403: {
18241
+ headers: {
18242
+ [name: string]: unknown;
18243
+ };
18244
+ content: {
18245
+ "application/json": components["schemas"]["Error"];
18246
+ };
18247
+ };
18248
+ /** @description Objeto desconocido */
18249
+ 404: {
18250
+ headers: {
18251
+ [name: string]: unknown;
18252
+ };
18253
+ content: {
18254
+ "application/json": components["schemas"]["Error"];
18255
+ };
18256
+ };
18257
+ /** @description Attribute inexistente/no-file, MIME no admitido o tamaño excedido */
18258
+ 422: {
18259
+ headers: {
18260
+ [name: string]: unknown;
18261
+ };
18262
+ content: {
18263
+ "application/json": components["schemas"]["Error"];
18264
+ };
18265
+ };
18266
+ };
18267
+ };
18021
18268
  "HablalaApiWeb.PublicApi.QueryController.create": {
18022
18269
  parameters: {
18023
18270
  query?: never;
package/openapi.json CHANGED
@@ -9356,6 +9356,7 @@
9356
9356
  "x-validate": null
9357
9357
  },
9358
9358
  "condition": {
9359
+ "description": "Predicado por fila: hoja `{attr, op, value|value_from}` o árbol `and`/`or`. `attr` admite un attribute path de UN nivel por un attribute `reference` (`pet.owner`: condiciona sobre el record destino; fail-closed si la referencia es nula).",
9359
9360
  "type": "object",
9360
9361
  "x-struct": null,
9361
9362
  "x-validate": null
@@ -9457,6 +9458,7 @@
9457
9458
  "x-validate": null
9458
9459
  },
9459
9460
  "condition": {
9461
+ "description": "Predicado por fila: hoja `{attr, op, value|value_from}` o árbol `and`/`or`. `attr` admite un attribute path de UN nivel por un attribute `reference` (`pet.owner`: condiciona sobre el record destino; fail-closed si la referencia es nula).",
9460
9462
  "type": "object",
9461
9463
  "x-struct": null,
9462
9464
  "x-validate": null
@@ -10476,6 +10478,96 @@
10476
10478
  "x-struct": "Elixir.HablalaApiWeb.Schemas.RecordResponse",
10477
10479
  "x-validate": null
10478
10480
  },
10481
+ "RecordUploadRequest": {
10482
+ "description": "Archivo a adjuntar a un attribute `file` de un record: para qué attribute es y qué se va a subir. El servidor lo valida contra el config del attribute (`accepted_content_types` con comodines `image/*`, `max_size_bytes`) antes de emitir la URL de subida.",
10483
+ "properties": {
10484
+ "attribute": {
10485
+ "description": "Slug del attribute `data_type: file` del object al que se adjuntará.",
10486
+ "type": "string",
10487
+ "x-struct": null,
10488
+ "x-validate": null
10489
+ },
10490
+ "contentType": {
10491
+ "description": "MIME type del archivo (p. ej. `image/jpeg`). Se valida contra `accepted_content_types` del attribute.",
10492
+ "type": "string",
10493
+ "x-struct": null,
10494
+ "x-validate": null
10495
+ },
10496
+ "filename": {
10497
+ "description": "Nombre del archivo (p. ej. `firulais.jpg`).",
10498
+ "type": "string",
10499
+ "x-struct": null,
10500
+ "x-validate": null
10501
+ },
10502
+ "size": {
10503
+ "description": "Tamaño declarado en bytes (opcional). Si excede `max_size_bytes` el presign se rechaza aquí (fail-fast); la verdad final la impone el storage al confirmar el record (el tamaño REAL se relee del blob).",
10504
+ "minimum": 1,
10505
+ "nullable": true,
10506
+ "type": "integer",
10507
+ "x-struct": null,
10508
+ "x-validate": null
10509
+ }
10510
+ },
10511
+ "required": [
10512
+ "attribute",
10513
+ "filename",
10514
+ "contentType"
10515
+ ],
10516
+ "title": "RecordUploadRequest",
10517
+ "type": "object",
10518
+ "x-struct": "Elixir.HablalaApiWeb.Schemas.RecordUploadRequest",
10519
+ "x-validate": null
10520
+ },
10521
+ "RecordUploadResponse": {
10522
+ "properties": {
10523
+ "data": {
10524
+ "$ref": "#/components/schemas/RecordUploadTarget"
10525
+ }
10526
+ },
10527
+ "required": [
10528
+ "data"
10529
+ ],
10530
+ "title": "RecordUploadResponse",
10531
+ "type": "object",
10532
+ "x-struct": "Elixir.HablalaApiWeb.Schemas.RecordUploadResponse",
10533
+ "x-validate": null
10534
+ },
10535
+ "RecordUploadTarget": {
10536
+ "description": "Destino de subida prefirmado (*staged upload target*, patrón Shopify `stagedUploadsCreate` / S3 presigned upload): el cliente hace `PUT` del blob crudo a `uploadUrl` con `headers`, y luego CONFIRMA escribiendo el record con `fileUrl` en el envelope del attribute `file` (`{\"url\": fileUrl, \"filename\": …, \"content_type\": …}`). Un blob nunca confirmado expira solo en el staging.",
10537
+ "properties": {
10538
+ "fileUrl": {
10539
+ "description": "URL del blob (en staging `pending/…`): va como `url` en el envelope del attribute `file` al crear/actualizar el record. Al confirmarse, el servidor la reescribe a la definitiva.",
10540
+ "type": "string",
10541
+ "x-struct": null,
10542
+ "x-validate": null
10543
+ },
10544
+ "headers": {
10545
+ "additionalProperties": {
10546
+ "type": "string",
10547
+ "x-struct": null,
10548
+ "x-validate": null
10549
+ },
10550
+ "description": "Cabeceras que el cliente debe enviar en el `PUT` (p. ej. `content-type`).",
10551
+ "type": "object",
10552
+ "x-struct": null,
10553
+ "x-validate": null
10554
+ },
10555
+ "uploadUrl": {
10556
+ "description": "Destino del `PUT` del blob (URL prefirmada, expira).",
10557
+ "type": "string",
10558
+ "x-struct": null,
10559
+ "x-validate": null
10560
+ }
10561
+ },
10562
+ "required": [
10563
+ "uploadUrl",
10564
+ "fileUrl"
10565
+ ],
10566
+ "title": "RecordUploadTarget",
10567
+ "type": "object",
10568
+ "x-struct": "Elixir.HablalaApiWeb.Schemas.RecordUploadTarget",
10569
+ "x-validate": null
10570
+ },
10479
10571
  "RegisterCredentials": {
10480
10572
  "properties": {
10481
10573
  "email": {
@@ -16775,6 +16867,73 @@
16775
16867
  ]
16776
16868
  }
16777
16869
  },
16870
+ "/v1/objects/{object_slug}/uploads": {
16871
+ "post": {
16872
+ "callbacks": {},
16873
+ "description": "Fase 1 del flujo de archivos de un record: valida el archivo declarado contra el config del attribute `file` (`accepted_content_types` con comodines `image/*`, `max_size_bytes`) y devuelve el destino de subida prefirmado. Luego: (2) `PUT` del blob crudo a `uploadUrl` con `headers`; (3) confirmar escribiendo el record con `fileUrl` en el envelope del attribute (`{\"url\": fileUrl, …}`) — el servidor verifica el blob contra el storage (tamaño/tipo REALES) y lo promueve a su ubicación definitiva. Un blob nunca confirmado expira solo. Gate `records:create`.",
16874
+ "operationId": "HablalaApiWeb.Machine.UploadController.create",
16875
+ "parameters": [
16876
+ {
16877
+ "description": "",
16878
+ "in": "path",
16879
+ "name": "object_slug",
16880
+ "required": true,
16881
+ "schema": {
16882
+ "type": "string",
16883
+ "x-struct": null,
16884
+ "x-validate": null
16885
+ }
16886
+ }
16887
+ ],
16888
+ "requestBody": {
16889
+ "content": {
16890
+ "application/json": {
16891
+ "schema": {
16892
+ "$ref": "#/components/schemas/RecordUploadRequest"
16893
+ }
16894
+ }
16895
+ },
16896
+ "description": "Archivo a subir",
16897
+ "required": false
16898
+ },
16899
+ "responses": {
16900
+ "200": {
16901
+ "content": {
16902
+ "application/json": {
16903
+ "schema": {
16904
+ "$ref": "#/components/schemas/RecordUploadResponse"
16905
+ }
16906
+ }
16907
+ },
16908
+ "description": "Destino de subida prefirmado"
16909
+ },
16910
+ "404": {
16911
+ "content": {
16912
+ "application/json": {
16913
+ "schema": {
16914
+ "$ref": "#/components/schemas/Error"
16915
+ }
16916
+ }
16917
+ },
16918
+ "description": "Objeto desconocido"
16919
+ },
16920
+ "422": {
16921
+ "content": {
16922
+ "application/json": {
16923
+ "schema": {
16924
+ "$ref": "#/components/schemas/Error"
16925
+ }
16926
+ }
16927
+ },
16928
+ "description": "Attribute inexistente/no-file, MIME no admitido o tamaño excedido"
16929
+ }
16930
+ },
16931
+ "summary": "Pedir una URL de subida para un attribute file (staged upload)",
16932
+ "tags": [
16933
+ "records"
16934
+ ]
16935
+ }
16936
+ },
16778
16937
  "/v1/organizations": {
16779
16938
  "get": {
16780
16939
  "callbacks": {},
@@ -28164,6 +28323,83 @@
28164
28323
  ]
28165
28324
  }
28166
28325
  },
28326
+ "/v1/public/me/objects/{object_slug}/uploads": {
28327
+ "post": {
28328
+ "callbacks": {},
28329
+ "description": "Como el staged upload del canal de máquina (`POST /v1/objects/{object_slug}/uploads`), pero gobernado por el ABAC default-deny del canal público: exige una policy `allow` de `create` o `update` para el principal (`public` con la API key; `end_user` con el session token en la variante `/me`) sobre el object — sin policy, 403. Flujo: presign → `PUT` del blob a `uploadUrl` → confirmar escribiendo el record con `fileUrl` en el envelope del attribute `file`.",
28330
+ "operationId": "HablalaApiWeb.PublicApi.UploadController.create",
28331
+ "parameters": [
28332
+ {
28333
+ "description": "",
28334
+ "in": "path",
28335
+ "name": "object_slug",
28336
+ "required": true,
28337
+ "schema": {
28338
+ "type": "string",
28339
+ "x-struct": null,
28340
+ "x-validate": null
28341
+ }
28342
+ }
28343
+ ],
28344
+ "requestBody": {
28345
+ "content": {
28346
+ "application/json": {
28347
+ "schema": {
28348
+ "$ref": "#/components/schemas/RecordUploadRequest"
28349
+ }
28350
+ }
28351
+ },
28352
+ "description": "Archivo a subir",
28353
+ "required": false
28354
+ },
28355
+ "responses": {
28356
+ "200": {
28357
+ "content": {
28358
+ "application/json": {
28359
+ "schema": {
28360
+ "$ref": "#/components/schemas/RecordUploadResponse"
28361
+ }
28362
+ }
28363
+ },
28364
+ "description": "Destino de subida prefirmado"
28365
+ },
28366
+ "403": {
28367
+ "content": {
28368
+ "application/json": {
28369
+ "schema": {
28370
+ "$ref": "#/components/schemas/Error"
28371
+ }
28372
+ }
28373
+ },
28374
+ "description": "Sin policy allow de create/update para este principal"
28375
+ },
28376
+ "404": {
28377
+ "content": {
28378
+ "application/json": {
28379
+ "schema": {
28380
+ "$ref": "#/components/schemas/Error"
28381
+ }
28382
+ }
28383
+ },
28384
+ "description": "Objeto desconocido"
28385
+ },
28386
+ "422": {
28387
+ "content": {
28388
+ "application/json": {
28389
+ "schema": {
28390
+ "$ref": "#/components/schemas/Error"
28391
+ }
28392
+ }
28393
+ },
28394
+ "description": "Attribute inexistente/no-file, MIME no admitido o tamaño excedido"
28395
+ }
28396
+ },
28397
+ "summary": "Pedir una URL de subida para un attribute file (canal público)",
28398
+ "tags": [
28399
+ "public"
28400
+ ]
28401
+ }
28402
+ },
28167
28403
  "/v1/public/me/query": {
28168
28404
  "post": {
28169
28405
  "callbacks": {},
@@ -28539,6 +28775,83 @@
28539
28775
  ]
28540
28776
  }
28541
28777
  },
28778
+ "/v1/public/objects/{object_slug}/uploads": {
28779
+ "post": {
28780
+ "callbacks": {},
28781
+ "description": "Como el staged upload del canal de máquina (`POST /v1/objects/{object_slug}/uploads`), pero gobernado por el ABAC default-deny del canal público: exige una policy `allow` de `create` o `update` para el principal (`public` con la API key; `end_user` con el session token en la variante `/me`) sobre el object — sin policy, 403. Flujo: presign → `PUT` del blob a `uploadUrl` → confirmar escribiendo el record con `fileUrl` en el envelope del attribute `file`.",
28782
+ "operationId": "HablalaApiWeb.PublicApi.UploadController.create (2)",
28783
+ "parameters": [
28784
+ {
28785
+ "description": "",
28786
+ "in": "path",
28787
+ "name": "object_slug",
28788
+ "required": true,
28789
+ "schema": {
28790
+ "type": "string",
28791
+ "x-struct": null,
28792
+ "x-validate": null
28793
+ }
28794
+ }
28795
+ ],
28796
+ "requestBody": {
28797
+ "content": {
28798
+ "application/json": {
28799
+ "schema": {
28800
+ "$ref": "#/components/schemas/RecordUploadRequest"
28801
+ }
28802
+ }
28803
+ },
28804
+ "description": "Archivo a subir",
28805
+ "required": false
28806
+ },
28807
+ "responses": {
28808
+ "200": {
28809
+ "content": {
28810
+ "application/json": {
28811
+ "schema": {
28812
+ "$ref": "#/components/schemas/RecordUploadResponse"
28813
+ }
28814
+ }
28815
+ },
28816
+ "description": "Destino de subida prefirmado"
28817
+ },
28818
+ "403": {
28819
+ "content": {
28820
+ "application/json": {
28821
+ "schema": {
28822
+ "$ref": "#/components/schemas/Error"
28823
+ }
28824
+ }
28825
+ },
28826
+ "description": "Sin policy allow de create/update para este principal"
28827
+ },
28828
+ "404": {
28829
+ "content": {
28830
+ "application/json": {
28831
+ "schema": {
28832
+ "$ref": "#/components/schemas/Error"
28833
+ }
28834
+ }
28835
+ },
28836
+ "description": "Objeto desconocido"
28837
+ },
28838
+ "422": {
28839
+ "content": {
28840
+ "application/json": {
28841
+ "schema": {
28842
+ "$ref": "#/components/schemas/Error"
28843
+ }
28844
+ }
28845
+ },
28846
+ "description": "Attribute inexistente/no-file, MIME no admitido o tamaño excedido"
28847
+ }
28848
+ },
28849
+ "summary": "Pedir una URL de subida para un attribute file (canal público)",
28850
+ "tags": [
28851
+ "public"
28852
+ ]
28853
+ }
28854
+ },
28542
28855
  "/v1/public/query": {
28543
28856
  "post": {
28544
28857
  "callbacks": {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hablala/api-contract",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "description": "Contrato tipado (OpenAPI → TypeScript) de la API de Hablalá — single source of truth generada desde el backend Elixir.",
6
6
  "license": "MIT",