Skip to content

Commit cb044cd

Browse files
author
Ivan S
committed
Merge branch 'alpha2' into alpha
2 parents 6f4af2e + 26ef12e commit cb044cd

7 files changed

Lines changed: 61 additions & 12 deletions

File tree

src/helpers/configHelper.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,16 @@ export class ConfigHelper {
146146
*/
147147
public static normalizeConfig(config: AdminpanelConfig): AdminpanelConfig {
148148
const defaultConfig = getDefaultConfig();
149+
const defaultPrefix = defaultConfig.routePrefix;
150+
const routePrefix = config.routePrefix ?? defaultPrefix;
151+
152+
// Built-in navbar links are written against the default prefix, so retarget
153+
// them whenever the app overrides routePrefix (otherwise they 404).
154+
const builtinLinks = (defaultConfig.navbar?.additionalLinks || []).map((item) =>
155+
typeof item.link === 'string' && item.link.startsWith(`${defaultPrefix}/`)
156+
? { ...item, link: routePrefix + item.link.slice(defaultPrefix.length) }
157+
: item
158+
);
149159

150160
const mergedConfig = {
151161
...defaultConfig,
@@ -158,7 +168,7 @@ export class ConfigHelper {
158168
...defaultConfig.navbar,
159169
...config.navbar,
160170
additionalLinks: [
161-
...(defaultConfig.navbar?.additionalLinks || []),
171+
...builtinLinks,
162172
...(config.navbar?.additionalLinks || [])
163173
]
164174
}

src/lib/DataAccessor.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,11 @@ export class DataAccessor {
200200
// Set required and type attributes
201201
fldConfig.required = Boolean(fldConfig.required ?? modelField.required);
202202
// Default type for field. Could be fetched from config file or model if not defined in config file.
203-
fldConfig.type = ((fldConfig.type || modelField.type).toLowerCase() as FieldsTypes);
203+
// The model layer has a single temporal type (`date`), the UI distinguishes
204+
// date/datetime/time. Default to `datetime` so the time part survives editing;
205+
// a date-only column can be narrowed with `type: "date"` in the model config.
206+
const modelFieldType = modelField.type === "date" ? "datetime" : modelField.type;
207+
fldConfig.type = ((fldConfig.type || modelFieldType).toLowerCase() as FieldsTypes);
204208

205209
// Normalize final configuration (fldConfig is always an object here, normalize never returns undefined)
206210
fldConfig = this.adminizer.configHelper.normalizeFieldConfig(this.adminizer, fldConfig, key, modelField)!;

src/lib/model/AbstractModel.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import { InternalModelCreateData, InternalModelRepository, InternalModelUpdateDa
77
import { INTERNAL_MODEL_ACCESS_TOKEN } from "./internalModelAccessToken";
88

99
export interface Attribute {
10-
type: 'association' | 'association-many' | 'number' | 'json' | 'string' | 'boolean' | 'ref';
10+
/**
11+
* `date` covers every temporal column (date, time, timestamp). It is deliberately
12+
* separate from `string`: dates must not take part in LIKE search, and the UI
13+
* renders them with date controls.
14+
*/
15+
type: 'association' | 'association-many' | 'number' | 'json' | 'string' | 'boolean' | 'date' | 'ref';
1116
required?: boolean;
1217
unique?: boolean;
1318
defaultsTo?: any;

src/lib/model/adapter/sequelize.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ function resolveType(type: any): Attribute["type"] {
2828
if (sqlType.includes("bool") || sqlType === "tinyint(1)") return "boolean";
2929
if (sqlType.includes("int") || sqlType.includes("float") || sqlType.includes("decimal")) return "number";
3030
if (sqlType.includes("json")) return "json";
31-
if (sqlType.includes("date")) return "string";
31+
// Sequelize renders temporal types per dialect: sqlite gives DATETIME/DATE,
32+
// postgres TIMESTAMP WITH TIME ZONE, mysql DATETIME/TIMESTAMP. Match all of them.
33+
if (sqlType.includes("date") || sqlType.includes("time")) return "date";
3234
return "ref";
3335
}
3436

@@ -115,6 +117,7 @@ type AbstractFieldType =
115117
| "number"
116118
| "boolean"
117119
| "json"
120+
| "date"
118121
| "ref"
119122
| "association"
120123
| "association-many";

src/lib/model/adapter/typeorm.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ function resolveAdminizerTypeFromColumn(column: any): Attribute["type"] {
4848
return "number";
4949
}
5050
if (type.includes("json")) return "json";
51-
if (type.includes("date") || type.includes("time")) return "string";
51+
if (type.includes("date") || type.includes("time")) return "date";
5252
if (type.includes("char") || type.includes("text") || type.includes("uuid") || type.includes("string")) {
5353
return "string";
5454
}

src/system/Router.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { Adminizer } from "../lib/Adminizer";
3030
import timezones from "../controllers/timezones";
3131
import { NotificationController } from "../controllers/notifications/NotificationController";
3232
import { HistoryController } from "../controllers/history-actions/HistoryController";
33+
import { AiAssistantController } from "../controllers/ai/AiAssistantController";
3334
import listUserFilters from "../controllers/listUserFilters";
3435
import {
3536
requireAnyPermission,
@@ -39,6 +40,7 @@ import {
3940
requirePermission
4041
} from "../policies/authPolicies";
4142
import {
43+
aiModelToken,
4244
catalogToken,
4345
groupFilterVisibilityToken,
4446
historyToken,
@@ -419,6 +421,26 @@ export default class Router {
419421
withPolicies(regenerateUserApiKey, requireAuthEnabled(), requireAuthAPI())
420422
);
421423

424+
/**
425+
* AI assistant — consumed by src/assets/js/contexts/AiAssistantContext.tsx
426+
*/
427+
adminizer.app.get(
428+
`${adminizer.config.routePrefix}/api/ai-assistant/models`,
429+
withPolicies(AiAssistantController.getModels, requireAuthAPI())
430+
);
431+
adminizer.app.get(
432+
`${adminizer.config.routePrefix}/api/ai-assistant/history/:modelId`,
433+
withPolicies(AiAssistantController.getHistory, requireAuthAPI(), requirePermission(aiModelToken))
434+
);
435+
adminizer.app.post(
436+
`${adminizer.config.routePrefix}/api/ai-assistant/query`,
437+
withPolicies(AiAssistantController.sendMessage, requireAuthAPI(), requirePermission(aiModelToken))
438+
);
439+
adminizer.app.delete(
440+
`${adminizer.config.routePrefix}/api/ai-assistant/history/:modelId`,
441+
withPolicies(AiAssistantController.resetHistory, requireAuthAPI(), requirePermission(aiModelToken))
442+
);
443+
422444

423445
adminizer.app.get(
424446
`${adminizer.config.routePrefix}/get-timezones`,

src/system/systemModelContracts.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ export const SYSTEM_MODEL_CONTRACTS: readonly SystemModelContract[] = [
8686
color: {type: "string"},
8787
version: {type: "number"},
8888
columns: {relation: {kind: "many", target: "FilterColumn"}},
89-
createdAt: {type: "string"},
90-
updatedAt: {type: "string"},
89+
createdAt: {type: "date"},
90+
updatedAt: {type: "date"},
9191
},
9292
},
9393
{
@@ -112,8 +112,8 @@ export const SYSTEM_MODEL_CONTRACTS: readonly SystemModelContract[] = [
112112
diff: {type: "json"},
113113
user: {relation: {kind: "one", target: "User"}},
114114
isCurrent: {type: "boolean"},
115-
createdAt: {type: "string"},
116-
updatedAt: {type: "string"},
115+
createdAt: {type: "date"},
116+
updatedAt: {type: "date"},
117117
preview: {type: "boolean"},
118118
},
119119
},
@@ -127,8 +127,8 @@ export const SYSTEM_MODEL_CONTRACTS: readonly SystemModelContract[] = [
127127
notificationClass: {type: "string"},
128128
channel: {type: "string"},
129129
metadata: {type: "json"},
130-
createdAt: {type: "string"},
131-
updatedAt: {type: "string"},
130+
createdAt: {type: "date"},
131+
updatedAt: {type: "date"},
132132
},
133133
},
134134
{
@@ -161,7 +161,12 @@ export function validateSystemModelContract(
161161
continue;
162162
}
163163

164-
if (expected.type && actual.type !== expected.type) {
164+
// Before `date` existed, temporal columns were reported as `string`.
165+
// Keep accepting that from third-party adapters that have not been updated.
166+
const typeMatches = expected.type === actual.type
167+
|| (expected.type === "date" && actual.type === "string");
168+
169+
if (expected.type && !typeMatches) {
165170
errors.push(`attribute "${attributeName}" must have type "${expected.type}", received "${actual.type}"`);
166171
}
167172
if (expected.required === true && actual.required !== true) {

0 commit comments

Comments
 (0)