zrb 1.2.1__py3-none-any.whl → 1.3.0__py3-none-any.whl
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.
- zrb/builtin/llm/llm_chat.py +68 -9
- zrb/builtin/llm/tool/api.py +4 -2
- zrb/builtin/llm/tool/file.py +39 -0
- zrb/builtin/llm/tool/rag.py +37 -22
- zrb/builtin/llm/tool/web.py +46 -20
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/add_column_util.py +28 -6
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/gateway/view/content/my-module/my-entity.html +206 -178
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/schema/my_entity.py +3 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_db_repository.py +18 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_repository.py +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/role_service.py +20 -11
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_db_repository.py +17 -2
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_repository.py +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service.py +19 -11
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/auth/permission.html +209 -180
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/auth/role.html +362 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/auth/user.html +377 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/common/util.js +68 -13
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/crud/util.js +50 -29
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/permission.py +3 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/role.py +6 -5
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/user.py +9 -3
- zrb/config.py +3 -1
- zrb/content_transformer/content_transformer.py +7 -1
- zrb/context/context.py +8 -2
- zrb/input/any_input.py +5 -0
- zrb/input/base_input.py +6 -0
- zrb/input/bool_input.py +2 -0
- zrb/input/float_input.py +2 -0
- zrb/input/int_input.py +2 -0
- zrb/input/option_input.py +2 -0
- zrb/input/password_input.py +2 -0
- zrb/input/text_input.py +11 -5
- zrb/runner/cli.py +1 -1
- zrb/runner/common_util.py +3 -3
- zrb/runner/web_route/task_input_api_route.py +1 -1
- zrb/task/llm_task.py +103 -16
- {zrb-1.2.1.dist-info → zrb-1.3.0.dist-info}/METADATA +85 -18
- {zrb-1.2.1.dist-info → zrb-1.3.0.dist-info}/RECORD +41 -40
- {zrb-1.2.1.dist-info → zrb-1.3.0.dist-info}/WHEEL +0 -0
- {zrb-1.2.1.dist-info → zrb-1.3.0.dist-info}/entry_points.txt +0 -0
@@ -1,23 +1,31 @@
|
|
1
1
|
<link rel="stylesheet" href="/static/crud/style.css">
|
2
2
|
|
3
|
-
<main
|
3
|
+
<main id="crud-app"
|
4
|
+
class="container"
|
5
|
+
data-page-size='{{ page_size | tojson }}'
|
6
|
+
data-page='{{ page | tojson }}'
|
7
|
+
data-sort='{{ sort | tojson }}'
|
8
|
+
data-filter='{{ filter | tojson }}'
|
9
|
+
data-allow-create='{{ allow_create | tojson }}'
|
10
|
+
data-allow-update='{{ allow_update | tojson }}'
|
11
|
+
data-allow-delete='{{ allow_delete | tojson }}'>
|
4
12
|
<article>
|
5
13
|
<h1>My Entity</h1>
|
6
14
|
|
7
15
|
<fieldset id="crud-table-fieldset" role="group" class="grid">
|
8
|
-
<input id="crud-filter"
|
9
|
-
<button
|
16
|
+
<input id="crud-filter-input" placeholder="🔍 Filter" aria-label="Search" />
|
17
|
+
<button id="crud-search-button">🔍 Search</button>
|
10
18
|
{% if allow_create %}
|
11
|
-
<button
|
19
|
+
<button id="crud-show-create-button" class="contrast">➕ Add</button>
|
12
20
|
{% endif %}
|
13
21
|
</fieldset>
|
22
|
+
|
14
23
|
<div id="crud-table-container">
|
15
24
|
<table id="crud-table" class="striped">
|
16
25
|
<thead>
|
17
26
|
<tr>
|
18
27
|
<th scope="col">ID</th>
|
19
28
|
<th scope="col">My Column</th>
|
20
|
-
<!-- Update this -->
|
21
29
|
{% if allow_update or allow_delete %}
|
22
30
|
<th scope="col">Actions</th>
|
23
31
|
{% endif %}
|
@@ -37,10 +45,9 @@
|
|
37
45
|
My Column:
|
38
46
|
<input type="text" name="my_column" required>
|
39
47
|
</label>
|
40
|
-
<!-- Update this -->
|
41
48
|
<footer>
|
42
|
-
<button
|
43
|
-
<button
|
49
|
+
<button id="crud-create-button">➕ Save</button>
|
50
|
+
<button id="crud-cancel-create-button" class="secondary">❌ Cancel</button>
|
44
51
|
</footer>
|
45
52
|
</form>
|
46
53
|
</article>
|
@@ -56,10 +63,9 @@
|
|
56
63
|
My Column:
|
57
64
|
<input type="text" name="my_column" required>
|
58
65
|
</label>
|
59
|
-
<!-- Update this -->
|
60
66
|
<footer>
|
61
|
-
<button
|
62
|
-
<button
|
67
|
+
<button id="crud-update-button">✏️ Save</button>
|
68
|
+
<button id="crud-cancel-update-button" class="secondary">❌ Cancel</button>
|
63
69
|
</footer>
|
64
70
|
</form>
|
65
71
|
</article>
|
@@ -75,10 +81,9 @@
|
|
75
81
|
My Column:
|
76
82
|
<input type="text" name="my_column" readonly>
|
77
83
|
</label>
|
78
|
-
<!-- Update this -->
|
79
84
|
<footer>
|
80
|
-
<button
|
81
|
-
<button
|
85
|
+
<button id="crud-cancel-delete-button" class="secondary">❌ Cancel</button>
|
86
|
+
<button id="crud-delete-button">🗑️ Delete</button>
|
82
87
|
</footer>
|
83
88
|
</form>
|
84
89
|
</article>
|
@@ -90,208 +95,231 @@
|
|
90
95
|
<h2 id="crud-alert-title">Error</h2>
|
91
96
|
<pre id="crud-alert-message"></pre>
|
92
97
|
<footer>
|
93
|
-
<button
|
98
|
+
<button id="crud-alert-close-button">Close</button>
|
94
99
|
</footer>
|
95
100
|
</article>
|
96
101
|
</dialog>
|
97
|
-
|
98
102
|
</article>
|
99
103
|
</main>
|
100
104
|
|
101
105
|
<script src="/static/crud/util.js"></script>
|
102
106
|
<script>
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
allowCreate: {{allow_create | tojson}},
|
110
|
-
allowUpdate: {{allow_update | tojson}},
|
111
|
-
allowDelete: {{allow_delete | tojson}},
|
112
|
-
updatedRowId: null,
|
113
|
-
deletedRowId: null,
|
114
|
-
};
|
107
|
+
class CrudApp {
|
108
|
+
constructor(apiUrl, initialState) {
|
109
|
+
this.apiUrl = apiUrl;
|
110
|
+
this.state = { ...initialState };
|
111
|
+
this.init();
|
112
|
+
}
|
115
113
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
114
|
+
init() {
|
115
|
+
// Cache common elements
|
116
|
+
this.filterInput = document.getElementById("crud-filter-input");
|
117
|
+
this.searchButton = document.getElementById("crud-search-button");
|
118
|
+
this.filterInput.value = this.state.filter;
|
121
119
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
);
|
137
|
-
renderRows(result.data);
|
138
|
-
const crudPagination = document.getElementById("crud-pagination");
|
139
|
-
CRUD_UTIL.renderPagination(
|
140
|
-
crudPagination, crudState, result.count, "fetchRows"
|
141
|
-
);
|
142
|
-
} catch (error) {
|
143
|
-
console.error("Error fetching items:", error);
|
120
|
+
this.filterInput.addEventListener("change", (e) => this.applySearch(e));
|
121
|
+
this.searchButton.addEventListener("click", (e) => this.applySearch(e));
|
122
|
+
|
123
|
+
// Attach optional events if elements exist
|
124
|
+
this.attachEvent("crud-show-create-button", this.showCreateForm.bind(this));
|
125
|
+
this.attachEvent("crud-create-button", this.createRow.bind(this));
|
126
|
+
this.attachEvent("crud-cancel-create-button", this.hideCreateForm.bind(this));
|
127
|
+
this.attachEvent("crud-update-button", this.updateRow.bind(this));
|
128
|
+
this.attachEvent("crud-cancel-update-button", this.hideUpdateForm.bind(this));
|
129
|
+
this.attachEvent("crud-delete-button", this.deleteRow.bind(this));
|
130
|
+
this.attachEvent("crud-cancel-delete-button", this.hideDeleteForm.bind(this));
|
131
|
+
this.attachEvent("crud-alert-close-button", this.hideAlert.bind(this));
|
132
|
+
|
133
|
+
// Initial data fetch
|
134
|
+
this.fetchRows(this.state.currentPage);
|
144
135
|
}
|
145
|
-
}
|
146
136
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
if (
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
137
|
+
attachEvent(elementId, handler) {
|
138
|
+
const el = document.getElementById(elementId);
|
139
|
+
if (el) el.addEventListener("click", handler);
|
140
|
+
}
|
141
|
+
|
142
|
+
async applySearch(event) {
|
143
|
+
if (event) event.preventDefault();
|
144
|
+
this.state.filter = this.filterInput.value;
|
145
|
+
await this.fetchRows(this.state.currentPage);
|
146
|
+
}
|
147
|
+
|
148
|
+
async fetchRows(page = null) {
|
149
|
+
try {
|
150
|
+
if (page !== null) {
|
151
|
+
this.state.currentPage = page;
|
152
|
+
}
|
153
|
+
const defaultSearchColumn = "my_column";
|
154
|
+
// Update address bar
|
155
|
+
const searchParam = CRUD_UTIL.getSearchParam(this.state, defaultSearchColumn, false);
|
156
|
+
const newUrl = `${window.location.pathname}?${searchParam}`;
|
157
|
+
window.history.pushState({ path: newUrl }, "", newUrl);
|
158
|
+
|
159
|
+
// Fetch table data
|
160
|
+
const apiSearchParam = CRUD_UTIL.getSearchParam(this.state, defaultSearchColumn, true);
|
161
|
+
const result = await UTIL.fetchAPI(`${this.apiUrl}?${apiSearchParam}`, { method: "GET" });
|
162
|
+
this.renderRows(result.data);
|
163
|
+
const crudPagination = document.getElementById("crud-pagination");
|
164
|
+
CRUD_UTIL.renderPagination(crudPagination, this, result.count);
|
165
|
+
} catch (error) {
|
166
|
+
console.error("Error fetching items:", error);
|
161
167
|
}
|
162
|
-
|
163
|
-
});
|
164
|
-
}
|
168
|
+
}
|
165
169
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
170
|
+
renderRows(rows) {
|
171
|
+
const tableBody = document.querySelector("#crud-table tbody");
|
172
|
+
let tableBodyHTML = "";
|
173
|
+
rows.forEach(row => {
|
174
|
+
const rowComponents = this.getRowComponents(row);
|
175
|
+
let actionColumn = "";
|
176
|
+
if (this.state.allowUpdate) {
|
177
|
+
actionColumn += `<button class="contrast" data-id="${row.id}" data-action="edit">✏️ Edit</button>`;
|
178
|
+
}
|
179
|
+
if (this.state.allowDelete) {
|
180
|
+
actionColumn += `<button class="secondary" data-id="${row.id}" data-action="delete">🗑️ Delete</button>`;
|
181
|
+
}
|
182
|
+
if (this.state.allowUpdate || this.state.allowDelete) {
|
183
|
+
actionColumn = `<td><fieldset class="grid" role="group">${actionColumn}</fieldset></td>`;
|
184
|
+
}
|
185
|
+
tableBodyHTML += `<tr>${rowComponents.join('')}${actionColumn}</tr>`;
|
186
|
+
});
|
187
|
+
tableBody.innerHTML = tableBodyHTML;
|
188
|
+
this.attachRowActionListeners();
|
189
|
+
}
|
173
190
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
191
|
+
attachRowActionListeners() {
|
192
|
+
document.querySelectorAll('button[data-action="edit"]').forEach(button => {
|
193
|
+
button.addEventListener("click", () => {
|
194
|
+
this.showUpdateForm(button.getAttribute("data-id"));
|
195
|
+
});
|
196
|
+
});
|
197
|
+
document.querySelectorAll('button[data-action="delete"]').forEach(button => {
|
198
|
+
button.addEventListener("click", () => {
|
199
|
+
this.showDeleteForm(button.getAttribute("data-id"));
|
200
|
+
});
|
201
|
+
});
|
202
|
+
}
|
181
203
|
|
182
|
-
|
183
|
-
|
184
|
-
|
204
|
+
getRowComponents(row) {
|
205
|
+
const rowComponents = [`<td>${row.id}</td>`, `<td>${row.my_column}</td>`];
|
206
|
+
return rowComponents;
|
185
207
|
}
|
186
|
-
|
208
|
+
|
209
|
+
// Create methods
|
210
|
+
showCreateForm(event = null) {
|
211
|
+
if (event) event.preventDefault();
|
212
|
+
const createDialog = document.getElementById("crud-create-form-dialog");
|
187
213
|
const createForm = document.getElementById("crud-create-form");
|
188
|
-
|
189
|
-
|
190
|
-
await fetchRows();
|
191
|
-
hideCreateForm();
|
192
|
-
} catch(error) {
|
193
|
-
showAlert("Create My Entity Error", error);
|
214
|
+
UTIL.clearFormData(createForm);
|
215
|
+
createDialog.showModal();
|
194
216
|
}
|
195
|
-
}
|
196
217
|
|
197
|
-
|
198
|
-
|
199
|
-
|
218
|
+
async createRow(event = null) {
|
219
|
+
if (event) event.preventDefault();
|
220
|
+
try {
|
221
|
+
const createForm = document.getElementById("crud-create-form");
|
222
|
+
const formData = UTIL.getFormData(createForm);
|
223
|
+
await UTIL.fetchAPI(this.apiUrl, { method: "POST", body: JSON.stringify(formData) });
|
224
|
+
await this.fetchRows();
|
225
|
+
this.hideCreateForm();
|
226
|
+
} catch (error) {
|
227
|
+
console.error(error);
|
228
|
+
this.showAlert("Create My Entity Error", error);
|
229
|
+
}
|
200
230
|
}
|
201
|
-
const createFormDialog = document.getElementById("crud-create-form-dialog");
|
202
|
-
createFormDialog.close();
|
203
|
-
}
|
204
|
-
{% endif %}
|
205
231
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
const updateFormDialog = document.getElementById("crud-update-form-dialog");
|
210
|
-
const updateForm = document.getElementById("crud-update-form");
|
211
|
-
result = await UTIL.fetchAPI(`${apiUrl}/${id}`, { method: "GET" });
|
212
|
-
UTIL.setFormData(updateForm, result);
|
213
|
-
updateFormDialog.showModal();
|
214
|
-
}
|
215
|
-
|
216
|
-
async function updateRow(event = null) {
|
217
|
-
if (event != null) {
|
218
|
-
event.preventDefault();
|
232
|
+
hideCreateForm(event = null) {
|
233
|
+
if (event) event.preventDefault();
|
234
|
+
document.getElementById("crud-create-form-dialog").close();
|
219
235
|
}
|
220
|
-
|
236
|
+
|
237
|
+
// Update methods
|
238
|
+
async showUpdateForm(id) {
|
239
|
+
this.state.updatedRowId = id;
|
240
|
+
const updateDialog = document.getElementById("crud-update-form-dialog");
|
221
241
|
const updateForm = document.getElementById("crud-update-form");
|
222
|
-
const formData =
|
223
|
-
|
224
|
-
|
225
|
-
);
|
226
|
-
await fetchRows();
|
227
|
-
hideUpdateForm();
|
228
|
-
} catch(error) {
|
229
|
-
showAlert("Update My Entity Error", error);
|
242
|
+
const formData = await UTIL.fetchAPI(`${this.apiUrl}/${id}`, { method: "GET" });
|
243
|
+
UTIL.setFormData(updateForm, formData);
|
244
|
+
updateDialog.showModal();
|
230
245
|
}
|
231
|
-
}
|
232
246
|
|
233
|
-
|
234
|
-
|
235
|
-
|
247
|
+
async updateRow(event = null) {
|
248
|
+
if (event) event.preventDefault();
|
249
|
+
try {
|
250
|
+
const updateForm = document.getElementById("crud-update-form");
|
251
|
+
const formData = UTIL.getFormData(updateForm);
|
252
|
+
await UTIL.fetchAPI(`${this.apiUrl}/${this.state.updatedRowId}`, {
|
253
|
+
method: "PUT",
|
254
|
+
body: JSON.stringify(formData)
|
255
|
+
});
|
256
|
+
await this.fetchRows();
|
257
|
+
this.hideUpdateForm();
|
258
|
+
} catch (error) {
|
259
|
+
console.error(error);
|
260
|
+
this.showAlert("Update My Entity Error", error);
|
261
|
+
}
|
236
262
|
}
|
237
|
-
const updateFormDialog = document.getElementById("crud-update-form-dialog");
|
238
|
-
updateFormDialog.close();
|
239
|
-
}
|
240
|
-
{% endif %}
|
241
263
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
const deleteForm = document.getElementById("crud-delete-form");
|
247
|
-
result = await UTIL.fetchAPI(`${apiUrl}/${id}`, { method: "GET" });
|
248
|
-
UTIL.setFormData(deleteForm, result);
|
249
|
-
deleteFormDialog.showModal();
|
250
|
-
}
|
264
|
+
hideUpdateForm(event = null) {
|
265
|
+
if (event) event.preventDefault();
|
266
|
+
document.getElementById("crud-update-form-dialog").close();
|
267
|
+
}
|
251
268
|
|
252
|
-
|
253
|
-
|
254
|
-
|
269
|
+
// Delete methods
|
270
|
+
async showDeleteForm(id) {
|
271
|
+
this.state.deletedRowId = id;
|
272
|
+
const deleteDialog = document.getElementById("crud-delete-form-dialog");
|
273
|
+
const deleteForm = document.getElementById("crud-delete-form");
|
274
|
+
const formData = await UTIL.fetchAPI(`${this.apiUrl}/${id}`, { method: "GET" });
|
275
|
+
UTIL.setFormData(deleteForm, formData);
|
276
|
+
deleteDialog.showModal();
|
255
277
|
}
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
278
|
+
|
279
|
+
async deleteRow(event = null) {
|
280
|
+
if (event) event.preventDefault();
|
281
|
+
try {
|
282
|
+
await UTIL.fetchAPI(`${this.apiUrl}/${this.state.deletedRowId}`, { method: "DELETE" });
|
283
|
+
await this.fetchRows();
|
284
|
+
this.hideDeleteForm();
|
285
|
+
} catch (error) {
|
286
|
+
console.error(error);
|
287
|
+
this.showAlert("Delete My Entity Error", error);
|
288
|
+
}
|
262
289
|
}
|
263
|
-
}
|
264
290
|
|
265
|
-
|
266
|
-
|
267
|
-
|
291
|
+
hideDeleteForm(event = null) {
|
292
|
+
if (event) event.preventDefault();
|
293
|
+
document.getElementById("crud-delete-form-dialog").close();
|
268
294
|
}
|
269
|
-
const deleteFormDialog = document.getElementById("crud-delete-form-dialog");
|
270
|
-
deleteFormDialog.close();
|
271
|
-
}
|
272
|
-
{% endif %}
|
273
295
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
alertDialog.showModal();
|
282
|
-
}
|
296
|
+
// Alert methods
|
297
|
+
showAlert(title, error) {
|
298
|
+
const alertDialog = document.getElementById("crud-alert-dialog");
|
299
|
+
document.getElementById("crud-alert-title").textContent = title;
|
300
|
+
document.getElementById("crud-alert-message").textContent = error.message || String(error);
|
301
|
+
alertDialog.showModal();
|
302
|
+
}
|
283
303
|
|
284
|
-
|
285
|
-
|
286
|
-
|
304
|
+
hideAlert(event = null) {
|
305
|
+
if (event) event.preventDefault();
|
306
|
+
document.getElementById("crud-alert-dialog").close();
|
287
307
|
}
|
288
|
-
const alertDialog = document.getElementById("crud-alert-dialog");
|
289
|
-
alertDialog.close();
|
290
308
|
}
|
291
309
|
|
310
|
+
// Initialize the CrudApp on DOM ready
|
292
311
|
document.addEventListener("DOMContentLoaded", () => {
|
293
|
-
const
|
294
|
-
|
295
|
-
|
312
|
+
const app = document.getElementById("crud-app");
|
313
|
+
new CrudApp("/api/v1/my-entities", {
|
314
|
+
pageSize: JSON.parse(app.dataset.pageSize),
|
315
|
+
currentPage: JSON.parse(app.dataset.page),
|
316
|
+
sort: JSON.parse(app.dataset.sort),
|
317
|
+
filter: JSON.parse(app.dataset.filter),
|
318
|
+
allowCreate: JSON.parse(app.dataset.allowCreate),
|
319
|
+
allowUpdate: JSON.parse(app.dataset.allowUpdate),
|
320
|
+
allowDelete: JSON.parse(app.dataset.allowDelete),
|
321
|
+
updatedRowId: null,
|
322
|
+
deletedRowId: null,
|
323
|
+
});
|
296
324
|
});
|
297
325
|
</script>
|
@@ -22,7 +22,9 @@ class MyEntityUpdate(SQLModel):
|
|
22
22
|
my_column: str | None = None
|
23
23
|
|
24
24
|
def with_audit(self, updated_by: str) -> "MyEntityUpdateWithAudit":
|
25
|
-
return MyEntityUpdateWithAudit(
|
25
|
+
return MyEntityUpdateWithAudit(
|
26
|
+
**self.model_dump(exclude_none=True), updated_by=updated_by
|
27
|
+
)
|
26
28
|
|
27
29
|
|
28
30
|
class MyEntityUpdateWithAudit(MyEntityUpdate):
|
@@ -3,6 +3,7 @@ from typing import Any
|
|
3
3
|
|
4
4
|
import ulid
|
5
5
|
from my_app_name.common.base_db_repository import BaseDBRepository
|
6
|
+
from my_app_name.common.error import InvalidValueError
|
6
7
|
from my_app_name.module.auth.service.role.repository.role_repository import (
|
7
8
|
RoleRepository,
|
8
9
|
)
|
@@ -65,6 +66,20 @@ class RoleDBRepository(
|
|
65
66
|
for data in role_map.values()
|
66
67
|
]
|
67
68
|
|
69
|
+
async def validate_permission_names(self, permission_names: list[str]):
|
70
|
+
async with self._session_scope() as session:
|
71
|
+
result = await self._execute_statement(
|
72
|
+
session,
|
73
|
+
select(Permission.name).where(Permission.name.in_(permission_names)),
|
74
|
+
)
|
75
|
+
existing_permissions = {row[0] for row in result.all()}
|
76
|
+
# Identify any missing permission names
|
77
|
+
missing_permissions = set(permission_names) - existing_permissions
|
78
|
+
if missing_permissions:
|
79
|
+
raise InvalidValueError(
|
80
|
+
f"Permission(s) not found: {', '.join(missing_permissions)}"
|
81
|
+
)
|
82
|
+
|
68
83
|
async def add_permissions(self, data: dict[str, list[str]], created_by: str):
|
69
84
|
now = datetime.datetime.now(datetime.timezone.utc)
|
70
85
|
# get mapping from perrmission names to permission ids
|
@@ -94,6 +109,8 @@ class RoleDBRepository(
|
|
94
109
|
)
|
95
110
|
)
|
96
111
|
)
|
112
|
+
if len(data_dict_list) == 0:
|
113
|
+
return
|
97
114
|
# Insert rolePermissions
|
98
115
|
async with self._session_scope() as session:
|
99
116
|
await self._execute_statement(
|
@@ -104,5 +121,5 @@ class RoleDBRepository(
|
|
104
121
|
async with self._session_scope() as session:
|
105
122
|
await self._execute_statement(
|
106
123
|
session,
|
107
|
-
delete(RolePermission).where(RolePermission.role_id.
|
124
|
+
delete(RolePermission).where(RolePermission.role_id.in_(role_ids)),
|
108
125
|
)
|
@@ -18,6 +18,10 @@ class RoleRepository(ABC):
|
|
18
18
|
async def get_by_ids(self, id_list: list[str]) -> RoleResponse:
|
19
19
|
"""Get roles by ids"""
|
20
20
|
|
21
|
+
@abstractmethod
|
22
|
+
async def validate_permission_names(self, permission_names: list[str]):
|
23
|
+
"""Validate Permission names"""
|
24
|
+
|
21
25
|
@abstractmethod
|
22
26
|
async def add_permissions(self, data: dict[str, list[str]], created_by: str):
|
23
27
|
"""Adding permissions to roles"""
|
@@ -50,13 +50,17 @@ class RoleService(BaseService):
|
|
50
50
|
async def create_role_bulk(
|
51
51
|
self, data: list[RoleCreateWithPermissionsAndAudit]
|
52
52
|
) -> list[RoleResponse]:
|
53
|
-
|
54
|
-
|
55
|
-
|
53
|
+
bulk_permission_names = [row.get_permission_names() for row in data]
|
54
|
+
for permission_names in bulk_permission_names:
|
55
|
+
await self.role_repository.validate_permission_names(permission_names)
|
56
|
+
bulk_role_data = [row.get_role_create_with_audit() for row in data]
|
57
|
+
roles = await self.role_repository.create_bulk(bulk_role_data)
|
56
58
|
if len(roles) > 0:
|
57
59
|
created_by = roles[0].created_by
|
58
60
|
await self.role_repository.add_permissions(
|
59
|
-
data={
|
61
|
+
data={
|
62
|
+
role.id: bulk_permission_names[i] for i, role in enumerate(roles)
|
63
|
+
},
|
60
64
|
created_by=created_by,
|
61
65
|
)
|
62
66
|
return await self.role_repository.get_by_ids([role.id for role in roles])
|
@@ -70,8 +74,9 @@ class RoleService(BaseService):
|
|
70
74
|
self, data: RoleCreateWithPermissionsAndAudit
|
71
75
|
) -> RoleResponse:
|
72
76
|
permission_names = data.get_permission_names()
|
73
|
-
|
74
|
-
|
77
|
+
await self.role_repository.validate_permission_names(permission_names)
|
78
|
+
role_data = data.get_role_create_with_audit()
|
79
|
+
role = await self.role_repository.create(role_data)
|
75
80
|
await self.role_repository.add_permissions(
|
76
81
|
data={role.id: permission_names}, created_by=role.created_by
|
77
82
|
)
|
@@ -85,15 +90,18 @@ class RoleService(BaseService):
|
|
85
90
|
async def update_role_bulk(
|
86
91
|
self, role_ids: list[str], data: RoleUpdateWithPermissionsAndAudit
|
87
92
|
) -> list[RoleResponse]:
|
88
|
-
|
89
|
-
|
90
|
-
|
93
|
+
bulk_permission_names = [row.get_permission_names() for row in data]
|
94
|
+
for permission_names in bulk_permission_names:
|
95
|
+
await self.role_repository.validate_permission_names(permission_names)
|
96
|
+
bulk_role_data = [row.get_role_update_with_audit() for row in data]
|
97
|
+
await self.role_repository.update_bulk(role_ids, bulk_role_data)
|
91
98
|
if len(role_ids) > 0:
|
92
|
-
updated_by =
|
99
|
+
updated_by = bulk_role_data[0].updated_by
|
93
100
|
await self.role_repository.remove_all_permissions(role_ids)
|
94
101
|
await self.role_repository.add_permissions(
|
95
102
|
data={
|
96
|
-
role_id:
|
103
|
+
role_id: bulk_permission_names[i]
|
104
|
+
for i, role_id in enumerate(role_ids)
|
97
105
|
},
|
98
106
|
created_by=updated_by,
|
99
107
|
)
|
@@ -108,6 +116,7 @@ class RoleService(BaseService):
|
|
108
116
|
self, role_id: str, data: RoleUpdateWithPermissionsAndAudit
|
109
117
|
) -> RoleResponse:
|
110
118
|
permission_names = data.get_permission_names()
|
119
|
+
await self.role_repository.validate_permission_names(permission_names)
|
111
120
|
role_data = data.get_role_update_with_audit()
|
112
121
|
await self.role_repository.update(role_id, role_data)
|
113
122
|
await self.role_repository.remove_all_permissions([role_id])
|
@@ -3,7 +3,7 @@ from typing import Any
|
|
3
3
|
|
4
4
|
import ulid
|
5
5
|
from my_app_name.common.base_db_repository import BaseDBRepository
|
6
|
-
from my_app_name.common.error import NotFoundError, UnauthorizedError
|
6
|
+
from my_app_name.common.error import InvalidValueError, NotFoundError, UnauthorizedError
|
7
7
|
from my_app_name.module.auth.service.user.repository.user_repository import (
|
8
8
|
UserRepository,
|
9
9
|
)
|
@@ -87,6 +87,19 @@ class UserDBRepository(
|
|
87
87
|
for data in user_map.values()
|
88
88
|
]
|
89
89
|
|
90
|
+
async def validate_role_names(self, role_names: list[str]):
|
91
|
+
async with self._session_scope() as session:
|
92
|
+
result = await self._execute_statement(
|
93
|
+
session, select(Role.name).where(Role.name.in_(role_names))
|
94
|
+
)
|
95
|
+
existing_roles = {row[0] for row in result.all()}
|
96
|
+
# Identify any missing role names
|
97
|
+
missing_roles = set(role_names) - existing_roles
|
98
|
+
if missing_roles:
|
99
|
+
raise InvalidValueError(
|
100
|
+
f"Role(s) not found: {', '.join(missing_roles)}"
|
101
|
+
)
|
102
|
+
|
90
103
|
async def add_roles(self, data: dict[str, list[str]], created_by: str):
|
91
104
|
now = datetime.datetime.now(datetime.timezone.utc)
|
92
105
|
# get mapping from role names to role ids
|
@@ -111,6 +124,8 @@ class UserDBRepository(
|
|
111
124
|
)
|
112
125
|
)
|
113
126
|
)
|
127
|
+
if len(data_dict_list) == 0:
|
128
|
+
return
|
114
129
|
async with self._session_scope() as session:
|
115
130
|
await self._execute_statement(
|
116
131
|
session, insert(UserRole).values(data_dict_list)
|
@@ -120,7 +135,7 @@ class UserDBRepository(
|
|
120
135
|
async with self._session_scope() as session:
|
121
136
|
await self._execute_statement(
|
122
137
|
session,
|
123
|
-
delete(UserRole).where(UserRole.user_id.
|
138
|
+
delete(UserRole).where(UserRole.user_id.in_(user_ids)),
|
124
139
|
)
|
125
140
|
|
126
141
|
async def get_by_credentials(self, username: str, password: str) -> UserResponse:
|