@netoalmanca/advpl-sensei 1.1.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/agents/changelog-generator.md +63 -0
- package/agents/code-generator.md +215 -0
- package/agents/code-reviewer.md +145 -0
- package/agents/debugger.md +83 -0
- package/agents/doc-generator.md +67 -0
- package/agents/docs-reference.md +86 -0
- package/agents/migrator.md +84 -0
- package/agents/process-consultant.md +97 -0
- package/agents/refactorer.md +75 -0
- package/agents/sx-configurator.md +67 -0
- package/commands/changelog.md +66 -0
- package/commands/diagnose.md +67 -0
- package/commands/docs.md +81 -0
- package/commands/document.md +67 -0
- package/commands/explain.md +60 -0
- package/commands/generate.md +111 -0
- package/commands/migrate.md +81 -0
- package/commands/process.md +111 -0
- package/commands/refactor.md +65 -0
- package/commands/review.md +60 -0
- package/commands/sxgen.md +98 -0
- package/commands/test.md +103 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +143 -0
- package/dist/index.js.map +1 -0
- package/package.json +30 -0
- package/skills/advpl-code-generation/SKILL.md +163 -0
- package/skills/advpl-code-generation/patterns-fwformbrowse.md +485 -0
- package/skills/advpl-code-generation/patterns-jobs.md +519 -0
- package/skills/advpl-code-generation/patterns-mvc.md +765 -0
- package/skills/advpl-code-generation/patterns-pontos-entrada.md +708 -0
- package/skills/advpl-code-generation/patterns-rest.md +974 -0
- package/skills/advpl-code-generation/patterns-soap.md +639 -0
- package/skills/advpl-code-generation/patterns-treport.md +481 -0
- package/skills/advpl-code-generation/patterns-workflow.md +779 -0
- package/skills/advpl-code-generation/templates-classes.md +1096 -0
- package/skills/advpl-code-review/SKILL.md +72 -0
- package/skills/advpl-code-review/rules-best-practices.md +444 -0
- package/skills/advpl-code-review/rules-modernization.md +290 -0
- package/skills/advpl-code-review/rules-performance.md +333 -0
- package/skills/advpl-code-review/rules-security.md +302 -0
- package/skills/advpl-debugging/SKILL.md +265 -0
- package/skills/advpl-debugging/common-errors.md +1124 -0
- package/skills/advpl-debugging/performance-tips.md +768 -0
- package/skills/advpl-refactoring/SKILL.md +139 -0
- package/skills/advpl-to-tlpp-migration/SKILL.md +293 -0
- package/skills/advpl-to-tlpp-migration/migration-checklist.md +122 -0
- package/skills/advpl-to-tlpp-migration/migration-rules.md +265 -0
- package/skills/changelog-patterns/SKILL.md +99 -0
- package/skills/code-explanation/SKILL.md +66 -0
- package/skills/documentation-patterns/SKILL.md +172 -0
- package/skills/embedded-sql/SKILL.md +379 -0
- package/skills/probat-testing/SKILL.md +226 -0
- package/skills/probat-testing/patterns-unit-tests.md +614 -0
- package/skills/protheus-business/SKILL.md +92 -0
- package/skills/protheus-business/modulo-compras.md +780 -0
- package/skills/protheus-business/modulo-contabilidade.md +874 -0
- package/skills/protheus-business/modulo-estoque.md +876 -0
- package/skills/protheus-business/modulo-faturamento.md +800 -0
- package/skills/protheus-business/modulo-financeiro.md +1015 -0
- package/skills/protheus-business/modulo-fiscal.md +749 -0
- package/skills/protheus-business/modulo-manutencao.md +848 -0
- package/skills/protheus-business/modulo-pcp.md +743 -0
- package/skills/protheus-reference/SKILL.md +119 -0
- package/skills/protheus-reference/native-functions.md +7029 -0
- package/skills/protheus-reference/rest-api-reference.md +1758 -0
- package/skills/protheus-reference/restricted-functions.md +265 -0
- package/skills/protheus-reference/sx-dictionary.md +854 -0
- package/skills/sx-configuration/SKILL.md +184 -0
|
@@ -0,0 +1,974 @@
|
|
|
1
|
+
# Protheus REST API Patterns
|
|
2
|
+
|
|
3
|
+
Patterns for implementing REST API endpoints in TOTVS Protheus using both WsRestFul and TLPP annotations.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. WsRestFul Pattern (Class-Based)
|
|
8
|
+
|
|
9
|
+
The `WsRestFul` approach uses class declarations to define REST services. This is the traditional Protheus REST pattern.
|
|
10
|
+
|
|
11
|
+
### 1.1 Service Declaration
|
|
12
|
+
|
|
13
|
+
```advpl
|
|
14
|
+
#Include "TOTVS.CH"
|
|
15
|
+
#Include "RestFul.ch"
|
|
16
|
+
|
|
17
|
+
/*/{Protheus.doc} CUSTOMERS
|
|
18
|
+
API REST para cadastro de clientes
|
|
19
|
+
@type WsRestFul
|
|
20
|
+
@author Autor
|
|
21
|
+
@since 01/01/2026
|
|
22
|
+
@version 1.0
|
|
23
|
+
/*/
|
|
24
|
+
WsRestFul CUSTOMERS Description "API de Clientes" Format APPLICATION_JSON
|
|
25
|
+
|
|
26
|
+
WsData nPage As Integer Optional
|
|
27
|
+
WsData nPageSize As Integer Optional
|
|
28
|
+
WsData cCodigo As Character Optional
|
|
29
|
+
WsData cLoja As Character Optional
|
|
30
|
+
|
|
31
|
+
WsMethod GET Description "Retorna clientes" WsSyntax "/customers" Path "/customers"
|
|
32
|
+
WsMethod POST Description "Inclui cliente" WsSyntax "/customers" Path "/customers"
|
|
33
|
+
WsMethod PUT Description "Altera cliente" WsSyntax "/customers/{codigo}/{loja}" Path "/customers/{codigo}/{loja}"
|
|
34
|
+
WsMethod DELETE Description "Exclui cliente" WsSyntax "/customers/{codigo}/{loja}" Path "/customers/{codigo}/{loja}"
|
|
35
|
+
|
|
36
|
+
End WsRestFul
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 1.2 GET Method - List/Search
|
|
40
|
+
|
|
41
|
+
```advpl
|
|
42
|
+
WsMethod GET WsReceive nPage, nPageSize WsService CUSTOMERS
|
|
43
|
+
Local lRet := .T.
|
|
44
|
+
Local oJsonArr := JsonObject():New()
|
|
45
|
+
Local oJsonObj := Nil
|
|
46
|
+
Local aArea := GetArea()
|
|
47
|
+
Local cAlias := "SA1"
|
|
48
|
+
Local nCount := 0
|
|
49
|
+
Local nSkip := 0
|
|
50
|
+
Local nPageLoc := IIf(::nPage == Nil, 1, ::nPage)
|
|
51
|
+
Local nSizeLoc := IIf(::nPageSize == Nil, 20, ::nPageSize)
|
|
52
|
+
Local aResult := {}
|
|
53
|
+
|
|
54
|
+
// Autenticacao
|
|
55
|
+
If !ValidToken(Self)
|
|
56
|
+
SetRestFault(401, "Token invalido ou ausente")
|
|
57
|
+
lRet := .F.
|
|
58
|
+
RestArea(aArea)
|
|
59
|
+
Return lRet
|
|
60
|
+
EndIf
|
|
61
|
+
|
|
62
|
+
nSkip := (nPageLoc - 1) * nSizeLoc
|
|
63
|
+
|
|
64
|
+
DbSelectArea(cAlias)
|
|
65
|
+
DbSetOrder(1)
|
|
66
|
+
DbGoTop()
|
|
67
|
+
|
|
68
|
+
While !Eof() .And. nCount < nSizeLoc
|
|
69
|
+
If xFilial(cAlias) == SA1->A1_FILIAL
|
|
70
|
+
nSkip--
|
|
71
|
+
If nSkip < 0
|
|
72
|
+
oJsonObj := JsonObject():New()
|
|
73
|
+
oJsonObj["codigo"] := AllTrim(SA1->A1_COD)
|
|
74
|
+
oJsonObj["loja"] := AllTrim(SA1->A1_LOJA)
|
|
75
|
+
oJsonObj["nome"] := AllTrim(SA1->A1_NOME)
|
|
76
|
+
oJsonObj["cnpj"] := AllTrim(SA1->A1_CGC)
|
|
77
|
+
oJsonObj["email"] := AllTrim(SA1->A1_EMAIL)
|
|
78
|
+
oJsonObj["telefone"] := AllTrim(SA1->A1_TEL)
|
|
79
|
+
|
|
80
|
+
aAdd(aResult, oJsonObj)
|
|
81
|
+
nCount++
|
|
82
|
+
EndIf
|
|
83
|
+
EndIf
|
|
84
|
+
DbSkip()
|
|
85
|
+
EndWh
|
|
86
|
+
|
|
87
|
+
oJsonArr["items"] := aResult
|
|
88
|
+
oJsonArr["page"] := nPageLoc
|
|
89
|
+
oJsonArr["pageSize"] := nSizeLoc
|
|
90
|
+
oJsonArr["hasNext"] := !Eof()
|
|
91
|
+
|
|
92
|
+
::SetContentType("application/json")
|
|
93
|
+
::SetResponse(oJsonArr:ToJson())
|
|
94
|
+
|
|
95
|
+
FreeObj(oJsonArr)
|
|
96
|
+
RestArea(aArea)
|
|
97
|
+
|
|
98
|
+
Return lRet
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 1.3 POST Method - Create
|
|
102
|
+
|
|
103
|
+
```advpl
|
|
104
|
+
WsMethod POST WsService CUSTOMERS
|
|
105
|
+
Local lRet := .T.
|
|
106
|
+
Local oJson := JsonObject():New()
|
|
107
|
+
Local oResp := Nil
|
|
108
|
+
Local cBody := ::GetContent()
|
|
109
|
+
Local nParsed := 0
|
|
110
|
+
Local aArea := GetArea()
|
|
111
|
+
|
|
112
|
+
// Autenticacao
|
|
113
|
+
If !ValidToken(Self)
|
|
114
|
+
SetRestFault(401, "Token invalido ou ausente")
|
|
115
|
+
lRet := .F.
|
|
116
|
+
RestArea(aArea)
|
|
117
|
+
Return lRet
|
|
118
|
+
EndIf
|
|
119
|
+
|
|
120
|
+
// Parse JSON body
|
|
121
|
+
nParsed := oJson:FromJson(cBody)
|
|
122
|
+
If nParsed <> 0
|
|
123
|
+
SetRestFault(400, "JSON invalido na posicao: " + cValToChar(nParsed))
|
|
124
|
+
lRet := .F.
|
|
125
|
+
FreeObj(oJson)
|
|
126
|
+
RestArea(aArea)
|
|
127
|
+
Return lRet
|
|
128
|
+
EndIf
|
|
129
|
+
|
|
130
|
+
// Validacao dos campos obrigatorios
|
|
131
|
+
If Empty(oJson["nome"]) .Or. Empty(oJson["cnpj"])
|
|
132
|
+
SetRestFault(422, "Campos obrigatorios: nome, cnpj")
|
|
133
|
+
lRet := .F.
|
|
134
|
+
FreeObj(oJson)
|
|
135
|
+
RestArea(aArea)
|
|
136
|
+
Return lRet
|
|
137
|
+
EndIf
|
|
138
|
+
|
|
139
|
+
// Inclusao
|
|
140
|
+
DbSelectArea("SA1")
|
|
141
|
+
|
|
142
|
+
Begin Transaction
|
|
143
|
+
RecLock("SA1", .T.)
|
|
144
|
+
SA1->A1_FILIAL := xFilial("SA1")
|
|
145
|
+
SA1->A1_COD := GetSXENum("SA1", "A1_COD")
|
|
146
|
+
SA1->A1_LOJA := "01"
|
|
147
|
+
SA1->A1_NOME := oJson["nome"]
|
|
148
|
+
SA1->A1_CGC := oJson["cnpj"]
|
|
149
|
+
SA1->A1_EMAIL := IIf(oJson["email"] <> Nil, oJson["email"], "")
|
|
150
|
+
SA1->A1_TEL := IIf(oJson["telefone"] <> Nil, oJson["telefone"], "")
|
|
151
|
+
MsUnlock()
|
|
152
|
+
ConfirmSX8()
|
|
153
|
+
End Transaction
|
|
154
|
+
|
|
155
|
+
// Response
|
|
156
|
+
oResp := JsonObject():New()
|
|
157
|
+
oResp["codigo"] := AllTrim(SA1->A1_COD)
|
|
158
|
+
oResp["loja"] := AllTrim(SA1->A1_LOJA)
|
|
159
|
+
oResp["nome"] := AllTrim(SA1->A1_NOME)
|
|
160
|
+
oResp["message"] := "Cliente incluido com sucesso"
|
|
161
|
+
|
|
162
|
+
::SetContentType("application/json")
|
|
163
|
+
::SetStatus(201)
|
|
164
|
+
::SetResponse(oResp:ToJson())
|
|
165
|
+
|
|
166
|
+
FreeObj(oJson)
|
|
167
|
+
FreeObj(oResp)
|
|
168
|
+
RestArea(aArea)
|
|
169
|
+
|
|
170
|
+
Return lRet
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### 1.4 PUT Method - Update
|
|
174
|
+
|
|
175
|
+
```advpl
|
|
176
|
+
WsMethod PUT WsReceive cCodigo, cLoja WsService CUSTOMERS
|
|
177
|
+
Local lRet := .T.
|
|
178
|
+
Local oJson := JsonObject():New()
|
|
179
|
+
Local oResp := Nil
|
|
180
|
+
Local cBody := ::GetContent()
|
|
181
|
+
Local nParsed := 0
|
|
182
|
+
Local aArea := GetArea()
|
|
183
|
+
|
|
184
|
+
If !ValidToken(Self)
|
|
185
|
+
SetRestFault(401, "Token invalido ou ausente")
|
|
186
|
+
lRet := .F.
|
|
187
|
+
RestArea(aArea)
|
|
188
|
+
Return lRet
|
|
189
|
+
EndIf
|
|
190
|
+
|
|
191
|
+
nParsed := oJson:FromJson(cBody)
|
|
192
|
+
If nParsed <> 0
|
|
193
|
+
SetRestFault(400, "JSON invalido")
|
|
194
|
+
lRet := .F.
|
|
195
|
+
FreeObj(oJson)
|
|
196
|
+
RestArea(aArea)
|
|
197
|
+
Return lRet
|
|
198
|
+
EndIf
|
|
199
|
+
|
|
200
|
+
DbSelectArea("SA1")
|
|
201
|
+
DbSetOrder(1)
|
|
202
|
+
|
|
203
|
+
If !DbSeek(xFilial("SA1") + PadR(::cCodigo, TamSX3("A1_COD")[1]) + PadR(::cLoja, TamSX3("A1_LOJA")[1]))
|
|
204
|
+
SetRestFault(404, "Cliente nao encontrado: " + ::cCodigo + "/" + ::cLoja)
|
|
205
|
+
lRet := .F.
|
|
206
|
+
FreeObj(oJson)
|
|
207
|
+
RestArea(aArea)
|
|
208
|
+
Return lRet
|
|
209
|
+
EndIf
|
|
210
|
+
|
|
211
|
+
Begin Transaction
|
|
212
|
+
RecLock("SA1", .F.)
|
|
213
|
+
If oJson["nome"] <> Nil
|
|
214
|
+
SA1->A1_NOME := oJson["nome"]
|
|
215
|
+
EndIf
|
|
216
|
+
If oJson["cnpj"] <> Nil
|
|
217
|
+
SA1->A1_CGC := oJson["cnpj"]
|
|
218
|
+
EndIf
|
|
219
|
+
If oJson["email"] <> Nil
|
|
220
|
+
SA1->A1_EMAIL := oJson["email"]
|
|
221
|
+
EndIf
|
|
222
|
+
If oJson["telefone"] <> Nil
|
|
223
|
+
SA1->A1_TEL := oJson["telefone"]
|
|
224
|
+
EndIf
|
|
225
|
+
MsUnlock()
|
|
226
|
+
End Transaction
|
|
227
|
+
|
|
228
|
+
oResp := JsonObject():New()
|
|
229
|
+
oResp["codigo"] := AllTrim(SA1->A1_COD)
|
|
230
|
+
oResp["loja"] := AllTrim(SA1->A1_LOJA)
|
|
231
|
+
oResp["message"] := "Cliente alterado com sucesso"
|
|
232
|
+
|
|
233
|
+
::SetContentType("application/json")
|
|
234
|
+
::SetResponse(oResp:ToJson())
|
|
235
|
+
|
|
236
|
+
FreeObj(oJson)
|
|
237
|
+
FreeObj(oResp)
|
|
238
|
+
RestArea(aArea)
|
|
239
|
+
|
|
240
|
+
Return lRet
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### 1.5 DELETE Method
|
|
244
|
+
|
|
245
|
+
```advpl
|
|
246
|
+
WsMethod DELETE WsReceive cCodigo, cLoja WsService CUSTOMERS
|
|
247
|
+
Local lRet := .T.
|
|
248
|
+
Local oResp := Nil
|
|
249
|
+
Local aArea := GetArea()
|
|
250
|
+
|
|
251
|
+
If !ValidToken(Self)
|
|
252
|
+
SetRestFault(401, "Token invalido ou ausente")
|
|
253
|
+
lRet := .F.
|
|
254
|
+
RestArea(aArea)
|
|
255
|
+
Return lRet
|
|
256
|
+
EndIf
|
|
257
|
+
|
|
258
|
+
DbSelectArea("SA1")
|
|
259
|
+
DbSetOrder(1)
|
|
260
|
+
|
|
261
|
+
If !DbSeek(xFilial("SA1") + PadR(::cCodigo, TamSX3("A1_COD")[1]) + PadR(::cLoja, TamSX3("A1_LOJA")[1]))
|
|
262
|
+
SetRestFault(404, "Cliente nao encontrado")
|
|
263
|
+
lRet := .F.
|
|
264
|
+
RestArea(aArea)
|
|
265
|
+
Return lRet
|
|
266
|
+
EndIf
|
|
267
|
+
|
|
268
|
+
Begin Transaction
|
|
269
|
+
RecLock("SA1", .F.)
|
|
270
|
+
SA1->D_E_L_E_T_ := "*"
|
|
271
|
+
MsUnlock()
|
|
272
|
+
End Transaction
|
|
273
|
+
|
|
274
|
+
oResp := JsonObject():New()
|
|
275
|
+
oResp["message"] := "Cliente excluido com sucesso"
|
|
276
|
+
|
|
277
|
+
::SetContentType("application/json")
|
|
278
|
+
::SetResponse(oResp:ToJson())
|
|
279
|
+
|
|
280
|
+
FreeObj(oResp)
|
|
281
|
+
RestArea(aArea)
|
|
282
|
+
|
|
283
|
+
Return lRet
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### 1.6 Authentication Helper
|
|
287
|
+
|
|
288
|
+
```advpl
|
|
289
|
+
Static Function ValidToken(oSelf)
|
|
290
|
+
Local lValid := .F.
|
|
291
|
+
Local cAuthHeader := ""
|
|
292
|
+
Local cToken := ""
|
|
293
|
+
|
|
294
|
+
cAuthHeader := oSelf:GetHeader("Authorization")
|
|
295
|
+
|
|
296
|
+
If !Empty(cAuthHeader)
|
|
297
|
+
// Exemplo: validar Bearer token
|
|
298
|
+
If Left(cAuthHeader, 7) == "Bearer "
|
|
299
|
+
cToken := SubStr(cAuthHeader, 8)
|
|
300
|
+
// Valide o token contra sua base/cache
|
|
301
|
+
lValid := ValidateJWT(cToken) // Implementar conforme necessidade
|
|
302
|
+
EndIf
|
|
303
|
+
EndIf
|
|
304
|
+
|
|
305
|
+
Return lValid
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## 2. TLPP REST Pattern (Annotation-Based)
|
|
311
|
+
|
|
312
|
+
The TLPP approach uses annotations (decorators) to define REST endpoints. This is the modern pattern available in newer Protheus versions.
|
|
313
|
+
|
|
314
|
+
### 2.1 Complete TLPP REST Service
|
|
315
|
+
|
|
316
|
+
```tlpp
|
|
317
|
+
#Include "tlpp-core.th"
|
|
318
|
+
#Include "tlpp-rest.th"
|
|
319
|
+
|
|
320
|
+
/*/{Protheus.doc} ProductsAPI
|
|
321
|
+
API REST de Produtos usando TLPP
|
|
322
|
+
@type Namespace tlpp.rest
|
|
323
|
+
@author Autor
|
|
324
|
+
@since 01/01/2026
|
|
325
|
+
@version 1.0
|
|
326
|
+
/*/
|
|
327
|
+
|
|
328
|
+
// =============================================================
|
|
329
|
+
// GET /api/v1/products - Lista produtos
|
|
330
|
+
// =============================================================
|
|
331
|
+
@RestService("/api/v1/products")
|
|
332
|
+
@Get("")
|
|
333
|
+
Function getProducts()
|
|
334
|
+
Local oJson := JsonObject():New()
|
|
335
|
+
Local oItem := Nil
|
|
336
|
+
Local aResult := {}
|
|
337
|
+
Local aArea := GetArea()
|
|
338
|
+
Local cPage := oRest:getQueryString():getValue("page")
|
|
339
|
+
Local cSize := oRest:getQueryString():getValue("pageSize")
|
|
340
|
+
Local nPage := IIf(Empty(cPage), 1, Val(cPage))
|
|
341
|
+
Local nSize := IIf(Empty(cSize), 20, Val(cSize))
|
|
342
|
+
Local nSkip := (nPage - 1) * nSize
|
|
343
|
+
Local nCount := 0
|
|
344
|
+
|
|
345
|
+
DbSelectArea("SB1")
|
|
346
|
+
DbSetOrder(1)
|
|
347
|
+
DbGoTop()
|
|
348
|
+
|
|
349
|
+
While !Eof() .And. nCount < nSize
|
|
350
|
+
If xFilial("SB1") == SB1->B1_FILIAL
|
|
351
|
+
nSkip--
|
|
352
|
+
If nSkip < 0
|
|
353
|
+
oItem := JsonObject():New()
|
|
354
|
+
oItem["codigo"] := AllTrim(SB1->B1_COD)
|
|
355
|
+
oItem["descricao"] := AllTrim(SB1->B1_DESC)
|
|
356
|
+
oItem["tipo"] := AllTrim(SB1->B1_TIPO)
|
|
357
|
+
oItem["unidade"] := AllTrim(SB1->B1_UM)
|
|
358
|
+
oItem["preco"] := SB1->B1_PRV1
|
|
359
|
+
|
|
360
|
+
aAdd(aResult, oItem)
|
|
361
|
+
nCount++
|
|
362
|
+
EndIf
|
|
363
|
+
EndIf
|
|
364
|
+
DbSkip()
|
|
365
|
+
EndWh
|
|
366
|
+
|
|
367
|
+
oJson["items"] := aResult
|
|
368
|
+
oJson["page"] := nPage
|
|
369
|
+
oJson["pageSize"] := nSize
|
|
370
|
+
oJson["hasNext"] := !Eof()
|
|
371
|
+
|
|
372
|
+
oRest:setResponse(oJson:ToJson())
|
|
373
|
+
oRest:setStatus(200)
|
|
374
|
+
|
|
375
|
+
FreeObj(oJson)
|
|
376
|
+
RestArea(aArea)
|
|
377
|
+
|
|
378
|
+
Return .T.
|
|
379
|
+
|
|
380
|
+
// =============================================================
|
|
381
|
+
// GET /api/v1/products/{codigo} - Busca produto por codigo
|
|
382
|
+
// =============================================================
|
|
383
|
+
@RestService("/api/v1/products")
|
|
384
|
+
@Get("/{codigo}")
|
|
385
|
+
Function getProductById()
|
|
386
|
+
Local oJson := Nil
|
|
387
|
+
Local aArea := GetArea()
|
|
388
|
+
Local cCodigo := oRest:getPathParamsRequest():getValue("codigo")
|
|
389
|
+
|
|
390
|
+
DbSelectArea("SB1")
|
|
391
|
+
DbSetOrder(1)
|
|
392
|
+
|
|
393
|
+
If !DbSeek(xFilial("SB1") + PadR(cCodigo, TamSX3("B1_COD")[1]))
|
|
394
|
+
oRest:setStatus(404)
|
|
395
|
+
oRest:setResponse('{"error":"Produto nao encontrado"}')
|
|
396
|
+
RestArea(aArea)
|
|
397
|
+
Return .T.
|
|
398
|
+
EndIf
|
|
399
|
+
|
|
400
|
+
oJson := JsonObject():New()
|
|
401
|
+
oJson["codigo"] := AllTrim(SB1->B1_COD)
|
|
402
|
+
oJson["descricao"] := AllTrim(SB1->B1_DESC)
|
|
403
|
+
oJson["tipo"] := AllTrim(SB1->B1_TIPO)
|
|
404
|
+
oJson["unidade"] := AllTrim(SB1->B1_UM)
|
|
405
|
+
oJson["grupo"] := AllTrim(SB1->B1_GRUPO)
|
|
406
|
+
oJson["preco"] := SB1->B1_PRV1
|
|
407
|
+
oJson["custo"] := SB1->B1_CUSTO1
|
|
408
|
+
|
|
409
|
+
oRest:setResponse(oJson:ToJson())
|
|
410
|
+
oRest:setStatus(200)
|
|
411
|
+
|
|
412
|
+
FreeObj(oJson)
|
|
413
|
+
RestArea(aArea)
|
|
414
|
+
|
|
415
|
+
Return .T.
|
|
416
|
+
|
|
417
|
+
// =============================================================
|
|
418
|
+
// POST /api/v1/products - Cria produto
|
|
419
|
+
// =============================================================
|
|
420
|
+
@RestService("/api/v1/products")
|
|
421
|
+
@Post("")
|
|
422
|
+
Function createProduct()
|
|
423
|
+
Local oJson := JsonObject():New()
|
|
424
|
+
Local oResp := Nil
|
|
425
|
+
Local cBody := oRest:getBody()
|
|
426
|
+
Local nParsed := oJson:FromJson(cBody)
|
|
427
|
+
Local aArea := GetArea()
|
|
428
|
+
|
|
429
|
+
If nParsed <> 0
|
|
430
|
+
oRest:setStatus(400)
|
|
431
|
+
oRest:setResponse('{"error":"JSON invalido"}')
|
|
432
|
+
FreeObj(oJson)
|
|
433
|
+
Return .T.
|
|
434
|
+
EndIf
|
|
435
|
+
|
|
436
|
+
If Empty(oJson["descricao"])
|
|
437
|
+
oRest:setStatus(422)
|
|
438
|
+
oRest:setResponse('{"error":"Campo descricao e obrigatorio"}')
|
|
439
|
+
FreeObj(oJson)
|
|
440
|
+
Return .T.
|
|
441
|
+
EndIf
|
|
442
|
+
|
|
443
|
+
DbSelectArea("SB1")
|
|
444
|
+
Begin Transaction
|
|
445
|
+
RecLock("SB1", .T.)
|
|
446
|
+
SB1->B1_FILIAL := xFilial("SB1")
|
|
447
|
+
SB1->B1_COD := GetSXENum("SB1", "B1_COD")
|
|
448
|
+
SB1->B1_DESC := oJson["descricao"]
|
|
449
|
+
SB1->B1_TIPO := IIf(oJson["tipo"] <> Nil, oJson["tipo"], "PA")
|
|
450
|
+
SB1->B1_UM := IIf(oJson["unidade"] <> Nil, oJson["unidade"], "UN")
|
|
451
|
+
SB1->B1_PRV1 := IIf(oJson["preco"] <> Nil, oJson["preco"], 0)
|
|
452
|
+
MsUnlock()
|
|
453
|
+
ConfirmSX8()
|
|
454
|
+
End Transaction
|
|
455
|
+
|
|
456
|
+
oResp := JsonObject():New()
|
|
457
|
+
oResp["codigo"] := AllTrim(SB1->B1_COD)
|
|
458
|
+
oResp["descricao"] := AllTrim(SB1->B1_DESC)
|
|
459
|
+
oResp["message"] := "Produto criado com sucesso"
|
|
460
|
+
|
|
461
|
+
oRest:setResponse(oResp:ToJson())
|
|
462
|
+
oRest:setStatus(201)
|
|
463
|
+
|
|
464
|
+
FreeObj(oJson)
|
|
465
|
+
FreeObj(oResp)
|
|
466
|
+
RestArea(aArea)
|
|
467
|
+
|
|
468
|
+
Return .T.
|
|
469
|
+
|
|
470
|
+
// =============================================================
|
|
471
|
+
// PUT /api/v1/products/{codigo} - Atualiza produto
|
|
472
|
+
// =============================================================
|
|
473
|
+
@RestService("/api/v1/products")
|
|
474
|
+
@Put("/{codigo}")
|
|
475
|
+
Function updateProduct()
|
|
476
|
+
Local oJson := JsonObject():New()
|
|
477
|
+
Local oResp := Nil
|
|
478
|
+
Local cBody := oRest:getBody()
|
|
479
|
+
Local nParsed := oJson:FromJson(cBody)
|
|
480
|
+
Local cCodigo := oRest:getPathParamsRequest():getValue("codigo")
|
|
481
|
+
Local aArea := GetArea()
|
|
482
|
+
|
|
483
|
+
If nParsed <> 0
|
|
484
|
+
oRest:setStatus(400)
|
|
485
|
+
oRest:setResponse('{"error":"JSON invalido"}')
|
|
486
|
+
FreeObj(oJson)
|
|
487
|
+
Return .T.
|
|
488
|
+
EndIf
|
|
489
|
+
|
|
490
|
+
DbSelectArea("SB1")
|
|
491
|
+
DbSetOrder(1)
|
|
492
|
+
|
|
493
|
+
If !DbSeek(xFilial("SB1") + PadR(cCodigo, TamSX3("B1_COD")[1]))
|
|
494
|
+
oRest:setStatus(404)
|
|
495
|
+
oRest:setResponse('{"error":"Produto nao encontrado"}')
|
|
496
|
+
FreeObj(oJson)
|
|
497
|
+
RestArea(aArea)
|
|
498
|
+
Return .T.
|
|
499
|
+
EndIf
|
|
500
|
+
|
|
501
|
+
Begin Transaction
|
|
502
|
+
RecLock("SB1", .F.)
|
|
503
|
+
If oJson["descricao"] <> Nil
|
|
504
|
+
SB1->B1_DESC := oJson["descricao"]
|
|
505
|
+
EndIf
|
|
506
|
+
If oJson["tipo"] <> Nil
|
|
507
|
+
SB1->B1_TIPO := oJson["tipo"]
|
|
508
|
+
EndIf
|
|
509
|
+
If oJson["unidade"] <> Nil
|
|
510
|
+
SB1->B1_UM := oJson["unidade"]
|
|
511
|
+
EndIf
|
|
512
|
+
If oJson["preco"] <> Nil
|
|
513
|
+
SB1->B1_PRV1 := oJson["preco"]
|
|
514
|
+
EndIf
|
|
515
|
+
MsUnlock()
|
|
516
|
+
End Transaction
|
|
517
|
+
|
|
518
|
+
oResp := JsonObject():New()
|
|
519
|
+
oResp["message"] := "Produto atualizado com sucesso"
|
|
520
|
+
|
|
521
|
+
oRest:setResponse(oResp:ToJson())
|
|
522
|
+
oRest:setStatus(200)
|
|
523
|
+
|
|
524
|
+
FreeObj(oJson)
|
|
525
|
+
FreeObj(oResp)
|
|
526
|
+
RestArea(aArea)
|
|
527
|
+
|
|
528
|
+
Return .T.
|
|
529
|
+
|
|
530
|
+
// =============================================================
|
|
531
|
+
// DELETE /api/v1/products/{codigo} - Remove produto
|
|
532
|
+
// =============================================================
|
|
533
|
+
@RestService("/api/v1/products")
|
|
534
|
+
@Delete("/{codigo}")
|
|
535
|
+
Function deleteProduct()
|
|
536
|
+
Local cCodigo := oRest:getPathParamsRequest():getValue("codigo")
|
|
537
|
+
Local aArea := GetArea()
|
|
538
|
+
|
|
539
|
+
DbSelectArea("SB1")
|
|
540
|
+
DbSetOrder(1)
|
|
541
|
+
|
|
542
|
+
If !DbSeek(xFilial("SB1") + PadR(cCodigo, TamSX3("B1_COD")[1]))
|
|
543
|
+
oRest:setStatus(404)
|
|
544
|
+
oRest:setResponse('{"error":"Produto nao encontrado"}')
|
|
545
|
+
RestArea(aArea)
|
|
546
|
+
Return .T.
|
|
547
|
+
EndIf
|
|
548
|
+
|
|
549
|
+
Begin Transaction
|
|
550
|
+
RecLock("SB1", .F.)
|
|
551
|
+
SB1->D_E_L_E_T_ := "*"
|
|
552
|
+
MsUnlock()
|
|
553
|
+
End Transaction
|
|
554
|
+
|
|
555
|
+
oRest:setResponse('{"message":"Produto excluido com sucesso"}')
|
|
556
|
+
oRest:setStatus(200)
|
|
557
|
+
|
|
558
|
+
RestArea(aArea)
|
|
559
|
+
|
|
560
|
+
Return .T.
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
---
|
|
564
|
+
|
|
565
|
+
## 3. JSON Handling
|
|
566
|
+
|
|
567
|
+
### 3.1 Creating JSON from ADVPL Data
|
|
568
|
+
|
|
569
|
+
```advpl
|
|
570
|
+
Local oJson := JsonObject():New()
|
|
571
|
+
Local oItem1 := Nil
|
|
572
|
+
Local oItem2 := Nil
|
|
573
|
+
Local aItens := {}
|
|
574
|
+
Local cJson := ""
|
|
575
|
+
|
|
576
|
+
// Simple values
|
|
577
|
+
oJson["nome"] := "Produto Teste"
|
|
578
|
+
oJson["codigo"] := 123
|
|
579
|
+
oJson["ativo"] := .T.
|
|
580
|
+
oJson["preco"] := 99.90
|
|
581
|
+
oJson["dataCriacao"] := DToS(Date())
|
|
582
|
+
|
|
583
|
+
// Nested object
|
|
584
|
+
oJson["endereco"] := JsonObject():New()
|
|
585
|
+
oJson["endereco"]["rua"] := "Rua Principal"
|
|
586
|
+
oJson["endereco"]["numero"] := "100"
|
|
587
|
+
oJson["endereco"]["cidade"] := "Sao Paulo"
|
|
588
|
+
|
|
589
|
+
// Array of objects
|
|
590
|
+
oItem1 := JsonObject():New()
|
|
591
|
+
oItem1["produto"] := "PROD001"
|
|
592
|
+
oItem1["quant"] := 10
|
|
593
|
+
aAdd(aItens, oItem1)
|
|
594
|
+
|
|
595
|
+
oItem2 := JsonObject():New()
|
|
596
|
+
oItem2["produto"] := "PROD002"
|
|
597
|
+
oItem2["quant"] := 5
|
|
598
|
+
aAdd(aItens, oItem2)
|
|
599
|
+
|
|
600
|
+
oJson["itens"] := aItens
|
|
601
|
+
|
|
602
|
+
// Convert to string
|
|
603
|
+
cJson := oJson:ToJson()
|
|
604
|
+
// Result: {"nome":"Produto Teste","codigo":123,"ativo":true,...}
|
|
605
|
+
|
|
606
|
+
FreeObj(oJson)
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
### 3.2 Parsing JSON Request Body
|
|
610
|
+
|
|
611
|
+
```advpl
|
|
612
|
+
Local oJson := JsonObject():New()
|
|
613
|
+
Local cBody := '{"nome":"Teste","valor":100,"itens":[{"cod":"001"},{"cod":"002"}]}'
|
|
614
|
+
Local nResult := 0
|
|
615
|
+
Local cNome := ""
|
|
616
|
+
Local nValor := 0
|
|
617
|
+
Local aItens := {}
|
|
618
|
+
Local nI := 0
|
|
619
|
+
|
|
620
|
+
nResult := oJson:FromJson(cBody)
|
|
621
|
+
|
|
622
|
+
If nResult <> 0
|
|
623
|
+
Conout("Erro ao parsear JSON na posicao: " + cValToChar(nResult))
|
|
624
|
+
FreeObj(oJson)
|
|
625
|
+
Return .F.
|
|
626
|
+
EndIf
|
|
627
|
+
|
|
628
|
+
// Accessing values
|
|
629
|
+
cNome := oJson["nome"] // "Teste"
|
|
630
|
+
nValor := oJson["valor"] // 100
|
|
631
|
+
|
|
632
|
+
// Accessing array
|
|
633
|
+
aItens := oJson["itens"]
|
|
634
|
+
For nI := 1 To Len(aItens)
|
|
635
|
+
Conout("Item: " + aItens[nI]["cod"])
|
|
636
|
+
Next nI
|
|
637
|
+
|
|
638
|
+
// Check if key exists (returns Nil if not found)
|
|
639
|
+
If oJson["campo_opcional"] <> Nil
|
|
640
|
+
// Campo existe
|
|
641
|
+
EndIf
|
|
642
|
+
|
|
643
|
+
FreeObj(oJson)
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
### 3.3 JSON Array Response
|
|
647
|
+
|
|
648
|
+
```advpl
|
|
649
|
+
Local oJsonArr := JsonObject():New()
|
|
650
|
+
Local oObj1 := Nil
|
|
651
|
+
Local oObj2 := Nil
|
|
652
|
+
Local aResult := {}
|
|
653
|
+
Local cResponse := ""
|
|
654
|
+
|
|
655
|
+
// Build array of objects
|
|
656
|
+
oObj1 := JsonObject():New()
|
|
657
|
+
oObj1["id"] := 1
|
|
658
|
+
oObj1["nome"] := "Item 1"
|
|
659
|
+
aAdd(aResult, oObj1)
|
|
660
|
+
|
|
661
|
+
oObj2 := JsonObject():New()
|
|
662
|
+
oObj2["id"] := 2
|
|
663
|
+
oObj2["nome"] := "Item 2"
|
|
664
|
+
aAdd(aResult, oObj2)
|
|
665
|
+
|
|
666
|
+
oJsonArr["data"] := aResult
|
|
667
|
+
oJsonArr["total"] := Len(aResult)
|
|
668
|
+
|
|
669
|
+
cResponse := oJsonArr:ToJson()
|
|
670
|
+
// {"data":[{"id":1,"nome":"Item 1"},{"id":2,"nome":"Item 2"}],"total":2}
|
|
671
|
+
|
|
672
|
+
FreeObj(oJsonArr)
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
---
|
|
676
|
+
|
|
677
|
+
## 4. Authentication Middleware
|
|
678
|
+
|
|
679
|
+
### 4.1 Bearer Token Authentication
|
|
680
|
+
|
|
681
|
+
```advpl
|
|
682
|
+
Static Function ValidToken(oSelf)
|
|
683
|
+
Local lValid := .F.
|
|
684
|
+
Local cAuthHeader := ""
|
|
685
|
+
Local cToken := ""
|
|
686
|
+
|
|
687
|
+
cAuthHeader := oSelf:GetHeader("Authorization")
|
|
688
|
+
|
|
689
|
+
If Empty(cAuthHeader)
|
|
690
|
+
Return .F.
|
|
691
|
+
EndIf
|
|
692
|
+
|
|
693
|
+
If Left(cAuthHeader, 7) <> "Bearer "
|
|
694
|
+
Return .F.
|
|
695
|
+
EndIf
|
|
696
|
+
|
|
697
|
+
cToken := SubStr(cAuthHeader, 8)
|
|
698
|
+
|
|
699
|
+
// Opcao 1: Validar contra tabela de tokens
|
|
700
|
+
DbSelectArea("SZ9") // Tabela customizada de tokens
|
|
701
|
+
DbSetOrder(1)
|
|
702
|
+
If DbSeek(cToken)
|
|
703
|
+
If SZ9->Z9_EXPIRA >= Date()
|
|
704
|
+
lValid := .T.
|
|
705
|
+
EndIf
|
|
706
|
+
EndIf
|
|
707
|
+
|
|
708
|
+
Return lValid
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
### 4.2 Basic Authentication
|
|
712
|
+
|
|
713
|
+
```advpl
|
|
714
|
+
Static Function ValidBasicAuth(oSelf)
|
|
715
|
+
Local lValid := .F.
|
|
716
|
+
Local cAuthHeader := ""
|
|
717
|
+
Local cBase64 := ""
|
|
718
|
+
Local cDecoded := ""
|
|
719
|
+
Local nPos := 0
|
|
720
|
+
Local cUser := ""
|
|
721
|
+
Local cPass := ""
|
|
722
|
+
|
|
723
|
+
cAuthHeader := oSelf:GetHeader("Authorization")
|
|
724
|
+
|
|
725
|
+
If Empty(cAuthHeader) .Or. Left(cAuthHeader, 6) <> "Basic "
|
|
726
|
+
Return .F.
|
|
727
|
+
EndIf
|
|
728
|
+
|
|
729
|
+
cBase64 := SubStr(cAuthHeader, 7)
|
|
730
|
+
cDecoded := Decode64(cBase64)
|
|
731
|
+
nPos := At(":", cDecoded)
|
|
732
|
+
|
|
733
|
+
If nPos > 0
|
|
734
|
+
cUser := Left(cDecoded, nPos - 1)
|
|
735
|
+
cPass := SubStr(cDecoded, nPos + 1)
|
|
736
|
+
|
|
737
|
+
// Validar usuario e senha contra base
|
|
738
|
+
lValid := FWCheckPass(cUser, cPass) // Funcao customizada
|
|
739
|
+
EndIf
|
|
740
|
+
|
|
741
|
+
Return lValid
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
### 4.3 Applying Authentication in WsRestFul
|
|
745
|
+
|
|
746
|
+
```advpl
|
|
747
|
+
WsMethod GET WsService CUSTOMERS
|
|
748
|
+
// Sempre verificar autenticacao no inicio de cada metodo
|
|
749
|
+
If !ValidToken(Self)
|
|
750
|
+
SetRestFault(401, '{"error":"Unauthorized","message":"Token invalido ou ausente"}')
|
|
751
|
+
Return .F.
|
|
752
|
+
EndIf
|
|
753
|
+
|
|
754
|
+
// ... resto da logica
|
|
755
|
+
Return .T.
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
---
|
|
759
|
+
|
|
760
|
+
## 5. CORS Configuration
|
|
761
|
+
|
|
762
|
+
### 5.1 CORS Headers in WsRestFul
|
|
763
|
+
|
|
764
|
+
```advpl
|
|
765
|
+
// Adicionar headers CORS no response
|
|
766
|
+
Static Function SetCorsHeaders(oSelf)
|
|
767
|
+
oSelf:SetHeader("Access-Control-Allow-Origin", "*")
|
|
768
|
+
oSelf:SetHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
|
769
|
+
oSelf:SetHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
|
|
770
|
+
oSelf:SetHeader("Access-Control-Max-Age", "3600")
|
|
771
|
+
Return Nil
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
### 5.2 Appserver.ini REST Configuration
|
|
775
|
+
|
|
776
|
+
CORS and REST settings can also be configured in `appserver.ini`:
|
|
777
|
+
|
|
778
|
+
```ini
|
|
779
|
+
[HTTPREST]
|
|
780
|
+
Port=8080
|
|
781
|
+
URIs=HTTPURI
|
|
782
|
+
|
|
783
|
+
[HTTPURI]
|
|
784
|
+
URL=/rest
|
|
785
|
+
PrepareIn=All
|
|
786
|
+
Instances=2,5
|
|
787
|
+
CORSOrigins=*
|
|
788
|
+
CORSHeaders=Content-Type,Authorization
|
|
789
|
+
CORSMethods=GET,POST,PUT,DELETE,OPTIONS
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
---
|
|
793
|
+
|
|
794
|
+
## 6. Common Patterns
|
|
795
|
+
|
|
796
|
+
### 6.1 Pagination
|
|
797
|
+
|
|
798
|
+
```advpl
|
|
799
|
+
Static Function GetPaginated(cAlias, nPage, nPageSize, oFilter)
|
|
800
|
+
Local aResult := {}
|
|
801
|
+
Local oResp := Nil
|
|
802
|
+
Local nSkip := (nPage - 1) * nPageSize
|
|
803
|
+
Local nCount := 0
|
|
804
|
+
Local nTotal := 0
|
|
805
|
+
|
|
806
|
+
DbSelectArea(cAlias)
|
|
807
|
+
DbSetOrder(1)
|
|
808
|
+
DbGoTop()
|
|
809
|
+
|
|
810
|
+
// Count total (optional, can be expensive)
|
|
811
|
+
Count To nTotal For xFilial(cAlias) == &(cAlias)->(FieldGet(1))
|
|
812
|
+
DbGoTop()
|
|
813
|
+
|
|
814
|
+
// Skip to page
|
|
815
|
+
While !Eof() .And. nSkip > 0
|
|
816
|
+
If xFilial(cAlias) == &(cAlias)->(FieldGet(1))
|
|
817
|
+
nSkip--
|
|
818
|
+
EndIf
|
|
819
|
+
DbSkip()
|
|
820
|
+
EndWh
|
|
821
|
+
|
|
822
|
+
// Collect results
|
|
823
|
+
While !Eof() .And. nCount < nPageSize
|
|
824
|
+
If xFilial(cAlias) == &(cAlias)->(FieldGet(1))
|
|
825
|
+
// Add record to aResult
|
|
826
|
+
nCount++
|
|
827
|
+
EndIf
|
|
828
|
+
DbSkip()
|
|
829
|
+
EndWh
|
|
830
|
+
|
|
831
|
+
oResp := JsonObject():New()
|
|
832
|
+
oResp["items"] := aResult
|
|
833
|
+
oResp["page"] := nPage
|
|
834
|
+
oResp["pageSize"] := nPageSize
|
|
835
|
+
oResp["totalItems"] := nTotal
|
|
836
|
+
oResp["totalPages"] := Ceiling(nTotal / nPageSize)
|
|
837
|
+
oResp["hasNext"] := (nPage * nPageSize) < nTotal
|
|
838
|
+
|
|
839
|
+
Return oResp
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
### 6.2 Filtering with Query Parameters
|
|
843
|
+
|
|
844
|
+
```advpl
|
|
845
|
+
// WsRestFul approach
|
|
846
|
+
WsMethod GET WsReceive cNome, cCidade, cEstado WsService CUSTOMERS
|
|
847
|
+
|
|
848
|
+
// TLPP approach
|
|
849
|
+
Local cNome := oRest:getQueryString():getValue("nome")
|
|
850
|
+
Local cCidade := oRest:getQueryString():getValue("cidade")
|
|
851
|
+
Local lInclude := .F.
|
|
852
|
+
|
|
853
|
+
// Build filter
|
|
854
|
+
DbSelectArea("SA1")
|
|
855
|
+
DbSetOrder(1)
|
|
856
|
+
DbGoTop()
|
|
857
|
+
|
|
858
|
+
While !Eof()
|
|
859
|
+
lInclude := .T.
|
|
860
|
+
|
|
861
|
+
If !Empty(cNome) .And. !(AllTrim(cNome) $ Upper(SA1->A1_NOME))
|
|
862
|
+
lInclude := .F.
|
|
863
|
+
EndIf
|
|
864
|
+
|
|
865
|
+
If !Empty(cCidade) .And. AllTrim(Upper(SA1->A1_MUN)) <> AllTrim(Upper(cCidade))
|
|
866
|
+
lInclude := .F.
|
|
867
|
+
EndIf
|
|
868
|
+
|
|
869
|
+
If lInclude
|
|
870
|
+
// Add to results
|
|
871
|
+
EndIf
|
|
872
|
+
|
|
873
|
+
DbSkip()
|
|
874
|
+
EndWh
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
### 6.3 Standard Error Response
|
|
878
|
+
|
|
879
|
+
```advpl
|
|
880
|
+
Static Function SendError(oSelf, nStatus, cCode, cMessage, cDetails)
|
|
881
|
+
Local oErr := JsonObject():New()
|
|
882
|
+
|
|
883
|
+
oErr["error"] := cCode
|
|
884
|
+
oErr["message"] := cMessage
|
|
885
|
+
oErr["status"] := nStatus
|
|
886
|
+
|
|
887
|
+
If !Empty(cDetails)
|
|
888
|
+
oErr["details"] := cDetails
|
|
889
|
+
EndIf
|
|
890
|
+
|
|
891
|
+
oSelf:SetContentType("application/json")
|
|
892
|
+
SetRestFault(nStatus, oErr:ToJson())
|
|
893
|
+
|
|
894
|
+
FreeObj(oErr)
|
|
895
|
+
|
|
896
|
+
Return Nil
|
|
897
|
+
|
|
898
|
+
// Usage:
|
|
899
|
+
// SendError(Self, 404, "NOT_FOUND", "Cliente nao encontrado", "Codigo: " + cCodigo)
|
|
900
|
+
// SendError(Self, 422, "VALIDATION_ERROR", "Campos invalidos", "Campo 'nome' e obrigatorio")
|
|
901
|
+
// SendError(Self, 500, "INTERNAL_ERROR", "Erro interno do servidor", "")
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
### 6.4 Standard Success Response
|
|
905
|
+
|
|
906
|
+
```advpl
|
|
907
|
+
Static Function SendSuccess(oSelf, nStatus, xData, cMessage)
|
|
908
|
+
Local oResp := JsonObject():New()
|
|
909
|
+
|
|
910
|
+
Default nStatus := 200
|
|
911
|
+
|
|
912
|
+
If ValType(xData) == "O" // JsonObject
|
|
913
|
+
oResp["data"] := xData
|
|
914
|
+
ElseIf ValType(xData) == "A" // Array
|
|
915
|
+
oResp["data"] := xData
|
|
916
|
+
EndIf
|
|
917
|
+
|
|
918
|
+
If !Empty(cMessage)
|
|
919
|
+
oResp["message"] := cMessage
|
|
920
|
+
EndIf
|
|
921
|
+
|
|
922
|
+
oSelf:SetContentType("application/json")
|
|
923
|
+
oSelf:SetStatus(nStatus)
|
|
924
|
+
oSelf:SetResponse(oResp:ToJson())
|
|
925
|
+
|
|
926
|
+
FreeObj(oResp)
|
|
927
|
+
|
|
928
|
+
Return Nil
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
### 6.5 Query with TCQuery (SQL)
|
|
932
|
+
|
|
933
|
+
For more efficient queries, use `TCQuery` instead of navigating records:
|
|
934
|
+
|
|
935
|
+
```advpl
|
|
936
|
+
Static Function QueryCustomers(cWhere, nPage, nPageSize)
|
|
937
|
+
Local cQuery := ""
|
|
938
|
+
Local cAlias := GetNextAlias()
|
|
939
|
+
Local aResult := {}
|
|
940
|
+
Local oItem := Nil
|
|
941
|
+
|
|
942
|
+
cQuery := "SELECT A1_COD, A1_LOJA, A1_NOME, A1_CGC, A1_EMAIL "
|
|
943
|
+
cQuery += "FROM " + RetSqlName("SA1") + " SA1 "
|
|
944
|
+
cQuery += "WHERE SA1.D_E_L_E_T_ = ' ' "
|
|
945
|
+
cQuery += "AND A1_FILIAL = '" + xFilial("SA1") + "' "
|
|
946
|
+
|
|
947
|
+
If !Empty(cWhere)
|
|
948
|
+
cQuery += "AND " + cWhere + " "
|
|
949
|
+
EndIf
|
|
950
|
+
|
|
951
|
+
cQuery += "ORDER BY A1_COD, A1_LOJA "
|
|
952
|
+
|
|
953
|
+
// Paginacao via SQL (depende do SGBD)
|
|
954
|
+
// SQL Server:
|
|
955
|
+
cQuery += "OFFSET " + cValToChar((nPage - 1) * nPageSize) + " ROWS "
|
|
956
|
+
cQuery += "FETCH NEXT " + cValToChar(nPageSize) + " ROWS ONLY"
|
|
957
|
+
|
|
958
|
+
TCQuery cQuery New Alias (cAlias)
|
|
959
|
+
|
|
960
|
+
While !(cAlias)->(Eof())
|
|
961
|
+
oItem := JsonObject():New()
|
|
962
|
+
oItem["codigo"] := AllTrim((cAlias)->A1_COD)
|
|
963
|
+
oItem["loja"] := AllTrim((cAlias)->A1_LOJA)
|
|
964
|
+
oItem["nome"] := AllTrim((cAlias)->A1_NOME)
|
|
965
|
+
oItem["cnpj"] := AllTrim((cAlias)->A1_CGC)
|
|
966
|
+
oItem["email"] := AllTrim((cAlias)->A1_EMAIL)
|
|
967
|
+
aAdd(aResult, oItem)
|
|
968
|
+
(cAlias)->(DbSkip())
|
|
969
|
+
EndWh
|
|
970
|
+
|
|
971
|
+
(cAlias)->(DbCloseArea())
|
|
972
|
+
|
|
973
|
+
Return aResult
|
|
974
|
+
```
|