Skip to content

Commit 19e60eb

Browse files
committed
2 parents cc42f17 + a1ccc72 commit 19e60eb

Some content is hidden

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

72 files changed

+1798
-406
lines changed

adminforth/auth.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,21 @@ class AdminForthAuth implements IAdminForthAuth {
8282
username: string,
8383
pk: string | null
8484
}) {
85+
console.log("in days", expireInDays);
8586
const expiresIn: string = expireInDays ? `${expireInDays}d` : (process.env.ADMINFORTH_AUTH_EXPIRESIN || '24h');
87+
console.log("in string", expiresIn);
8688
// might be h,m,d in string
89+
8790
const expiresInSec = parseTimeToSeconds(expiresIn);
8891

92+
console.log("expiresInSec", expiresInSec);
93+
8994
const token = this.issueJWT({ username, pk}, 'auth', expiresIn);
95+
console.log("token", token);
9096
const expiresCookieFormat = new Date(Date.now() + expiresInSec * 1000).toUTCString();
91-
97+
console.log("expiresCookieFormat", expiresCookieFormat);
9298
const brandSlug = this.adminforth.config.customization.brandNameSlug;
99+
console.log("brandSlug", brandSlug);
93100
response.setHeader('Set-Cookie', `adminforth_${brandSlug}_jwt=${token}; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=${expiresCookieFormat}`);
94101
}
95102

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Express } from "express";
2+
import { IAdminForth } from "adminforth";
3+
4+
export function initApi(app: Express, admin: IAdminForth) {
5+
app.get(`${admin.config.baseUrl}/api/hello/`,
6+
(req, res) => {
7+
res.json({ message: "Hello from AdminForth API!" });
8+
}
9+
);
10+
}

adminforth/commands/createApp/templates/index.ts.hbs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import usersResource from "./resources/adminuser.js";
44
import { fileURLToPath } from 'url';
55
import path from 'path';
66
import { Filters } from 'adminforth';
7-
7+
import { initApi } from './api.js';
8+
89
const ADMIN_BASE_URL = '';
910

1011
export const admin = new AdminForth({
@@ -69,6 +70,8 @@ if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
6970
const app = express();
7071
app.use(express.json());
7172

73+
initApi(app, admin);
74+
7275
const port = 3500;
7376

7477
admin.bundleNow({ hotReload: process.env.NODE_ENV === 'development' }).then(() => {

adminforth/commands/createApp/utils.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,11 @@ async function writeTemplateFiles(dirname, cwd, options) {
235235
dest: 'index.ts',
236236
data: { appName },
237237
},
238+
{
239+
src: 'api.ts.hbs',
240+
dest: 'api.ts',
241+
data: {},
242+
},
238243
{
239244
src: '.gitignore.hbs',
240245
dest: '.gitignore',

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: 28 additions & 3 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,
@@ -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

adminforth/dataConnectors/postgres.ts

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -97,57 +97,62 @@ class PostgresConnector extends AdminForthBaseConnector implements IAdminForthDa
9797
rows.forEach((row) => {
9898
const field: any = {};
9999
const baseType = row.type.toLowerCase();
100-
if (baseType == 'int') {
100+
const isPgArray = baseType.endsWith('[]');
101+
const normalizedBaseType = isPgArray ? baseType.slice(0, -2) : baseType;
102+
if (normalizedBaseType == 'int') {
101103
field.type = AdminForthDataTypes.INTEGER;
102104
field._underlineType = 'int';
103105

104-
} else if (baseType.includes('float') || baseType.includes('double')) {
106+
} else if (normalizedBaseType.includes('float') || normalizedBaseType.includes('double')) {
105107
field.type = AdminForthDataTypes.FLOAT;
106108
field._underlineType = 'float';
107109

108-
} else if (baseType.includes('bool')) {
110+
} else if (normalizedBaseType.includes('bool')) {
109111
field.type = AdminForthDataTypes.BOOLEAN;
110112
field._underlineType = 'bool';
111113

112-
} else if (baseType == 'uuid') {
114+
} else if (normalizedBaseType == 'uuid') {
113115
field.type = AdminForthDataTypes.STRING;
114116
field._underlineType = 'uuid';
115117

116-
} else if (baseType.includes('character varying')) {
118+
} else if (normalizedBaseType.includes('character varying')) {
117119
field.type = AdminForthDataTypes.STRING;
118120
field._underlineType = 'varchar';
119-
const length = baseType.match(/\d+/);
121+
const length = normalizedBaseType.match(/\d+/);
120122
field.maxLength = length ? parseInt(length[0]) : null;
121123

122-
} else if (baseType == 'text') {
124+
} else if (normalizedBaseType == 'text') {
123125
field.type = AdminForthDataTypes.TEXT;
124126
field._underlineType = 'text';
125127

126-
} else if (baseType.includes('decimal(') || baseType.includes('numeric(')) {
128+
} else if (normalizedBaseType.includes('decimal(') || normalizedBaseType.includes('numeric(')) {
127129
field.type = AdminForthDataTypes.DECIMAL;
128130
field._underlineType = 'decimal';
129-
const [precision, scale] = baseType.match(/\d+/g);
131+
const [precision, scale] = normalizedBaseType.match(/\d+/g);
130132
field.precision = parseInt(precision);
131133
field.scale = parseInt(scale);
132134

133-
} else if (baseType == 'real') {
135+
} else if (normalizedBaseType == 'real') {
134136
field.type = AdminForthDataTypes.FLOAT;
135137
field._underlineType = 'real';
136138

137-
} else if (baseType == 'date') {
139+
} else if (normalizedBaseType == 'date') {
138140
field.type = AdminForthDataTypes.DATE;
139141
field._underlineType = 'timestamp';
140142

141-
} else if (baseType.includes('date') || baseType.includes('time')) {
143+
} else if (normalizedBaseType.includes('date') || normalizedBaseType.includes('time')) {
142144
field.type = AdminForthDataTypes.DATETIME;
143145
field._underlineType = 'timestamp';
144-
} else if (baseType == 'json' || baseType == 'jsonb') {
146+
} else if (normalizedBaseType == 'json' || normalizedBaseType == 'jsonb') {
145147
field.type = AdminForthDataTypes.JSON;
146148
field._underlineType = 'json';
147149
} else {
148150
field.type = 'unknown'
149151
}
150152
field._baseTypeDebug = baseType;
153+
if (isPgArray) {
154+
field._isPgArray = true;
155+
}
151156
field.primaryKey = row.pk == 1;
152157
field.default = row.dflt_value;
153158
field.required = row.notnull && !row.dflt_value;
@@ -211,11 +216,22 @@ class PostgresConnector extends AdminForthBaseConnector implements IAdminForthDa
211216
} else if (field._underlineType == 'varchar') {
212217
return dayjs(value).toISOString();
213218
}
219+
} else if (field.isArray?.enabled) {
220+
if (value === null || value === undefined) {
221+
return null;
222+
}
223+
if (field._isPgArray) {
224+
return value;
225+
}
226+
if (field._underlineType == 'json') {
227+
return JSON.stringify(value);
228+
}
229+
return JSON.stringify(value);
214230
} else if (field.type == AdminForthDataTypes.BOOLEAN) {
215231
return value === null ? null : (value ? 1 : 0);
216232
} else if (field.type == AdminForthDataTypes.JSON) {
217233
if (field._underlineType == 'json') {
218-
return value;
234+
return typeof value === 'string' || value === null ? value : JSON.stringify(value);
219235
} else {
220236
return JSON.stringify(value);
221237
}
135 KB
Loading
157 KB
Loading

0 commit comments

Comments
 (0)