@fui-org/fui-cli 1.2.1 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fui-y8an39cn.js +420 -0
- package/dist/fui.js +1 -429
- package/package.json +7 -3
- package/skills/fui/SKILL.md +3 -3
- package/skills/fui-skill-cli/SKILL.md +139 -0
- package/skills/fui-skill-cli/references/INDEX.md +110 -0
- package/skills/fui-skill-cli/references/advanced-techniques.md +168 -0
- package/skills/fui-skill-cli/references/coding-standards.md +112 -0
- package/skills/fui-skill-cli/references/component-design.md +448 -0
- package/skills/fui-skill-cli/references/component-quickref.md +78 -0
- package/skills/fui-skill-cli/references/component-table.md +248 -0
- package/skills/fui-skill-cli/references/components-dialog.md +191 -0
- package/skills/fui-skill-cli/references/components-display.md +141 -0
- package/skills/fui-skill-cli/references/components-echart.md +316 -0
- package/skills/fui-skill-cli/references/components-input.md +335 -0
- package/skills/fui-skill-cli/references/controls-patterns.md +701 -0
- package/skills/fui-skill-cli/references/controls-styling-vocabulary.md +137 -0
- package/skills/fui-skill-cli/references/db-table-design.md +73 -0
- package/skills/fui-skill-cli/references/db-workflow.md +288 -0
- package/skills/fui-skill-cli/references/default-function.md +425 -0
- package/skills/fui-skill-cli/references/design-modes.md +57 -0
- package/skills/fui-skill-cli/references/echart-templates.md +489 -0
- package/skills/fui-skill-cli/references/fastproject.md +99 -0
- package/skills/fui-skill-cli/references/fsheet.md +203 -0
- package/skills/fui-skill-cli/references/fullstack-workflow.md +313 -0
- package/skills/fui-skill-cli/references/module-data-patterns.md +117 -0
- package/skills/fui-skill-cli/references/module-json-anatomy.md +132 -0
- package/skills/fui-skill-cli/references/module-structure.md +141 -0
- package/skills/fui-skill-cli/references/new-session.md +85 -0
- package/skills/fui-skill-cli/references/pdfmake.md +60 -0
- package/skills/fui-skill-cli/references/permission-system.md +150 -0
- package/skills/fui-skill-cli/references/platform-architecture.md +269 -0
- package/skills/fui-skill-cli/references/project-config.md +303 -0
- package/skills/fui-skill-cli/references/project-provisioning.md +278 -0
- package/skills/fui-skill-cli/references/script-map.md +262 -0
- package/skills/fui-skill-cli/references/sql-clr-functions.md +225 -0
- package/skills/fui-skill-cli/references/system-design.md +89 -0
- package/skills/fui-skill-cli/references/tapi-file-api.md +185 -0
- package/skills/fui-skill-cli/references/tapi-permission-patterns.md +156 -0
- package/skills/fui-skill-cli/references/tapi-reference.md +474 -0
- package/skills/fui-skill-cli/references/tools-registry.md +84 -0
- package/skills/fui-skill-cli/references/ui-crosswindow-patterns.md +321 -0
- package/skills/fui-skill-cli/references/ui-dialog-patterns.md +255 -0
- package/skills/fui-skill-cli/references/ui-layout-patterns.md +176 -0
- package/skills/fui-skill-cli/references/ui-patterns.md +303 -0
- package/skills/fui-skill-cli/references/ui-screenshot-review.md +95 -0
- package/skills/fui-skill-cli/references/ui-table-cell-patterns.md +318 -0
- package/skills/fui-skill-cli/references/ui-templates.md +22 -0
- package/skills/fui-skill-cli/references/verification.md +236 -0
- package/skills/fui-skill-cli/references/watcher-patterns.md +163 -0
- package/skills/fui-skill-cli/references/websocket-realtime.md +271 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# SQL CLR Functions — DLL-backed SQL Server functions
|
|
2
|
+
|
|
3
|
+
> Owns: **SQL CLR functions: httpCall, sendMail, ImageResize, FileReader, RegexMatch...**
|
|
4
|
+
|
|
5
|
+
A project DB may have **SQL CLR scalar functions** (precompiled .NET DLL, registered via `CREATE ASSEMBLY` + `CREATE FUNCTION ... EXTERNAL NAME`). If they appear in `fui schema` or in an existing SP definition, **call them directly in T-SQL** like built-ins — no install, no need to know the C#.
|
|
6
|
+
|
|
7
|
+
A needed function is not in the DB → **do not invent it**; tell the user the DB lacks it.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. `SQLImageHelper` — images
|
|
12
|
+
|
|
13
|
+
### `dbo.ImageResize`
|
|
14
|
+
|
|
15
|
+
```sql
|
|
16
|
+
dbo.ImageResize(@extension nvarchar(10), @OriginalImage varbinary(max), @maxWidth int, @maxHeight int, @preserverAspectRatio bit, @quality int)
|
|
17
|
+
RETURNS varbinary(max)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Resizes to at most `@maxWidth` x `@maxHeight`.
|
|
21
|
+
|
|
22
|
+
| Param | Meaning |
|
|
23
|
+
| ------------------------- | ------------------------------------------------------------------------------ |
|
|
24
|
+
| `@extension` | Output format, e.g. `'jpg'`, `'png'` |
|
|
25
|
+
| `@OriginalImage` | Source image `varbinary` |
|
|
26
|
+
| `@maxWidth`, `@maxHeight` | Max size (px) |
|
|
27
|
+
| `@preserverAspectRatio` | `1` = keep ratio, fit longest side; `0` = force exactly `maxWidth`x`maxHeight` |
|
|
28
|
+
| `@quality` | Compression quality (usually 1-100) |
|
|
29
|
+
|
|
30
|
+
Returns resized `varbinary` — write to a `varbinary` column/file or return via tAPI File.
|
|
31
|
+
|
|
32
|
+
### `dbo.ImageCrop`
|
|
33
|
+
|
|
34
|
+
```sql
|
|
35
|
+
dbo.ImageCrop(@OriginalImage varbinary(max), @StartAtX int, @StartAtY int, @Width int, @Height int)
|
|
36
|
+
RETURNS varbinary(max)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Crops a `@Width` x `@Height` rectangle starting at `(@StartAtX, @StartAtY)`.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 2. `SQLHttpHelper` — HTTP, public value store, mail
|
|
44
|
+
|
|
45
|
+
### `dbo.publicValueUUID` / `publicValueGet` / `publicValueSet`
|
|
46
|
+
|
|
47
|
+
Server-level shared key-value store (not a SQL table) — keeps temp state across SP calls without a table.
|
|
48
|
+
|
|
49
|
+
```sql
|
|
50
|
+
dbo.publicValueUUID() RETURNS nvarchar(max) -- sinh 1 UUID mới, dùng làm key
|
|
51
|
+
dbo.publicValueGet(@name nvarchar(max)) RETURNS nvarchar(max) -- đọc value theo key
|
|
52
|
+
dbo.publicValueSet(@name nvarchar(max), @Data nvarchar(max)) RETURNS int -- ghi value theo key
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `dbo.httpCall` / `dbo.httpAPI` — HTTP from T-SQL
|
|
56
|
+
|
|
57
|
+
```sql
|
|
58
|
+
dbo.httpCall(@Method nvarchar(max), @URL nvarchar(max), @Header nvarchar(max), @Data nvarchar(max))
|
|
59
|
+
RETURNS nvarchar(max)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`httpAPI` has the **identical** signature (different internals): use `httpAPI` for internal tAPI calls, `httpCall` for general outbound HTTP.
|
|
63
|
+
|
|
64
|
+
| Param | Meaning |
|
|
65
|
+
| --------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
66
|
+
| `@Method` | `'GET'`, `'POST'`, `'PUT'`, `'DELETE'`... (case-insensitive; empty → `GET`) |
|
|
67
|
+
| `@URL` | Full, valid absolute URL |
|
|
68
|
+
| `@Header` | Header list, syntax below — `''` for none |
|
|
69
|
+
| `@Data` | JSON string body — **sent only when Method is not GET/HEAD**; always `Content-Type: application/json` unless `@Header` overrides |
|
|
70
|
+
|
|
71
|
+
**Example:**
|
|
72
|
+
|
|
73
|
+
```sql
|
|
74
|
+
SELECT dbo.httpCall('POST', 'https://tapi.example.vn/me/auth/ApplicationList', '', '')
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### `@Header` syntax — pipe-delimited `Key: Value`
|
|
78
|
+
|
|
79
|
+
Most important part. One string; pairs separated by `|`; each pair `Key: Value`, split on the **first** `:` (the rest, including further `:`, is the value):
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
"Authorization: Bearer eyJhbGciOi...|X-Custom-Header: abc123"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Header names are **case-insensitive**; special handling:
|
|
86
|
+
|
|
87
|
+
| Header name | Behavior |
|
|
88
|
+
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
89
|
+
| `Authorization`, `X-*`, any other | Added to request headers as-is (`TryAddWithoutValidation`) — tokens, API keys |
|
|
90
|
+
| `referer` / `referrer` | Parsed to URI → `request.Headers.Referrer` |
|
|
91
|
+
| `user-agent` | Overrides default `User-Agent` |
|
|
92
|
+
| `accept` | Overrides `Accept` |
|
|
93
|
+
| `accept-language` | Overrides `Accept-Language` |
|
|
94
|
+
| `accept-encoding` | Overrides `Accept-Encoding` |
|
|
95
|
+
| `content-type` | With body (Method not GET/HEAD) → `Content.Headers.Content-Type` (overrides default `application/json`); no body → normal request headers |
|
|
96
|
+
| Any other `content-*` | With body → routed to `Content.Headers` instead of request headers |
|
|
97
|
+
|
|
98
|
+
**Bearer token + custom header:**
|
|
99
|
+
|
|
100
|
+
```sql
|
|
101
|
+
SELECT dbo.httpCall(
|
|
102
|
+
'POST',
|
|
103
|
+
'https://api.example.com/data',
|
|
104
|
+
'Authorization: Bearer abc123xyz|Content-Type: application/json; charset=utf-8',
|
|
105
|
+
'{"key":"value"}'
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Joining headers:** exactly one `|` between pairs, no extra/missing `|`. A pair without `:` is **silently skipped** (no error).
|
|
110
|
+
|
|
111
|
+
#### `httpCall`/`httpAPI` response
|
|
112
|
+
|
|
113
|
+
- Status **200**: raw response body verbatim (JSON, text, anything).
|
|
114
|
+
- Status **not 200** (400, 401, 404, 500...): wrapped JSON:
|
|
115
|
+
```json
|
|
116
|
+
{ "httpErrorCode": 404, "responseData": "<nội dung body, hoặc escaped string nếu không phải JSON>" }
|
|
117
|
+
```
|
|
118
|
+
→ in SPs, always check for an `httpErrorCode` field to tell error from success.
|
|
119
|
+
- Invalid URL: `{"httpErrorCode":0,"responseData":"Invalid URL"}`.
|
|
120
|
+
- Timeout/other exception: plain-text error message (not JSON) — check the format before `OPENJSON`.
|
|
121
|
+
|
|
122
|
+
### `dbo.sendMail`
|
|
123
|
+
|
|
124
|
+
```sql
|
|
125
|
+
dbo.sendMail(@email_to nvarchar(max), @subject nvarchar(max), @content nvarchar(max), @from nvarchar(max), @fromDisplayName nvarchar(max), @smtp_server nvarchar(max), @smtp_port int, @userName nvarchar(max), @Password nvarchar(max), @EnableSSL bit)
|
|
126
|
+
RETURNS nvarchar(max)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Sends email over SMTP from T-SQL.
|
|
130
|
+
|
|
131
|
+
| Param | Meaning |
|
|
132
|
+
| ---------------------------- | ---------------------------------------------------------- |
|
|
133
|
+
| `@email_to` | Recipient |
|
|
134
|
+
| `@subject` | Subject |
|
|
135
|
+
| `@content` | Body (HTML or text) |
|
|
136
|
+
| `@from` | Sender address (must match/be allowed by the SMTP account) |
|
|
137
|
+
| `@fromDisplayName` | Sender display name |
|
|
138
|
+
| `@smtp_server`, `@smtp_port` | e.g. `'smtp.gmail.com'`, `587` |
|
|
139
|
+
| `@userName`, `@Password` | SMTP auth account |
|
|
140
|
+
| `@EnableSSL` | `1` = SSL/TLS (required for Gmail SMTP) |
|
|
141
|
+
|
|
142
|
+
```sql
|
|
143
|
+
SELECT dbo.sendMail('user@example.vn','Tiêu đề','Nội dung','noreply@example.vn','My System','smtp.gmail.com',587,'noreply@example.vn','app-password',1)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
> **Security:** avoid hardcoding the SMTP `@Password` in the SP — store it via `publicValueSet`/`publicValueGet` or a permission-restricted config table so it is not exposed in the SP definition.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 3. `SQLFileSystem` — server files
|
|
151
|
+
|
|
152
|
+
```sql
|
|
153
|
+
dbo.FileCreateFolder(@Path nvarchar(max)) RETURNS nvarchar(max)
|
|
154
|
+
dbo.FileReader(@Path nvarchar(max)) RETURNS varbinary(max)
|
|
155
|
+
dbo.FileWriter(@Content varbinary(max), @Path nvarchar(max)) RETURNS nvarchar(max)
|
|
156
|
+
dbo.FileExists(@Path nvarchar(max)) RETURNS bit
|
|
157
|
+
dbo.FileDelete(@Path nvarchar(max)) RETURNS nvarchar(max)
|
|
158
|
+
dbo.FileMove(@SourceFileName nvarchar(max), @DestFileName nvarchar(max)) RETURNS nvarchar(max)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Operates on the **SQL Server machine's** filesystem (not the client); `@Path` is an absolute server path. Use when an SP saves output (e.g. `ImageResize` result, exported PDF) to a fixed folder served by tAPI File API or IIS.
|
|
162
|
+
|
|
163
|
+
| Function | Meaning |
|
|
164
|
+
| ------------------ | ------------------------------------------------------------- |
|
|
165
|
+
| `FileCreateFolder` | Creates folder (nested too), returns result message |
|
|
166
|
+
| `FileReader` | Reads whole file as `varbinary` |
|
|
167
|
+
| `FileWriter` | Writes `@Content` (varbinary) to `@Path`, create or overwrite |
|
|
168
|
+
| `FileExists` | Returns `bit` |
|
|
169
|
+
| `FileDelete` | Deletes file |
|
|
170
|
+
| `FileMove` | Moves/renames `@SourceFileName` → `@DestFileName` |
|
|
171
|
+
|
|
172
|
+
> Real server disk: add a permission check in the SP before letting client input drive `FileDelete`/`FileWriter` — same DDL/DML guard principle as `fui exec`.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 4. `SQLExpansionFunction` — regex & string compare
|
|
177
|
+
|
|
178
|
+
```sql
|
|
179
|
+
dbo.RegexReplace(@Input nvarchar(max), @Pattern nvarchar(1000), @Replacement nvarchar(4000)) RETURNS nvarchar(max)
|
|
180
|
+
dbo.RegexIsMatch(@Input nvarchar(max), @Pattern nvarchar(1000)) RETURNS bit
|
|
181
|
+
dbo.RegexMatch(@Input nvarchar(max), @Pattern nvarchar(1000)) RETURNS nvarchar(max)
|
|
182
|
+
dbo.RegexMatchArray(@Input nvarchar(max), @Pattern nvarchar(1000)) RETURNS nvarchar(max)
|
|
183
|
+
dbo.CompareWord(@String1 nvarchar(max), @String2 nvarchar(max)) RETURNS int
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
| Function | Meaning |
|
|
187
|
+
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
188
|
+
| `RegexReplace` | .NET regex replace — when T-SQL `REPLACE` (literal only) is not enough |
|
|
189
|
+
| `RegexIsMatch` | `bit` match test — validate format (email, phone, codes...) in the SP |
|
|
190
|
+
| `RegexMatch` | **First match** only (`Match.Value`, whole match, no groups); empty if none |
|
|
191
|
+
| `RegexMatchArray` | **All matches with each capture group**, as a **flat JSON object** (not array) — see below |
|
|
192
|
+
| `CompareWord` | Similarity of 2 strings as `int` (likely differing-char count or a score — check the scale with `SELECT dbo.CompareWord('a','b')` if needed) |
|
|
193
|
+
|
|
194
|
+
`@Pattern` is standard .NET regex (`\d`, `\w`, `^$`, groups `()`...), not T-SQL `LIKE`. All 4 regex functions **always** use `IgnoreCase | Multiline | Singleline`: case-insensitive, `^`/`$` per line, `.` matches newlines — cannot be turned off.
|
|
195
|
+
|
|
196
|
+
#### `RegexMatchArray` shape — JSON object, not array
|
|
197
|
+
|
|
198
|
+
Flat object (`{...}`, not `[...]`), keys `"M{m}G{g}"`:
|
|
199
|
+
|
|
200
|
+
- `m` = match index in `@Input`, from 1.
|
|
201
|
+
- `g` = group index **within that match**, from 1: `G1` is always the **whole match** (`Match.Groups[0]`, same as `RegexMatch`); `G2`, `G3`... are the capture groups `(...)` in `@Pattern`, if any.
|
|
202
|
+
- No match → `"{}"`.
|
|
203
|
+
- Values have tab/newline/CR (`\t`, `\n`, `\r`) removed and `"` escaped.
|
|
204
|
+
|
|
205
|
+
`RegexMatchArray('SDT: 0901 - 0902', '(\d{4})')` (one group) returns:
|
|
206
|
+
|
|
207
|
+
```json
|
|
208
|
+
{ "M1G1": "0901", "M1G2": "0901", "M2G1": "0902", "M2G2": "0902" }
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
(G1 = G2 here because the whole match equals the only group; with no groups each match yields only `G1`.)
|
|
212
|
+
|
|
213
|
+
Parsing in T-SQL (it is an object):
|
|
214
|
+
|
|
215
|
+
- `OPENJSON(@result)` still works but returns `key/value/type` rows (one row per key like `"M1G1"`), not one structured row per match.
|
|
216
|
+
- One known match/group: `JSON_VALUE(@result, '$."M1G1"')`.
|
|
217
|
+
- Iterate all: `OPENJSON(@result)` without path, parse the `M{m}G{g}` prefix from `key` to group by match.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## 5. When to use them in SPs
|
|
222
|
+
|
|
223
|
+
- Always confirm the function exists first (`fui schema` filtered to functions, or `SELECT * FROM sys.objects WHERE type='FS'`) — usable only if the assembly was `CREATE ASSEMBLY`'d on that DB. Not every DB has them.
|
|
224
|
+
- Use in `spAPI_*` when needed: external API (`httpCall`), uploaded image processing (`ImageResize`/`ImageCrop`), notification email (`sendMail`), regex validation (`RegexIsMatch`), saving files to server disk (`FileWriter`).
|
|
225
|
+
- JSON `nvarchar(max)` results (`httpCall`, `RegexMatchArray`) → parse with `OPENJSON`/`JSON_VALUE` in T-SQL before returning via tAPI.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# System Design — Multi-Module App
|
|
2
|
+
|
|
3
|
+
> Owns: **multi-module app design from a description: decomposition, schema-first, menu, permissions**. Use when the user describes a whole application, not one module. Each module still follows [fullstack-workflow.md](fullstack-workflow.md).
|
|
4
|
+
|
|
5
|
+
> Read order: this file → [db-table-design.md](db-table-design.md) (schema) → [tapi-reference.md](tapi-reference.md) (API) → [fullstack-workflow.md](fullstack-workflow.md) (per module) → [project-config.md](project-config.md) (menu/rights).
|
|
6
|
+
|
|
7
|
+
## Step 0 — Clarify scope
|
|
8
|
+
|
|
9
|
+
Don't code yet. Confirm:
|
|
10
|
+
|
|
11
|
+
- **Users/roles** (admin, staff, customer...) → drives permissions & menu.
|
|
12
|
+
- **Main end-to-end flows** (e.g. "create order → approve → ship").
|
|
13
|
+
- **Core entities** + relations (1-n, n-n).
|
|
14
|
+
- **Existing DB/project or fully new?**
|
|
15
|
+
- Existing → `fui project list`, `fui module list`, `fui schema` first.
|
|
16
|
+
- Fully new → do [project-provisioning.md](project-provisioning.md) **first** (no DB/project = nothing to design on); return once `fui db add-by-name` and `fui project new` succeed.
|
|
17
|
+
|
|
18
|
+
Missing info → **ask**; never assume and build the wrong system.
|
|
19
|
+
|
|
20
|
+
## Step 1 — Decompose into modules
|
|
21
|
+
|
|
22
|
+
| Principle | Example |
|
|
23
|
+
| ------------------------------------------- | -------------------------------------------------------- |
|
|
24
|
+
| One module = one coherent screen/function | "Student management", "Revenue report", "Order approval" |
|
|
25
|
+
| Group by main entity | One entity's CRUD in one module (list + add/edit dialog) |
|
|
26
|
+
| Split by role when flows differ | Admin vs end-user screens → separate modules |
|
|
27
|
+
| Separate dashboards/reports from data entry | Chart summary page on its own, not inside CRUD |
|
|
28
|
+
|
|
29
|
+
**Output:** module table — name, purpose, entities, access roles.
|
|
30
|
+
|
|
31
|
+
All modules in a project follow the **same convention** (control naming, toolbar/filter/action placement); check existing ones with `fui module get` before inventing a style.
|
|
32
|
+
|
|
33
|
+
## Step 2 — Schema first
|
|
34
|
+
|
|
35
|
+
Before any module.json:
|
|
36
|
+
|
|
37
|
+
1. Tables + PK + FK for **all** entities. Per [db-table-design.md](db-table-design.md): `tbl` + PascalCase, PK `[Entity]ID`, audit `CreateUser/CreateTime/UpdateUser/UpdateTime`.
|
|
38
|
+
2. n-n → junction table (e.g. `tblUserRole`).
|
|
39
|
+
3. **`CREATE TABLE` is run by the developer** — fui blocks it; present DDL for review. Existing tables, gated by data loss: `ALTER TABLE ... ADD` and `DROP CONSTRAINT` run directly; `ALTER COLUMN` needs `--confirm-write`; `DROP COLUMN`/`DROP TABLE`/`SWITCH` hard-blocked.
|
|
40
|
+
4. Design lookup/catalog tables early (shared dropdowns).
|
|
41
|
+
|
|
42
|
+
**Output:** tables + relations, user-confirmed.
|
|
43
|
+
|
|
44
|
+
## Step 3 — SPs per entity
|
|
45
|
+
|
|
46
|
+
Each entity → standard set `List/Get/Insert/Update/Delete` + `AUTH_*Options` for dropdowns ([tapi-reference.md](tapi-reference.md) §1, [fullstack-workflow.md](fullstack-workflow.md) Steps 2-3).
|
|
47
|
+
|
|
48
|
+
- **Shared SPs** (catalogs, lookups) → neutral names.
|
|
49
|
+
- Consistent authorization via `@sys_SystemRight`/`@sys_FunctionRight` ([tapi-permission-patterns.md](tapi-permission-patterns.md)).
|
|
50
|
+
|
|
51
|
+
## Step 4 — Project structure (system level only)
|
|
52
|
+
|
|
53
|
+
- **Menu & navigation:** `project.json` (menuLeft/menu/menuStyle) — [project-config.md](project-config.md), `examples/project-patterns.json`.
|
|
54
|
+
- **Components shared across modules** → project scope `{projectId}/components/` (`fui component new`/`update` with a project, no module; see `fui component --help`) so they auto-load everywhere — [component-design.md](component-design.md).
|
|
55
|
+
- **Cross-module navigation:** `pushRouter`/`openWindow` ([ui-crosswindow-patterns.md](ui-crosswindow-patterns.md)); pass IDs via URL, child module queries itself.
|
|
56
|
+
- **Cross-module permissions:** unify `SystemRight`/`FunctionRight` by Step-0 roles; **define real codes before guarding** (Step 4b).
|
|
57
|
+
|
|
58
|
+
## Step 4b — Create right DEFINITIONS before any guarded module.json
|
|
59
|
+
|
|
60
|
+
A code exists only as a row in the project's `acc.tblSysRight`/`acc.tblFunction`. `FunctionRight.includes('42')` **does not create** 42; a missing code silently hides the control ([permission-system.md](permission-system.md) §"Mandatory: a right code must EXIST before you guard with it"). Often forgotten: it is not in the project DB, schema, or any workspace file.
|
|
61
|
+
|
|
62
|
+
1. `fui right system list` + `fui right function list` — see what exists (new projects usually empty); writes cache `_db/rights.json` **keyed by `apiName`** for `fui module validate`. Multi-alias → run **per alias**; never reuse one alias's cache for another.
|
|
63
|
+
2. Each Step-0 role → a `SystemRight` level (`fui right system new`, level + note). `9` = Project Admin exists already.
|
|
64
|
+
3. Each separately toggled operation (approve, export, delete…) → a `FunctionRight` (`fui right function new`, code + name). Keep one `code → meaning` table in the design shared by all modules; no module invents numbers.
|
|
65
|
+
4. Lock the table with the user **before** coding — changing codes after assigning real users is manual work outside fui.
|
|
66
|
+
|
|
67
|
+
## Step 5 — Build order (lowest risk first)
|
|
68
|
+
|
|
69
|
+
0. **Infrastructure** (fully new only): database + two SQL accounts + alias `acc` + FUI project + admin rights — [project-provisioning.md](project-provisioning.md).
|
|
70
|
+
1. **Schema + lookup data** — inspect with `fui query` (TOP 20).
|
|
71
|
+
2. **Right definitions** (4b) — SP `@sys_FunctionRight` checks and `v-if` guards reference them; creating later means everything was written with non-existent codes.
|
|
72
|
+
3. **Core-entity SPs**, statically verified with `fui sp verify` (reads body, no live call).
|
|
73
|
+
4. **Main-entity CRUD module** — convention template for the rest.
|
|
74
|
+
5. **Dependent modules** (reports, dashboards) once data exists.
|
|
75
|
+
6. **Menu + shared components** last.
|
|
76
|
+
|
|
77
|
+
Each module: full [fullstack-workflow.md](fullstack-workflow.md) — analyze → **Design Review Gate (§Step 1b)** → hard-coded UI → contract → SP → API wiring → static verification.
|
|
78
|
+
|
|
79
|
+
## Checklist
|
|
80
|
+
|
|
81
|
+
- [ ] All modules + access roles listed; scope user-confirmed
|
|
82
|
+
- [ ] Overall schema (PK/FK, junction, lookup) locked before coding
|
|
83
|
+
- [ ] Same convention across modules
|
|
84
|
+
- [ ] Shared components at project scope, not copied
|
|
85
|
+
- [ ] Menu/navigation in project.json
|
|
86
|
+
- [ ] **Real right definitions** (`fui right system new`/`fui right function new`) for every role/operation, user-locked `code → meaning` table — no guard uses a non-existent code
|
|
87
|
+
- [ ] `fui module validate` shows no right-code warnings (`fui right * list` run for cache)
|
|
88
|
+
- [ ] Permissions consistent by role across SPs
|
|
89
|
+
- [ ] Order: schema → **right definitions** → SP → core module → dependent modules → menu
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# tAPI — File API (GET / UPLOAD)
|
|
2
|
+
|
|
3
|
+
> Owns: **file APIs via SP: `spAPIFILE_*`, `tblFileData`, alias `fileContent`, `@sys_FileContent`/`@sys_FileName`**. Load when a module needs upload/download/view of files. **Never infer from training data** — tAPI has its own conventions. SP basics (naming, params, response): [tapi-reference.md](tapi-reference.md).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## GET file
|
|
8
|
+
|
|
9
|
+
SP must SELECT **at least 3 required columns** (extra metadata columns allowed):
|
|
10
|
+
|
|
11
|
+
| Column | Required | Meaning |
|
|
12
|
+
| ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| `FileName` | ✅ | Returned file name — may be an expression, e.g. `FileName + FileType` |
|
|
14
|
+
| `ContentType` | ✅ | MIME type (e.g. `application/pdf`, `image/png`) |
|
|
15
|
+
| `fileContent` | ✅ | Binary content — alias of an **`image` or `varbinary`** column. tAPI detects the file content to stream by this data type |
|
|
16
|
+
| other columns | ❌ | Optional metadata, e.g. `Modified`, `FileSize` — must be normal (non-binary) types |
|
|
17
|
+
|
|
18
|
+
```sql
|
|
19
|
+
-- Ví dụ thực tế: FileName ghép đuôi, thêm cột Modified, binary aliased từ FileContentBIN
|
|
20
|
+
CREATE PROCEDURE spAPIFILE_moduleImportFile
|
|
21
|
+
@fileID varchar(50)
|
|
22
|
+
AS BEGIN
|
|
23
|
+
SELECT
|
|
24
|
+
FileName = FileName + FileType,
|
|
25
|
+
ContentType,
|
|
26
|
+
Modified = ISNULL(UpdateTime, CreateTime),
|
|
27
|
+
fileContent = FileContentBIN
|
|
28
|
+
FROM tblA_ImportFile
|
|
29
|
+
WHERE fileID = LEFT(@fileID, 32)
|
|
30
|
+
END
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
> `fileContent` must alias an `image` or `varbinary(max)` column — tAPI uses the type to detect and stream the file. Table column name is free (`FileContentBIN`, `BinaryContent`, ...).
|
|
34
|
+
> `FileName` should include the extension (`.pdf`, `.xlsx`...) so the browser detects the type.
|
|
35
|
+
|
|
36
|
+
**URL params — `@fileID` and `@filename`:**
|
|
37
|
+
|
|
38
|
+
| Param | Required | Source | Meaning |
|
|
39
|
+
| ------------------------ | -------- | -------------------------------- | ------------------------------------------------------------------------------------ |
|
|
40
|
+
| `@fileID varchar(...)` | ✅ | First segment after FunctionName | ID of the file |
|
|
41
|
+
| `@filename varchar(...)` | ❌ | LAST URL segment | Display file name in the URL (browser/download manager uses it to name the download) |
|
|
42
|
+
|
|
43
|
+
Extra `@sys_*` auth params may be declared for permission checks.
|
|
44
|
+
|
|
45
|
+
```sql
|
|
46
|
+
CREATE PROCEDURE spAPIFILE_Document
|
|
47
|
+
@fileID varchar(50), -- bắt buộc, inject từ URL
|
|
48
|
+
@filename varchar(200), -- tùy chọn — inject từ segment cuối URL; SP có thể dùng để
|
|
49
|
+
-- ghi log, kiểm tra, hoặc override FileName trả về
|
|
50
|
+
@sys_UserID varchar(9) -- tùy chọn, inject từ token
|
|
51
|
+
AS BEGIN
|
|
52
|
+
...
|
|
53
|
+
END
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**4 supported routes:**
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
{domain}/{apiName}/{FunctionName}/{fileID}
|
|
60
|
+
{domain}/{apiName}/{FunctionName}/{fileID}/{filename}
|
|
61
|
+
{domain}/{apiName}/download/{FunctionName}/{fileID}/{filename}
|
|
62
|
+
{domain}/{apiName}/viewer/{FunctionName}/{fileID}/{filename}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`{FunctionName}` is the name after the `spAPIFILE_` prefix: SP `spAPIFILE_Document` → URL uses `Document`. `/viewer/` = view inline in browser; `/download/` = download.
|
|
66
|
+
|
|
67
|
+
> **`@filename`:** usually the original name (e.g. `BaoCao_2024.pdf`) used by browser/download manager to name the file; need not match `FileName` in the DB. If the SP doesn't declare `@filename`, tAPI still accepts the route with that segment — the param is simply ignored.
|
|
68
|
+
|
|
69
|
+
## Upload file
|
|
70
|
+
|
|
71
|
+
**Special params injected by tAPI on upload:**
|
|
72
|
+
|
|
73
|
+
| Param | Type | Meaning |
|
|
74
|
+
| ---------------------- | -------------- | --------------------------------- |
|
|
75
|
+
| `@sys_FileContent` | varbinary(max) | File binary content |
|
|
76
|
+
| `@sys_FileName` | nvarchar(200) | Original file name |
|
|
77
|
+
| `@sys_FileContentType` | varchar(100) | MIME type |
|
|
78
|
+
| `@sys_FileSize` | int | File size (bytes) |
|
|
79
|
+
| `@sys_UploadData` | nvarchar(max) | Extra JSON data passed via header |
|
|
80
|
+
|
|
81
|
+
All `@sys_` auth params are also available: `@sys_UserID`, `@sys_UserName`, `@sys_SessionID`, `@sys_GroupID`, `@sys_DepartmentID`, `@sys_SystemRight`, `@sys_FunctionRight`.
|
|
82
|
+
|
|
83
|
+
```sql
|
|
84
|
+
CREATE PROCEDURE spAPIFILE_UPLOAD_Attachment
|
|
85
|
+
@url1_SessionID varchar(36),
|
|
86
|
+
@url2_Mode varchar(20),
|
|
87
|
+
@sys_UserID varchar(9),
|
|
88
|
+
@sys_FileContent varbinary(max),
|
|
89
|
+
@sys_FileName nvarchar(200),
|
|
90
|
+
@sys_FileContentType varchar(100),
|
|
91
|
+
@sys_FileSize int
|
|
92
|
+
AS BEGIN
|
|
93
|
+
INSERT INTO tblAttachFiles(FileName, Capacity, SessionID, CreateUser)
|
|
94
|
+
VALUES(@sys_FileName, @sys_FileSize, @url1_SessionID, @sys_UserID)
|
|
95
|
+
END
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Route: `POST {domain}/{apiName}/upload/{spName}/{url1}/{url2}...`
|
|
99
|
+
|
|
100
|
+
## Standard file table — `tblFileData`
|
|
101
|
+
|
|
102
|
+
Standard structure shared by `spAPIFILE_` (GET) and `spAPIFILE_UPLOAD_` (POST). For modules with files, create this table (or similar) before writing SPs.
|
|
103
|
+
|
|
104
|
+
```sql
|
|
105
|
+
CREATE TABLE [dbo].[tblFileData](
|
|
106
|
+
[FileID] [char](36) NOT NULL, -- UUID, DEFAULT newid()
|
|
107
|
+
[FileName] [nvarchar](200) NOT NULL,
|
|
108
|
+
[PhysicalName] [char](36) NOT NULL, -- UUID, DEFAULT newid()
|
|
109
|
+
[PhysicalSegment] [char](10) NOT NULL, -- subfolder theo ngày, DEFAULT getPhysicalSegment(getdate())
|
|
110
|
+
[CreateTime] [datetime] NOT NULL, -- DEFAULT getdate()
|
|
111
|
+
[WriteToDisk] [datetime] NULL, -- null = chưa ghi ra disk
|
|
112
|
+
[BinaryContent] [image] NULL, -- binary file content — alias là fileContent trong SP GET
|
|
113
|
+
[ContentType] [varchar](100) NULL, -- MIME type, DEFAULT ''
|
|
114
|
+
[Size] [int] NULL,
|
|
115
|
+
CONSTRAINT [PK_tblFileData] PRIMARY KEY CLUSTERED ([FileID] ASC)
|
|
116
|
+
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
|
|
117
|
+
GO
|
|
118
|
+
|
|
119
|
+
ALTER TABLE [dbo].[tblFileData] ADD CONSTRAINT [DF_tblFileData_FileID] DEFAULT (newid()) FOR [FileID]
|
|
120
|
+
ALTER TABLE [dbo].[tblFileData] ADD CONSTRAINT [DF_tblFileData_PhysicalName] DEFAULT (newid()) FOR [PhysicalName]
|
|
121
|
+
ALTER TABLE [dbo].[tblFileData] ADD CONSTRAINT [DF_tblFileData_PhysicalSegment] DEFAULT ([dbo].[getPhysicalSegment](getdate())) FOR [PhysicalSegment]
|
|
122
|
+
ALTER TABLE [dbo].[tblFileData] ADD CONSTRAINT [DF_tblFileData_CreateTime] DEFAULT (getdate()) FOR [CreateTime]
|
|
123
|
+
ALTER TABLE [dbo].[tblFileData] ADD CONSTRAINT [DF_tblFileData_ContentType] DEFAULT ('') FOR [ContentType]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
> **DDL:** the `ALTER TABLE ... ADD CONSTRAINT` statements **can run via fui** (ADD is additive). `CREATE TABLE` cannot — fui blocks it; the developer must run it. This is a structure reference.
|
|
127
|
+
|
|
128
|
+
**Fields:**
|
|
129
|
+
|
|
130
|
+
| Field | Type | Meaning |
|
|
131
|
+
| ----------------- | ------------- | ----------------------------------------------------------------------------- |
|
|
132
|
+
| `FileID` | char(36) PK | Auto UUID via `newid()` — used as the URL key |
|
|
133
|
+
| `FileName` | nvarchar(200) | Original upload name |
|
|
134
|
+
| `PhysicalName` | char(36) | Auto UUID — actual file name on disk (avoids collisions) |
|
|
135
|
+
| `PhysicalSegment` | char(10) | Date-based subfolder from `getPhysicalSegment(date)` |
|
|
136
|
+
| `CreateTime` | datetime | Record creation time, DEFAULT getdate() |
|
|
137
|
+
| `WriteToDisk` | datetime NULL | When the binary was written to disk; `NULL` = DB only, not on file system yet |
|
|
138
|
+
| `BinaryContent` | image NULL | File binary — GET SP must alias it `fileContent` |
|
|
139
|
+
| `ContentType` | varchar(100) | MIME type, e.g. `application/pdf`, `image/png` |
|
|
140
|
+
| `Size` | int NULL | Size in bytes |
|
|
141
|
+
|
|
142
|
+
**GET SP on the table:**
|
|
143
|
+
|
|
144
|
+
```sql
|
|
145
|
+
CREATE PROCEDURE spAPIFILE_Document
|
|
146
|
+
@FileID char(36),
|
|
147
|
+
@sys_UserID varchar(9),
|
|
148
|
+
@sys_SystemRight int
|
|
149
|
+
AS BEGIN
|
|
150
|
+
IF @sys_SystemRight < 1
|
|
151
|
+
BEGIN RAISERROR(N'[Unauthorized]Bạn không có quyền.', 16, 1) RETURN END
|
|
152
|
+
|
|
153
|
+
-- BinaryContent phải alias là fileContent — tAPI đọc đúng tên này
|
|
154
|
+
SELECT FileName, ContentType, fileContent = BinaryContent
|
|
155
|
+
FROM tblFileData WHERE FileID = @FileID
|
|
156
|
+
END
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**UPLOAD SP into the table:**
|
|
160
|
+
|
|
161
|
+
```sql
|
|
162
|
+
CREATE PROCEDURE spAPIFILE_UPLOAD_Document
|
|
163
|
+
@url1_RefID varchar(50),
|
|
164
|
+
@sys_UserID varchar(9),
|
|
165
|
+
@sys_SystemRight int,
|
|
166
|
+
@sys_FileContent varbinary(max),
|
|
167
|
+
@sys_FileName nvarchar(200),
|
|
168
|
+
@sys_FileContentType varchar(100),
|
|
169
|
+
@sys_FileSize int
|
|
170
|
+
AS BEGIN
|
|
171
|
+
IF @sys_SystemRight < 2
|
|
172
|
+
BEGIN RAISERROR(N'[Unauthorized]Bạn không có quyền.', 16, 1) RETURN END
|
|
173
|
+
|
|
174
|
+
DECLARE @NewFileID char(36) = newid() -- sinh UUID trước, dùng để SELECT lại sau
|
|
175
|
+
|
|
176
|
+
INSERT INTO tblFileData(FileID, FileName, BinaryContent, ContentType, Size)
|
|
177
|
+
VALUES(@NewFileID, @sys_FileName, @sys_FileContent, @sys_FileContentType, @sys_FileSize)
|
|
178
|
+
|
|
179
|
+
-- Trả về record vừa insert để client cập nhật danh sách
|
|
180
|
+
SELECT FileID, FileName, Size, ContentType, CreateTime
|
|
181
|
+
FROM tblFileData WHERE FileID = @NewFileID
|
|
182
|
+
END
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
> **`BinaryContent [image]`**: `image` is legacy SQL Server but works fine with tAPI. `varbinary(max)` also works — both map to the `fileContent` alias.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# tAPI — Permission Check Patterns
|
|
2
|
+
|
|
3
|
+
> Owns: **5 permission-check patterns in SP bodies (`@sys_SystemRight`, `getSystemRight`, RAISERROR)**. Load when an SP needs authorization. SP basics (naming, params, response, RAISERROR): [tapi-reference.md](tapi-reference.md). `SystemRight`/`FunctionRight` concepts, `tblSysRight`/`tblFunction`, right-definition commands: [permission-system.md](permission-system.md).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Permission-related system params
|
|
8
|
+
|
|
9
|
+
Injected by tAPI — the client never sends them:
|
|
10
|
+
|
|
11
|
+
| Param | Type | Meaning |
|
|
12
|
+
| -------------------- | ------------- | ------------------------------------- |
|
|
13
|
+
| `@sys_UserID` | varchar(9) | Logged-in user ID |
|
|
14
|
+
| `@sys_SystemRight` | int | User's level on the current module |
|
|
15
|
+
| `@sys_FunctionRight` | varchar(2000) | Function rights like `[AD][RPT][MGR]` |
|
|
16
|
+
|
|
17
|
+
**Declare** at the end of the param list. **No default on any param, including `@sys_*`** ([tapi-reference.md](tapi-reference.md) §2 — tAPI always passes every param, `NULL` when missing, so `DEFAULT` is never used; `fui sp deploy` hard-blocks it):
|
|
18
|
+
|
|
19
|
+
```sql
|
|
20
|
+
CREATE PROCEDURE spAPI_EntityAction
|
|
21
|
+
@BusinessParam1 int,
|
|
22
|
+
@BusinessParam2 nvarchar(100),
|
|
23
|
+
@sys_UserID varchar(9),
|
|
24
|
+
@sys_SystemRight int
|
|
25
|
+
AS
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## `@sys_SystemRight` levels
|
|
29
|
+
|
|
30
|
+
Meaning is **defined per project** (`tblSysRight.Note`, managed via `fui right system list`/`update` — [permission-system.md](permission-system.md)). Common convention for SP checks — not platform-fixed, except `9` is always Project Admin:
|
|
31
|
+
|
|
32
|
+
| Value | Common meaning |
|
|
33
|
+
| ----- | -------------------------------------------------- |
|
|
34
|
+
| `0` | No right / not granted on the module |
|
|
35
|
+
| `1` | Basic view |
|
|
36
|
+
| `2` | Standard operations (read, add, normal edit) |
|
|
37
|
+
| `3` | Advanced (internal management, sensitive ops) |
|
|
38
|
+
| `9` | Module Admin / Project Admin (fixed platform-wide) |
|
|
39
|
+
|
|
40
|
+
## Helper: rights on a specific module
|
|
41
|
+
|
|
42
|
+
```sql
|
|
43
|
+
dbo.getSystemRight(@ModuleID, @sys_UserID)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Returns the user's `SysRight` on a given module — use to check rights on a module **other than** the current one.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Pattern 1 — Basic access block (READ)
|
|
51
|
+
|
|
52
|
+
Every read SP that needs login. Requires `SysRight >= 2`.
|
|
53
|
+
|
|
54
|
+
```sql
|
|
55
|
+
If @sys_SystemRight < 2
|
|
56
|
+
Begin
|
|
57
|
+
Raiserror(N'[Unauthorized]Bạn không có quyền trên chức năng này.', 16, 1)
|
|
58
|
+
Return
|
|
59
|
+
End
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Pattern 2 — Block only users with no right (minimal)
|
|
63
|
+
|
|
64
|
+
Allows `SysRight = 1`; blocks only `SysRight = 0`.
|
|
65
|
+
|
|
66
|
+
```sql
|
|
67
|
+
If @sys_SystemRight = 0
|
|
68
|
+
Begin
|
|
69
|
+
Raiserror(N'[Unauthorized]Bạn không có quyền trên chức năng này.', 16, 1)
|
|
70
|
+
Return
|
|
71
|
+
End
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Pattern 3 — 2-tier: global + module admin (WRITE)
|
|
75
|
+
|
|
76
|
+
Writes affecting module config or other users' data. User must meet **either**:
|
|
77
|
+
|
|
78
|
+
- `@sys_SystemRight >= 3` (high global right), **or**
|
|
79
|
+
- Module Admin (`getSystemRight >= 9`) of that module.
|
|
80
|
+
|
|
81
|
+
```sql
|
|
82
|
+
-- Tầng 1: phải có quyền cơ bản
|
|
83
|
+
If @sys_SystemRight < 2
|
|
84
|
+
Begin
|
|
85
|
+
Raiserror(N'[Unauthorized]Bạn không có quyền trên chức năng này.', 16, 1)
|
|
86
|
+
Return
|
|
87
|
+
End
|
|
88
|
+
|
|
89
|
+
-- Tầng 2: phải là admin hệ thống hoặc admin của module đó
|
|
90
|
+
If @sys_SystemRight < 3 and dbo.getSystemRight(@ModuleID, @sys_UserID) < 9
|
|
91
|
+
Begin
|
|
92
|
+
Raiserror(N'[Unauthorized]Bạn không có quyền trên Module này.', 16, 1)
|
|
93
|
+
Return
|
|
94
|
+
End
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Pattern 4 — Business-rule check before executing
|
|
98
|
+
|
|
99
|
+
Authorized but violates a data constraint (e.g. deleting a parent that still has children).
|
|
100
|
+
|
|
101
|
+
```sql
|
|
102
|
+
-- Kiểm tra ràng buộc trước khi xóa
|
|
103
|
+
If exists(Select * from tblChild WHERE ParentID = @ParentID)
|
|
104
|
+
Begin
|
|
105
|
+
Raiserror(N'Phải xóa dữ liệu liên quan trước khi thực hiện thao tác này.', 16, 1)
|
|
106
|
+
Return
|
|
107
|
+
End
|
|
108
|
+
|
|
109
|
+
DELETE tblParent WHERE ParentID = @ParentID
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Pattern 5 — `@sys_FunctionRight` check
|
|
113
|
+
|
|
114
|
+
Authorization by specific function (not just numeric level).
|
|
115
|
+
|
|
116
|
+
```sql
|
|
117
|
+
If CHARINDEX('[ADMIN]', @sys_FunctionRight) = 0
|
|
118
|
+
Begin
|
|
119
|
+
Raiserror(N'[Unauthorized]Bạn không có quyền thực hiện chức năng này.', 16, 1)
|
|
120
|
+
Return
|
|
121
|
+
End
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Check order
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
1. Kiểm tra quyền hệ thống (@sys_SystemRight) ← luôn đầu tiên
|
|
130
|
+
2. Kiểm tra quyền module (getSystemRight) ← nếu cần 2 tầng
|
|
131
|
+
3. Kiểm tra quyền chức năng (@sys_FunctionRight) ← nếu dùng FunctionRight
|
|
132
|
+
4. Kiểm tra ràng buộc nghiệp vụ ← sau cùng, trước khi ghi
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Error message conventions
|
|
136
|
+
|
|
137
|
+
| Case | Standard message |
|
|
138
|
+
| ---------------------------------------- | --------------------------------------------------------------------- |
|
|
139
|
+
| Missing global right | `N'[Unauthorized]Bạn không có quyền trên chức năng này.'` |
|
|
140
|
+
| Missing module right | `N'[Unauthorized]Bạn không có quyền trên Module này.'` |
|
|
141
|
+
| Missing function right (`FunctionRight`) | `N'[Unauthorized]Bạn không có quyền thực hiện chức năng này.'` |
|
|
142
|
+
| Constraint violation | Specific description, **no** prefix: `N'Phải xóa X trước khi xóa Y.'` |
|
|
143
|
+
| Invalid data | `N'Dữ liệu không hợp lệ.'` or specific description, **no** prefix |
|
|
144
|
+
|
|
145
|
+
**Mandatory `RAISERROR` format:**
|
|
146
|
+
|
|
147
|
+
```sql
|
|
148
|
+
RAISERROR(N'Thông báo lỗi tiếng Việt.', 16, 1)
|
|
149
|
+
RETURN
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
- **Severity always `16`** — every error kind (validation, unauthorized, business logic)
|
|
153
|
+
- `N` prefix required for Vietnamese Unicode
|
|
154
|
+
- `RETURN` required right after `RAISERROR` — without it the SP keeps running after the error
|
|
155
|
+
- Client receives `{ "Message": "..." }`
|
|
156
|
+
- **Every missing-right error in the 5 patterns must start with `[Unauthorized]`** (e.g. `N'[Unauthorized]Bạn không có quyền...'`) — tAPI uses this prefix to return HTTP `401` instead of the default, and strips `[Unauthorized]` from `Message` before returning. Constraint/business errors (Pattern 4) do **not** use it — mechanism + client 401 handling: [tapi-reference.md](tapi-reference.md) §5.
|