@fui-org/fui-cli 2.1.4 → 2.2.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/README.md +27 -7
- package/dist/fui-qrb1j9jz.js +397 -0
- package/dist/fui.js +1 -1
- package/package.json +1 -1
- package/skills/fui/SKILL.md +143 -38
- package/skills/fui/agents/fui-builder.md +62 -0
- package/skills/fui/agents/fui-tester.md +77 -0
- package/skills/{fui-skill → fui}/assets/projectdefaultstyle.css +489 -489
- package/skills/{fui-skill → fui}/examples/component.vue +162 -162
- package/skills/{fui-skill → fui}/references/INDEX.md +5 -4
- package/skills/{fui-skill → fui}/references/advanced-techniques.md +170 -168
- package/skills/{fui-skill → fui}/references/component-design.md +2 -2
- package/skills/{fui-skill → fui}/references/component-quickref.md +78 -78
- package/skills/{fui-skill → fui}/references/component-table.md +287 -287
- package/skills/{fui-skill → fui}/references/components-dialog.md +191 -191
- package/skills/{fui-skill → fui}/references/components-display.md +141 -141
- package/skills/{fui-skill → fui}/references/components-echart.md +316 -316
- package/skills/{fui-skill → fui}/references/components-input.md +335 -335
- package/skills/{fui-skill → fui}/references/controls-patterns.md +2 -2
- package/skills/{fui-skill → fui}/references/controls-styling-vocabulary.md +173 -173
- package/skills/{fui-skill → fui}/references/db-workflow.md +9 -9
- package/skills/{fui-skill → fui}/references/default-function.md +456 -456
- package/skills/{fui-skill → fui}/references/design-modes.md +57 -57
- package/skills/{fui-skill → fui}/references/echart-templates.md +489 -489
- package/skills/{fui-skill → fui}/references/fastproject.md +99 -99
- package/skills/{fui-skill → fui}/references/fullstack-workflow.md +314 -313
- package/skills/{fui-skill → fui}/references/module-data-patterns.md +117 -117
- package/skills/{fui-skill → fui}/references/module-json-anatomy.md +132 -132
- package/skills/{fui-skill → fui}/references/module-structure.md +144 -144
- package/skills/{fui-skill → fui}/references/new-session.md +1 -2
- package/skills/{fui-skill → fui}/references/pdfmake.md +60 -60
- package/skills/{fui-skill → fui}/references/permission-system.md +1 -1
- package/skills/{fui-skill → fui}/references/platform-architecture.md +269 -269
- package/skills/{fui-skill → fui}/references/project-config.md +303 -303
- package/skills/{fui-skill → fui}/references/project-provisioning.md +4 -4
- package/skills/{fui-skill → fui}/references/script-map.md +262 -262
- package/skills/{fui-skill → fui}/references/sql-clr-functions.md +225 -225
- package/skills/{fui-skill → fui}/references/system-design.md +2 -2
- package/skills/{fui-skill → fui}/references/tapi-permission-patterns.md +156 -156
- package/skills/{fui-skill → fui}/references/tapi-reference.md +474 -474
- package/skills/fui/references/team-run.md +141 -0
- package/skills/fui/references/tools-registry.md +211 -0
- package/skills/{fui-skill → fui}/references/ui-crosswindow-patterns.md +321 -321
- package/skills/{fui-skill → fui}/references/ui-dialog-patterns.md +255 -255
- package/skills/{fui-skill → fui}/references/ui-layout-patterns.md +176 -176
- package/skills/{fui-skill → fui}/references/ui-patterns.md +303 -303
- package/skills/{fui-skill → fui}/references/ui-screenshot-review.md +95 -95
- package/skills/{fui-skill → fui}/references/ui-table-cell-patterns.md +318 -318
- package/skills/{fui-skill → fui}/references/verification.md +11 -11
- package/skills/{fui-skill → fui}/references/watcher-patterns.md +163 -163
- package/skills/{fui-skill → fui}/references/websocket-realtime.md +271 -271
- package/skills/{fui-skill → fui}/scripts/component-3.0.js +2715 -2715
- package/skills/{fui-skill → fui}/scripts/component.js +3147 -3147
- package/skills/{fui-skill → fui}/scripts/componentTable-3.0.js +1038 -1038
- package/skills/{fui-skill → fui}/scripts/componentTable.js +891 -891
- package/skills/{fui-skill → fui}/scripts/defaultfunction-3.0.js +866 -866
- package/skills/{fui-skill → fui}/scripts/defaultfunction.js +1051 -1051
- package/skills/{fui-skill → fui}/scripts/fastproject-3.0.js +870 -870
- package/skills/{fui-skill → fui}/scripts/fastproject.js +828 -828
- package/dist/fui-h4z7zhk2.js +0 -381
- package/skills/fui-skill/SKILL.md +0 -139
- package/skills/fui-skill/references/tools-registry.md +0 -84
- /package/skills/{fui-skill → fui}/assets/projectdefaultstyle-3.0.css +0 -0
- /package/skills/{fui-skill → fui}/design-md/airbnb/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/airbnb/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/airtable/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/airtable/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/apple/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/apple/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/asu/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/asu/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/binance/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/binance/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/bmw/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/bmw/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/bmw-m/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/bmw-m/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/bugatti/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/bugatti/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/cal/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/cal/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/claude/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/claude/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/clay/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/clay/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/clickhouse/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/clickhouse/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/cohere/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/cohere/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/coinbase/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/coinbase/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/composio/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/composio/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/cursor/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/cursor/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/elevenlabs/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/elevenlabs/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/expo/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/expo/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/ferrari/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/ferrari/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/figma/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/figma/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/framer/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/framer/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/fui/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/hashicorp/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/hashicorp/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/ibm/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/ibm/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/intercom/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/intercom/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/kraken/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/kraken/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/lamborghini/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/lamborghini/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/linear.app/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/linear.app/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/lovable/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/lovable/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mastercard/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mastercard/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/meta/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/meta/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/minimax/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/minimax/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mintlify/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mintlify/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/miro/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/miro/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mistral.ai/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mistral.ai/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mongodb/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mongodb/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/nike/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/nike/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/notion/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/notion/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/nvidia/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/nvidia/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/ollama/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/ollama/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/opencode.ai/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/opencode.ai/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/pinterest/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/pinterest/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/playstation/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/playstation/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/posthog/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/posthog/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/raycast/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/raycast/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/renault/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/renault/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/replicate/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/replicate/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/resend/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/resend/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/revolut/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/revolut/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/runwayml/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/runwayml/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/sanity/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/sanity/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/sentry/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/sentry/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/shopify/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/shopify/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/slack/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/spacex/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/spacex/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/spotify/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/spotify/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/starbucks/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/starbucks/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/stripe/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/stripe/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/supabase/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/supabase/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/superhuman/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/superhuman/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/tesla/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/tesla/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/theverge/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/theverge/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/together.ai/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/together.ai/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/uber/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/uber/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/vercel/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/vercel/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/vodafone/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/vodafone/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/voltagent/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/voltagent/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/warp/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/warp/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/webflow/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/webflow/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/wired/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/wired/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/wise/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/wise/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/x.ai/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/x.ai/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/zapier/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/zapier/README.md +0 -0
- /package/skills/{fui-skill → fui}/examples/f-table-patterns.json +0 -0
- /package/skills/{fui-skill → fui}/examples/module-patterns.json +0 -0
- /package/skills/{fui-skill → fui}/examples/project-patterns.json +0 -0
- /package/skills/{fui-skill → fui}/references/coding-standards.md +0 -0
- /package/skills/{fui-skill → fui}/references/db-table-design.md +0 -0
- /package/skills/{fui-skill → fui}/references/fsheet.md +0 -0
- /package/skills/{fui-skill → fui}/references/tapi-file-api.md +0 -0
- /package/skills/{fui-skill → fui}/references/ui-templates.md +0 -0
- /package/skills/{fui-skill → fui}/scripts/fechart.js +0 -0
- /package/skills/{fui-skill → fui}/scripts/fsheet.js +0 -0
|
@@ -1,474 +1,474 @@
|
|
|
1
|
-
# tAPI Reference — Transparent Query API (Core)
|
|
2
|
-
|
|
3
|
-
> Owns: **tAPI conventions: SP naming (`spAPI_*`/`spAPI_AUTH_*`/`spAPIFILE_*`), params, URL, response contract, `/help` route**. Deploy workflow: [db-workflow.md](db-workflow.md).
|
|
4
|
-
|
|
5
|
-
tAPI auto-generates APIs from SQL SPs; the SP name sets URL, auth type and access (no controllers/routes). Accepts **GET and POST** — don't specify a method in the SP; module.json picks it. Load as needed:
|
|
6
|
-
|
|
7
|
-
| Topic | File |
|
|
8
|
-
| ------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
9
|
-
| Permission checks in SP body (5 patterns, `@sys_SystemRight`, `getSystemRight`) | [tapi-permission-patterns.md](tapi-permission-patterns.md) |
|
|
10
|
-
| File API — GET/UPLOAD, `spAPIFILE_`, `fileContent`, `tblFileData` | [tapi-file-api.md](tapi-file-api.md) |
|
|
11
|
-
|
|
12
|
-
> Image processing, outbound HTTP, email, server file I/O, regex → first check for an existing **SQL CLR function** (`httpCall`, `sendMail`, `ImageResize`, `FileWriter`, `RegexMatch`...): [sql-clr-functions.md](sql-clr-functions.md).
|
|
13
|
-
|
|
14
|
-
## 1. SP naming
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
spAPI_[AUTH_]FunctionName
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
| Part | Required | Meaning |
|
|
21
|
-
| -------------- | -------- | ---------------------------------------------------------------- |
|
|
22
|
-
| `spAPI_` | ✅ | Marks SP as API-callable. Missing → API cannot call it |
|
|
23
|
-
| `AUTH_` | ❌ | No auth required. Omitted → valid token mandatory |
|
|
24
|
-
| `FunctionName` | ✅ | API function name used in URL, e.g. `MessageHome`, `UserSetting` |
|
|
25
|
-
|
|
26
|
-
```sql
|
|
27
|
-
spAPI_MessageHome -- có auth
|
|
28
|
-
spAPI_AUTH_SelectChat -- không cần auth
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
File API:
|
|
32
|
-
|
|
33
|
-
```
|
|
34
|
-
spAPIFILE_Document -- SP để GET file
|
|
35
|
-
spAPIFILE_UPLOAD_Document -- SP để nhận file upload
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Details: [tapi-file-api.md](tapi-file-api.md).
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## 2. SP parameters
|
|
43
|
-
|
|
44
|
-
### `@url1_`, `@url2_`, `@url3_`... — route params
|
|
45
|
-
|
|
46
|
-
Taken from URL segments in order:
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
https://api.domain.vn/app/FunctionName/value1/value2
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
```sql
|
|
53
|
-
@url1_GroupID varchar(50), -- nhận "value1"
|
|
54
|
-
@url2_Mode varchar(50), -- nhận "value2"
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### `@sys_` — system params (injected by tAPI, never sent by client)
|
|
58
|
-
|
|
59
|
-
| Param | Type | Meaning |
|
|
60
|
-
| -------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
61
|
-
| `@sys_UserID` | varchar(9) | Authenticated user ID |
|
|
62
|
-
| `@sys_UserName` | varchar(50) | Login name |
|
|
63
|
-
| `@sys_GroupID` | int | User's group |
|
|
64
|
-
| `@sys_DepartmentID` | varchar(9) | Department ID |
|
|
65
|
-
| `@sys_SystemRight` | int | Tiered right (1=low, 9=high) |
|
|
66
|
-
| `@sys_FunctionRight` | varchar(2000) | Function rights as `[AD][RPT][MGR]` |
|
|
67
|
-
| `@sys_SessionID` | varchar(50) | Auth session ID |
|
|
68
|
-
| `@sys_RawData` | nvarchar(max) | Full client JSON body |
|
|
69
|
-
| `@sys_header_{HeaderName}` | varchar(n) | **A family, not one fixed name** — reads any HTTP header; `NULL` if client didn't send it. Naming rule below |
|
|
70
|
-
|
|
71
|
-
> **CRITICAL — never put `@sys_` in module.json `IN`.** tAPI fills all `@sys_*` from the session; client values are **ignored or overwritten**.
|
|
72
|
-
>
|
|
73
|
-
> ```json
|
|
74
|
-
> // ❌ SAI — không đưa sys_ vào IN
|
|
75
|
-
> "IN": { "ModuleID": "sModuleID", "sys_UserID": "vueData.user.UserID" }
|
|
76
|
-
>
|
|
77
|
-
> // ✅ ĐÚNG — chỉ truyền custom params
|
|
78
|
-
> "IN": { "ModuleID": "sModuleID" }
|
|
79
|
-
> ```
|
|
80
|
-
|
|
81
|
-
#### `@sys_header_*` — read HTTP headers: replace each `-` with `_`
|
|
82
|
-
|
|
83
|
-
Works for **any** header. `-` is invalid in SQL Server param names, so the literal name won't compile:
|
|
84
|
-
|
|
85
|
-
```sql
|
|
86
|
-
-- ❌ SAI — lỗi cú pháp ngay lúc CREATE PROCEDURE, không phải lỗi runtime
|
|
87
|
-
@sys_header_User-Agent varchar(500)
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
- **No quoting works**: `@[sys_header_User-Agent]` and `@"sys_header_User-Agent"` are both invalid. Renaming is the **only** way.
|
|
91
|
-
- **Each `-` → one `_`.** 1-to-1: don't drop characters, change case, or merge segments.
|
|
92
|
-
|
|
93
|
-
| HTTP header | SP param |
|
|
94
|
-
| ------------------ | ---------------------------------------- |
|
|
95
|
-
| `User-Agent` | `@sys_header_User_Agent` |
|
|
96
|
-
| `Sec-Ch-Ua-Mobile` | `@sys_header_Sec_Ch_Ua_Mobile` |
|
|
97
|
-
| `X-Forwarded-For` | `@sys_header_X_Forwarded_For` |
|
|
98
|
-
| `Accept-Language` | `@sys_header_Accept_Language` |
|
|
99
|
-
| `Cookie` | `@sys_header_Cookie` — no `-`, unchanged |
|
|
100
|
-
|
|
101
|
-
- Reverse (reading an existing SP): drop `@sys_header_`, turn remaining `_` into `-` — `@sys_header_Accept_Language` → `Accept-Language`.
|
|
102
|
-
- Matching is **case-insensitive** (prefix and name): `@sys_header_User_Agent`, `@sys_Header_User_Agent`, `@sys_header_USER_AGENT` are the same header (so `@sys_Header_RequestID` works). Still use standard casing (`User-Agent`, not `USER_AGENT`). Only `_` for `-` is mandatory.
|
|
103
|
-
|
|
104
|
-
```sql
|
|
105
|
-
CREATE PROCEDURE spAPI_AuditLog_Insert
|
|
106
|
-
@Action nvarchar(100),
|
|
107
|
-
@sys_UserID varchar(9),
|
|
108
|
-
@sys_header_User_Agent nvarchar(500),
|
|
109
|
-
@sys_header_X_Forwarded_For varchar(200)
|
|
110
|
-
AS
|
|
111
|
-
BEGIN
|
|
112
|
-
-- ISNULL vì client không gửi header thì tham số là NULL, không phải chuỗi rỗng
|
|
113
|
-
INSERT INTO tblAuditLog (Action, UserID, UserAgent, ClientIP, CreateTime)
|
|
114
|
-
VALUES (@Action, @sys_UserID,
|
|
115
|
-
ISNULL(@sys_header_User_Agent, N'(không có)'),
|
|
116
|
-
ISNULL(@sys_header_X_Forwarded_For, ''),
|
|
117
|
-
GETDATE());
|
|
118
|
-
END
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
- **Still `@sys_*`:** auto-filled, **not** in `IN`, **no default** (`= null` is a hard error; `fui sp deploy` blocks it, see below).
|
|
122
|
-
- **Missing header → `NULL`** (not `''`) — normal, e.g. script/server-to-server calls often lack `User-Agent`. Wrap in `ISNULL(...)` at use: comparisons give UNKNOWN (`WHERE @sys_header_User_Agent <> 'bot'` **drops** the row), and `+` concatenation gives `NULL` (blanks the whole log string).
|
|
123
|
-
- **Untrusted (client-supplied, spoofable).** Log/stats only, never authorization; take rights from `@sys_UserID`/`@sys_SystemRight` (from the verified token).
|
|
124
|
-
|
|
125
|
-
Check the name tAPI sees: `fui sp help <name>` (§3.1).
|
|
126
|
-
|
|
127
|
-
### Custom params — business params
|
|
128
|
-
|
|
129
|
-
Param name = JSON body field name:
|
|
130
|
-
|
|
131
|
-
```sql
|
|
132
|
-
@StudentID varchar(9),
|
|
133
|
-
@SemesterID int,
|
|
134
|
-
@Keyword nvarchar(200)
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
#### Param binding is LENIENT — never errors
|
|
138
|
-
|
|
139
|
-
- Body **lacks** a key → param gets **`NULL`**. Body has an **extra** key → **silently ignored**.
|
|
140
|
-
- A typo in `IN` (`StudentId` vs `StudentID`) → SP gets `NULL`, empty/wrong data, HTTP 200. Most common silent wiring bug.
|
|
141
|
-
- **Renaming/removing an SP param is a contract change with NO signal**: old modules keep sending old keys. Audit callers or compare with `fui sp help <name>` (§3.1).
|
|
142
|
-
- **`NULL` follows T-SQL rules** (`<>` → UNKNOWN drops rows; `+` → `NULL`): wrap possibly-absent params in `ISNULL(...)` at use.
|
|
143
|
-
|
|
144
|
-
> **CRITICAL — no default value on any param** (`= value` or `= null`, including `@sys_*`). tAPI **always passes every param** (`NULL` if not sent), so `@HeID int = 99` silently gives `NULL`, not `99`. `fui sp deploy` hard-blocks it for `spAPI_*`/`spAPIFILE_
|
|
145
|
-
>
|
|
146
|
-
> ```sql
|
|
147
|
-
> -- ❌ SAI
|
|
148
|
-
> CREATE PROCEDURE spAPI_TS_Report
|
|
149
|
-
> @HeID int = 99,
|
|
150
|
-
> @NamHoc int = 2024,
|
|
151
|
-
> @sys_UserID varchar(9) = null,
|
|
152
|
-
> @sys_SystemRight int
|
|
153
|
-
>
|
|
154
|
-
> -- ✅ ĐÚNG
|
|
155
|
-
> CREATE PROCEDURE spAPI_TS_Report
|
|
156
|
-
> @HeID int,
|
|
157
|
-
> @NamHoc int,
|
|
158
|
-
> @sys_UserID varchar(9),
|
|
159
|
-
> @sys_SystemRight int
|
|
160
|
-
> ```
|
|
161
|
-
|
|
162
|
-
Client sends: `POST /app/FunctionName` with body `{ "StudentID": "SV001", "SemesterID": 1 }`
|
|
163
|
-
|
|
164
|
-
---
|
|
165
|
-
|
|
166
|
-
## 3. API URL
|
|
167
|
-
|
|
168
|
-
```
|
|
169
|
-
{domain}/{apiName}/{FunctionName}/{url1}/{url2}...
|
|
170
|
-
{domain}/{apiName}/auth/{FunctionName}/{url1}/{url2}...
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
| Part | Meaning |
|
|
174
|
-
| ----------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
175
|
-
| `domain` | API domain, e.g. `https://api.example.vn` |
|
|
176
|
-
| `apiName` | App apiName: `me`, `congvan`, `calen`... — also the **DB connection alias** in the workspace (`_db/{apiName}/`) |
|
|
177
|
-
| `auth` | Add when SP has `AUTH_` (no token) |
|
|
178
|
-
| `FunctionName` | SP name after the prefix |
|
|
179
|
-
| `url1`, `url2`... | Route params for `@url1_`, `@url2_` |
|
|
180
|
-
|
|
181
|
-
**A project can have MANY `apiName`s** (one DB alias each). `"API": "/{alias}/Ten"` in module.json names the SP's database; fui uses that alias for `_db/{alias}/` and as `--db` of DB commands ([db-workflow.md](db-workflow.md) §1, §4).
|
|
182
|
-
|
|
183
|
-
```
|
|
184
|
-
SP: spAPI_AUTH_Select_Chat
|
|
185
|
-
URL: https://api.example.vn/me/auth/Select_Chat
|
|
186
|
-
|
|
187
|
-
SP: spAPI_GetStudentList
|
|
188
|
-
URL: https://api.example.vn/me/GetStudentList
|
|
189
|
-
|
|
190
|
-
SP: spAPI_GetStudentDetail (@url1_StudentID)
|
|
191
|
-
URL: https://api.example.vn/me/GetStudentDetail/SV001
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
> **CRITICAL — common bug: missing `/` between `domain` and `apiName`.** `domain` NEVER ends with `/`; insert exactly ONE `/` before `apiName`.
|
|
195
|
-
>
|
|
196
|
-
> ```
|
|
197
|
-
> domain=tapi.lhu.edu.vn, apiName=ts, function=TS_Report_HeXetTuyenSelectAll
|
|
198
|
-
>
|
|
199
|
-
> ❌ SAI (thiếu /, "ts" bị nuốt vào domain): https://tapi.lhu.edu.vnts/TS_Report_HeXetTuyenSelectAll
|
|
200
|
-
> ✅ ĐÚNG: https://tapi.lhu.edu.vn/ts/TS_Report_HeXetTuyenSelectAll
|
|
201
|
-
> ```
|
|
202
|
-
>
|
|
203
|
-
> **Don't build URLs by hand/memory** — copy the `
|
|
204
|
-
|
|
205
|
-
### 3.1 Param cache & `/help` route
|
|
206
|
-
|
|
207
|
-
tAPI **caches each function's param signature** after the first call. After ALTERing a `spAPI_*`/`spAPIFILE_*` SP's params (add/remove/rename/retype), the live endpoint keeps the **old** signature until cleared. Route = API URL with `/help` right after domain:
|
|
208
|
-
|
|
209
|
-
```
|
|
210
|
-
{domain}/help/{apiName}/{FunctionName} ← xoá cache + trả về tham số của hàm
|
|
211
|
-
{domain}/help/{apiName}/auth/{FunctionName}
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
It **(a) clears the cache** and **(b) returns the input params** (useful before wiring `IN`).
|
|
215
|
-
|
|
216
|
-
**Mandatory after every SP ALTER/UPDATE** (else old signature → param errors even with a correct SP):
|
|
217
|
-
|
|
218
|
-
- `fui sp deploy` auto-calls `/help` **once, standard URL; ignore failure** (SP is already deployed).
|
|
219
|
-
- `ALTER PROCEDURE` via `fui exec`: **no auto-clear → run `fui sp help` manually** right after. The only manual case.
|
|
220
|
-
- **`/help` errors → STOP, no URL variants.** `/auth/` or not follows from the SP name; retrying changes nothing. Re-run `fui sp help` only if the live endpoint truly still takes old params.
|
|
221
|
-
|
|
222
|
-
---
|
|
223
|
-
|
|
224
|
-
## 4. Response format
|
|
225
|
-
|
|
226
|
-
### 4.1 One SELECT → array
|
|
227
|
-
|
|
228
|
-
```sql
|
|
229
|
-
SELECT StudentID, FullName, GPA FROM tblStudent WHERE ClassID = @ClassID
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
```json
|
|
233
|
-
{
|
|
234
|
-
"data": [
|
|
235
|
-
{ "StudentID": "SV001", "FullName": "Nguyen Van A", "GPA": 3.5 },
|
|
236
|
-
{ "StudentID": "SV002", "FullName": "Tran Thi B", "GPA": 3.2 }
|
|
237
|
-
]
|
|
238
|
-
}
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
### 4.2 Multiple SELECTs → array of arrays
|
|
242
|
-
|
|
243
|
-
Each `SELECT` becomes one sub-array in `data`:
|
|
244
|
-
|
|
245
|
-
```sql
|
|
246
|
-
SELECT TotalCount = COUNT(*) FROM tblStudent WHERE ClassID = @ClassID
|
|
247
|
-
SELECT StudentID, FullName FROM tblStudent WHERE ClassID = @ClassID
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
```json
|
|
251
|
-
{
|
|
252
|
-
"data": [
|
|
253
|
-
[{ "TotalCount": 42 }],
|
|
254
|
-
[
|
|
255
|
-
{ "StudentID": "SV001", "FullName": "Nguyen Van A" },
|
|
256
|
-
{ "StudentID": "SV002", "FullName": "Tran Thi B" }
|
|
257
|
-
]
|
|
258
|
-
]
|
|
259
|
-
}
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
In module.json, read sub-arrays by index:
|
|
263
|
-
|
|
264
|
-
```json
|
|
265
|
-
"OUT": "studentList",
|
|
266
|
-
"CALLBACK": { "EXE": "vueData.total = vueData.studentList[0][0].TotalCount; vueData.students = vueData.studentList[1]" }
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
### 4.3 `convert_to_object` — row → object
|
|
270
|
-
|
|
271
|
-
Returns an object instead of an array. **Many SELECTs** may use it; all merge into one response object.
|
|
272
|
-
|
|
273
|
-
> ⚠️ When a SELECT has `convert_to_object` **and** a `json_data` column with a JSON string, it must return **exactly 1 row**. More than 1 → tAPI returns `{}`.
|
|
274
|
-
|
|
275
|
-
- `convert_to_object = 'key'` → nested object under that key
|
|
276
|
-
- `convert_to_object = ''` → merged into root
|
|
277
|
-
|
|
278
|
-
**Ex 1 — named key + root merge:**
|
|
279
|
-
|
|
280
|
-
```sql
|
|
281
|
-
SELECT convert_to_object = 'sinhvien',
|
|
282
|
-
sys_SystemRight = @sys_SystemRight,
|
|
283
|
-
sys_FunctionRight = @sys_FunctionRight
|
|
284
|
-
|
|
285
|
-
SELECT convert_to_object = '',
|
|
286
|
-
ngaythang = GETDATE(), count = 10
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
```json
|
|
290
|
-
{
|
|
291
|
-
"sinhvien": {
|
|
292
|
-
"sys_SystemRight": 9,
|
|
293
|
-
"sys_FunctionRight": "[11][3]"
|
|
294
|
-
},
|
|
295
|
-
"ngaythang": "2018-09-11T11:20:47.397",
|
|
296
|
-
"count": 10
|
|
297
|
-
}
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
**Ex 2 — `json_data` overrides same-name fields** (parsed and merged after SQL columns, so `json_data` wins, e.g. `ProjectName`):
|
|
301
|
-
|
|
302
|
-
```sql
|
|
303
|
-
SELECT convert_to_object = '',
|
|
304
|
-
ProjectName = N'abc',
|
|
305
|
-
stt = 123,
|
|
306
|
-
json_data = '{"ProjectName":"T-A-P-I","menu":[{"name":"Croatia","link":"vn"},{"name":"England","link":"/tuyensinh"}]}'
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
```json
|
|
310
|
-
{
|
|
311
|
-
"ProjectName": "T-A-P-I",
|
|
312
|
-
"stt": 123,
|
|
313
|
-
"menu": [
|
|
314
|
-
{ "name": "Croatia", "link": "vn" },
|
|
315
|
-
{ "name": "England", "link": "/tuyensinh" }
|
|
316
|
-
]
|
|
317
|
-
}
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
**Ex 3 — multiple `convert_to_object = ''` SELECTs all merge into root:**
|
|
321
|
-
|
|
322
|
-
```sql
|
|
323
|
-
SELECT convert_to_object = '',
|
|
324
|
-
ProjectName = N'abc', stt = 123,
|
|
325
|
-
json_data = '{"ProjectName":"T-A-P-I","menu":[{"name":"Croatia","link":"vn"},{"name":"England","link":"/tuyensinh"}]}'
|
|
326
|
-
|
|
327
|
-
SELECT convert_to_object = '',
|
|
328
|
-
TruyVan2 = N'Dữ liệu của truy vấn 2',
|
|
329
|
-
json_data = '{"TruyVan2_json_string": 99}'
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
```json
|
|
333
|
-
{
|
|
334
|
-
"ProjectName": "T-A-P-I",
|
|
335
|
-
"stt": 123,
|
|
336
|
-
"menu": [
|
|
337
|
-
{ "name": "Croatia", "link": "vn" },
|
|
338
|
-
{ "name": "England", "link": "/tuyensinh" }
|
|
339
|
-
],
|
|
340
|
-
"TruyVan2": "Dữ liệu của truy vấn 2",
|
|
341
|
-
"TruyVan2_json_string": 99
|
|
342
|
-
}
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
### 4.4 `[json_data:FieldName]` — JSON string column → object
|
|
346
|
-
|
|
347
|
-
Return a JSON-string column as an object:
|
|
348
|
-
|
|
349
|
-
```sql
|
|
350
|
-
SELECT PartID, PartName, [json_data:FileInfo] = FileInfo
|
|
351
|
-
FROM tblPart WHERE PartID = @url1_PartID
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
tAPI parses it into a nested object:
|
|
355
|
-
|
|
356
|
-
```json
|
|
357
|
-
{
|
|
358
|
-
"PartID": "P001",
|
|
359
|
-
"PartName": "Tài liệu",
|
|
360
|
-
"FileInfo": { "fileName": "doc.pdf", "size": 1024 }
|
|
361
|
-
}
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
DB column stays a string. Name after `:` = response field; after `=` = real column.
|
|
365
|
-
|
|
366
|
-
---
|
|
367
|
-
|
|
368
|
-
## 5. Error handling in SP
|
|
369
|
-
|
|
370
|
-
All errors use `RAISERROR` with severity **16**.
|
|
371
|
-
|
|
372
|
-
### Normal errors (validation, data constraints)
|
|
373
|
-
|
|
374
|
-
```sql
|
|
375
|
-
RAISERROR(N'Dữ liệu không hợp lệ', 16, 1)
|
|
376
|
-
RETURN
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
```json
|
|
380
|
-
{ "Message": "Dữ liệu không hợp lệ" }
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
### Unauthorized — prefix `[Unauthorized]` → HTTP 401
|
|
384
|
-
|
|
385
|
-
```sql
|
|
386
|
-
IF @sys_SystemRight < 2
|
|
387
|
-
BEGIN
|
|
388
|
-
RAISERROR(N'[Unauthorized]Bạn không có quyền trên chức năng này.', 16, 1)
|
|
389
|
-
RETURN
|
|
390
|
-
END
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
```json
|
|
394
|
-
{ "Message": "Bạn không có quyền trên chức năng này." }
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
**Mandatory:**
|
|
398
|
-
|
|
399
|
-
- Always severity `16` — never `11`, `14` or anything else.
|
|
400
|
-
- Always `RETURN` right after `RAISERROR`.
|
|
401
|
-
- `N` prefix required for Vietnamese Unicode.
|
|
402
|
-
- **Every permission error (missing `SystemRight`/`FunctionRight`) must start with `[Unauthorized]`** → HTTP `401` instead of `200`/`500`; client `errorMess()` (`fastproject.js`) redirects to login if message contains "token", else shows a permission error. tAPI strips the prefix from `Message`. Validation errors **don't** use it.
|
|
403
|
-
|
|
404
|
-
> Detailed permission checks (5 patterns, `getSystemRight`, check order, message conventions): [tapi-permission-patterns.md](tapi-permission-patterns.md).
|
|
405
|
-
|
|
406
|
-
---
|
|
407
|
-
|
|
408
|
-
## 6. SP design rules
|
|
409
|
-
|
|
410
|
-
### Naming
|
|
411
|
-
|
|
412
|
-
```
|
|
413
|
-
spAPI_{Entity}{Action}
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
| Action | Meaning |
|
|
417
|
-
| ------------------- | ----------------------- |
|
|
418
|
-
| `Select` / `List` | List query |
|
|
419
|
-
| `Get` | One record's detail |
|
|
420
|
-
| `Insert` / `Add` | Create |
|
|
421
|
-
| `Update` / `Edit` | Update |
|
|
422
|
-
| `Delete` / `Remove` | Delete |
|
|
423
|
-
| `AUTH_Select` | Public query (no token) |
|
|
424
|
-
|
|
425
|
-
```sql
|
|
426
|
-
spAPI_StudentList -- GET danh sách sinh viên
|
|
427
|
-
spAPI_StudentGet -- GET chi tiết (url1 = StudentID)
|
|
428
|
-
spAPI_StudentInsert -- POST thêm mới
|
|
429
|
-
spAPI_StudentUpdate -- POST cập nhật
|
|
430
|
-
spAPI_StudentDelete -- POST xoá (url1 = StudentID)
|
|
431
|
-
spAPI_AUTH_CourseList -- Danh sách môn học công khai
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
### Wiring in FUI module.json
|
|
435
|
-
|
|
436
|
-
```json
|
|
437
|
-
"fetchStudents": {
|
|
438
|
-
"API": "/me/StudentList",
|
|
439
|
-
"IN": { "Keyword": "vueData.keyword", "ClassID": "vueData.selectedClass" },
|
|
440
|
-
"OUT": "studentList"
|
|
441
|
-
},
|
|
442
|
-
"addStudent": {
|
|
443
|
-
"API": "/me/StudentInsert",
|
|
444
|
-
"IN": {
|
|
445
|
-
"StudentID": "vueData.form.StudentID",
|
|
446
|
-
"FullName": "vueData.form.FullName",
|
|
447
|
-
"ClassID": "vueData.form.ClassID"
|
|
448
|
-
},
|
|
449
|
-
"CALLBACK": [ { "CALL": "vueData.fetchStudents" }, { "MESS": "Đã thêm thành công" } ]
|
|
450
|
-
},
|
|
451
|
-
"deleteStudent": {
|
|
452
|
-
"API": "/me/StudentDelete/`{{vueData.selectedID}}",
|
|
453
|
-
"CONFIRM": "Bạn có chắc muốn xoá?",
|
|
454
|
-
"CALLBACK": { "CALL": "vueData.fetchStudents" }
|
|
455
|
-
}
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
---
|
|
459
|
-
|
|
460
|
-
## 7. SP design checklist
|
|
461
|
-
|
|
462
|
-
1. `spAPI_` prefix mandatory — missing means API can't call it.
|
|
463
|
-
2. Add `AUTH_` for public endpoints (login page, lookup data).
|
|
464
|
-
3. Always include `@sys_UserID` in SPs that write data (audit trail).
|
|
465
|
-
4. No `SELECT *` — list columns.
|
|
466
|
-
5. Check permissions at SP start if needed, with `RAISERROR` + `RETURN` — see [tapi-permission-patterns.md](tapi-permission-patterns.md).
|
|
467
|
-
6. Multiple SELECTs → client reads by index `data[0]`, `data[1]`.
|
|
468
|
-
7. Single record → use `convert_to_object` instead of `data[0][0]`.
|
|
469
|
-
8. **No `GRANT EXECUTE` in the .sql file** — the SP body runs to end of batch and tAPI has no `GO` support, so a GRANT after `END` is **swallowed into the SP body** (runs every call, grants nothing, no error). `fui sp deploy` grants and confirms via `sys.database_permissions` — read its `✓`/`❗` line ([verification.md](verification.md#security)).
|
|
470
|
-
9. **Never put `sys_*` params in module.json `IN`** — tAPI injects them.
|
|
471
|
-
9b. **Headers** → `@sys_header_{HeaderName}`, **each `-` → `_`**; `-` is a syntax error, brackets don't help (§2). Log only, never gate permissions.
|
|
472
|
-
9c. **Binding never errors** (missing → `NULL`, extra → ignored): check `IN` names with `fui sp help`. `DEFAULT` is useless; use `SET @x = ISNULL(@x, <giá trị>)`.
|
|
473
|
-
10. **File GET/UPLOAD**: prefix `spAPIFILE_`, binary column alias `fileContent`, params `@sys_FileContent`/`@sys_FileName`, standard table `tblFileData` — see [tapi-file-api.md](tapi-file-api.md). Don't infer from training data.
|
|
474
|
-
11. **Any ALTER/UPDATE of a deployed SP** (params, logic, anything) → **MUST clear tAPI cache** via `fui sp help` (§3.1). `fui sp deploy` does it (once; ignore failure); only ALTER via `fui exec` needs it manually.
|
|
1
|
+
# tAPI Reference — Transparent Query API (Core)
|
|
2
|
+
|
|
3
|
+
> Owns: **tAPI conventions: SP naming (`spAPI_*`/`spAPI_AUTH_*`/`spAPIFILE_*`), params, URL, response contract, `/help` route**. Deploy workflow: [db-workflow.md](db-workflow.md).
|
|
4
|
+
|
|
5
|
+
tAPI auto-generates APIs from SQL SPs; the SP name sets URL, auth type and access (no controllers/routes). Accepts **GET and POST** — don't specify a method in the SP; module.json picks it. Load as needed:
|
|
6
|
+
|
|
7
|
+
| Topic | File |
|
|
8
|
+
| ------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
9
|
+
| Permission checks in SP body (5 patterns, `@sys_SystemRight`, `getSystemRight`) | [tapi-permission-patterns.md](tapi-permission-patterns.md) |
|
|
10
|
+
| File API — GET/UPLOAD, `spAPIFILE_`, `fileContent`, `tblFileData` | [tapi-file-api.md](tapi-file-api.md) |
|
|
11
|
+
|
|
12
|
+
> Image processing, outbound HTTP, email, server file I/O, regex → first check for an existing **SQL CLR function** (`httpCall`, `sendMail`, `ImageResize`, `FileWriter`, `RegexMatch`...): [sql-clr-functions.md](sql-clr-functions.md).
|
|
13
|
+
|
|
14
|
+
## 1. SP naming
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
spAPI_[AUTH_]FunctionName
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
| Part | Required | Meaning |
|
|
21
|
+
| -------------- | -------- | ---------------------------------------------------------------- |
|
|
22
|
+
| `spAPI_` | ✅ | Marks SP as API-callable. Missing → API cannot call it |
|
|
23
|
+
| `AUTH_` | ❌ | No auth required. Omitted → valid token mandatory |
|
|
24
|
+
| `FunctionName` | ✅ | API function name used in URL, e.g. `MessageHome`, `UserSetting` |
|
|
25
|
+
|
|
26
|
+
```sql
|
|
27
|
+
spAPI_MessageHome -- có auth
|
|
28
|
+
spAPI_AUTH_SelectChat -- không cần auth
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
File API:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
spAPIFILE_Document -- SP để GET file
|
|
35
|
+
spAPIFILE_UPLOAD_Document -- SP để nhận file upload
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Details: [tapi-file-api.md](tapi-file-api.md).
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 2. SP parameters
|
|
43
|
+
|
|
44
|
+
### `@url1_`, `@url2_`, `@url3_`... — route params
|
|
45
|
+
|
|
46
|
+
Taken from URL segments in order:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
https://api.domain.vn/app/FunctionName/value1/value2
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```sql
|
|
53
|
+
@url1_GroupID varchar(50), -- nhận "value1"
|
|
54
|
+
@url2_Mode varchar(50), -- nhận "value2"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### `@sys_` — system params (injected by tAPI, never sent by client)
|
|
58
|
+
|
|
59
|
+
| Param | Type | Meaning |
|
|
60
|
+
| -------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
61
|
+
| `@sys_UserID` | varchar(9) | Authenticated user ID |
|
|
62
|
+
| `@sys_UserName` | varchar(50) | Login name |
|
|
63
|
+
| `@sys_GroupID` | int | User's group |
|
|
64
|
+
| `@sys_DepartmentID` | varchar(9) | Department ID |
|
|
65
|
+
| `@sys_SystemRight` | int | Tiered right (1=low, 9=high) |
|
|
66
|
+
| `@sys_FunctionRight` | varchar(2000) | Function rights as `[AD][RPT][MGR]` |
|
|
67
|
+
| `@sys_SessionID` | varchar(50) | Auth session ID |
|
|
68
|
+
| `@sys_RawData` | nvarchar(max) | Full client JSON body |
|
|
69
|
+
| `@sys_header_{HeaderName}` | varchar(n) | **A family, not one fixed name** — reads any HTTP header; `NULL` if client didn't send it. Naming rule below |
|
|
70
|
+
|
|
71
|
+
> **CRITICAL — never put `@sys_` in module.json `IN`.** tAPI fills all `@sys_*` from the session; client values are **ignored or overwritten**.
|
|
72
|
+
>
|
|
73
|
+
> ```json
|
|
74
|
+
> // ❌ SAI — không đưa sys_ vào IN
|
|
75
|
+
> "IN": { "ModuleID": "sModuleID", "sys_UserID": "vueData.user.UserID" }
|
|
76
|
+
>
|
|
77
|
+
> // ✅ ĐÚNG — chỉ truyền custom params
|
|
78
|
+
> "IN": { "ModuleID": "sModuleID" }
|
|
79
|
+
> ```
|
|
80
|
+
|
|
81
|
+
#### `@sys_header_*` — read HTTP headers: replace each `-` with `_`
|
|
82
|
+
|
|
83
|
+
Works for **any** header. `-` is invalid in SQL Server param names, so the literal name won't compile:
|
|
84
|
+
|
|
85
|
+
```sql
|
|
86
|
+
-- ❌ SAI — lỗi cú pháp ngay lúc CREATE PROCEDURE, không phải lỗi runtime
|
|
87
|
+
@sys_header_User-Agent varchar(500)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- **No quoting works**: `@[sys_header_User-Agent]` and `@"sys_header_User-Agent"` are both invalid. Renaming is the **only** way.
|
|
91
|
+
- **Each `-` → one `_`.** 1-to-1: don't drop characters, change case, or merge segments.
|
|
92
|
+
|
|
93
|
+
| HTTP header | SP param |
|
|
94
|
+
| ------------------ | ---------------------------------------- |
|
|
95
|
+
| `User-Agent` | `@sys_header_User_Agent` |
|
|
96
|
+
| `Sec-Ch-Ua-Mobile` | `@sys_header_Sec_Ch_Ua_Mobile` |
|
|
97
|
+
| `X-Forwarded-For` | `@sys_header_X_Forwarded_For` |
|
|
98
|
+
| `Accept-Language` | `@sys_header_Accept_Language` |
|
|
99
|
+
| `Cookie` | `@sys_header_Cookie` — no `-`, unchanged |
|
|
100
|
+
|
|
101
|
+
- Reverse (reading an existing SP): drop `@sys_header_`, turn remaining `_` into `-` — `@sys_header_Accept_Language` → `Accept-Language`.
|
|
102
|
+
- Matching is **case-insensitive** (prefix and name): `@sys_header_User_Agent`, `@sys_Header_User_Agent`, `@sys_header_USER_AGENT` are the same header (so `@sys_Header_RequestID` works). Still use standard casing (`User-Agent`, not `USER_AGENT`). Only `_` for `-` is mandatory.
|
|
103
|
+
|
|
104
|
+
```sql
|
|
105
|
+
CREATE PROCEDURE spAPI_AuditLog_Insert
|
|
106
|
+
@Action nvarchar(100),
|
|
107
|
+
@sys_UserID varchar(9),
|
|
108
|
+
@sys_header_User_Agent nvarchar(500),
|
|
109
|
+
@sys_header_X_Forwarded_For varchar(200)
|
|
110
|
+
AS
|
|
111
|
+
BEGIN
|
|
112
|
+
-- ISNULL vì client không gửi header thì tham số là NULL, không phải chuỗi rỗng
|
|
113
|
+
INSERT INTO tblAuditLog (Action, UserID, UserAgent, ClientIP, CreateTime)
|
|
114
|
+
VALUES (@Action, @sys_UserID,
|
|
115
|
+
ISNULL(@sys_header_User_Agent, N'(không có)'),
|
|
116
|
+
ISNULL(@sys_header_X_Forwarded_For, ''),
|
|
117
|
+
GETDATE());
|
|
118
|
+
END
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
- **Still `@sys_*`:** auto-filled, **not** in `IN`, **no default** (`= null` is a hard error; `fui sp deploy` blocks it, see below).
|
|
122
|
+
- **Missing header → `NULL`** (not `''`) — normal, e.g. script/server-to-server calls often lack `User-Agent`. Wrap in `ISNULL(...)` at use: comparisons give UNKNOWN (`WHERE @sys_header_User_Agent <> 'bot'` **drops** the row), and `+` concatenation gives `NULL` (blanks the whole log string).
|
|
123
|
+
- **Untrusted (client-supplied, spoofable).** Log/stats only, never authorization; take rights from `@sys_UserID`/`@sys_SystemRight` (from the verified token).
|
|
124
|
+
|
|
125
|
+
Check the name tAPI sees: `fui sp help <name>` (§3.1).
|
|
126
|
+
|
|
127
|
+
### Custom params — business params
|
|
128
|
+
|
|
129
|
+
Param name = JSON body field name:
|
|
130
|
+
|
|
131
|
+
```sql
|
|
132
|
+
@StudentID varchar(9),
|
|
133
|
+
@SemesterID int,
|
|
134
|
+
@Keyword nvarchar(200)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### Param binding is LENIENT — never errors
|
|
138
|
+
|
|
139
|
+
- Body **lacks** a key → param gets **`NULL`**. Body has an **extra** key → **silently ignored**.
|
|
140
|
+
- A typo in `IN` (`StudentId` vs `StudentID`) → SP gets `NULL`, empty/wrong data, HTTP 200. Most common silent wiring bug.
|
|
141
|
+
- **Renaming/removing an SP param is a contract change with NO signal**: old modules keep sending old keys. Audit callers or compare with `fui sp help <name>` (§3.1).
|
|
142
|
+
- **`NULL` follows T-SQL rules** (`<>` → UNKNOWN drops rows; `+` → `NULL`): wrap possibly-absent params in `ISNULL(...)` at use.
|
|
143
|
+
|
|
144
|
+
> **CRITICAL — no default value on any param** (`= value` or `= null`, including `@sys_*`). tAPI **always passes every param** (`NULL` if not sent), so `@HeID int = 99` silently gives `NULL`, not `99`. `fui sp deploy` hard-blocks it for `spAPI_*`/`spAPIFILE_*`; `fui sp verify` warns earlier. Set defaults **in the body**: `SET @HeID = ISNULL(@HeID, 99)`.
|
|
145
|
+
>
|
|
146
|
+
> ```sql
|
|
147
|
+
> -- ❌ SAI
|
|
148
|
+
> CREATE PROCEDURE spAPI_TS_Report
|
|
149
|
+
> @HeID int = 99,
|
|
150
|
+
> @NamHoc int = 2024,
|
|
151
|
+
> @sys_UserID varchar(9) = null,
|
|
152
|
+
> @sys_SystemRight int
|
|
153
|
+
>
|
|
154
|
+
> -- ✅ ĐÚNG
|
|
155
|
+
> CREATE PROCEDURE spAPI_TS_Report
|
|
156
|
+
> @HeID int,
|
|
157
|
+
> @NamHoc int,
|
|
158
|
+
> @sys_UserID varchar(9),
|
|
159
|
+
> @sys_SystemRight int
|
|
160
|
+
> ```
|
|
161
|
+
|
|
162
|
+
Client sends: `POST /app/FunctionName` with body `{ "StudentID": "SV001", "SemesterID": 1 }`
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 3. API URL
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
{domain}/{apiName}/{FunctionName}/{url1}/{url2}...
|
|
170
|
+
{domain}/{apiName}/auth/{FunctionName}/{url1}/{url2}...
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
| Part | Meaning |
|
|
174
|
+
| ----------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
175
|
+
| `domain` | API domain, e.g. `https://api.example.vn` |
|
|
176
|
+
| `apiName` | App apiName: `me`, `congvan`, `calen`... — also the **DB connection alias** in the workspace (`_db/{apiName}/`) |
|
|
177
|
+
| `auth` | Add when SP has `AUTH_` (no token) |
|
|
178
|
+
| `FunctionName` | SP name after the prefix |
|
|
179
|
+
| `url1`, `url2`... | Route params for `@url1_`, `@url2_` |
|
|
180
|
+
|
|
181
|
+
**A project can have MANY `apiName`s** (one DB alias each). `"API": "/{alias}/Ten"` in module.json names the SP's database; fui uses that alias for `_db/{alias}/` and as `--db` of DB commands ([db-workflow.md](db-workflow.md) §1, §4).
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
SP: spAPI_AUTH_Select_Chat
|
|
185
|
+
URL: https://api.example.vn/me/auth/Select_Chat
|
|
186
|
+
|
|
187
|
+
SP: spAPI_GetStudentList
|
|
188
|
+
URL: https://api.example.vn/me/GetStudentList
|
|
189
|
+
|
|
190
|
+
SP: spAPI_GetStudentDetail (@url1_StudentID)
|
|
191
|
+
URL: https://api.example.vn/me/GetStudentDetail/SV001
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
> **CRITICAL — common bug: missing `/` between `domain` and `apiName`.** `domain` NEVER ends with `/`; insert exactly ONE `/` before `apiName`.
|
|
195
|
+
>
|
|
196
|
+
> ```
|
|
197
|
+
> domain=tapi.lhu.edu.vn, apiName=ts, function=TS_Report_HeXetTuyenSelectAll
|
|
198
|
+
>
|
|
199
|
+
> ❌ SAI (thiếu /, "ts" bị nuốt vào domain): https://tapi.lhu.edu.vnts/TS_Report_HeXetTuyenSelectAll
|
|
200
|
+
> ✅ ĐÚNG: https://tapi.lhu.edu.vn/ts/TS_Report_HeXetTuyenSelectAll
|
|
201
|
+
> ```
|
|
202
|
+
>
|
|
203
|
+
> **Don't build URLs by hand/memory** — copy the `url (reference only, not called; copy verbatim): ...` line from `fui sp verify` (or `fui sp help`) verbatim.
|
|
204
|
+
|
|
205
|
+
### 3.1 Param cache & `/help` route
|
|
206
|
+
|
|
207
|
+
tAPI **caches each function's param signature** after the first call. After ALTERing a `spAPI_*`/`spAPIFILE_*` SP's params (add/remove/rename/retype), the live endpoint keeps the **old** signature until cleared. Route = API URL with `/help` right after domain:
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
{domain}/help/{apiName}/{FunctionName} ← xoá cache + trả về tham số của hàm
|
|
211
|
+
{domain}/help/{apiName}/auth/{FunctionName}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
It **(a) clears the cache** and **(b) returns the input params** (useful before wiring `IN`).
|
|
215
|
+
|
|
216
|
+
**Mandatory after every SP ALTER/UPDATE** (else old signature → param errors even with a correct SP):
|
|
217
|
+
|
|
218
|
+
- `fui sp deploy` auto-calls `/help` **once, standard URL; ignore failure** (SP is already deployed).
|
|
219
|
+
- `ALTER PROCEDURE` via `fui exec`: **no auto-clear → run `fui sp help` manually** right after. The only manual case.
|
|
220
|
+
- **`/help` errors → STOP, no URL variants.** `/auth/` or not follows from the SP name; retrying changes nothing. Re-run `fui sp help` only if the live endpoint truly still takes old params.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## 4. Response format
|
|
225
|
+
|
|
226
|
+
### 4.1 One SELECT → array
|
|
227
|
+
|
|
228
|
+
```sql
|
|
229
|
+
SELECT StudentID, FullName, GPA FROM tblStudent WHERE ClassID = @ClassID
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"data": [
|
|
235
|
+
{ "StudentID": "SV001", "FullName": "Nguyen Van A", "GPA": 3.5 },
|
|
236
|
+
{ "StudentID": "SV002", "FullName": "Tran Thi B", "GPA": 3.2 }
|
|
237
|
+
]
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### 4.2 Multiple SELECTs → array of arrays
|
|
242
|
+
|
|
243
|
+
Each `SELECT` becomes one sub-array in `data`:
|
|
244
|
+
|
|
245
|
+
```sql
|
|
246
|
+
SELECT TotalCount = COUNT(*) FROM tblStudent WHERE ClassID = @ClassID
|
|
247
|
+
SELECT StudentID, FullName FROM tblStudent WHERE ClassID = @ClassID
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
```json
|
|
251
|
+
{
|
|
252
|
+
"data": [
|
|
253
|
+
[{ "TotalCount": 42 }],
|
|
254
|
+
[
|
|
255
|
+
{ "StudentID": "SV001", "FullName": "Nguyen Van A" },
|
|
256
|
+
{ "StudentID": "SV002", "FullName": "Tran Thi B" }
|
|
257
|
+
]
|
|
258
|
+
]
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
In module.json, read sub-arrays by index:
|
|
263
|
+
|
|
264
|
+
```json
|
|
265
|
+
"OUT": "studentList",
|
|
266
|
+
"CALLBACK": { "EXE": "vueData.total = vueData.studentList[0][0].TotalCount; vueData.students = vueData.studentList[1]" }
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### 4.3 `convert_to_object` — row → object
|
|
270
|
+
|
|
271
|
+
Returns an object instead of an array. **Many SELECTs** may use it; all merge into one response object.
|
|
272
|
+
|
|
273
|
+
> ⚠️ When a SELECT has `convert_to_object` **and** a `json_data` column with a JSON string, it must return **exactly 1 row**. More than 1 → tAPI returns `{}`.
|
|
274
|
+
|
|
275
|
+
- `convert_to_object = 'key'` → nested object under that key
|
|
276
|
+
- `convert_to_object = ''` → merged into root
|
|
277
|
+
|
|
278
|
+
**Ex 1 — named key + root merge:**
|
|
279
|
+
|
|
280
|
+
```sql
|
|
281
|
+
SELECT convert_to_object = 'sinhvien',
|
|
282
|
+
sys_SystemRight = @sys_SystemRight,
|
|
283
|
+
sys_FunctionRight = @sys_FunctionRight
|
|
284
|
+
|
|
285
|
+
SELECT convert_to_object = '',
|
|
286
|
+
ngaythang = GETDATE(), count = 10
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
```json
|
|
290
|
+
{
|
|
291
|
+
"sinhvien": {
|
|
292
|
+
"sys_SystemRight": 9,
|
|
293
|
+
"sys_FunctionRight": "[11][3]"
|
|
294
|
+
},
|
|
295
|
+
"ngaythang": "2018-09-11T11:20:47.397",
|
|
296
|
+
"count": 10
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Ex 2 — `json_data` overrides same-name fields** (parsed and merged after SQL columns, so `json_data` wins, e.g. `ProjectName`):
|
|
301
|
+
|
|
302
|
+
```sql
|
|
303
|
+
SELECT convert_to_object = '',
|
|
304
|
+
ProjectName = N'abc',
|
|
305
|
+
stt = 123,
|
|
306
|
+
json_data = '{"ProjectName":"T-A-P-I","menu":[{"name":"Croatia","link":"vn"},{"name":"England","link":"/tuyensinh"}]}'
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
```json
|
|
310
|
+
{
|
|
311
|
+
"ProjectName": "T-A-P-I",
|
|
312
|
+
"stt": 123,
|
|
313
|
+
"menu": [
|
|
314
|
+
{ "name": "Croatia", "link": "vn" },
|
|
315
|
+
{ "name": "England", "link": "/tuyensinh" }
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**Ex 3 — multiple `convert_to_object = ''` SELECTs all merge into root:**
|
|
321
|
+
|
|
322
|
+
```sql
|
|
323
|
+
SELECT convert_to_object = '',
|
|
324
|
+
ProjectName = N'abc', stt = 123,
|
|
325
|
+
json_data = '{"ProjectName":"T-A-P-I","menu":[{"name":"Croatia","link":"vn"},{"name":"England","link":"/tuyensinh"}]}'
|
|
326
|
+
|
|
327
|
+
SELECT convert_to_object = '',
|
|
328
|
+
TruyVan2 = N'Dữ liệu của truy vấn 2',
|
|
329
|
+
json_data = '{"TruyVan2_json_string": 99}'
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
```json
|
|
333
|
+
{
|
|
334
|
+
"ProjectName": "T-A-P-I",
|
|
335
|
+
"stt": 123,
|
|
336
|
+
"menu": [
|
|
337
|
+
{ "name": "Croatia", "link": "vn" },
|
|
338
|
+
{ "name": "England", "link": "/tuyensinh" }
|
|
339
|
+
],
|
|
340
|
+
"TruyVan2": "Dữ liệu của truy vấn 2",
|
|
341
|
+
"TruyVan2_json_string": 99
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### 4.4 `[json_data:FieldName]` — JSON string column → object
|
|
346
|
+
|
|
347
|
+
Return a JSON-string column as an object:
|
|
348
|
+
|
|
349
|
+
```sql
|
|
350
|
+
SELECT PartID, PartName, [json_data:FileInfo] = FileInfo
|
|
351
|
+
FROM tblPart WHERE PartID = @url1_PartID
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
tAPI parses it into a nested object:
|
|
355
|
+
|
|
356
|
+
```json
|
|
357
|
+
{
|
|
358
|
+
"PartID": "P001",
|
|
359
|
+
"PartName": "Tài liệu",
|
|
360
|
+
"FileInfo": { "fileName": "doc.pdf", "size": 1024 }
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
DB column stays a string. Name after `:` = response field; after `=` = real column.
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## 5. Error handling in SP
|
|
369
|
+
|
|
370
|
+
All errors use `RAISERROR` with severity **16**.
|
|
371
|
+
|
|
372
|
+
### Normal errors (validation, data constraints)
|
|
373
|
+
|
|
374
|
+
```sql
|
|
375
|
+
RAISERROR(N'Dữ liệu không hợp lệ', 16, 1)
|
|
376
|
+
RETURN
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
```json
|
|
380
|
+
{ "Message": "Dữ liệu không hợp lệ" }
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Unauthorized — prefix `[Unauthorized]` → HTTP 401
|
|
384
|
+
|
|
385
|
+
```sql
|
|
386
|
+
IF @sys_SystemRight < 2
|
|
387
|
+
BEGIN
|
|
388
|
+
RAISERROR(N'[Unauthorized]Bạn không có quyền trên chức năng này.', 16, 1)
|
|
389
|
+
RETURN
|
|
390
|
+
END
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
```json
|
|
394
|
+
{ "Message": "Bạn không có quyền trên chức năng này." }
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
**Mandatory:**
|
|
398
|
+
|
|
399
|
+
- Always severity `16` — never `11`, `14` or anything else.
|
|
400
|
+
- Always `RETURN` right after `RAISERROR`.
|
|
401
|
+
- `N` prefix required for Vietnamese Unicode.
|
|
402
|
+
- **Every permission error (missing `SystemRight`/`FunctionRight`) must start with `[Unauthorized]`** → HTTP `401` instead of `200`/`500`; client `errorMess()` (`fastproject.js`) redirects to login if message contains "token", else shows a permission error. tAPI strips the prefix from `Message`. Validation errors **don't** use it.
|
|
403
|
+
|
|
404
|
+
> Detailed permission checks (5 patterns, `getSystemRight`, check order, message conventions): [tapi-permission-patterns.md](tapi-permission-patterns.md).
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## 6. SP design rules
|
|
409
|
+
|
|
410
|
+
### Naming
|
|
411
|
+
|
|
412
|
+
```
|
|
413
|
+
spAPI_{Entity}{Action}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
| Action | Meaning |
|
|
417
|
+
| ------------------- | ----------------------- |
|
|
418
|
+
| `Select` / `List` | List query |
|
|
419
|
+
| `Get` | One record's detail |
|
|
420
|
+
| `Insert` / `Add` | Create |
|
|
421
|
+
| `Update` / `Edit` | Update |
|
|
422
|
+
| `Delete` / `Remove` | Delete |
|
|
423
|
+
| `AUTH_Select` | Public query (no token) |
|
|
424
|
+
|
|
425
|
+
```sql
|
|
426
|
+
spAPI_StudentList -- GET danh sách sinh viên
|
|
427
|
+
spAPI_StudentGet -- GET chi tiết (url1 = StudentID)
|
|
428
|
+
spAPI_StudentInsert -- POST thêm mới
|
|
429
|
+
spAPI_StudentUpdate -- POST cập nhật
|
|
430
|
+
spAPI_StudentDelete -- POST xoá (url1 = StudentID)
|
|
431
|
+
spAPI_AUTH_CourseList -- Danh sách môn học công khai
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Wiring in FUI module.json
|
|
435
|
+
|
|
436
|
+
```json
|
|
437
|
+
"fetchStudents": {
|
|
438
|
+
"API": "/me/StudentList",
|
|
439
|
+
"IN": { "Keyword": "vueData.keyword", "ClassID": "vueData.selectedClass" },
|
|
440
|
+
"OUT": "studentList"
|
|
441
|
+
},
|
|
442
|
+
"addStudent": {
|
|
443
|
+
"API": "/me/StudentInsert",
|
|
444
|
+
"IN": {
|
|
445
|
+
"StudentID": "vueData.form.StudentID",
|
|
446
|
+
"FullName": "vueData.form.FullName",
|
|
447
|
+
"ClassID": "vueData.form.ClassID"
|
|
448
|
+
},
|
|
449
|
+
"CALLBACK": [ { "CALL": "vueData.fetchStudents" }, { "MESS": "Đã thêm thành công" } ]
|
|
450
|
+
},
|
|
451
|
+
"deleteStudent": {
|
|
452
|
+
"API": "/me/StudentDelete/`{{vueData.selectedID}}",
|
|
453
|
+
"CONFIRM": "Bạn có chắc muốn xoá?",
|
|
454
|
+
"CALLBACK": { "CALL": "vueData.fetchStudents" }
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
## 7. SP design checklist
|
|
461
|
+
|
|
462
|
+
1. `spAPI_` prefix mandatory — missing means API can't call it.
|
|
463
|
+
2. Add `AUTH_` for public endpoints (login page, lookup data).
|
|
464
|
+
3. Always include `@sys_UserID` in SPs that write data (audit trail).
|
|
465
|
+
4. No `SELECT *` — list columns.
|
|
466
|
+
5. Check permissions at SP start if needed, with `RAISERROR` + `RETURN` — see [tapi-permission-patterns.md](tapi-permission-patterns.md).
|
|
467
|
+
6. Multiple SELECTs → client reads by index `data[0]`, `data[1]`.
|
|
468
|
+
7. Single record → use `convert_to_object` instead of `data[0][0]`.
|
|
469
|
+
8. **No `GRANT EXECUTE` in the .sql file** — the SP body runs to end of batch and tAPI has no `GO` support, so a GRANT after `END` is **swallowed into the SP body** (runs every call, grants nothing, no error). `fui sp deploy` grants and confirms via `sys.database_permissions` — read its `✓`/`❗` line ([verification.md](verification.md#security)).
|
|
470
|
+
9. **Never put `sys_*` params in module.json `IN`** — tAPI injects them.
|
|
471
|
+
9b. **Headers** → `@sys_header_{HeaderName}`, **each `-` → `_`**; `-` is a syntax error, brackets don't help (§2). Log only, never gate permissions.
|
|
472
|
+
9c. **Binding never errors** (missing → `NULL`, extra → ignored): check `IN` names with `fui sp help`. `DEFAULT` is useless; use `SET @x = ISNULL(@x, <giá trị>)`.
|
|
473
|
+
10. **File GET/UPLOAD**: prefix `spAPIFILE_`, binary column alias `fileContent`, params `@sys_FileContent`/`@sys_FileName`, standard table `tblFileData` — see [tapi-file-api.md](tapi-file-api.md). Don't infer from training data.
|
|
474
|
+
11. **Any ALTER/UPDATE of a deployed SP** (params, logic, anything) → **MUST clear tAPI cache** via `fui sp help` (§3.1). `fui sp deploy` does it (once; ignore failure); only ALTER via `fui exec` needs it manually.
|