appflare 0.2.7 → 0.2.9
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/cli/commands/index.ts +9 -21
- package/cli/templates/dashboard/builders/table-routes/table/get-route.ts +132 -7
- package/cli/templates/dashboard/builders/table-routes/table/index.ts +3 -0
- package/cli/templates/dashboard/builders/table-routes/table/post-routes.ts +52 -0
- package/cli/templates/dashboard/index.ts +1 -1
- package/package.json +1 -1
package/cli/commands/index.ts
CHANGED
|
@@ -93,7 +93,6 @@ export async function runMigrate(
|
|
|
93
93
|
options: MigrateOptions = {},
|
|
94
94
|
): Promise<void> {
|
|
95
95
|
const loadedConfig = await loadConfig(configPath);
|
|
96
|
-
const npxCommand = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
97
96
|
const packageDir = findNearestPackageDir(process.cwd());
|
|
98
97
|
const selectedTargetCount = [
|
|
99
98
|
Boolean(options.local),
|
|
@@ -110,7 +109,7 @@ export async function runMigrate(
|
|
|
110
109
|
"drizzle.config.ts",
|
|
111
110
|
);
|
|
112
111
|
const drizzleGenerate = Bun.spawn(
|
|
113
|
-
[
|
|
112
|
+
["drizzle-kit", "generate", "--config", drizzleConfigPath],
|
|
114
113
|
{
|
|
115
114
|
cwd: packageDir,
|
|
116
115
|
stdin: "inherit",
|
|
@@ -119,7 +118,6 @@ export async function runMigrate(
|
|
|
119
118
|
},
|
|
120
119
|
);
|
|
121
120
|
|
|
122
|
-
console.log(`npx drizzle-kit generate --config ${drizzleConfigPath}`);
|
|
123
121
|
const drizzleExitCode = await drizzleGenerate.exited;
|
|
124
122
|
if (drizzleExitCode !== 0) {
|
|
125
123
|
throw new Error(
|
|
@@ -128,14 +126,7 @@ export async function runMigrate(
|
|
|
128
126
|
}
|
|
129
127
|
|
|
130
128
|
const databaseName = loadedConfig.config.database[0].databaseName;
|
|
131
|
-
const wranglerArgs = [
|
|
132
|
-
npxCommand,
|
|
133
|
-
"wrangler",
|
|
134
|
-
"d1",
|
|
135
|
-
"migrations",
|
|
136
|
-
"apply",
|
|
137
|
-
databaseName,
|
|
138
|
-
];
|
|
129
|
+
const wranglerArgs = ["wrangler", "d1", "migrations", "apply", databaseName];
|
|
139
130
|
|
|
140
131
|
if (options.local) {
|
|
141
132
|
wranglerArgs.push("--local");
|
|
@@ -171,7 +162,6 @@ export async function runAddAdmin(
|
|
|
171
162
|
} = { name: "", email: "", password: "" },
|
|
172
163
|
): Promise<void> {
|
|
173
164
|
const loadedConfig = await loadConfig(configPath);
|
|
174
|
-
const npxCommand = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
175
165
|
|
|
176
166
|
const selectedTargetCount = [
|
|
177
167
|
Boolean(options.local),
|
|
@@ -192,22 +182,20 @@ export async function runAddAdmin(
|
|
|
192
182
|
const safeName = options.name.replace(/'/g, "''");
|
|
193
183
|
const safeEmail = options.email.replace(/'/g, "''");
|
|
194
184
|
|
|
195
|
-
const sqlQuery =
|
|
196
|
-
INSERT INTO users (id, name, email, email_verified, created_at, updated_at, role, banned)
|
|
197
|
-
VALUES ('${userId}', '${safeName}', '${safeEmail}', 1, ${now}, ${now}, 'admin', 0)
|
|
198
|
-
INSERT INTO accounts (id, account_id, provider_id, user_id, password, created_at, updated_at)
|
|
199
|
-
VALUES ('${accountId}', '${safeEmail}', 'credential', '${userId}', '${passwordHash}', ${now}, ${now})
|
|
200
|
-
|
|
185
|
+
const sqlQuery = [
|
|
186
|
+
"INSERT INTO users (id, name, email, email_verified, created_at, updated_at, role, banned)",
|
|
187
|
+
`VALUES ('${userId}', '${safeName}', '${safeEmail}', 1, ${now}, ${now}, 'admin', 0);`,
|
|
188
|
+
"INSERT INTO accounts (id, account_id, provider_id, user_id, password, created_at, updated_at)",
|
|
189
|
+
`VALUES ('${accountId}', '${safeEmail}', 'credential', '${userId}', '${passwordHash}', ${now}, ${now});`,
|
|
190
|
+
].join(" ");
|
|
201
191
|
|
|
202
192
|
const databaseName = loadedConfig.config.database[0].databaseName;
|
|
203
193
|
const wranglerArgs = [
|
|
204
|
-
npxCommand,
|
|
205
194
|
"wrangler",
|
|
206
195
|
"d1",
|
|
207
196
|
"execute",
|
|
208
197
|
databaseName,
|
|
209
|
-
|
|
210
|
-
sqlQuery,
|
|
198
|
+
`--command=${sqlQuery}`,
|
|
211
199
|
];
|
|
212
200
|
|
|
213
201
|
if (options.local) {
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { DiscoveredTable } from "../../../types";
|
|
2
|
-
import { buildColumnHeaders, buildRowCells } from "../fragments";
|
|
3
2
|
import { buildPaginationHtml } from "../common/pagination";
|
|
4
3
|
import { buildSearchBarHtml } from "../common/search-bar";
|
|
5
|
-
import { buildActionsCell } from "./actions-cell";
|
|
6
|
-
import { resolvePrimaryKey } from "../helpers";
|
|
7
|
-
import { buildFieldInput, buildSearchConditions } from "../fragments";
|
|
8
|
-
import { shouldIncludeEditField, shouldIncludeCreateField } from "../helpers";
|
|
9
4
|
|
|
10
5
|
/**
|
|
11
6
|
* Builds the GET route handler code string for /admin/table/:tableName
|
|
@@ -14,6 +9,8 @@ import { shouldIncludeEditField, shouldIncludeCreateField } from "../helpers";
|
|
|
14
9
|
export function buildTableGetRoute(
|
|
15
10
|
table: DiscoveredTable,
|
|
16
11
|
defaultSort: string,
|
|
12
|
+
primaryKey: string,
|
|
13
|
+
hasPrimaryKey: boolean,
|
|
17
14
|
columns: string[],
|
|
18
15
|
searchConditions: string,
|
|
19
16
|
headers: string,
|
|
@@ -28,6 +25,133 @@ export function buildTableGetRoute(
|
|
|
28
25
|
`/admin/table/${table.exportName}`,
|
|
29
26
|
"Search term or filter...",
|
|
30
27
|
);
|
|
28
|
+
const headerSelectionCell = hasPrimaryKey
|
|
29
|
+
? `<th class="w-10"><input id="select-all-${table.exportName}" type="checkbox" class="checkbox checkbox-xs" /></th>`
|
|
30
|
+
: `<th class="w-10"><input type="checkbox" class="checkbox checkbox-xs opacity-30" disabled /></th>`;
|
|
31
|
+
const rowSelectionCell = hasPrimaryKey
|
|
32
|
+
? `<td><input type="checkbox" class="checkbox checkbox-xs row-select-checkbox" value="\${String((row as any).${primaryKey} ?? '')}" /></td>`
|
|
33
|
+
: `<td><input type="checkbox" class="checkbox checkbox-xs opacity-30" disabled /></td>`;
|
|
34
|
+
const bulkDeleteUi = hasPrimaryKey
|
|
35
|
+
? `
|
|
36
|
+
\t\t\t\t\t\t<div id="bulk-delete-bar-${table.exportName}" class="fixed bottom-4 left-1/2 -translate-x-1/2 z-40 hidden">
|
|
37
|
+
\t\t\t\t\t\t\t<div class="bg-base-100 border border-base-200 rounded-xl shadow-lg px-3 py-2 flex items-center gap-3">
|
|
38
|
+
\t\t\t\t\t\t\t\t<div class="text-xs text-base-content/70">
|
|
39
|
+
\t\t\t\t\t\t\t\t\t<span id="bulk-selected-count-${table.exportName}" class="font-medium text-base-content">0</span> selected
|
|
40
|
+
\t\t\t\t\t\t\t\t</div>
|
|
41
|
+
\t\t\t\t\t\t\t\t<label class="label cursor-pointer gap-2 py-0 px-1">
|
|
42
|
+
\t\t\t\t\t\t\t\t\t<span class="label-text text-xs">All matching (\${total})</span>
|
|
43
|
+
\t\t\t\t\t\t\t\t\t<input id="bulk-all-matching-${table.exportName}" type="checkbox" class="checkbox checkbox-xs" />
|
|
44
|
+
\t\t\t\t\t\t\t\t</label>
|
|
45
|
+
\t\t\t\t\t\t\t\t<button id="bulk-delete-trigger-${table.exportName}" type="button" class="btn btn-error btn-xs gap-1">
|
|
46
|
+
\t\t\t\t\t\t\t\t\t<iconify-icon icon="mdi:delete-outline" width="14" height="14"></iconify-icon>
|
|
47
|
+
\t\t\t\t\t\t\t\t\tDelete selected
|
|
48
|
+
\t\t\t\t\t\t\t\t</button>
|
|
49
|
+
\t\t\t\t\t\t\t</div>
|
|
50
|
+
\t\t\t\t\t\t</div>
|
|
51
|
+
|
|
52
|
+
\t\t\t\t\t\t<dialog id="bulk-delete-modal-${table.exportName}" class="modal">
|
|
53
|
+
\t\t\t\t\t\t\t<div class="modal-box max-w-sm p-6 space-y-4">
|
|
54
|
+
\t\t\t\t\t\t\t\t<h3 class="font-semibold text-base">Delete selected rows?</h3>
|
|
55
|
+
\t\t\t\t\t\t\t\t<p class="text-sm text-base-content/70" id="bulk-delete-description-${table.exportName}">This action cannot be undone.</p>
|
|
56
|
+
\t\t\t\t\t\t\t\t<form id="bulk-delete-form-${table.exportName}" hx-post="/admin/table/${table.exportName}/delete-bulk" hx-target="#main-content" hx-swap="outerHTML" class="space-y-3">
|
|
57
|
+
\t\t\t\t\t\t\t\t\t<input type="hidden" name="bulkMode" id="bulk-delete-mode-${table.exportName}" value="selected" />
|
|
58
|
+
\t\t\t\t\t\t\t\t\t<input type="hidden" name="selectedIds" id="bulk-delete-ids-${table.exportName}" value="" />
|
|
59
|
+
\t\t\t\t\t\t\t\t\t<input type="hidden" name="sort" value="\${sort}" />
|
|
60
|
+
\t\t\t\t\t\t\t\t\t<input type="hidden" name="order" value="\${order}" />
|
|
61
|
+
\t\t\t\t\t\t\t\t\t<input type="hidden" name="search" value="\${search}" />
|
|
62
|
+
\t\t\t\t\t\t\t\t\t<input type="hidden" name="page" value="\${page}" />
|
|
63
|
+
\t\t\t\t\t\t\t\t\t<div class="flex justify-end gap-2 pt-1">
|
|
64
|
+
\t\t\t\t\t\t\t\t\t\t<button type="button" class="btn btn-ghost btn-sm" onclick="this.closest('dialog')?.close()">Cancel</button>
|
|
65
|
+
\t\t\t\t\t\t\t\t\t\t<button type="submit" class="btn btn-error btn-sm">Delete</button>
|
|
66
|
+
\t\t\t\t\t\t\t\t\t</div>
|
|
67
|
+
\t\t\t\t\t\t\t\t</form>
|
|
68
|
+
\t\t\t\t\t\t\t</div>
|
|
69
|
+
\t\t\t\t\t\t\t<form method="dialog" class="modal-backdrop"><button>close</button></form>
|
|
70
|
+
\t\t\t\t\t\t</dialog>
|
|
71
|
+
|
|
72
|
+
\t\t\t\t\t\t<script>
|
|
73
|
+
\t\t\t\t\t\t\t(() => {
|
|
74
|
+
\t\t\t\t\t\t\t\tconst container = document.querySelector('#main-content');
|
|
75
|
+
\t\t\t\t\t\t\t\tif (!container) return;
|
|
76
|
+
|
|
77
|
+
\t\t\t\t\t\t\t\tconst rowCheckboxes = Array.from(container.querySelectorAll('.row-select-checkbox')).filter((node) => node instanceof HTMLInputElement);
|
|
78
|
+
\t\t\t\t\t\t\t\tif (rowCheckboxes.length === 0) return;
|
|
79
|
+
|
|
80
|
+
\t\t\t\t\t\t\t\tconst selectAll = container.querySelector('#select-all-${table.exportName}');
|
|
81
|
+
\t\t\t\t\t\t\t\tconst bar = container.querySelector('#bulk-delete-bar-${table.exportName}');
|
|
82
|
+
\t\t\t\t\t\t\t\tconst selectedCount = container.querySelector('#bulk-selected-count-${table.exportName}');
|
|
83
|
+
\t\t\t\t\t\t\t\tconst allMatching = container.querySelector('#bulk-all-matching-${table.exportName}');
|
|
84
|
+
\t\t\t\t\t\t\t\tconst trigger = container.querySelector('#bulk-delete-trigger-${table.exportName}');
|
|
85
|
+
\t\t\t\t\t\t\t\tconst modal = container.querySelector('#bulk-delete-modal-${table.exportName}');
|
|
86
|
+
\t\t\t\t\t\t\t\tconst modeInput = container.querySelector('#bulk-delete-mode-${table.exportName}');
|
|
87
|
+
\t\t\t\t\t\t\t\tconst idsInput = container.querySelector('#bulk-delete-ids-${table.exportName}');
|
|
88
|
+
\t\t\t\t\t\t\t\tconst description = container.querySelector('#bulk-delete-description-${table.exportName}');
|
|
89
|
+
|
|
90
|
+
\t\t\t\t\t\t\t\tif (!(selectAll instanceof HTMLInputElement) ||
|
|
91
|
+
\t\t\t\t\t\t\t\t\t!(bar instanceof HTMLElement) ||
|
|
92
|
+
\t\t\t\t\t\t\t\t\t!(selectedCount instanceof HTMLElement) ||
|
|
93
|
+
\t\t\t\t\t\t\t\t\t!(allMatching instanceof HTMLInputElement) ||
|
|
94
|
+
\t\t\t\t\t\t\t\t\t!(trigger instanceof HTMLButtonElement) ||
|
|
95
|
+
\t\t\t\t\t\t\t\t\t!(modal instanceof HTMLDialogElement) ||
|
|
96
|
+
\t\t\t\t\t\t\t\t\t!(modeInput instanceof HTMLInputElement) ||
|
|
97
|
+
\t\t\t\t\t\t\t\t\t!(idsInput instanceof HTMLInputElement) ||
|
|
98
|
+
\t\t\t\t\t\t\t\t\t!(description instanceof HTMLElement)) {
|
|
99
|
+
\t\t\t\t\t\t\t\t\treturn;
|
|
100
|
+
\t\t\t\t\t\t\t\t}
|
|
101
|
+
|
|
102
|
+
\t\t\t\t\t\t\t\tconst totalRows = Number(\${total});
|
|
103
|
+
\t\t\t\t\t\t\t\tconst getSelectedIds = () => rowCheckboxes.filter((checkbox) => checkbox.checked).map((checkbox) => checkbox.value).filter((value) => value.length > 0);
|
|
104
|
+
|
|
105
|
+
\t\t\t\t\t\t\t\tconst updateUi = () => {
|
|
106
|
+
\t\t\t\t\t\t\t\t\tconst ids = getSelectedIds();
|
|
107
|
+
\t\t\t\t\t\t\t\t\tconst selected = allMatching.checked ? totalRows : ids.length;
|
|
108
|
+
\t\t\t\t\t\t\t\t\tselectedCount.textContent = String(selected);
|
|
109
|
+
\t\t\t\t\t\t\t\t\tbar.classList.toggle('hidden', selected === 0);
|
|
110
|
+
\t\t\t\t\t\t\t\t\tconst checkedCount = ids.length;
|
|
111
|
+
\t\t\t\t\t\t\t\t\tselectAll.checked = checkedCount > 0 && checkedCount === rowCheckboxes.length;
|
|
112
|
+
\t\t\t\t\t\t\t\t\tselectAll.indeterminate = checkedCount > 0 && checkedCount < rowCheckboxes.length;
|
|
113
|
+
\t\t\t\t\t\t\t\t};
|
|
114
|
+
|
|
115
|
+
\t\t\t\t\t\t\t\trowCheckboxes.forEach((checkbox) => {
|
|
116
|
+
\t\t\t\t\t\t\t\t\tcheckbox.addEventListener('change', () => {
|
|
117
|
+
\t\t\t\t\t\t\t\t\t\tif (allMatching.checked) allMatching.checked = false;
|
|
118
|
+
\t\t\t\t\t\t\t\t\t\tupdateUi();
|
|
119
|
+
\t\t\t\t\t\t\t\t\t});
|
|
120
|
+
\t\t\t\t\t\t\t\t});
|
|
121
|
+
|
|
122
|
+
\t\t\t\t\t\t\t\tselectAll.addEventListener('change', () => {
|
|
123
|
+
\t\t\t\t\t\t\t\t\trowCheckboxes.forEach((checkbox) => {
|
|
124
|
+
\t\t\t\t\t\t\t\t\t\tcheckbox.checked = selectAll.checked;
|
|
125
|
+
\t\t\t\t\t\t\t\t\t});
|
|
126
|
+
\t\t\t\t\t\t\t\t\tif (allMatching.checked && !selectAll.checked) allMatching.checked = false;
|
|
127
|
+
\t\t\t\t\t\t\t\t\tupdateUi();
|
|
128
|
+
\t\t\t\t\t\t\t\t});
|
|
129
|
+
|
|
130
|
+
\t\t\t\t\t\t\t\tallMatching.addEventListener('change', () => {
|
|
131
|
+
\t\t\t\t\t\t\t\t\tif (allMatching.checked) {
|
|
132
|
+
\t\t\t\t\t\t\t\t\t\trowCheckboxes.forEach((checkbox) => {
|
|
133
|
+
\t\t\t\t\t\t\t\t\t\t\tcheckbox.checked = true;
|
|
134
|
+
\t\t\t\t\t\t\t\t\t\t});
|
|
135
|
+
\t\t\t\t\t\t\t\t\t}
|
|
136
|
+
\t\t\t\t\t\t\t\t\tupdateUi();
|
|
137
|
+
\t\t\t\t\t\t\t\t});
|
|
138
|
+
|
|
139
|
+
\t\t\t\t\t\t\t\ttrigger.addEventListener('click', () => {
|
|
140
|
+
\t\t\t\t\t\t\t\t\tconst ids = getSelectedIds();
|
|
141
|
+
\t\t\t\t\t\t\t\t\tif (!allMatching.checked && ids.length === 0) return;
|
|
142
|
+
\t\t\t\t\t\t\t\t\tmodeInput.value = allMatching.checked ? 'all-matching' : 'selected';
|
|
143
|
+
\t\t\t\t\t\t\t\t\tidsInput.value = ids.join(',');
|
|
144
|
+
\t\t\t\t\t\t\t\t\tdescription.textContent = allMatching.checked
|
|
145
|
+
\t\t\t\t\t\t\t\t\t\t? 'This will permanently delete all rows matching the current search context.'
|
|
146
|
+
\t\t\t\t\t\t\t\t\t\t: 'This action cannot be undone.';
|
|
147
|
+
\t\t\t\t\t\t\t\t\tmodal.showModal();
|
|
148
|
+
\t\t\t\t\t\t\t\t});
|
|
149
|
+
|
|
150
|
+
\t\t\t\t\t\t\t\tupdateUi();
|
|
151
|
+
\t\t\t\t\t\t\t})();
|
|
152
|
+
\t\t\t\t\t\t</script>
|
|
153
|
+
`
|
|
154
|
+
: "";
|
|
31
155
|
|
|
32
156
|
return `
|
|
33
157
|
\tadminApp.get('/table/${table.exportName}', async (c) => {
|
|
@@ -69,7 +193,7 @@ export function buildTableGetRoute(
|
|
|
69
193
|
\t\t\t\t\t<table class="table table-sm md:table-md w-full">
|
|
70
194
|
\t\t\t\t\t\t<thead>
|
|
71
195
|
\t\t\t\t\t\t\t<tr class="border-b border-base-200">
|
|
72
|
-
\t\t\t\t\t\t\t\t
|
|
196
|
+
\t\t\t\t\t\t\t\t${headerSelectionCell}
|
|
73
197
|
\t\t\t\t\t\t\t\t${headers}
|
|
74
198
|
\t\t\t\t\t\t\t\t<th class="w-[100px] text-right">
|
|
75
199
|
\t\t\t\t\t\t\t\t\t<iconify-icon icon="mdi:dots-horizontal" width="16" height="16" class="opacity-30"></iconify-icon>
|
|
@@ -79,7 +203,7 @@ export function buildTableGetRoute(
|
|
|
79
203
|
\t\t\t\t\t\t<tbody>
|
|
80
204
|
\t\t\t\t\t\t\t\${data.map((row, rowIndex) => html\`
|
|
81
205
|
\t\t\t\t\t\t\t<tr class="hover:bg-base-200/30 transition-colors">
|
|
82
|
-
\t\t\t\t\t\t\t\t
|
|
206
|
+
\t\t\t\t\t\t\t\t${rowSelectionCell}
|
|
83
207
|
\t\t\t\t\t\t\t\t${rowCells}
|
|
84
208
|
\t\t\t\t\t\t\t\t${actionsCell}
|
|
85
209
|
\t\t\t\t\t\t\t</tr>
|
|
@@ -130,6 +254,7 @@ export function buildTableGetRoute(
|
|
|
130
254
|
\t\t\t\t\t\t\t${searchBarHtml}
|
|
131
255
|
\t\t\t\t\t\t</div>
|
|
132
256
|
\t\t\t\t\t\t\${tableHtml}
|
|
257
|
+
\t\t\t\t\t\t${bulkDeleteUi}
|
|
133
258
|
\t\t\t\t\t</div>
|
|
134
259
|
\t\t\t\t\t<div class="drawer-side z-50">
|
|
135
260
|
\t\t\t\t\t\t<label for="create-drawer-${table.exportName}" aria-label="close sidebar" class="drawer-overlay"></label>
|
|
@@ -56,6 +56,8 @@ export function buildTableRoute(table: DiscoveredTable): string {
|
|
|
56
56
|
buildTableGetRoute(
|
|
57
57
|
table,
|
|
58
58
|
defaultSort,
|
|
59
|
+
primaryKey,
|
|
60
|
+
hasPrimaryKey,
|
|
59
61
|
columns,
|
|
60
62
|
searchConditions,
|
|
61
63
|
headers,
|
|
@@ -70,6 +72,7 @@ export function buildTableRoute(table: DiscoveredTable): string {
|
|
|
70
72
|
primaryKey,
|
|
71
73
|
primaryKeyType,
|
|
72
74
|
hasPrimaryKey,
|
|
75
|
+
searchConditions,
|
|
73
76
|
createAssignments,
|
|
74
77
|
editAssignments,
|
|
75
78
|
)
|
|
@@ -8,6 +8,7 @@ export function buildTablePostRoutes(
|
|
|
8
8
|
primaryKey: string,
|
|
9
9
|
primaryKeyType: string | undefined,
|
|
10
10
|
hasPrimaryKey: boolean,
|
|
11
|
+
searchConditions: string,
|
|
11
12
|
createAssignments: string,
|
|
12
13
|
editAssignments: string,
|
|
13
14
|
): string {
|
|
@@ -20,6 +21,11 @@ export function buildTablePostRoutes(
|
|
|
20
21
|
\t\t`
|
|
21
22
|
: "";
|
|
22
23
|
|
|
24
|
+
const parseBulkIds =
|
|
25
|
+
primaryKeyType === "number"
|
|
26
|
+
? "idValues.map((value) => Number(value)).filter((value) => !Number.isNaN(value))"
|
|
27
|
+
: "idValues";
|
|
28
|
+
|
|
23
29
|
const editRoute = hasPrimaryKey
|
|
24
30
|
? `
|
|
25
31
|
\tadminApp.post('/table/${exportName}/edit', async (c) => {
|
|
@@ -74,6 +80,52 @@ export function buildTablePostRoutes(
|
|
|
74
80
|
\t\t\t.where(eq(tableSchema.${primaryKey}, idValue as any))
|
|
75
81
|
\t\t\t.execute();
|
|
76
82
|
|
|
83
|
+
\t\tconst query = new URLSearchParams({
|
|
84
|
+
\t\t\tpage: getValue(body.page) || '1',
|
|
85
|
+
\t\t\tsort: getValue(body.sort) || '${defaultSort}',
|
|
86
|
+
\t\t\torder: getValue(body.order) || 'desc',
|
|
87
|
+
\t\t\tsearch: getValue(body.search) || '',
|
|
88
|
+
\t\t});
|
|
89
|
+
\t\treturn c.redirect('/admin/table/${exportName}?' + query.toString());
|
|
90
|
+
\t});
|
|
91
|
+
|
|
92
|
+
\tadminApp.post('/table/${exportName}/delete-bulk', async (c) => {
|
|
93
|
+
\t\tconst db = drizzle(c.env[options.databaseBinding], { schema });
|
|
94
|
+
\t\tconst tableSchema = (schema as any).${exportName};
|
|
95
|
+
\t\tif (!tableSchema) return c.text('Table missing', 404);
|
|
96
|
+
|
|
97
|
+
\t\tconst body = await c.req.parseBody();
|
|
98
|
+
\t\tconst getValue = (value: unknown) => (typeof value === 'string' ? value : '');
|
|
99
|
+
\t\tconst mode = getValue(body.bulkMode);
|
|
100
|
+
\t\tconst selectedIdsRaw = getValue(body.selectedIds);
|
|
101
|
+
\t\tconst search = getValue(body.search);
|
|
102
|
+
|
|
103
|
+
\t\tif (mode === 'all-matching') {
|
|
104
|
+
\t\t\tlet deleteQuery = db.delete(tableSchema);
|
|
105
|
+
\t\t\tif (search) {
|
|
106
|
+
\t\t\t\tconst searchConditions = [];
|
|
107
|
+
\t\t\t\t${searchConditions}
|
|
108
|
+
\t\t\t\tif (searchConditions.length > 0) {
|
|
109
|
+
\t\t\t\t\tdeleteQuery = deleteQuery.where(or(...searchConditions)) as any;
|
|
110
|
+
\t\t\t\t}
|
|
111
|
+
\t\t\t}
|
|
112
|
+
\t\t\tawait deleteQuery.execute();
|
|
113
|
+
\t\t} else {
|
|
114
|
+
\t\t\tconst idValues = selectedIdsRaw
|
|
115
|
+
\t\t\t\t.split(',')
|
|
116
|
+
\t\t\t\t.map((value) => value.trim())
|
|
117
|
+
\t\t\t\t.filter((value) => value.length > 0);
|
|
118
|
+
\t\t\tif (idValues.length === 0) return c.text('No rows selected', 400);
|
|
119
|
+
|
|
120
|
+
\t\t\tconst parsedIds: unknown[] = ${parseBulkIds};
|
|
121
|
+
\t\t\tif (parsedIds.length === 0) return c.text('No valid selected rows', 400);
|
|
122
|
+
|
|
123
|
+
\t\t\tawait db
|
|
124
|
+
\t\t\t\t.delete(tableSchema)
|
|
125
|
+
\t\t\t\t.where(inArray(tableSchema.${primaryKey}, parsedIds as any))
|
|
126
|
+
\t\t\t\t.execute();
|
|
127
|
+
\t\t}
|
|
128
|
+
|
|
77
129
|
\t\tconst query = new URLSearchParams({
|
|
78
130
|
\t\t\tpage: getValue(body.page) || '1',
|
|
79
131
|
\t\t\tsort: getValue(body.sort) || '${defaultSort}',
|
|
@@ -31,7 +31,7 @@ export function generateDashboardSource(
|
|
|
31
31
|
return `import { Hono } from "hono";
|
|
32
32
|
import { html, raw } from "hono/html";
|
|
33
33
|
import { drizzle } from "drizzle-orm/d1";
|
|
34
|
-
import { eq, desc, asc, sql, like, or } from "drizzle-orm";
|
|
34
|
+
import { eq, desc, asc, sql, like, or, inArray } from "drizzle-orm";
|
|
35
35
|
import { createAuth } from "./auth.config";
|
|
36
36
|
import * as schema from "${schemaImportPath}";
|
|
37
37
|
import { users } from "./auth.schema";
|