adminforth 1.4.3-next.20 → 1.4.3-next.21
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/adminforth/auth.d.ts +31 -0
- package/dist/adminforth/auth.d.ts.map +1 -0
- package/dist/adminforth/auth.js +119 -0
- package/dist/adminforth/auth.js.map +1 -0
- package/dist/adminforth/basePlugin.d.ts +23 -0
- package/dist/adminforth/basePlugin.d.ts.map +1 -0
- package/dist/adminforth/basePlugin.js +51 -0
- package/dist/adminforth/basePlugin.js.map +1 -0
- package/dist/adminforth/dataConnectors/baseConnector.d.ts +95 -0
- package/dist/adminforth/dataConnectors/baseConnector.d.ts.map +1 -0
- package/dist/adminforth/dataConnectors/baseConnector.js +178 -0
- package/dist/adminforth/dataConnectors/baseConnector.js.map +1 -0
- package/dist/adminforth/dataConnectors/clickhouse.d.ts +93 -0
- package/dist/adminforth/dataConnectors/clickhouse.d.ts.map +1 -0
- package/dist/adminforth/dataConnectors/clickhouse.js +297 -0
- package/dist/adminforth/dataConnectors/clickhouse.js.map +1 -0
- package/dist/adminforth/dataConnectors/mongo.d.ts +94 -0
- package/dist/adminforth/dataConnectors/mongo.d.ts.map +1 -0
- package/dist/adminforth/dataConnectors/mongo.js +168 -0
- package/dist/adminforth/dataConnectors/mongo.js.map +1 -0
- package/dist/adminforth/dataConnectors/postgres.d.ts +72 -0
- package/dist/adminforth/dataConnectors/postgres.d.ts.map +1 -0
- package/dist/adminforth/dataConnectors/postgres.js +298 -0
- package/dist/adminforth/dataConnectors/postgres.js.map +1 -0
- package/dist/adminforth/dataConnectors/sqlite.d.ts +67 -0
- package/dist/adminforth/dataConnectors/sqlite.d.ts.map +1 -0
- package/dist/adminforth/dataConnectors/sqlite.js +251 -0
- package/dist/adminforth/dataConnectors/sqlite.js.map +1 -0
- package/dist/adminforth/index.d.ts +94 -0
- package/dist/adminforth/index.d.ts.map +1 -0
- package/dist/adminforth/index.js +357 -0
- package/dist/adminforth/index.js.map +1 -0
- package/dist/adminforth/modules/codeInjector.d.ts +38 -0
- package/dist/adminforth/modules/codeInjector.d.ts.map +1 -0
- package/dist/adminforth/modules/codeInjector.js +673 -0
- package/dist/adminforth/modules/codeInjector.js.map +1 -0
- package/dist/adminforth/modules/configValidator.d.ts +13 -0
- package/dist/adminforth/modules/configValidator.d.ts.map +1 -0
- package/dist/adminforth/modules/configValidator.js +511 -0
- package/dist/adminforth/modules/configValidator.js.map +1 -0
- package/dist/adminforth/modules/operationalResource.d.ts +17 -0
- package/dist/adminforth/modules/operationalResource.d.ts.map +1 -0
- package/dist/adminforth/modules/operationalResource.js +75 -0
- package/dist/adminforth/modules/operationalResource.js.map +1 -0
- package/dist/adminforth/modules/restApi.d.ts +11 -0
- package/dist/adminforth/modules/restApi.d.ts.map +1 -0
- package/dist/adminforth/modules/restApi.js +657 -0
- package/dist/adminforth/modules/restApi.js.map +1 -0
- package/dist/adminforth/modules/styleGenerator.d.ts +9 -0
- package/dist/adminforth/modules/styleGenerator.d.ts.map +1 -0
- package/dist/adminforth/modules/styleGenerator.js +47 -0
- package/dist/adminforth/modules/styleGenerator.js.map +1 -0
- package/dist/adminforth/modules/styles.d.ts +96 -0
- package/dist/adminforth/modules/styles.d.ts.map +1 -0
- package/dist/adminforth/modules/styles.js +105 -0
- package/dist/adminforth/modules/styles.js.map +1 -0
- package/dist/adminforth/modules/utils.d.ts +39 -0
- package/dist/adminforth/modules/utils.d.ts.map +1 -0
- package/dist/adminforth/modules/utils.js +421 -0
- package/dist/adminforth/modules/utils.js.map +1 -0
- package/dist/adminforth/plugins/audit-log/index.d.ts +25 -0
- package/dist/adminforth/plugins/audit-log/index.d.ts.map +1 -0
- package/dist/adminforth/plugins/audit-log/index.js +129 -0
- package/dist/adminforth/plugins/audit-log/index.js.map +1 -0
- package/dist/adminforth/plugins/audit-log/types.d.ts +35 -0
- package/dist/adminforth/plugins/audit-log/types.d.ts.map +1 -0
- package/dist/adminforth/plugins/audit-log/types.js +2 -0
- package/dist/adminforth/plugins/audit-log/types.js.map +1 -0
- package/dist/adminforth/plugins/chat-gpt/index.d.ts +14 -0
- package/dist/adminforth/plugins/chat-gpt/index.d.ts.map +1 -0
- package/dist/adminforth/plugins/chat-gpt/index.js +147 -0
- package/dist/adminforth/plugins/chat-gpt/index.js.map +1 -0
- package/dist/adminforth/plugins/chat-gpt/types.d.ts +82 -0
- package/dist/adminforth/plugins/chat-gpt/types.d.ts.map +1 -0
- package/dist/adminforth/plugins/chat-gpt/types.js +2 -0
- package/dist/adminforth/plugins/chat-gpt/types.js.map +1 -0
- package/dist/adminforth/plugins/email-password-reset/index.d.ts +15 -0
- package/dist/adminforth/plugins/email-password-reset/index.d.ts.map +1 -0
- package/dist/adminforth/plugins/email-password-reset/index.js +176 -0
- package/dist/adminforth/plugins/email-password-reset/index.js.map +1 -0
- package/dist/adminforth/plugins/email-password-reset/types.d.ts +28 -0
- package/dist/adminforth/plugins/email-password-reset/types.d.ts.map +1 -0
- package/dist/adminforth/plugins/email-password-reset/types.js +2 -0
- package/dist/adminforth/plugins/email-password-reset/types.js.map +1 -0
- package/dist/adminforth/plugins/foreign-inline-list/index.d.ts +13 -0
- package/dist/adminforth/plugins/foreign-inline-list/index.d.ts.map +1 -0
- package/dist/adminforth/plugins/foreign-inline-list/index.js +56 -0
- package/dist/adminforth/plugins/foreign-inline-list/index.js.map +1 -0
- package/dist/adminforth/plugins/foreign-inline-list/types.d.ts +19 -0
- package/dist/adminforth/plugins/foreign-inline-list/types.d.ts.map +1 -0
- package/dist/adminforth/plugins/foreign-inline-list/types.js +2 -0
- package/dist/adminforth/plugins/foreign-inline-list/types.js.map +1 -0
- package/dist/adminforth/plugins/import-export/index.d.ts +15 -0
- package/dist/adminforth/plugins/import-export/index.d.ts.map +1 -0
- package/dist/adminforth/plugins/import-export/index.js +127 -0
- package/dist/adminforth/plugins/import-export/index.js.map +1 -0
- package/dist/adminforth/plugins/import-export/types.d.ts +3 -0
- package/dist/adminforth/plugins/import-export/types.d.ts.map +1 -0
- package/dist/adminforth/plugins/import-export/types.js +2 -0
- package/dist/adminforth/plugins/import-export/types.js.map +1 -0
- package/dist/adminforth/plugins/rich-editor/index.d.ts +17 -0
- package/dist/adminforth/plugins/rich-editor/index.d.ts.map +1 -0
- package/dist/adminforth/plugins/rich-editor/index.js +262 -0
- package/dist/adminforth/plugins/rich-editor/index.js.map +1 -0
- package/dist/adminforth/plugins/rich-editor/types.d.ts +153 -0
- package/dist/adminforth/plugins/rich-editor/types.d.ts.map +1 -0
- package/dist/adminforth/plugins/rich-editor/types.js +16 -0
- package/dist/adminforth/plugins/rich-editor/types.js.map +1 -0
- package/dist/adminforth/plugins/two-factors-auth/index.d.ts +16 -0
- package/dist/adminforth/plugins/two-factors-auth/index.d.ts.map +1 -0
- package/dist/adminforth/plugins/two-factors-auth/index.js +134 -0
- package/dist/adminforth/plugins/two-factors-auth/index.js.map +1 -0
- package/dist/adminforth/plugins/two-factors-auth/types.d.ts +18 -0
- package/dist/adminforth/plugins/two-factors-auth/types.d.ts.map +1 -0
- package/dist/adminforth/plugins/two-factors-auth/types.js +2 -0
- package/dist/adminforth/plugins/two-factors-auth/types.js.map +1 -0
- package/dist/adminforth/plugins/upload/index.d.ts +14 -0
- package/dist/adminforth/plugins/upload/index.d.ts.map +1 -0
- package/dist/adminforth/plugins/upload/index.js +450 -0
- package/dist/adminforth/plugins/upload/index.js.map +1 -0
- package/dist/adminforth/plugins/upload/types.d.ts +132 -0
- package/dist/adminforth/plugins/upload/types.d.ts.map +1 -0
- package/dist/adminforth/plugins/upload/types.js +2 -0
- package/dist/adminforth/plugins/upload/types.js.map +1 -0
- package/dist/adminforth/servers/express.d.ts +18 -0
- package/dist/adminforth/servers/express.d.ts.map +1 -0
- package/dist/adminforth/servers/express.js +238 -0
- package/dist/adminforth/servers/express.js.map +1 -0
- package/dist/adminforth/types/Back.d.ts +1103 -0
- package/dist/adminforth/types/Back.d.ts.map +1 -0
- package/dist/adminforth/types/Back.js +86 -0
- package/dist/adminforth/types/Back.js.map +1 -0
- package/dist/adminforth/types/Common.d.ts +616 -0
- package/dist/adminforth/types/Common.d.ts.map +1 -0
- package/dist/adminforth/types/Common.js +65 -0
- package/dist/adminforth/types/Common.js.map +1 -0
- package/dist/adminforth/types/FrontendAPI.d.ts +134 -0
- package/dist/adminforth/types/FrontendAPI.d.ts.map +1 -0
- package/dist/adminforth/types/FrontendAPI.js +8 -0
- package/dist/adminforth/types/FrontendAPI.js.map +1 -0
- package/dist/dev-demo/index.d.ts +3 -0
- package/dist/dev-demo/index.d.ts.map +1 -0
- package/dist/dev-demo/index.js +1052 -0
- package/dist/dev-demo/index.js.map +1 -0
- package/dist/spa/src/types/Back.ts +25 -5
- package/package.json +1 -1
|
@@ -0,0 +1,1052 @@
|
|
|
1
|
+
import betterSqlite3 from 'better-sqlite3';
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import AdminForth, { AdminForthDataTypes, Filters } from '../adminforth/index.js';
|
|
4
|
+
import { v1 as uuid } from 'uuid';
|
|
5
|
+
import ForeignInlineListPlugin from '../adminforth/plugins/foreign-inline-list/index.js';
|
|
6
|
+
import AuditLogPlugin from '../adminforth/plugins/audit-log/index.js';
|
|
7
|
+
import TwoFactorsAuthPlugin from '../adminforth/plugins/two-factors-auth/index.js';
|
|
8
|
+
import UploadPlugin from '../adminforth/plugins/upload/index.js';
|
|
9
|
+
import RichEditorPlugin from '../adminforth/plugins/rich-editor/index.js';
|
|
10
|
+
import EmailResetPasswordPlugin from '../adminforth/plugins/email-password-reset/index.js';
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import ImportExportPlugin from '../adminforth/plugins/import-export/index.js';
|
|
13
|
+
import { generate } from "random-words";
|
|
14
|
+
const ADMIN_BASE_URL = '';
|
|
15
|
+
// create test1.db
|
|
16
|
+
try {
|
|
17
|
+
fs.mkdirSync('db');
|
|
18
|
+
}
|
|
19
|
+
catch (e) { }
|
|
20
|
+
const dbPath = 'db/test.sqlite';
|
|
21
|
+
const db = betterSqlite3(dbPath);
|
|
22
|
+
const tableExists = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='apartments';`).get();
|
|
23
|
+
if (!tableExists) {
|
|
24
|
+
await db.prepare(`
|
|
25
|
+
CREATE TABLE apartments (
|
|
26
|
+
id VARCHAR(20) PRIMARY KEY NOT NULL,
|
|
27
|
+
title VARCHAR(255) NOT NULL,
|
|
28
|
+
square_meter REAL,
|
|
29
|
+
price DECIMAL(10, 2) NOT NULL,
|
|
30
|
+
number_of_rooms INT,
|
|
31
|
+
description TEXT,
|
|
32
|
+
property_type VARCHAR(255) DEFAULT 'apartment',
|
|
33
|
+
listed BOOLEAN DEFAULT FALSE,
|
|
34
|
+
created_at TIMESTAMP,
|
|
35
|
+
user_id VARCHAR(255),
|
|
36
|
+
country VARCHAR(5)
|
|
37
|
+
);`).run();
|
|
38
|
+
await db.prepare(`
|
|
39
|
+
CREATE TABLE users (
|
|
40
|
+
id VARCHAR(255) PRIMARY KEY NOT NULL,
|
|
41
|
+
email VARCHAR(255) NOT NULL,
|
|
42
|
+
secret2fa VARCHAR(255) ,
|
|
43
|
+
password_hash VARCHAR(255) NOT NULL,
|
|
44
|
+
created_at VARCHAR(255) NOT NULL,
|
|
45
|
+
role VARCHAR(255) NOT NULL
|
|
46
|
+
);`).run();
|
|
47
|
+
for (let i = 0; i < 50; i++) {
|
|
48
|
+
await db.prepare(`
|
|
49
|
+
INSERT INTO apartments (
|
|
50
|
+
id, title, square_meter, price, number_of_rooms, description, created_at, listed, property_type
|
|
51
|
+
) VALUES ('${i}',
|
|
52
|
+
'${generate({ min: 2, max: 5, join: ' ' })}',
|
|
53
|
+
${(Math.random() * 1000000).toFixed(1)}, ${(Math.random() * 10000).toFixed(2)}, ${Math
|
|
54
|
+
.floor(Math.random() * 5)},
|
|
55
|
+
'Next gen apartments',
|
|
56
|
+
${Date.now() / 1000 - 60 * 60 * 24 * 7 * Math.random()},
|
|
57
|
+
${Math.random() > 0.5 ? 1 : 0},
|
|
58
|
+
${Math.random() > 0.5 ? "'house'" : "'apartment'"});
|
|
59
|
+
`).run();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const tableExistsAuditLog = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='audit_log';`).get();
|
|
63
|
+
if (!tableExistsAuditLog) {
|
|
64
|
+
await db.prepare(`
|
|
65
|
+
CREATE TABLE audit_log (
|
|
66
|
+
id uuid NOT NULL, -- identifier of applied change record
|
|
67
|
+
created_at timestamp without time zone, -- timestamp of applied change
|
|
68
|
+
resource_id varchar(255), -- identifier of resource where change were applied
|
|
69
|
+
user_id uuid, -- identifier of user who made the changes
|
|
70
|
+
"action" varchar(255), -- type of change (create, edit, delete)
|
|
71
|
+
diff text, -- delta betwen before/after versions
|
|
72
|
+
record_id varchar, -- identifier of record that been changed
|
|
73
|
+
PRIMARY KEY(id)
|
|
74
|
+
);`).run();
|
|
75
|
+
}
|
|
76
|
+
const tableExistsDescriptionImage = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='description_image';`).get();
|
|
77
|
+
if (!tableExistsDescriptionImage) {
|
|
78
|
+
await db.prepare(`
|
|
79
|
+
CREATE TABLE description_image (
|
|
80
|
+
id uuid NOT NULL, -- identifier of applied change record
|
|
81
|
+
created_at timestamp without time zone, -- timestamp of applied change
|
|
82
|
+
resource_id varchar(255), -- identifier of resource where change were applied
|
|
83
|
+
record_id varchar, -- identifier of record that been changed
|
|
84
|
+
image_path varchar(255), -- path to image
|
|
85
|
+
PRIMARY KEY(id)
|
|
86
|
+
);`).run();
|
|
87
|
+
}
|
|
88
|
+
// check column apartment_image in aparts table
|
|
89
|
+
const columns = await db.prepare('PRAGMA table_info(apartments);').all();
|
|
90
|
+
const columnExists = columns.some((c) => c.name === 'apartment_image');
|
|
91
|
+
if (!columnExists) {
|
|
92
|
+
await db.prepare('ALTER TABLE apartments ADD COLUMN apartment_image VARCHAR(255);').run();
|
|
93
|
+
}
|
|
94
|
+
const demoChecker = async ({ record, adminUser, resource }) => {
|
|
95
|
+
if (adminUser.dbUser.role !== 'superadmin') {
|
|
96
|
+
return { ok: false, error: "You can't do this on demo.adminforth.dev" };
|
|
97
|
+
}
|
|
98
|
+
return { ok: true };
|
|
99
|
+
};
|
|
100
|
+
export const admin = new AdminForth({
|
|
101
|
+
baseUrl: ADMIN_BASE_URL,
|
|
102
|
+
// deleteConfirmation: false,
|
|
103
|
+
auth: {
|
|
104
|
+
resourceId: 'users', // resource for getting user
|
|
105
|
+
usernameField: 'email',
|
|
106
|
+
passwordHashField: 'password_hash',
|
|
107
|
+
userFullNameField: 'fullName', // optional
|
|
108
|
+
loginBackgroundImage: 'https://images.unsplash.com/photo-1534239697798-120952b76f2b?q=80&w=3389&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
|
|
109
|
+
loginBackgroundPosition: '1/2', // over, 3/4, 2/5, 3/5 (tailwind grid)
|
|
110
|
+
demoCredentials: "adminforth:adminforth", // never use it for production
|
|
111
|
+
loginPromptHTML: "Use email <b>adminforth</b> and password <b>adminforth</b> to login",
|
|
112
|
+
// loginBackgroundImage: '@@/pho.jpg',
|
|
113
|
+
rememberMeDays: 30,
|
|
114
|
+
},
|
|
115
|
+
customization: {
|
|
116
|
+
customComponentsDir: './custom',
|
|
117
|
+
globalInjections: {
|
|
118
|
+
userMenu: '@@/login2.vue',
|
|
119
|
+
},
|
|
120
|
+
customPages: [{
|
|
121
|
+
path: '/login2',
|
|
122
|
+
component: {
|
|
123
|
+
file: '@@/login2.vue',
|
|
124
|
+
meta: {
|
|
125
|
+
customLayout: true,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}],
|
|
129
|
+
vueUsesFile: '@@/vueUses.ts', // @@ is alias to custom directory,
|
|
130
|
+
brandName: 'New Reality',
|
|
131
|
+
showBrandNameInSidebar: true,
|
|
132
|
+
// datesFormat: 'D MMM YY',
|
|
133
|
+
// timeFormat: 'HH:mm:ss',
|
|
134
|
+
datesFormat: 'DD MMM',
|
|
135
|
+
timeFormat: 'hh:mm a',
|
|
136
|
+
title: 'Devforth Admin',
|
|
137
|
+
// brandLogo: '@@/df.svg',
|
|
138
|
+
emptyFieldPlaceholder: '-',
|
|
139
|
+
// styles:{
|
|
140
|
+
// colors: {
|
|
141
|
+
// light: {
|
|
142
|
+
// primary: '#425BB8',
|
|
143
|
+
// sidebar: {main:'#1c2a5b', text:'white'},
|
|
144
|
+
// },
|
|
145
|
+
// }
|
|
146
|
+
// },
|
|
147
|
+
styles: {
|
|
148
|
+
colors: {
|
|
149
|
+
light: {
|
|
150
|
+
// color for buttons, links, etc.
|
|
151
|
+
primary: '#16537e',
|
|
152
|
+
// color for sidebar and text
|
|
153
|
+
sidebar: {
|
|
154
|
+
main: '#232323',
|
|
155
|
+
text: 'white'
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
dark: {
|
|
159
|
+
primary: '#8a158d',
|
|
160
|
+
sidebar: {
|
|
161
|
+
main: '#8a158d',
|
|
162
|
+
text: 'white'
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
announcementBadge: (adminUser) => {
|
|
168
|
+
return {
|
|
169
|
+
html: `
|
|
170
|
+
<svg xmlns="http://www.w3.org/2000/svg" style="display:inline; margin-top: -4px" width="16" height="16" viewBox="0 0 24 24"><path d="M12 .587l3.668 7.568 8.332 1.151-6.064 5.828 1.48 8.279-7.416-3.967-7.417 3.967 1.481-8.279-6.064-5.828 8.332-1.151z"/></svg>
|
|
171
|
+
<a href="https://github.com/devforth/adminforth" style="font-weight: bold; text-decoration: underline" target="_blank">Star us on GitHub</a> to support a project!`,
|
|
172
|
+
closable: true,
|
|
173
|
+
title: 'Support us for free',
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
dataSources: [
|
|
178
|
+
{
|
|
179
|
+
id: 'maindb',
|
|
180
|
+
url: `sqlite://${dbPath}`
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: 'db2',
|
|
184
|
+
url: 'postgres://postgres:35ozenad@test-db.c3sosskwwcnd.eu-central-1.rds.amazonaws.com:5432'
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: 'db3',
|
|
188
|
+
url: 'mongodb://127.0.0.1:27017/betbolt?retryWrites=true&w=majority&authSource=admin',
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: 'ch',
|
|
192
|
+
url: 'clickhouse://demo:demo@localhost:8124/demo',
|
|
193
|
+
}
|
|
194
|
+
],
|
|
195
|
+
resources: [
|
|
196
|
+
{
|
|
197
|
+
dataSource: 'ch',
|
|
198
|
+
table: 'clicks',
|
|
199
|
+
/*
|
|
200
|
+
SQL to create table run SQL in http://localhost:8123/play
|
|
201
|
+
CREATE TABLE demo.clicks (
|
|
202
|
+
clickid UUID PRIMARY KEY,
|
|
203
|
+
element String,
|
|
204
|
+
clientX Int32,
|
|
205
|
+
created_at DateTime,
|
|
206
|
+
aggressiveness Float32,
|
|
207
|
+
click_price Decimal(10, 2)
|
|
208
|
+
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
*/
|
|
212
|
+
columns: [
|
|
213
|
+
{
|
|
214
|
+
name: 'clickid', primaryKey: true, required: false, fillOnCreate: ({ initialRecord }) => uuid(),
|
|
215
|
+
showIn: ['list', 'filter', 'show'],
|
|
216
|
+
components: {
|
|
217
|
+
list: '@/renderers/CompactUUID.vue'
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
{ name: 'element',
|
|
221
|
+
type: AdminForthDataTypes.STRING,
|
|
222
|
+
required: false,
|
|
223
|
+
enum: [
|
|
224
|
+
{ value: 'button', label: 'Button' },
|
|
225
|
+
{ value: 'link', label: 'Link' },
|
|
226
|
+
{ value: 'image', label: 'Image' },
|
|
227
|
+
]
|
|
228
|
+
},
|
|
229
|
+
{ name: 'clientX',
|
|
230
|
+
type: AdminForthDataTypes.INTEGER,
|
|
231
|
+
allowMinMaxQuery: true,
|
|
232
|
+
required: false },
|
|
233
|
+
{ name: 'created_at',
|
|
234
|
+
type: AdminForthDataTypes.DATETIME,
|
|
235
|
+
required: false },
|
|
236
|
+
{ name: 'aggressiveness',
|
|
237
|
+
allowMinMaxQuery: true,
|
|
238
|
+
type: AdminForthDataTypes.FLOAT,
|
|
239
|
+
required: false,
|
|
240
|
+
showIn: ['filter', 'show', 'edit'],
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
name: 'click_price',
|
|
244
|
+
}
|
|
245
|
+
],
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
dataSource: 'maindb', table: 'audit_log',
|
|
249
|
+
columns: [
|
|
250
|
+
{ name: 'id', primaryKey: true, required: false, fillOnCreate: ({ initialRecord }) => uuid() },
|
|
251
|
+
{ name: 'created_at', required: false },
|
|
252
|
+
{ name: 'resource_id', required: false },
|
|
253
|
+
{ name: 'user_id', required: false,
|
|
254
|
+
foreignResource: {
|
|
255
|
+
resourceId: 'users',
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
{ name: 'action', required: false },
|
|
259
|
+
{ name: 'diff', required: false, type: AdminForthDataTypes.JSON },
|
|
260
|
+
{ name: 'record_id', required: false },
|
|
261
|
+
],
|
|
262
|
+
options: {
|
|
263
|
+
allowedActions: {
|
|
264
|
+
edit: false,
|
|
265
|
+
delete: false,
|
|
266
|
+
create: false,
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
plugins: [
|
|
270
|
+
new AuditLogPlugin({
|
|
271
|
+
resourceColumns: {
|
|
272
|
+
resourceUserIdColumnName: 'user_id',
|
|
273
|
+
resourceRecordIdColumnName: 'record_id',
|
|
274
|
+
resourceActionColumnName: 'action',
|
|
275
|
+
resourceDataColumnName: 'diff',
|
|
276
|
+
resourceCreatedColumnName: 'created_at',
|
|
277
|
+
resourceIdColumnName: 'resource_id',
|
|
278
|
+
},
|
|
279
|
+
}),
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
dataSource: 'maindb', table: 'apartments',
|
|
284
|
+
resourceId: 'aparts', // resourceId is defaulted to table name but you can change it e.g.
|
|
285
|
+
// in case of same table names from different data sources
|
|
286
|
+
label: 'Apartments', // label is defaulted to table name but you can change it
|
|
287
|
+
recordLabel: (r) => `🏡 ${r.title}`,
|
|
288
|
+
hooks: {
|
|
289
|
+
delete: {
|
|
290
|
+
beforeSave: demoChecker,
|
|
291
|
+
},
|
|
292
|
+
list: {
|
|
293
|
+
afterDatasourceResponse: async ({ response }) => {
|
|
294
|
+
response.forEach((r) => {
|
|
295
|
+
// random country
|
|
296
|
+
const countries = ['US', 'DE', 'FR', 'GB', 'NL', 'IT', 'ES', 'DK', 'PL', 'UA',
|
|
297
|
+
'CA', 'AU', 'BR', 'JP', 'CN', 'IN', 'KR', 'TR', 'MX', 'ID',
|
|
298
|
+
'NG', 'SA', 'EG', 'ZA', 'AR', 'SE', 'CH', 'AT', 'BE', 'FI', 'NO', 'PT', 'GR', 'HU', 'IE', 'IL', 'LU', 'SK', 'SI', 'LT', 'LV', 'EE', 'HR', 'CZ', 'BG', 'RO', 'RS', 'IS', 'MT', 'CY', 'AL', 'MK', 'ME', 'BA', 'XK', 'MD', 'LI', 'AD', 'MC', 'SM', 'VA', 'UA', 'BY'
|
|
299
|
+
];
|
|
300
|
+
r.country = countries[Math.floor(Math.random() * countries.length)];
|
|
301
|
+
});
|
|
302
|
+
return { ok: true, error: "" };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
columns: [
|
|
307
|
+
{
|
|
308
|
+
name: 'id',
|
|
309
|
+
label: 'Identifier', // if you wish you can redefine label
|
|
310
|
+
showIn: ['filter', 'show', 'list'], // show in filter and in show page
|
|
311
|
+
primaryKey: true,
|
|
312
|
+
// @ts-ignore
|
|
313
|
+
fillOnCreate: ({ initialRecord, adminUser }) => Math.random().toString(36).substring(7),
|
|
314
|
+
// fillOnCreate: ({initialRecord}: any) => randomUUID(),
|
|
315
|
+
components: {
|
|
316
|
+
list: '@/renderers/CompactUUID.vue'
|
|
317
|
+
} // initialRecord is values user entered, adminUser object of user who creates record
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
name: 'title',
|
|
321
|
+
// type: AdminForthDataTypes.JSON,
|
|
322
|
+
required: true,
|
|
323
|
+
showIn: ['list', 'create', 'edit', 'filter', 'show'], // the default is full set
|
|
324
|
+
maxLength: 255, // you can set max length for string fields
|
|
325
|
+
minLength: 3, // you can set min length for string fields
|
|
326
|
+
components: {
|
|
327
|
+
// edit: {
|
|
328
|
+
// file: '@@/IdShow.vue',
|
|
329
|
+
// meta: {
|
|
330
|
+
// title: 'Title',
|
|
331
|
+
// description: 'This is title of apartment'
|
|
332
|
+
// }
|
|
333
|
+
// },
|
|
334
|
+
// show: '@@/IdShow.vue',
|
|
335
|
+
// create: {
|
|
336
|
+
// file: '@@/IdShow.vue',
|
|
337
|
+
// meta: {
|
|
338
|
+
// title: 'Title',
|
|
339
|
+
// description: 'This is title of apartment'
|
|
340
|
+
// }
|
|
341
|
+
// }
|
|
342
|
+
// list: '@@/IdShow.vue',
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: 'country',
|
|
347
|
+
components: {
|
|
348
|
+
list: {
|
|
349
|
+
file: '@/renderers/CountryFlag.vue',
|
|
350
|
+
meta: {
|
|
351
|
+
showCountryName: false,
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
enum: [{
|
|
356
|
+
value: 'US',
|
|
357
|
+
label: 'United States'
|
|
358
|
+
}, {
|
|
359
|
+
value: 'DE',
|
|
360
|
+
label: 'Germany'
|
|
361
|
+
}, {
|
|
362
|
+
value: 'FR',
|
|
363
|
+
label: 'France'
|
|
364
|
+
}, {
|
|
365
|
+
value: 'GB',
|
|
366
|
+
label: 'United Kingdom'
|
|
367
|
+
}, {
|
|
368
|
+
value: 'NL',
|
|
369
|
+
label: 'Netherlands'
|
|
370
|
+
}, {
|
|
371
|
+
value: 'IT',
|
|
372
|
+
label: 'Italy'
|
|
373
|
+
}, {
|
|
374
|
+
value: 'ES',
|
|
375
|
+
label: 'Spain'
|
|
376
|
+
}, {
|
|
377
|
+
value: 'DK',
|
|
378
|
+
label: 'Denmark'
|
|
379
|
+
}, {
|
|
380
|
+
value: 'PL',
|
|
381
|
+
label: 'Poland'
|
|
382
|
+
}, {
|
|
383
|
+
value: 'UA',
|
|
384
|
+
label: 'Ukraine'
|
|
385
|
+
}, {
|
|
386
|
+
value: null,
|
|
387
|
+
label: 'Not defined'
|
|
388
|
+
}],
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
name: 'created_at',
|
|
392
|
+
type: AdminForthDataTypes.DATETIME,
|
|
393
|
+
allowMinMaxQuery: true,
|
|
394
|
+
showIn: ['list', 'filter', 'show', 'edit'],
|
|
395
|
+
components: {
|
|
396
|
+
list: '@/renderers/RelativeTime.vue'
|
|
397
|
+
},
|
|
398
|
+
// @ts-ignore
|
|
399
|
+
fillOnCreate: ({ initialRecord, adminUser }) => (new Date()).toISOString(),
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
name: 'apartment_image',
|
|
403
|
+
showIn: ['show',],
|
|
404
|
+
required: false,
|
|
405
|
+
editingNote: 'Upload image of apartment',
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
name: 'price',
|
|
409
|
+
showIn: ['create', 'edit', 'filter', 'show'],
|
|
410
|
+
allowMinMaxQuery: true, // use better experience for filtering e.g. date range, set it only if you have index on this column or if there will be low number of rows
|
|
411
|
+
editingNote: 'Price is in USD', // you can appear note on editing or creating page
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
name: 'square_meter',
|
|
415
|
+
label: 'Square',
|
|
416
|
+
// allowMinMaxQuery: true,
|
|
417
|
+
minValue: 1, // you can set min /max value for number fields
|
|
418
|
+
maxValue: 100000000,
|
|
419
|
+
components: {
|
|
420
|
+
list: {
|
|
421
|
+
file: '@/renderers/HumanNumber.vue',
|
|
422
|
+
meta: {
|
|
423
|
+
showCountryName: true,
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
name: 'number_of_rooms',
|
|
430
|
+
allowMinMaxQuery: true,
|
|
431
|
+
enum: [
|
|
432
|
+
{ value: 1, label: '1 room' },
|
|
433
|
+
{ value: 2, label: '2 rooms' },
|
|
434
|
+
{ value: 3, label: '3 rooms' },
|
|
435
|
+
{ value: 4, label: '4 rooms' },
|
|
436
|
+
{ value: 5, label: '5 rooms' },
|
|
437
|
+
],
|
|
438
|
+
showIn: ['filter', 'show', 'create', 'edit'],
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
name: 'description',
|
|
442
|
+
sortable: false,
|
|
443
|
+
type: AdminForthDataTypes.RICHTEXT,
|
|
444
|
+
showIn: ['filter', 'show', 'edit', 'create'],
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
name: 'property_type',
|
|
448
|
+
enum: [{
|
|
449
|
+
value: 'house',
|
|
450
|
+
label: 'House'
|
|
451
|
+
}, {
|
|
452
|
+
value: 'apartment',
|
|
453
|
+
label: 'Apartment'
|
|
454
|
+
}, {
|
|
455
|
+
value: null,
|
|
456
|
+
label: 'Not defined'
|
|
457
|
+
}],
|
|
458
|
+
// allowCustomValue: true,
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
name: 'listed',
|
|
462
|
+
required: true, // will be required on create/edit
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
name: 'user_id',
|
|
466
|
+
showIn: ['filter', 'show', 'edit', 'list', 'create'],
|
|
467
|
+
foreignResource: {
|
|
468
|
+
resourceId: 'users',
|
|
469
|
+
hooks: {
|
|
470
|
+
dropdownList: {
|
|
471
|
+
beforeDatasourceRequest: async ({ query, adminUser }) => {
|
|
472
|
+
return { ok: true, error: "" };
|
|
473
|
+
},
|
|
474
|
+
afterDatasourceResponse: async ({ response, adminUser }) => {
|
|
475
|
+
return { ok: true, error: "" };
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
],
|
|
482
|
+
plugins: [
|
|
483
|
+
...(process.env.AWS_ACCESS_KEY_ID ? [
|
|
484
|
+
new UploadPlugin({
|
|
485
|
+
pathColumnName: 'apartment_image',
|
|
486
|
+
s3Bucket: 'tmpbucket-adminforth',
|
|
487
|
+
s3Region: 'eu-central-1',
|
|
488
|
+
allowedFileExtensions: ['jpg', 'jpeg', 'png', 'gif', 'webm', 'exe', 'webp'],
|
|
489
|
+
maxFileSize: 1024 * 1024 * 20, // 5MB
|
|
490
|
+
s3AccessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
491
|
+
s3SecretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
492
|
+
// s3ACL: 'public-read', // ACL which will be set to uploaded file
|
|
493
|
+
s3Path: ({ originalFilename, originalExtension, contentType }) => `aparts/${new Date().getFullYear()}/${uuid()}/${originalFilename}.${originalExtension}`,
|
|
494
|
+
generation: {
|
|
495
|
+
provider: 'openai-dall-e',
|
|
496
|
+
countToGenerate: 2,
|
|
497
|
+
openAiOptions: {
|
|
498
|
+
model: 'dall-e-3',
|
|
499
|
+
size: '1792x1024',
|
|
500
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
501
|
+
},
|
|
502
|
+
fieldsForContext: ['title'],
|
|
503
|
+
rateLimit: {
|
|
504
|
+
limit: '2/1m',
|
|
505
|
+
errorMessage: 'For demo purposes, you can generate only 2 images per minute',
|
|
506
|
+
}
|
|
507
|
+
},
|
|
508
|
+
preview: {
|
|
509
|
+
// Used to display preview (if it is image) in list and show views
|
|
510
|
+
// previewUrl: ({s3Path}) => `https://tmpbucket-adminforth.s3.eu-central-1.amazonaws.com/${s3Path}`,
|
|
511
|
+
showInList: true,
|
|
512
|
+
}
|
|
513
|
+
}),
|
|
514
|
+
] : []),
|
|
515
|
+
new ImportExportPlugin({}),
|
|
516
|
+
// new ChatGptPlugin({
|
|
517
|
+
// openAiApiKey: process.env.OPENAI_API_KEY as string,
|
|
518
|
+
// model: 'gpt-4o',
|
|
519
|
+
// fieldName: 'title',
|
|
520
|
+
// expert: {
|
|
521
|
+
// debounceTime: 250,
|
|
522
|
+
// }
|
|
523
|
+
// }),
|
|
524
|
+
// new ChatGptPlugin({
|
|
525
|
+
// openAiApiKey: process.env.OPENAI_API_KEY as string,
|
|
526
|
+
// fieldName: 'description',
|
|
527
|
+
// model: 'gpt-4o',
|
|
528
|
+
// expert: {
|
|
529
|
+
// debounceTime: 250,
|
|
530
|
+
// }
|
|
531
|
+
// }),
|
|
532
|
+
new RichEditorPlugin(Object.assign({ htmlFieldName: 'description', completion: {
|
|
533
|
+
provider: 'openai-chat-gpt',
|
|
534
|
+
params: {
|
|
535
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
536
|
+
model: 'gpt-4o',
|
|
537
|
+
},
|
|
538
|
+
expert: {
|
|
539
|
+
debounceTime: 250,
|
|
540
|
+
}
|
|
541
|
+
} }, (process.env.AWS_ACCESS_KEY_ID ? {
|
|
542
|
+
attachments: {
|
|
543
|
+
attachmentResource: 'description_images',
|
|
544
|
+
attachmentFieldName: 'image_path',
|
|
545
|
+
attachmentRecordIdFieldName: 'record_id',
|
|
546
|
+
attachmentResourceIdFieldName: 'resource_id',
|
|
547
|
+
},
|
|
548
|
+
} : {}))),
|
|
549
|
+
],
|
|
550
|
+
options: {
|
|
551
|
+
listRowsAutoRefreshSeconds: 100,
|
|
552
|
+
pageInjections: {
|
|
553
|
+
list: {
|
|
554
|
+
customActionIcons: '@@/IdShow.vue',
|
|
555
|
+
// bottom: {
|
|
556
|
+
// file: '@@/TopLine.vue',
|
|
557
|
+
// meta: {
|
|
558
|
+
// thinEnoughToShrinkTable: true,
|
|
559
|
+
// }
|
|
560
|
+
// }
|
|
561
|
+
},
|
|
562
|
+
// show: {
|
|
563
|
+
// beforeBreadcrumbs: '@@/TopLine.vue',
|
|
564
|
+
// },
|
|
565
|
+
},
|
|
566
|
+
listPageSize: 25,
|
|
567
|
+
// listTableClickUrl: async (record, adminUser) => null,
|
|
568
|
+
createEditGroups: [
|
|
569
|
+
{
|
|
570
|
+
groupName: 'Main info',
|
|
571
|
+
columns: ['id', 'title', 'description', 'country', 'apartment_image']
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
groupName: 'Characteristics',
|
|
575
|
+
columns: ['price', 'square_meter', 'number_of_rooms', "property_type", "listed"]
|
|
576
|
+
}
|
|
577
|
+
],
|
|
578
|
+
bulkActions: [
|
|
579
|
+
{
|
|
580
|
+
label: 'Mark as listed',
|
|
581
|
+
// icon: 'typcn:archive',
|
|
582
|
+
state: 'active',
|
|
583
|
+
confirm: 'Are you sure you want to mark all selected apartments as listed?',
|
|
584
|
+
action: async function ({ selectedIds, adminUser }) {
|
|
585
|
+
const stmt = db.prepare(`UPDATE apartments SET listed = 1 WHERE id IN (${selectedIds.map(() => '?').join(',')})`);
|
|
586
|
+
await stmt.run(...selectedIds);
|
|
587
|
+
return { ok: true, successMessage: 'Marked as listed' };
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
],
|
|
591
|
+
allowedActions: {
|
|
592
|
+
edit: true,
|
|
593
|
+
delete: async (p) => { return true; },
|
|
594
|
+
show: true,
|
|
595
|
+
filter: true,
|
|
596
|
+
create: true,
|
|
597
|
+
},
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
dataSource: 'maindb',
|
|
602
|
+
table: 'users',
|
|
603
|
+
resourceId: 'users',
|
|
604
|
+
label: 'Users',
|
|
605
|
+
recordLabel: (r) => `👤 ${r.email}`,
|
|
606
|
+
plugins: [
|
|
607
|
+
new ForeignInlineListPlugin({
|
|
608
|
+
foreignResourceId: 'aparts',
|
|
609
|
+
modifyTableResourceConfig: (resourceConfig) => {
|
|
610
|
+
// hide column 'square_meter' from both 'list' and 'filter'
|
|
611
|
+
resourceConfig.columns.find((c) => c.name === 'square_meter').showIn = [];
|
|
612
|
+
resourceConfig.options.listPageSize = 3;
|
|
613
|
+
},
|
|
614
|
+
}),
|
|
615
|
+
new ForeignInlineListPlugin({
|
|
616
|
+
foreignResourceId: 'audit_log',
|
|
617
|
+
}),
|
|
618
|
+
new TwoFactorsAuthPlugin({
|
|
619
|
+
twoFaSecretFieldName: 'secret2fa',
|
|
620
|
+
// optional callback to define which users should be enforced to use 2FA
|
|
621
|
+
usersFilterToApply: (adminUser) => {
|
|
622
|
+
if (process.env.NODE_ENV === 'development') {
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
// return true if user should be enforced to use 2FA,
|
|
626
|
+
// return true;
|
|
627
|
+
return adminUser.dbUser.email !== 'adminforth';
|
|
628
|
+
},
|
|
629
|
+
}),
|
|
630
|
+
new EmailResetPasswordPlugin({
|
|
631
|
+
emailProvider: 'AWS_SES',
|
|
632
|
+
emailField: 'email',
|
|
633
|
+
sendFrom: 'no-reply@devforth.io',
|
|
634
|
+
providerOptions: {
|
|
635
|
+
AWS_SES: {
|
|
636
|
+
region: 'eu-central-1',
|
|
637
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
638
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
639
|
+
}
|
|
640
|
+
},
|
|
641
|
+
passwordField: 'password',
|
|
642
|
+
}),
|
|
643
|
+
],
|
|
644
|
+
options: {
|
|
645
|
+
allowedActions: {
|
|
646
|
+
create: async ({ adminUser, meta }) => {
|
|
647
|
+
// console.log('create', adminUser, meta);
|
|
648
|
+
return true;
|
|
649
|
+
},
|
|
650
|
+
delete: true,
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
columns: [
|
|
654
|
+
{
|
|
655
|
+
name: 'id',
|
|
656
|
+
primaryKey: true,
|
|
657
|
+
fillOnCreate: ({ initialRecord, adminUser }) => uuid(),
|
|
658
|
+
showIn: ['list', 'filter', 'show'], // the default is full set
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
name: 'secret2fa',
|
|
662
|
+
type: AdminForthDataTypes.STRING,
|
|
663
|
+
showIn: [],
|
|
664
|
+
backendOnly: true,
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
name: 'email',
|
|
668
|
+
isUnique: true,
|
|
669
|
+
required: true,
|
|
670
|
+
enforceLowerCase: true,
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
name: 'created_at',
|
|
674
|
+
type: AdminForthDataTypes.DATETIME,
|
|
675
|
+
showIn: ['list', 'filter', 'show'],
|
|
676
|
+
fillOnCreate: ({ initialRecord, adminUser }) => (new Date()).toISOString(),
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
name: 'password_hash',
|
|
680
|
+
showIn: [],
|
|
681
|
+
backendOnly: true, // will never go to frontend
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
name: 'role',
|
|
685
|
+
enum: [
|
|
686
|
+
// { value: 'superadmin', label: 'Super Admin' },
|
|
687
|
+
{ value: 'user', label: 'User' },
|
|
688
|
+
]
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
name: 'password',
|
|
692
|
+
virtual: true, // field will not be persisted into db
|
|
693
|
+
required: { create: true }, // to show only in create page
|
|
694
|
+
editingNote: { edit: 'Leave empty to keep password unchanged' },
|
|
695
|
+
minLength: 8,
|
|
696
|
+
validation: [
|
|
697
|
+
AdminForth.Utils.PASSWORD_VALIDATORS.UP_LOW_NUM,
|
|
698
|
+
],
|
|
699
|
+
type: AdminForthDataTypes.STRING,
|
|
700
|
+
showIn: ['create', 'edit'], // to show in create and edit pages
|
|
701
|
+
masked: true, // to show stars in input field
|
|
702
|
+
}
|
|
703
|
+
],
|
|
704
|
+
hooks: {
|
|
705
|
+
create: {
|
|
706
|
+
beforeSave: async ({ record, adminUser, resource }) => {
|
|
707
|
+
record.password_hash = await AdminForth.Utils.generatePasswordHash(record.password);
|
|
708
|
+
return { ok: true, error: "" };
|
|
709
|
+
// if return 'error': , record will not be saved and error will be proxied
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
edit: {
|
|
713
|
+
beforeSave: async ({ record, adminUser, resource }) => {
|
|
714
|
+
if (record.password) {
|
|
715
|
+
record.password_hash = await AdminForth.Utils.generatePasswordHash(record.password);
|
|
716
|
+
}
|
|
717
|
+
return { ok: true, error: "" };
|
|
718
|
+
},
|
|
719
|
+
// beforeDatasourceRequest: async ({ query, adminUser, resource }) => {
|
|
720
|
+
// return { ok: true, error: false }
|
|
721
|
+
// },
|
|
722
|
+
// afterDatasourceResponse: async ({ response, adminUser }) => {
|
|
723
|
+
// return { ok: true, error: false }
|
|
724
|
+
// }
|
|
725
|
+
},
|
|
726
|
+
// list: {
|
|
727
|
+
// beforeDatasourceRequest: async ({ query, adminUser }) => {
|
|
728
|
+
// return { ok: true, error: false }
|
|
729
|
+
// },
|
|
730
|
+
// afterDatasourceResponse: async ({ response, adminUser }) => {
|
|
731
|
+
// return { ok: true, error: false }
|
|
732
|
+
// }
|
|
733
|
+
// },
|
|
734
|
+
// show: {
|
|
735
|
+
// beforeDatasourceRequest: async ({ query, adminUser, resource }) => {
|
|
736
|
+
// return { ok: true, error: false }
|
|
737
|
+
// },
|
|
738
|
+
// afterDatasourceResponse: async ({ response, adminUser, resource }) => {
|
|
739
|
+
// return { ok: true, error: false }
|
|
740
|
+
// }
|
|
741
|
+
// },
|
|
742
|
+
}
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
dataSource: 'maindb',
|
|
746
|
+
table: 'description_image',
|
|
747
|
+
resourceId: 'description_images',
|
|
748
|
+
label: 'Description images',
|
|
749
|
+
columns: [
|
|
750
|
+
{ name: 'id', primaryKey: true, required: false, fillOnCreate: ({ initialRecord }) => uuid() },
|
|
751
|
+
{ name: 'created_at', required: false, fillOnCreate: ({ initialRecord }) => (new Date()).toISOString() },
|
|
752
|
+
{ name: 'resource_id', required: false },
|
|
753
|
+
{ name: 'record_id', required: false },
|
|
754
|
+
{ name: 'image_path', required: false },
|
|
755
|
+
],
|
|
756
|
+
plugins: [
|
|
757
|
+
...(process.env.AWS_ACCESS_KEY_ID ? [
|
|
758
|
+
new UploadPlugin({
|
|
759
|
+
pathColumnName: 'image_path',
|
|
760
|
+
s3Bucket: 'tmpbucket-adminforth',
|
|
761
|
+
s3Region: 'eu-central-1',
|
|
762
|
+
allowedFileExtensions: ['jpg', 'jpeg', 'png', 'gif', 'webm', 'exe', 'webp'],
|
|
763
|
+
maxFileSize: 1024 * 1024 * 20, // 5MB
|
|
764
|
+
s3AccessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
765
|
+
s3SecretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
766
|
+
// rich editor plugin supports only 'public-read' ACL images for SEO purposes (instead of presigned URLs which change every time)
|
|
767
|
+
s3ACL: 'public-read', // ACL which will be set to uploaded file
|
|
768
|
+
s3Path: ({ originalFilename, originalExtension, contentType }) => `description_images/${new Date().getFullYear()}/${uuid()}/${originalFilename}.${originalExtension}`,
|
|
769
|
+
preview: {
|
|
770
|
+
// Used to display preview (if it is image) in list and show views instead of just path
|
|
771
|
+
// previewUrl: ({s3Path}) => `https://tmpbucket-adminforth.s3.eu-central-1.amazonaws.com/${s3Path}`,
|
|
772
|
+
// show image preview instead of path in list view
|
|
773
|
+
// showInList: false,
|
|
774
|
+
}
|
|
775
|
+
}),
|
|
776
|
+
] : []),
|
|
777
|
+
],
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
dataSource: 'db2', table: 'games',
|
|
781
|
+
resourceId: 'games',
|
|
782
|
+
label: 'Games',
|
|
783
|
+
columns: [
|
|
784
|
+
{
|
|
785
|
+
name: 'id',
|
|
786
|
+
required: false,
|
|
787
|
+
label: 'Identifier', fillOnCreate: ({ initialRecord }) => uuid(),
|
|
788
|
+
showIn: ['list', 'filter', 'show'], // the default is full set
|
|
789
|
+
},
|
|
790
|
+
{ name: 'name', required: true, isUnique: true },
|
|
791
|
+
{ name: 'created_by', required: true,
|
|
792
|
+
enum: [
|
|
793
|
+
{ value: 'CD Projekt Red', label: 'CD Projekt Red' },
|
|
794
|
+
{ value: 'Rockstar Studios', label: 'Rockstar' },
|
|
795
|
+
{ value: 'Bethesda Game Studios', label: 'Bethesda' },
|
|
796
|
+
]
|
|
797
|
+
},
|
|
798
|
+
{ name: 'release_date', fillOnCreate: ({ initialRecord }) => (new Date()).toISOString() },
|
|
799
|
+
{ name: 'release_date2' },
|
|
800
|
+
{ name: 'description' },
|
|
801
|
+
{ name: 'price' },
|
|
802
|
+
{ name: 'enabled' },
|
|
803
|
+
],
|
|
804
|
+
options: {
|
|
805
|
+
listPageSize: 10,
|
|
806
|
+
},
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
dataSource: 'db2', table: 'users',
|
|
810
|
+
resourceId: 'games_users',
|
|
811
|
+
label: 'Games users',
|
|
812
|
+
columns: [
|
|
813
|
+
{
|
|
814
|
+
name: 'id',
|
|
815
|
+
primaryKey: true,
|
|
816
|
+
fillOnCreate: ({ initialRecord, adminUser }) => uuid(),
|
|
817
|
+
showIn: ['list', 'filter', 'show'], // the default is full set
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
name: 'email',
|
|
821
|
+
required: true,
|
|
822
|
+
isUnique: true,
|
|
823
|
+
sortable: false,
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
name: 'created_at',
|
|
827
|
+
type: AdminForthDataTypes.DATETIME,
|
|
828
|
+
showIn: ['list', 'filter', 'show'],
|
|
829
|
+
fillOnCreate: ({ initialRecord, adminUser }) => (new Date()).toISOString(),
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
name: 'role',
|
|
833
|
+
enum: [
|
|
834
|
+
{ value: 'superadmin', label: 'Super Admin' },
|
|
835
|
+
{ value: 'user', label: 'User' },
|
|
836
|
+
]
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
name: 'password',
|
|
840
|
+
virtual: true, // field will not be persisted into db
|
|
841
|
+
required: { create: true }, // to show only in create page
|
|
842
|
+
editingNote: { edit: 'Leave empty to keep password unchanged' },
|
|
843
|
+
minLength: 8,
|
|
844
|
+
type: AdminForthDataTypes.STRING,
|
|
845
|
+
showIn: ['create', 'edit'], // to show in create and edit pages
|
|
846
|
+
masked: true, // to show stars in input field
|
|
847
|
+
}
|
|
848
|
+
],
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
dataSource: 'db3', table: 'game',
|
|
852
|
+
columns: [
|
|
853
|
+
{ name: '_id', primaryKey: true,
|
|
854
|
+
type: AdminForthDataTypes.STRING,
|
|
855
|
+
"maxLength": 255, "required": true, },
|
|
856
|
+
{ name: 'bb_enabled',
|
|
857
|
+
"type": AdminForthDataTypes.BOOLEAN,
|
|
858
|
+
"required": false, backendOnly: true
|
|
859
|
+
},
|
|
860
|
+
{ name: 'bb_rank',
|
|
861
|
+
"type": AdminForthDataTypes.INTEGER,
|
|
862
|
+
"required": false,
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
name: 'blocked_countries',
|
|
866
|
+
"type": AdminForthDataTypes.STRING,
|
|
867
|
+
"maxLength": 255, "required": false,
|
|
868
|
+
enum: [
|
|
869
|
+
{ value: 'TR', label: 'Turkey' },
|
|
870
|
+
{ value: 'DE', label: 'Germany' },
|
|
871
|
+
{ value: 'RU', label: 'Russia' },
|
|
872
|
+
{ value: 'US', label: 'United States' },
|
|
873
|
+
{ value: 'GB', label: 'United Kingdom' },
|
|
874
|
+
{ value: 'FR', label: 'France' },
|
|
875
|
+
{ value: 'IT', label: 'Italy' },
|
|
876
|
+
{ value: 'ES', label: 'Spain' },
|
|
877
|
+
{ value: 'BR', label: 'Brazil' },
|
|
878
|
+
{ value: 'CA', label: 'Canada' },
|
|
879
|
+
{ value: 'AU', label: 'Australia' },
|
|
880
|
+
{ value: 'NL', label: 'Netherlands' },
|
|
881
|
+
{ value: 'SE', label: 'Sweden' },
|
|
882
|
+
{ value: 'NO', label: 'Norway' },
|
|
883
|
+
{ value: 'FI', label: 'Finland' },
|
|
884
|
+
{ value: 'DK', label: 'Denmark' },
|
|
885
|
+
{ value: 'PL', label: 'Poland' },
|
|
886
|
+
{ value: 'CZ', label: 'Czechia' },
|
|
887
|
+
{ value: 'SK', label: 'Slovakia' },
|
|
888
|
+
{ value: 'HU', label: 'Hungary' },
|
|
889
|
+
{ value: 'RO', label: 'Romania' },
|
|
890
|
+
{ value: 'BG', label: 'Bulgaria' },
|
|
891
|
+
{ value: 'GR', label: 'Greece' },
|
|
892
|
+
{ value: 'TR', label: 'Turkey' }
|
|
893
|
+
]
|
|
894
|
+
},
|
|
895
|
+
{ name: 'release_date', "type": AdminForthDataTypes.DATETIME,
|
|
896
|
+
"required": false,
|
|
897
|
+
},
|
|
898
|
+
]
|
|
899
|
+
}
|
|
900
|
+
],
|
|
901
|
+
menu: [
|
|
902
|
+
{
|
|
903
|
+
label: 'Dashboard',
|
|
904
|
+
icon: 'flowbite:chart-pie-solid',
|
|
905
|
+
component: '@@/Dash.vue',
|
|
906
|
+
path: '/dashboard',
|
|
907
|
+
// homepage: true,
|
|
908
|
+
isStaticRoute: false,
|
|
909
|
+
meta: {
|
|
910
|
+
title: 'Dashboard',
|
|
911
|
+
}
|
|
912
|
+
},
|
|
913
|
+
{
|
|
914
|
+
label: 'Core',
|
|
915
|
+
icon: 'flowbite:brain-solid', //from here https://icon-sets.iconify.design/flowbite/
|
|
916
|
+
open: true,
|
|
917
|
+
children: [
|
|
918
|
+
{
|
|
919
|
+
label: 'Apartments',
|
|
920
|
+
icon: 'flowbite:home-solid',
|
|
921
|
+
resourceId: 'aparts',
|
|
922
|
+
// badge: async (adminUser) => {
|
|
923
|
+
// return '10'
|
|
924
|
+
// }
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
label: 'Description Images',
|
|
928
|
+
resourceId: 'description_images',
|
|
929
|
+
icon: 'flowbite:image-solid',
|
|
930
|
+
},
|
|
931
|
+
// {
|
|
932
|
+
// label: 'Games',
|
|
933
|
+
// icon: 'flowbite:caret-right-solid',
|
|
934
|
+
// resourceId: 'games',
|
|
935
|
+
// },
|
|
936
|
+
// {
|
|
937
|
+
// label: 'Games Users',
|
|
938
|
+
// icon: 'flowbite:user-solid',
|
|
939
|
+
// resourceId: 'games_users',
|
|
940
|
+
// visible:(user) => {
|
|
941
|
+
// return user.dbUser.role === 'superadmin'
|
|
942
|
+
// }
|
|
943
|
+
// },
|
|
944
|
+
// {
|
|
945
|
+
// label: 'Casino Games',
|
|
946
|
+
// icon: 'flowbite:caret-right-solid',
|
|
947
|
+
// resourceId: 'game',
|
|
948
|
+
// },
|
|
949
|
+
{
|
|
950
|
+
label: 'Clicks',
|
|
951
|
+
icon: 'flowbite:search-outline',
|
|
952
|
+
resourceId: 'clicks',
|
|
953
|
+
},
|
|
954
|
+
]
|
|
955
|
+
},
|
|
956
|
+
{
|
|
957
|
+
type: 'gap'
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
type: 'divider'
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
type: 'heading',
|
|
964
|
+
label: 'SYSTEM',
|
|
965
|
+
},
|
|
966
|
+
{
|
|
967
|
+
label: 'Users',
|
|
968
|
+
icon: 'flowbite:user-solid',
|
|
969
|
+
resourceId: 'users',
|
|
970
|
+
visible: (user) => {
|
|
971
|
+
return user.dbUser.role === 'superadmin';
|
|
972
|
+
}
|
|
973
|
+
},
|
|
974
|
+
{
|
|
975
|
+
label: 'Logs',
|
|
976
|
+
icon: 'flowbite:search-outline',
|
|
977
|
+
resourceId: 'audit_log',
|
|
978
|
+
},
|
|
979
|
+
],
|
|
980
|
+
});
|
|
981
|
+
const app = express();
|
|
982
|
+
app.use(express.json());
|
|
983
|
+
const port = 3000;
|
|
984
|
+
(async () => {
|
|
985
|
+
// needed to compile SPA. Call it here or from a build script e.g. in Docker build time to reduce downtime
|
|
986
|
+
await admin.bundleNow({ hotReload: process.env.NODE_ENV === 'development' });
|
|
987
|
+
console.log('Bundling AdminForth done. For faster serving consider calling bundleNow() from a build script.');
|
|
988
|
+
})();
|
|
989
|
+
// add api before .serve
|
|
990
|
+
app.get('/api/testtest/', admin.express.authorize(async (req, res, next) => {
|
|
991
|
+
res.json({ ok: true, data: [1, 2, 3], adminUser: req.adminUser });
|
|
992
|
+
}));
|
|
993
|
+
app.get(`${ADMIN_BASE_URL}/api/dashboard/`, admin.express.authorize(async (req, res) => {
|
|
994
|
+
admin.getPluginByClassName('AuditLogPlugin').logCustomAction('aparts', null, 'visitedDashboard', { dashboard: 'main' }, req.adminUser);
|
|
995
|
+
const days = req.body.days || 7;
|
|
996
|
+
const apartsByDays = await db.prepare(`SELECT
|
|
997
|
+
strftime('%Y-%m-%d', created_at, 'unixepoch') as day,
|
|
998
|
+
COUNT(*) as count
|
|
999
|
+
FROM apartments
|
|
1000
|
+
GROUP BY day
|
|
1001
|
+
ORDER BY day DESC
|
|
1002
|
+
LIMIT ?;
|
|
1003
|
+
`).all(days);
|
|
1004
|
+
const totalAparts = apartsByDays.reduce((acc, { count }) => acc + count, 0);
|
|
1005
|
+
// add listed, unlisted, listedPrice, unlistedPrice
|
|
1006
|
+
const listedVsUnlistedByDays = await db.prepare(`SELECT
|
|
1007
|
+
strftime('%Y-%m-%d', created_at, 'unixepoch') as day,
|
|
1008
|
+
SUM(listed) as listed,
|
|
1009
|
+
COUNT(*) - SUM(listed) as unlisted,
|
|
1010
|
+
SUM(listed * price) as listedPrice,
|
|
1011
|
+
SUM((1 - listed) * price) as unlistedPrice
|
|
1012
|
+
FROM apartments
|
|
1013
|
+
GROUP BY day
|
|
1014
|
+
ORDER BY day DESC
|
|
1015
|
+
LIMIT ?;
|
|
1016
|
+
`).all(days);
|
|
1017
|
+
const listedVsUnlistedPriceByDays = await db.prepare(`SELECT
|
|
1018
|
+
strftime('%Y-%m-%d', created_at, 'unixepoch') as day,
|
|
1019
|
+
SUM(listed * price) as listedPrice,
|
|
1020
|
+
SUM((1 - listed) * price) as unlistedPrice
|
|
1021
|
+
FROM apartments
|
|
1022
|
+
GROUP BY day
|
|
1023
|
+
ORDER BY day DESC
|
|
1024
|
+
LIMIT ?;
|
|
1025
|
+
`).all(days);
|
|
1026
|
+
const totalListedPrice = Math.round(listedVsUnlistedByDays.reduce((acc, { listedPrice }) => acc + listedPrice, 0));
|
|
1027
|
+
const totalUnlistedPrice = Math.round(listedVsUnlistedByDays.reduce((acc, { unlistedPrice }) => acc + unlistedPrice, 0));
|
|
1028
|
+
res.json({
|
|
1029
|
+
apartsByDays,
|
|
1030
|
+
totalAparts,
|
|
1031
|
+
listedVsUnlistedByDays,
|
|
1032
|
+
totalListedPrice,
|
|
1033
|
+
totalUnlistedPrice,
|
|
1034
|
+
listedVsUnlistedPriceByDays,
|
|
1035
|
+
});
|
|
1036
|
+
}));
|
|
1037
|
+
// serve after you added all api
|
|
1038
|
+
admin.express.serve(app);
|
|
1039
|
+
admin.discoverDatabases().then(async () => {
|
|
1040
|
+
if (!await admin.resource('users').get([Filters.EQ('email', 'adminforth')])) {
|
|
1041
|
+
await admin.resource('users').create({
|
|
1042
|
+
email: 'adminforth',
|
|
1043
|
+
password_hash: await AdminForth.Utils.generatePasswordHash('adminforth'),
|
|
1044
|
+
role: 'superadmin',
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
});
|
|
1048
|
+
app.listen(port, () => {
|
|
1049
|
+
console.log(`Example app listening at http://localhost:${port}`);
|
|
1050
|
+
console.log(`\n⚡ AdminForth is available at http://localhost:${port}${ADMIN_BASE_URL}\n`);
|
|
1051
|
+
});
|
|
1052
|
+
//# sourceMappingURL=index.js.map
|