@onroad/core 4.0.0-alpha.39 → 4.0.0-alpha.40
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/README.md +195 -47
- package/dist/storage/AnexoService.d.ts +1 -1
- package/dist/storage/AnexoService.d.ts.map +1 -1
- package/dist/storage/AnexoService.js +18 -11
- package/dist/storage/AnexoService.js.map +1 -1
- package/dist/storage/index.d.ts +0 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +0 -1
- package/dist/storage/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -97,72 +97,88 @@ await app.buildServer({ port: 3001 })
|
|
|
97
97
|
|
|
98
98
|
## Architecture Overview
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
100
|
+
TypeRoad follows a multi-tenant architecture designed for high isolation and developer productivity. The framework orchestrates the request lifecycle using three main internal layers: `RequestContext`, `OnRoadContainer` (DI), and the `Model/Repository` layer.
|
|
101
|
+
|
|
102
|
+
### The "Matryoshka" Isolation Pattern
|
|
103
|
+
|
|
104
|
+
Isolation in runtime is achieved through a nested hierarchy that ensures one tenant's request never sees another's data or instances.
|
|
105
|
+
|
|
106
|
+
1. **Nível 1: RequestContext (AsyncLocalStorage)**
|
|
107
|
+
- O `OnRoadExpress` envolve cada execução de rota em um bloco `requestContext.run()`.
|
|
108
|
+
- Atua como um "envelope" de memória global para a thread lógica atual, armazenando metadados como `tenantId`, `logger` e `appToken`.
|
|
109
|
+
- Permite que qualquer parte do sistema acesse o estado da requisição via `getRequestContext()` sem precisar de injeção manual.
|
|
110
|
+
|
|
111
|
+
2. **Nível 2: OnRoadContainer (RequestScope)**
|
|
112
|
+
- Dentro do contexto da requisição, o Container cria um objeto `RequestScope`.
|
|
113
|
+
- Este escopo é um mapa de instâncias isolado por um `UUID` único para aquela chamada HTTP.
|
|
114
|
+
- Se múltiplos serviços injetarem o mesmo `Repository`, o Container garante que eles recebam a **mesma instância** dentro daquela requisição (reuso de estado), mas instâncias **completamente diferentes** de outras requisições concorrentes.
|
|
115
|
+
|
|
116
|
+
3. **Nível 3: Repository & Connection Isolation**
|
|
117
|
+
- Repositórios são instanciados pelo Container e recebem automaticamente o `tenantId` do escopo atual.
|
|
118
|
+
- Ao executar uma query (ex: `find()`), o repositório usa seu atributo interno `this.tenant` para solicitar ao `ConnectionManager` o pool de conexões específico daquele banco de dados.
|
|
119
|
+
- Isso garante que o isolamento chegue até o nível físico do banco de dados (schema ou DB separado).
|
|
120
|
+
|
|
121
|
+
```mermaid
|
|
122
|
+
graph TD
|
|
123
|
+
subgraph "Nível 1: RequestContext (AsyncLocalStorage)"
|
|
124
|
+
direction TB
|
|
125
|
+
Metadata["{ tenant: 'cliente_a', logger, token }"]
|
|
126
|
+
|
|
127
|
+
subgraph "Nível 2: OnRoadContainer (RequestScope)"
|
|
128
|
+
direction TB
|
|
129
|
+
ScopeID["Scope: UUID-123 (Tenant: 'cliente_a')"]
|
|
130
|
+
Instances["Map { Controller, Service, Repository }"]
|
|
131
|
+
|
|
132
|
+
subgraph "Nível 3: Instância do Repository"
|
|
133
|
+
direction TB
|
|
134
|
+
Atributo["this.tenant = 'cliente_a'"]
|
|
135
|
+
Metodo["getConnection() -> Pede ao ConnectionManager o banco 'onroad_cliente_a'"]
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
126
139
|
```
|
|
127
140
|
|
|
128
141
|
---
|
|
129
142
|
|
|
130
143
|
## DI Container & Decorators
|
|
131
144
|
|
|
132
|
-
TypeRoad uses a decorator-based DI container with automatic class scanning.
|
|
145
|
+
TypeRoad uses a decorator-based DI container with automatic class scanning. It manages the lifecycle of your components through defined scopes.
|
|
133
146
|
|
|
134
147
|
### Decorators
|
|
135
148
|
|
|
136
|
-
| Decorator | Scope | Purpose |
|
|
137
|
-
|
|
138
|
-
| `@
|
|
139
|
-
| `@
|
|
140
|
-
| `@
|
|
141
|
-
| `@
|
|
149
|
+
| Decorator | Scope | Relationship | Purpose |
|
|
150
|
+
|-----------|-------|:---:|---------|
|
|
151
|
+
| `@Controller("/path")` | `REQUEST` | entry-point | Injetado com Services; lida com req/res Express. |
|
|
152
|
+
| `@Service()` | `REQUEST` | logic | Camada de orquestração e regras de negócio. |
|
|
153
|
+
| `@Repository()` | `REQUEST` | data | Camada de acesso ao banco (Sequelize/Mongoose). |
|
|
154
|
+
| `@Injectable()` | `TRANSIENT` | utility | Classes utilitárias instanciadas a cada uso. |
|
|
142
155
|
|
|
143
156
|
### Scopes
|
|
144
157
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
| `REQUEST` | One instance per request scope |
|
|
149
|
-
| `TRANSIENT` | New instance on every resolve |
|
|
158
|
+
- **`SINGLETON`**: Uma única instância para toda a aplicação (ex: `EventBus`, `ConnectionManager`).
|
|
159
|
+
- **`REQUEST`**: Uma instância por requisição HTTP. É o escopo padrão para Controllers e Services, garantindo que o estado do tenant esteja isolado.
|
|
160
|
+
- **`TRANSIENT`**: Uma nova instância é criada toda vez que a classe é injetada ou resolvida.
|
|
150
161
|
|
|
151
|
-
###
|
|
162
|
+
### Controller, Service and Repository linkage
|
|
163
|
+
|
|
164
|
+
The container manages a strict hierarchy: `Controller` -> `Service` -> `Repository`.
|
|
165
|
+
|
|
166
|
+
- **Automatic Wiring**: When you register a module via `app.register([MyController, MyService, MyRepository])`, the container scans the metadata and prepares the injection tree.
|
|
167
|
+
- **Stateful Injection**: A Repository inheriting from `SequelizeRepository` is automatically configured by the container with the current `tenant` and `connectionManager` during its instantiation in the `RequestScope`.
|
|
152
168
|
|
|
153
169
|
```ts
|
|
154
|
-
import {
|
|
170
|
+
import { Service, AbstractService } from "@onroad/core"
|
|
155
171
|
|
|
156
|
-
@
|
|
157
|
-
class
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
172
|
+
@Service()
|
|
173
|
+
class OrdemService extends AbstractService<OrdemRepository> {
|
|
174
|
+
constructor() {
|
|
175
|
+
// Shared state: the container ensures OrdemRepository
|
|
176
|
+
// is instantiated within the same RequestScope as this service.
|
|
177
|
+
super({ repository: OrdemRepository })
|
|
178
|
+
}
|
|
161
179
|
}
|
|
162
180
|
```
|
|
163
181
|
|
|
164
|
-
Register all classes via `app.register([...])` — the container auto-scans metadata.
|
|
165
|
-
|
|
166
182
|
---
|
|
167
183
|
|
|
168
184
|
## Controllers, Services & Repositories
|
|
@@ -996,6 +1012,138 @@ Fix: disable `emitDecoratorMetadata`. @onroad/core uses only custom metadata key
|
|
|
996
1012
|
|
|
997
1013
|
---
|
|
998
1014
|
|
|
1015
|
+
### Custom route service methods must extract params from `req` — not expect primitive arguments
|
|
1016
|
+
|
|
1017
|
+
**`AbstractController` always calls custom service methods with the raw `(req, res)` objects.** It only extracts params for the five default CRUD methods (`create`, `read`, `readAll`, `update`, `delete`). Every method registered under `routes:` in the controller config receives `(req, res)` forwarded directly.
|
|
1018
|
+
|
|
1019
|
+
If your service method signature expects primitive parameters (`id: number`, `dataInicio: string`, etc.), it will receive the `req` object instead — causing silent failures (empty queries, NaN ids, wrong SQL filters) because the framework does **not** throw, it just passes the wrong value.
|
|
1020
|
+
|
|
1021
|
+
```ts
|
|
1022
|
+
// ❌ Wrong — service expects primitives; controller passes (req, res)
|
|
1023
|
+
// Controller calls: this.service.findByBetweenDates(req, res)
|
|
1024
|
+
async findByBetweenDates(dataInicio: string, dataFim: string) {
|
|
1025
|
+
// dataInicio === req object → Sequelize WHERE clause receives an object → returns []
|
|
1026
|
+
return this.repository.findAll({ where: { dataDeAbertura: { [Op.gte]: dataInicio } } })
|
|
1027
|
+
}
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
```ts
|
|
1031
|
+
// ✅ Correct — service extracts its own params from req
|
|
1032
|
+
async findByBetweenDates(req: any, res?: any) {
|
|
1033
|
+
const dataInicio: string = req.params.dataInicio
|
|
1034
|
+
const dataFim: string = req.params.dataFim
|
|
1035
|
+
return this.repository.findAll({ where: { dataDeAbertura: { [Op.gte]: dataInicio, [Op.lte]: dataFim } } })
|
|
1036
|
+
}
|
|
1037
|
+
```
|
|
1038
|
+
|
|
1039
|
+
**Affected params by HTTP position:**
|
|
1040
|
+
|
|
1041
|
+
| Source | How to extract |
|
|
1042
|
+
|------------------|-----------------------------|
|
|
1043
|
+
| Route param (`:id`) | `req.params.id` |
|
|
1044
|
+
| Query string | `req.query.field` |
|
|
1045
|
+
| Request body | `req.body.field` |
|
|
1046
|
+
| Auth / identity | `req.decoded.userId` |
|
|
1047
|
+
|
|
1048
|
+
> **Rule of thumb:** In a TypeRoad service, if the method is registered as a custom route, **the first argument is always `req`**. Never write `async myMethod(id: number)` for a method that is called through a route config key — always write `async myMethod(req: any, res?: any)` and extract from `req` internally.
|
|
1049
|
+
|
|
1050
|
+
**Scope of impact in `teraprox-api-manutencao` (audit — 2025-04-05):**
|
|
1051
|
+
|
|
1052
|
+
The following service methods were found with the wrong signature (primitive params instead of `req`). All calls through their controller routes were silently broken:
|
|
1053
|
+
|
|
1054
|
+
| Service | Method | Broken params |
|
|
1055
|
+
|---------|--------|---------------|
|
|
1056
|
+
| `OrdemDeServicoService` | `findByBetweenDates` | `dataInicio`, `dataFim` — **fixed** |
|
|
1057
|
+
| `OrdemDeServicoService` | `findByRecursoBetweenDates` | `recursoId`, `dataInicio`, `dataFim` |
|
|
1058
|
+
| `OrdemDeServicoService` | `findByBranchIdBetweenDates` | `branchId`, `dataInicio`, `dataFim` |
|
|
1059
|
+
| `OrdemDeServicoService` | `findByRecursoId` | `recursoId` |
|
|
1060
|
+
| `OrdemDeServicoService` | `findMonitoramentoByCriticidade` | `criticidade` |
|
|
1061
|
+
| `OrdemDeServicoService` | `findByModoDeFalhaId` | `modoDeFalhaId` |
|
|
1062
|
+
| `OrdemDeServicoService` | `encerraOS` | `id`, `form` |
|
|
1063
|
+
| `OrdemDeServicoService` | `setStatusOs` | `id`, `form` |
|
|
1064
|
+
| `OrdemDeServicoService` | `setStatusOsBulk` | `osIds`, `status`, `extraData` |
|
|
1065
|
+
| `OrdemDeServicoService` | `iniciarOsBulk` | `osIds`, `status` |
|
|
1066
|
+
| `OrdemDeServicoService` | `addTarefaToOrdem` | `osId`, `tarefa` |
|
|
1067
|
+
| `OrdemDeServicoService` | `updateDescricaoOs` | `osId`, `descricao` |
|
|
1068
|
+
| `OrdemDeServicoService` | `shiftRecursoOs` | `osId`, `recId` |
|
|
1069
|
+
| `OrdemDeServicoService` | `findInspecoesBetweenDates` | `recursoId`, `dataInicio`, `dataFim` |
|
|
1070
|
+
| `OrdemDeServicoService` | `putAnexos` | `id`, `anexos` |
|
|
1071
|
+
| `OrdemDeServicoService` | `findOrdensInBatch` | `ids` |
|
|
1072
|
+
| `OrdemDeServicoService` | `loadOsForOcpMigration` | `osIds` |
|
|
1073
|
+
| `RecursoService` | `findRecursoFatherByRecursoId` | `recursoId` |
|
|
1074
|
+
| `RecursoService` | `findByTagDescription` | `tag` |
|
|
1075
|
+
| `RecursoService` | `findRecursoByTagId` | `tagId` |
|
|
1076
|
+
| `RecursoService` | `findParadasByRecursoId` | `recursoId` |
|
|
1077
|
+
| `RecursoService` | `findSemParadasByRecursoId` | `recursoId` |
|
|
1078
|
+
| `TarefaService` | `encerrarTarefa` | `id` |
|
|
1079
|
+
| `TarefaService` | `addUnidadeMaterialTarefa` | `tarefaId`, `form` |
|
|
1080
|
+
| `TarefaService` | `addObservacaoTarefa` | `tarefaId`, `form` |
|
|
1081
|
+
| `TarefaService` | `deleteObservacaoTarefa` | `justificativaId` |
|
|
1082
|
+
| `TarefaService` | `loadMultipleIds` | `tarefaIds` |
|
|
1083
|
+
| `TarefaService` | `removeAnexo` | `id`, `anexoKey` |
|
|
1084
|
+
| `SolicitacaoDeServicoService` | `shiftRecursoSs` | `ssId`, `recId` |
|
|
1085
|
+
| `SolicitacaoDeServicoService` | `aprovaSolicitacao` | `id`, `form` |
|
|
1086
|
+
| `SolicitacaoDeServicoService` | `reprovaSolicitacao` | `id`, `form` |
|
|
1087
|
+
| `SolicitacaoDeServicoService` | `updateDescricaoSs` | `ssId`, `descricao` |
|
|
1088
|
+
| `SolicitacaoDeServicoService` | `findByRecursoId` | `recursoId` |
|
|
1089
|
+
| `SolicitacaoDeServicoService` | `findBetweenDates` | `start`, `end` |
|
|
1090
|
+
| `SolicitacaoDeServicoService` | `putAnexos` | `id`, `anexos` |
|
|
1091
|
+
|
|
1092
|
+
---
|
|
1093
|
+
|
|
1094
|
+
### Custom route key name must exactly match the service method name
|
|
1095
|
+
|
|
1096
|
+
When you register a custom route in the controller config, the key name is used **verbatim** as the service method name to call. If the service was later renamed (e.g. during a refactor), the controller silently returns `null` because the `typeof service[methodName] === 'function'` guard evaluates to `false`.
|
|
1097
|
+
|
|
1098
|
+
```ts
|
|
1099
|
+
// ❌ Wrong — controller key "addModoDeFalhaOS" but service method is "addModoDeFalha"
|
|
1100
|
+
routes: { addModoDeFalhaOS: { method: "post", path: "/..." } }
|
|
1101
|
+
// → service.addModoDeFalhaOS is undefined → always returns null (no error thrown)
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
**Known mismatches in `teraprox-api-manutencao` (audit — 2025-04-05):**
|
|
1105
|
+
|
|
1106
|
+
| Controller route key | Expected service method (by key) | Actual service method |
|
|
1107
|
+
|----------------------|-----------------------------------|-----------------------|
|
|
1108
|
+
| `addModoDeFalhaOS` | `addModoDeFalhaOS` | `addModoDeFalha` |
|
|
1109
|
+
| `updateTipoDeOrdem` | `updateTipoDeOrdem` | `setTipoOrdem` |
|
|
1110
|
+
| `updateTipoDeOrdemBulk` | `updateTipoDeOrdemBulk` | `setTipoOrdemBulk` |
|
|
1111
|
+
| `removeAnexo` | `removeAnexo` | `removeAnexoOs` |
|
|
1112
|
+
| `readConcluidasCount` | `readConcluidasCount` | `countTarefasConcluidas` |
|
|
1113
|
+
| `findPlanned` | `findPlanned` | `findPlannedOrdensBetweenDatesV2` |
|
|
1114
|
+
| `findPlannedV3` | `findPlannedV3` | `findPlannedOrdensBetweenDatesV3` |
|
|
1115
|
+
| `findPlannedAgregador` | `findPlannedAgregador` | `findPlannedOrdensWithAgregadores` |
|
|
1116
|
+
| `findRecorrenciaPlanned` | `findRecorrenciaPlanned` | `findPlannedOrdensFromRecorrenciaBetweenDates` |
|
|
1117
|
+
| `findAgregadorPlanned` | `findAgregadorPlanned` | `findPlannedOrdensFromAgregadorBetweenDates` |
|
|
1118
|
+
| `createInBulk` | `createInBulk` | `createInBulkOs` |
|
|
1119
|
+
| `findDashByOs` | `findDashByOs` | `findWithDashboardAssociation` |
|
|
1120
|
+
|
|
1121
|
+
> Fix: either rename the service method to match the route key, or rename the route key to match the service method. Both sides must be in sync.
|
|
1122
|
+
|
|
1123
|
+
---
|
|
1124
|
+
|
|
1125
|
+
### `BelongsToMany` association returns `null` (not `[]`) when no records exist
|
|
1126
|
+
|
|
1127
|
+
Sequelize's `BelongsToMany` association populates the association property with `null` (not an empty array) when no join-table records exist for a given entity. Any code that iterates over that property with `for...of` or `.forEach()` without a guard will throw `TypeError: x is not iterable`.
|
|
1128
|
+
|
|
1129
|
+
```ts
|
|
1130
|
+
// ❌ Wrong — crashes with "TypeError: o.tarefas is not iterable" when OS has no tarefas
|
|
1131
|
+
for (const tarefa of o.tarefas) { ... }
|
|
1132
|
+
o.tarefas.forEach(...)
|
|
1133
|
+
o.tarefas.flatMap(...)
|
|
1134
|
+
```
|
|
1135
|
+
|
|
1136
|
+
```ts
|
|
1137
|
+
// ✅ Correct — guard with nullish coalescing
|
|
1138
|
+
for (const tarefa of (o.tarefas ?? [])) { ... }
|
|
1139
|
+
;(o.tarefas ?? []).forEach(...)
|
|
1140
|
+
ordens.flatMap((o) => (o.tarefas ?? []).map(...))
|
|
1141
|
+
```
|
|
1142
|
+
|
|
1143
|
+
> This was the direct cause of `TypeError: o.tarefas is not iterable` at `OrdemDeServicoService.buildTarefa` (line 235) and `findByBetweenDates`. Apply the same `?? []` guard to **all** `BelongsToMany` associations before iterating.
|
|
1144
|
+
|
|
1145
|
+
---
|
|
1146
|
+
|
|
999
1147
|
## Migration Guide — Lessons Learned (v3 → v4)
|
|
1000
1148
|
|
|
1001
1149
|
This section documents the key issues found while migrating `teraprox-api-user` from onRoad v3 to TypeRoad v4. Use this as a checklist when migrating other APIs (`api-manutencao`, `api-processo`, etc.).
|
|
@@ -6,7 +6,7 @@ export declare class AnexoService extends AbstractService<AnexoRepository> {
|
|
|
6
6
|
protected storage: StorageProvider;
|
|
7
7
|
constructor();
|
|
8
8
|
/** Lazily initialise the StorageProvider. Override to use a custom provider. */
|
|
9
|
-
protected getStorage(): StorageProvider
|
|
9
|
+
protected getStorage(): Promise<StorageProvider>;
|
|
10
10
|
/** Allow external configuration of the storage provider. */
|
|
11
11
|
setStorageProvider(provider: StorageProvider): void;
|
|
12
12
|
createUploadIntent(payload: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnexoService.d.ts","sourceRoot":"","sources":["../../src/storage/AnexoService.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAG9C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"AnexoService.d.ts","sourceRoot":"","sources":["../../src/storage/AnexoService.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAG9C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAItD,qBACa,YAAa,SAAQ,eAAe,CAAC,eAAe,CAAC;IAChE,SAAS,CAAC,OAAO,EAAG,eAAe,CAAA;;IAMnC,gFAAgF;cAChE,UAAU,IAAI,OAAO,CAAC,eAAe,CAAC;IAQtD,4DAA4D;IAC5D,kBAAkB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;IAI7C,kBAAkB,CAAC,OAAO,EAAE;QAChC,QAAQ,EAAE,MAAM,CAAA;QAChB,WAAW,EAAE,MAAM,CAAA;QACnB,MAAM,EAAE,MAAM,CAAA;QACd,WAAW,EAAE,MAAM,CAAA;KACpB;;;;;;;;IAsBK,aAAa,CACjB,OAAO,EAAE;QACP,GAAG,EAAE,MAAM,CAAA;QACX,MAAM,EAAE,MAAM,CAAA;QACd,WAAW,EAAE,MAAM,CAAA;QACnB,QAAQ,EAAE,MAAM,CAAA;QAChB,WAAW,EAAE,MAAM,CAAA;KACpB,EACD,QAAQ,CAAC,EAAE,QAAQ;YAaK,MAAM;qBAAe,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;IASvD,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,EAAE,QAAQ;YAEhE,MAAM;qBACG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;IAQxC;;;OAGG;IACY,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE;IAQjD,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;IA6BhD,iBAAiB,CACrB,OAAO,EAAE,MAAM,EAAE,EACjB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IA6BnB,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM;IAc9C,sDAAsD;IAChD,YAAY,CAChB,MAAM,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,EACzE,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,QAAQ;IAyBf,SAAS,CAAC,GAAG,EAAE,MAAM;IAIrB,YAAY,CAAC,GAAG,EAAE,MAAM;CAI/B"}
|
|
@@ -11,7 +11,6 @@ import { Service } from "../container/index.js";
|
|
|
11
11
|
import { AbstractService } from "../core/AbstractService.js";
|
|
12
12
|
import { MatchingObject } from "../messaging/index.js";
|
|
13
13
|
import { getRequestContext } from "../context/RequestContext.js";
|
|
14
|
-
import { GCSStorageProvider } from "./GCSStorageProvider.js";
|
|
15
14
|
import { AnexoRepository } from "./AnexoRepository.js";
|
|
16
15
|
const ANEXO_CONTEXT = "anexos";
|
|
17
16
|
let AnexoService = class AnexoService extends AbstractService {
|
|
@@ -20,8 +19,9 @@ let AnexoService = class AnexoService extends AbstractService {
|
|
|
20
19
|
super({ repository: AnexoRepository });
|
|
21
20
|
}
|
|
22
21
|
/** Lazily initialise the StorageProvider. Override to use a custom provider. */
|
|
23
|
-
getStorage() {
|
|
22
|
+
async getStorage() {
|
|
24
23
|
if (!this.storage) {
|
|
24
|
+
const { GCSStorageProvider } = await import("./GCSStorageProvider.js");
|
|
25
25
|
this.storage = new GCSStorageProvider(process.env.GCS_BUCKET || "teraprox-storage");
|
|
26
26
|
}
|
|
27
27
|
return this.storage;
|
|
@@ -33,14 +33,15 @@ let AnexoService = class AnexoService extends AbstractService {
|
|
|
33
33
|
async createUploadIntent(payload) {
|
|
34
34
|
const { tenant } = getRequestContext();
|
|
35
35
|
const { fileName, contentType, dataId, dataContext } = payload;
|
|
36
|
-
const
|
|
36
|
+
const storage = await this.getStorage();
|
|
37
|
+
const key = storage.buildObjectKey({
|
|
37
38
|
tenant,
|
|
38
39
|
dataContext,
|
|
39
40
|
originalName: fileName,
|
|
40
41
|
contentType,
|
|
41
42
|
author: "system",
|
|
42
43
|
});
|
|
43
|
-
const url = await
|
|
44
|
+
const url = await storage.generateSignedUrl(key, {
|
|
44
45
|
action: "write",
|
|
45
46
|
contentType,
|
|
46
47
|
expiresMs: 3600_000,
|
|
@@ -48,7 +49,8 @@ let AnexoService = class AnexoService extends AbstractService {
|
|
|
48
49
|
return { uploadUrl: url, key, dataId, dataContext, fileName, contentType };
|
|
49
50
|
}
|
|
50
51
|
async confirmUpload(payload, sentinel) {
|
|
51
|
-
const
|
|
52
|
+
const storage = await this.getStorage();
|
|
53
|
+
const metadata = await storage.getObjectMetadata(payload.key);
|
|
52
54
|
const anexo = (await this.repository.create({
|
|
53
55
|
key: payload.key,
|
|
54
56
|
dataId: payload.dataId,
|
|
@@ -85,7 +87,8 @@ let AnexoService = class AnexoService extends AbstractService {
|
|
|
85
87
|
return await Promise.all(anexos.map(async (anexo) => {
|
|
86
88
|
const data = (anexo.dataValues ?? anexo);
|
|
87
89
|
try {
|
|
88
|
-
|
|
90
|
+
const s = await this.getStorage();
|
|
91
|
+
data.signedUrl = await s.generateSignedUrl(data.key, {
|
|
89
92
|
action: "read",
|
|
90
93
|
});
|
|
91
94
|
}
|
|
@@ -110,7 +113,8 @@ let AnexoService = class AnexoService extends AbstractService {
|
|
|
110
113
|
const data = (anexo.dataValues ?? anexo);
|
|
111
114
|
const dId = String(data.dataId);
|
|
112
115
|
try {
|
|
113
|
-
|
|
116
|
+
const s = await this.getStorage();
|
|
117
|
+
data.signedUrl = await s.generateSignedUrl(data.key, {
|
|
114
118
|
action: "read",
|
|
115
119
|
});
|
|
116
120
|
}
|
|
@@ -138,7 +142,8 @@ let AnexoService = class AnexoService extends AbstractService {
|
|
|
138
142
|
anexoId = anexo.id;
|
|
139
143
|
}
|
|
140
144
|
if (key) {
|
|
141
|
-
await this.getStorage()
|
|
145
|
+
const storage = await this.getStorage();
|
|
146
|
+
await storage.deleteObject(key);
|
|
142
147
|
}
|
|
143
148
|
return await this.repository.destroy(anexoId);
|
|
144
149
|
}
|
|
@@ -149,14 +154,15 @@ let AnexoService = class AnexoService extends AbstractService {
|
|
|
149
154
|
"unknown";
|
|
150
155
|
for (const anx of anexos) {
|
|
151
156
|
const { buffer, originalname, mimetype } = anx;
|
|
152
|
-
const
|
|
157
|
+
const storage = await this.getStorage();
|
|
158
|
+
const key = storage.buildObjectKey({
|
|
153
159
|
tenant,
|
|
154
160
|
dataContext,
|
|
155
161
|
originalName: originalname,
|
|
156
162
|
contentType: mimetype,
|
|
157
163
|
author,
|
|
158
164
|
});
|
|
159
|
-
await
|
|
165
|
+
await storage.upload("", key, buffer, mimetype);
|
|
160
166
|
await this.create({ key, originalName: originalname, mimetype, dataContext, dataId, author }, sentinel);
|
|
161
167
|
}
|
|
162
168
|
}
|
|
@@ -164,7 +170,8 @@ let AnexoService = class AnexoService extends AbstractService {
|
|
|
164
170
|
return await this.repository.findOne({ where: { key } });
|
|
165
171
|
}
|
|
166
172
|
async getSignedUrl(key) {
|
|
167
|
-
|
|
173
|
+
const storage = await this.getStorage();
|
|
174
|
+
return await storage.generateSignedUrl(key, { action: "read" });
|
|
168
175
|
}
|
|
169
176
|
};
|
|
170
177
|
AnexoService = __decorate([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnexoService.js","sourceRoot":"","sources":["../../src/storage/AnexoService.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAE5D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AAEhE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"AnexoService.js","sourceRoot":"","sources":["../../src/storage/AnexoService.ts"],"names":[],"mappings":"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"}
|
package/dist/storage/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { StorageProvider, type UploadOptions, type SignedUrlOptions, type ObjectMetadata } from "./StorageProvider.js";
|
|
2
|
-
export { GCSStorageProvider } from "./GCSStorageProvider.js";
|
|
3
2
|
export { Anexo } from "./AnexoEntity.js";
|
|
4
3
|
export { AnexoRepository } from "./AnexoRepository.js";
|
|
5
4
|
export { AnexoService } from "./AnexoService.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,KAAK,gBAAgB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACtH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,KAAK,gBAAgB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACtH,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA"}
|
package/dist/storage/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { StorageProvider } from "./StorageProvider.js";
|
|
2
|
-
export { GCSStorageProvider } from "./GCSStorageProvider.js";
|
|
3
2
|
export { Anexo } from "./AnexoEntity.js";
|
|
4
3
|
export { AnexoRepository } from "./AnexoRepository.js";
|
|
5
4
|
export { AnexoService } from "./AnexoService.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAkE,MAAM,sBAAsB,CAAA;AACtH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAkE,MAAM,sBAAsB,CAAA;AACtH,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA"}
|
package/package.json
CHANGED