Skip to content

Commit 7b6bba7

Browse files
committed
Merge branch 'next' of github.com:devforth/adminforth into feature/AdminForth/893/bug-with-login-via-google
2 parents d89d047 + cc6c8ed commit 7b6bba7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+3837
-1117
lines changed

adminforth/commands/createApp/templates/package.json.hbs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,6 @@
3131
"@types/express": "latest",
3232
"@types/node": "latest",
3333
"@prisma/client": "latest",
34-
"prisma": "latest"
34+
"prisma": "^7.0.0"
3535
}
3636
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import 'dotenv/config'
2+
import { defineConfig, env } from 'prisma/config'
3+
4+
export default defineConfig({
5+
datasource: {
6+
url: env('PRISMA_DATABASE_URL'),
7+
},
8+
})

adminforth/commands/createApp/templates/schema.prisma.hbs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ generator client {
44

55
datasource db {
66
provider = "{{provider}}"
7-
url = env("PRISMA_DATABASE_URL")
87
}
98

109
model adminuser {

adminforth/commands/createApp/utils.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,11 @@ async function writeTemplateFiles(dirname, cwd, options) {
222222
data: { provider },
223223
condition: Boolean(prismaDbUrl), // only create if prismaDbUrl is truthy
224224
},
225+
{
226+
src: 'prisma.config.ts.hbs',
227+
dest: 'prisma.config.ts',
228+
data: {},
229+
},
225230
{
226231
src: 'package.json.hbs',
227232
dest: 'package.json',

adminforth/commands/createCustomComponent/main.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ async function handleCrudPageInjectionCreation(config, resources) {
188188
const injectionPosition = await select({
189189
message: 'Where exactly do you want to inject the component?',
190190
choices: [
191+
...(crudType === 'create' || crudType === 'edit'
192+
? [{ name: '💾 Save button on create/edit page', value: 'saveButton' }, new Separator()]
193+
: []),
191194
{ name: '⬆️ Before Breadcrumbs', value: 'beforeBreadcrumbs' },
192195
{ name: '➡️ Before Action Buttons', value: 'beforeActionButtons' },
193196
{ name: '⬇️ After Breadcrumbs', value: 'afterBreadcrumbs' },
@@ -207,13 +210,15 @@ async function handleCrudPageInjectionCreation(config, resources) {
207210
},
208211
});
209212

210-
const isThin = await select({
211-
message: 'Will this component be thin enough to fit on the same page with list (so list will still shrink)?',
212-
choices: [
213-
{ name: 'Yes', value: true },
214-
{ name: 'No', value: false },
215-
],
216-
});
213+
const isThin = crudType === 'list'
214+
? await select({
215+
message: 'Will this component be thin enough to fit on the same page with list (so list will still shrink)?',
216+
choices: [
217+
{ name: 'Yes', value: true },
218+
{ name: 'No', value: false },
219+
],
220+
})
221+
: false;
217222
const formattedAdditionalName = additionalName
218223
? additionalName[0].toUpperCase() + additionalName.slice(1)
219224
: '';
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<template>
2+
<button
3+
class="px-4 py-2 border border-blue-500 text-blue-600 rounded hover:bg-blue-50 disabled:opacity-50"
4+
:disabled="props.disabled || props.saving || !props.isValid"
5+
@click="props.saveRecord()"
6+
>
7+
<span v-if="props.saving">Saving…</span>
8+
<span v-else>Save</span>
9+
</button>
10+
</template>
11+
12+
<script setup lang="ts">
13+
14+
const props = defineProps<{
15+
record: any
16+
resource: any
17+
adminUser: any
18+
meta: any
19+
saving: boolean
20+
validating: boolean
21+
isValid: boolean
22+
disabled: boolean
23+
saveRecord: () => Promise<void>
24+
}>();
25+
</script>
26+
27+
<style scoped>
28+
</style>

adminforth/dataConnectors/baseConnector.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -227,11 +227,19 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
227227
throw new Error('Method not implemented.');
228228
}
229229

230-
async checkUnique(resource: AdminForthResource, column: AdminForthResourceColumn, value: any) {
230+
async checkUnique(resource: AdminForthResource, column: AdminForthResourceColumn, value: any, record?: any): Promise<boolean> {
231231
process.env.HEAVY_DEBUG && console.log('☝️🪲🪲🪲🪲 checkUnique|||', column, value);
232+
233+
const primaryKeyField = this.getPrimaryKey(resource);
232234
const existingRecord = await this.getData({
233235
resource,
234-
filters: { operator: AdminForthFilterOperators.AND, subFilters: [{ field: column.name, operator: AdminForthFilterOperators.EQ, value }]},
236+
filters: {
237+
operator: AdminForthFilterOperators.AND,
238+
subFilters: [
239+
{ field: column.name, operator: AdminForthFilterOperators.EQ, value },
240+
...(record ? [{ field: primaryKeyField, operator: AdminForthFilterOperators.NE as AdminForthFilterOperators.NE, value: record[primaryKeyField] }] : [])
241+
]
242+
},
235243
limit: 1,
236244
sort: [],
237245
offset: 0,
@@ -245,13 +253,13 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
245253
resource: AdminForthResource; record: any; adminUser: any;
246254
}): Promise<{ error?: string; ok: boolean; createdRecord?: any; }> {
247255
// transform value using setFieldValue and call createRecordOriginalValues
248-
256+
249257
const filledRecord = {...record};
250258
const recordWithOriginalValues = {...record};
251259

252260
for (const col of resource.dataSourceColumns) {
253261
if (col.fillOnCreate) {
254-
if (filledRecord[col.name] === undefined) {
262+
if (filledRecord[col.name] === undefined || (Array.isArray(filledRecord[col.name]) && filledRecord[col.name].length === 0)) {
255263
filledRecord[col.name] = col.fillOnCreate({
256264
initialRecord: record,
257265
adminUser
@@ -306,7 +314,7 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
306314
async updateRecord({ resource, recordId, newValues }: { resource: AdminForthResource; recordId: string; newValues: any; }): Promise<{ error?: string; ok: boolean; }> {
307315
// transform value using setFieldValue and call updateRecordOriginalValues
308316
const recordWithOriginalValues = {...newValues};
309-
317+
310318
for (const field of Object.keys(newValues)) {
311319
const col = resource.dataSourceColumns.find((col) => col.name == field);
312320
// todo instead of throwing error, we can just not use setFieldValue here, and pass original value to updateRecordOriginalValues
@@ -319,6 +327,23 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
319327
}
320328
recordWithOriginalValues[col.name] = this.setFieldValue(col, newValues[col.name]);
321329
}
330+
const record = await this.getRecordByPrimaryKey(resource, recordId);
331+
let error: string | null = null;
332+
await Promise.all(
333+
resource.dataSourceColumns.map(async (col) => {
334+
if (col.isUnique && !col.virtual && !error && Object.prototype.hasOwnProperty.call(recordWithOriginalValues, col.name)) {
335+
const exists = await this.checkUnique(resource, col, recordWithOriginalValues[col.name], record);
336+
if (exists) {
337+
error = `Record with ${col.name} ${recordWithOriginalValues[col.name]} already exists`;
338+
}
339+
}
340+
})
341+
);
342+
if (error) {
343+
process.env.HEAVY_DEBUG && console.log('🪲🆕 check unique error', error);
344+
return { error, ok: false };
345+
}
346+
322347

323348
process.env.HEAVY_DEBUG && console.log(`🪲✏️ updating record id:${recordId}, values: ${JSON.stringify(recordWithOriginalValues)}`);
324349

0 commit comments

Comments
 (0)