feat(api): add view configuration API routes (#13177)

* feat: add view_configurations feature flag

  - Add feature flag provider and hooks to admin dashboard
  - Add backend API endpoint for feature flags
  - Create view_configurations feature flag (disabled by default)
  - Update order list table to use legacy version when flag is disabled
  - Can be enabled with MEDUSA_FF_VIEW_CONFIGURATIONS=true env var

* fix: naming

* fix: feature flags unauthenticated

* fix: add test

* feat: add settings module

* fix: deps

* fix: cleanup

* fix: add more tetsts

* fix: rm changelog

* fix: deps

* fix: add settings module to default modules list

* feat(api): add view configuration API routes

- Add CRUD endpoints for view configurations
- Add active view configuration management endpoints
- Add feature flag middleware for view config routes
- Add comprehensive integration tests
- Add HTTP types for view configuration payloads and responses
- Support system defaults and user-specific configurations
- Enable setting views as active during create/update operations

* fix: test

* fix: test

* fix: test

* fix: change view configuration path

* fix: tests

* fix: remove manual settings module config from integration tests

* fix: container typing

* fix: workflows
This commit is contained in:
Sebastian Rindom
2025-08-15 13:17:52 +02:00
committed by GitHub
parent f7fc05307f
commit 12a38bcd2b
28 changed files with 1858 additions and 10 deletions

View File

@@ -22,16 +22,18 @@ export const adminHeaders = {
export const createAdminUser = async ( export const createAdminUser = async (
dbConnection, dbConnection,
adminHeaders, adminHeaders,
container? container?,
options?: { email?: string }
) => { ) => {
const appContainer = container ?? getContainer()! const appContainer = container ?? getContainer()!
const email = options?.email ?? "admin@medusa.js"
const userModule: IUserModuleService = appContainer.resolve(Modules.USER) const userModule: IUserModuleService = appContainer.resolve(Modules.USER)
const authModule: IAuthModuleService = appContainer.resolve(Modules.AUTH) const authModule: IAuthModuleService = appContainer.resolve(Modules.AUTH)
const user = await userModule.createUsers({ const user = await userModule.createUsers({
first_name: "Admin", first_name: "Admin",
last_name: "User", last_name: "User",
email: "admin@medusa.js", email,
}) })
const hashConfig = { logN: 15, r: 8, p: 1 } const hashConfig = { logN: 15, r: 8, p: 1 }
@@ -41,7 +43,7 @@ export const createAdminUser = async (
provider_identities: [ provider_identities: [
{ {
provider: "emailpass", provider: "emailpass",
entity_id: "admin@medusa.js", entity_id: email,
provider_metadata: { provider_metadata: {
password: passwordHash.toString("base64"), password: passwordHash.toString("base64"),
}, },

View File

@@ -0,0 +1,949 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { adminHeaders, createAdminUser } from "../../helpers/create-admin-user"
import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils"
jest.setTimeout(50000)
const env = { MEDUSA_FF_MEDUSA_V2: true, MEDUSA_FF_VIEW_CONFIGURATIONS: true }
medusaIntegrationTestRunner({
env,
testSuite: ({ dbConnection, api, getContainer }) => {
describe("View Configurations API", () => {
let adminHeader
let secondAdminHeader
let secondAdminUserId
let adminUserId
beforeEach(async () => {
const container = getContainer()
const { user: adminUser } = await createAdminUser(
dbConnection,
adminHeaders,
container
)
adminHeader = adminHeaders.headers
adminUserId = adminUser.id
// Create a second admin user
const secondAdminHeaders = { headers: {} }
const { user: secondAdminUser } = await createAdminUser(
dbConnection,
secondAdminHeaders,
container,
{ email: "admin2@test.com" }
)
secondAdminUserId = secondAdminUser.id
secondAdminHeader = secondAdminHeaders.headers
})
describe("POST /admin/views/{entity}/configurations", () => {
it("should create a personal view configuration", async () => {
const payload = {
name: "My Order View",
configuration: {
visible_columns: ["id", "display_id", "created_at"],
column_order: ["display_id", "id", "created_at"],
},
}
const response = await api.post(
"/admin/views/orders/configurations",
payload,
{
headers: secondAdminHeader,
}
)
expect(response.status).toBe(200)
expect(response.data.view_configuration).toMatchObject({
entity: "orders",
name: "My Order View",
user_id: secondAdminUserId,
configuration: payload.configuration,
})
expect(response.data.view_configuration.is_system_default).toBeFalsy()
})
it("should create a system default view as admin", async () => {
const payload = {
name: "Default Order View",
is_system_default: true,
configuration: {
visible_columns: ["id", "display_id", "created_at", "total"],
column_order: ["display_id", "created_at", "total", "id"],
},
}
const response = await api.post(
"/admin/views/orders/configurations",
payload,
{
headers: adminHeader,
}
)
expect(response.status).toBe(200)
expect(response.data.view_configuration).toMatchObject({
entity: "orders",
name: "Default Order View",
user_id: null,
is_system_default: true,
configuration: payload.configuration,
})
})
})
describe("GET /admin/views/{entity}/configurations", () => {
let systemView
let personalView
let otherUserView
beforeEach(async () => {
const container = getContainer()
const settingsService = container.resolve("settings")
// Create system default view
systemView = await settingsService.createViewConfigurations({
entity: "orders",
name: "System Default",
is_system_default: true,
user_id: null,
configuration: {
visible_columns: ["id", "display_id"],
column_order: ["display_id", "id"],
},
})
// Create personal view for non-admin user
personalView = await settingsService.createViewConfigurations({
entity: "orders",
name: "My Personal View",
is_system_default: false,
user_id: secondAdminUserId,
configuration: {
visible_columns: ["id", "total"],
column_order: ["total", "id"],
},
})
// Create view for another user
otherUserView = await settingsService.createViewConfigurations({
entity: "orders",
name: "Other User View",
is_system_default: false,
user_id: "other-user-id",
configuration: {
visible_columns: ["id"],
column_order: ["id"],
},
})
})
it("should list system defaults and personal views", async () => {
const response = await api.get("/admin/views/orders/configurations", {
headers: secondAdminHeader,
})
expect(response.status).toBe(200)
expect(response.data.view_configurations).toHaveLength(2)
expect(response.data.view_configurations).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: systemView.id }),
expect.objectContaining({ id: personalView.id }),
])
)
// Should not include other user's view
expect(response.data.view_configurations).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ id: otherUserView.id }),
])
)
})
it("should filter by entity", async () => {
const response = await api.get(
"/admin/views/products/configurations",
{
headers: secondAdminHeader,
}
)
expect(response.status).toBe(200)
expect(response.data.view_configurations).toHaveLength(0)
})
})
describe("GET /admin/views/{entity}/configurations/:id", () => {
let viewConfig
beforeEach(async () => {
const container = getContainer()
const settingsService = container.resolve("settings")
viewConfig = await settingsService.createViewConfigurations({
entity: "orders",
name: "Test View",
is_system_default: false,
user_id: secondAdminUserId,
configuration: {
visible_columns: ["id"],
column_order: ["id"],
},
})
})
it("should retrieve own view configuration", async () => {
const response = await api.get(
`/admin/views/orders/configurations/${viewConfig.id}`,
{
headers: secondAdminHeader,
}
)
expect(response.status).toBe(200)
expect(response.data.view_configuration).toMatchObject({
id: viewConfig.id,
entity: "orders",
name: "Test View",
})
})
it("should prevent access to other user's view", async () => {
const container = getContainer()
const settingsService = container.resolve("settings")
const otherView = await settingsService.createViewConfigurations({
entity: "orders",
name: "Other View",
is_system_default: false,
user_id: "other-user-id",
configuration: {
visible_columns: ["id"],
column_order: ["id"],
},
})
const response = await api
.get(`/admin/views/orders/configurations/${otherView.id}`, {
headers: secondAdminHeader,
})
.catch((e) => e.response)
expect(response.status).toBe(400)
})
})
describe("POST /admin/views/{entity}/configurations/:id", () => {
let viewConfig
beforeEach(async () => {
const container = getContainer()
const settingsService = container.resolve("settings")
viewConfig = await settingsService.createViewConfigurations({
entity: "orders",
name: "Test View",
is_system_default: false,
user_id: secondAdminUserId,
configuration: {
visible_columns: ["id"],
column_order: ["id"],
},
})
})
it("should update own view configuration", async () => {
const payload = {
name: "Updated View",
configuration: {
visible_columns: ["id", "total"],
column_order: ["total", "id"],
},
}
const response = await api.post(
`/admin/views/orders/configurations/${viewConfig.id}`,
payload,
{
headers: secondAdminHeader,
}
)
expect(response.status).toBe(200)
expect(response.data.view_configuration).toMatchObject({
id: viewConfig.id,
name: "Updated View",
configuration: payload.configuration,
})
})
})
describe("DELETE /admin/views/{entity}/configurations/:id", () => {
let viewConfig
beforeEach(async () => {
const container = getContainer()
const settingsService = container.resolve("settings")
viewConfig = await settingsService.createViewConfigurations({
entity: "orders",
name: "Test View",
is_system_default: false,
user_id: secondAdminUserId,
configuration: {
visible_columns: ["id"],
column_order: ["id"],
},
})
})
it("should delete own view configuration", async () => {
const response = await api.delete(
`/admin/views/orders/configurations/${viewConfig.id}`,
{
headers: secondAdminHeader,
}
)
expect(response.status).toBe(200)
expect(response.data).toMatchObject({
id: viewConfig.id,
object: "view_configuration",
deleted: true,
})
// Verify it's deleted
const getResponse = await api
.get(`/admin/views/orders/configurations/${viewConfig.id}`, {
headers: secondAdminHeader,
})
.catch((e) => e.response)
expect(getResponse.status).toBe(404)
})
})
describe("GET /admin/views/{entity}/configurations/active", () => {
beforeEach(async () => {
const container = getContainer()
const settingsService = container.resolve("settings")
// Create and set active view
const viewConfig = await settingsService.createViewConfigurations({
entity: "orders",
name: "Active View",
is_system_default: false,
user_id: secondAdminUserId,
configuration: {
visible_columns: ["id", "total"],
column_order: ["total", "id"],
},
})
await settingsService.setActiveViewConfiguration(
"orders",
secondAdminUserId,
viewConfig.id
)
})
it("should retrieve active view configuration", async () => {
const response = await api.get(
"/admin/views/orders/configurations/active",
{
headers: secondAdminHeader,
}
)
expect(response.status).toBe(200)
expect(response.data.view_configuration).toMatchObject({
entity: "orders",
name: "Active View",
user_id: secondAdminUserId,
})
})
it("should return null when no active view", async () => {
const response = await api.get(
"/admin/views/products/configurations/active",
{
headers: secondAdminHeader,
}
)
expect(response.status).toBe(200)
expect(response.data.view_configuration).toBeNull()
})
})
describe("POST /admin/views/{entity}/configurations/active", () => {
let viewConfig
beforeEach(async () => {
const container = getContainer()
const settingsService = container.resolve("settings")
viewConfig = await settingsService.createViewConfigurations({
entity: "orders",
name: "Test View",
is_system_default: false,
user_id: secondAdminUserId,
configuration: {
visible_columns: ["id"],
column_order: ["id"],
},
})
})
it("should set active view configuration", async () => {
const response = await api.post(
"/admin/views/orders/configurations/active",
{
view_configuration_id: viewConfig.id,
},
{
headers: secondAdminHeader,
}
)
expect(response.status).toBe(200)
expect(response.data.success).toBe(true)
// Verify it's active
const activeResponse = await api.get(
"/admin/views/orders/configurations/active",
{
headers: secondAdminHeader,
}
)
expect(activeResponse.data.view_configuration.id).toBe(viewConfig.id)
})
it("should clear active view and return to default when setting view_configuration_id to null", async () => {
// First set an active view
await api.post(
"/admin/views/orders/configurations/active",
{
view_configuration_id: viewConfig.id,
},
{
headers: secondAdminHeader,
}
)
// Verify it's active
let activeResponse = await api.get(
"/admin/views/orders/configurations/active",
{
headers: secondAdminHeader,
}
)
expect(activeResponse.data.view_configuration.id).toBe(viewConfig.id)
// Now clear the active view
const clearResponse = await api.post(
"/admin/views/orders/configurations/active",
{
view_configuration_id: null,
},
{
headers: secondAdminHeader,
}
)
expect(clearResponse.status).toBe(200)
expect(clearResponse.data.success).toBe(true)
// Verify the active view is cleared
activeResponse = await api.get(
"/admin/views/orders/configurations/active",
{
headers: secondAdminHeader,
}
)
// Debug output
if (activeResponse.data.view_configuration) {
console.log("Active view after clearing:", {
id: activeResponse.data.view_configuration.id,
name: activeResponse.data.view_configuration.name,
is_system_default:
activeResponse.data.view_configuration.is_system_default,
})
}
// Should either return null or a system default if one exists
if (activeResponse.data.view_configuration) {
expect(
activeResponse.data.view_configuration.is_system_default
).toBe(true)
} else {
expect(activeResponse.data.view_configuration).toBeNull()
}
expect(activeResponse.data.is_default_active).toBe(true)
})
})
describe("System Default Views", () => {
it("should make system default views available to all users", async () => {
const container = getContainer()
// Create a third admin user
const thirdAdminHeaders = { headers: {} }
const { user: thirdAdminUser } = await createAdminUser(
dbConnection,
thirdAdminHeaders,
container,
{ email: "admin3@test.com" }
)
// Admin 1 creates a system default view
const systemDefaultView = await api.post(
"/admin/views/orders/configurations",
{
name: "System Default View",
configuration: {
visible_columns: ["id", "display_id", "created_at"],
column_order: ["display_id", "id", "created_at"],
},
is_system_default: true,
},
adminHeaders
)
expect(systemDefaultView.status).toEqual(200)
expect(systemDefaultView.data.view_configuration.user_id).toBeNull()
// Admin 3 should be able to see this view
const viewsForAdmin3 = await api.get(
"/admin/views/orders/configurations",
thirdAdminHeaders
)
expect(viewsForAdmin3.status).toEqual(200)
const systemDefaults = viewsForAdmin3.data.view_configurations.filter(
(v: any) => v.is_system_default
)
expect(systemDefaults).toHaveLength(1)
expect(systemDefaults[0].name).toEqual("System Default View")
// Admin 3 should also be able to retrieve it directly
const directRetrieve = await api.get(
`/admin/views/orders/configurations/${systemDefaultView.data.view_configuration.id}`,
thirdAdminHeaders
)
expect(directRetrieve.status).toEqual(200)
expect(directRetrieve.data.view_configuration.name).toEqual(
"System Default View"
)
})
it("should allow creating system default without name", async () => {
// Create a system default view without providing a name
const systemDefaultView = await api.post(
"/admin/views/customers/configurations",
{
is_system_default: true,
configuration: {
visible_columns: ["id", "email", "first_name", "last_name"],
column_order: ["email", "first_name", "last_name", "id"],
},
// Note: no name field
},
adminHeaders
)
expect(systemDefaultView.status).toEqual(200)
expect(systemDefaultView.data.view_configuration.user_id).toBeNull()
expect(
systemDefaultView.data.view_configuration.is_system_default
).toBe(true)
// Name should be undefined/null when not provided
expect(systemDefaultView.data.view_configuration.name).toBeFalsy()
})
it("should set view as active when created with set_active flag", async () => {
// Create a view with set_active = true
const viewConfig = await api.post(
"/admin/views/orders/configurations",
{
name: "Auto-Active View",
configuration: {
visible_columns: ["id", "display_id", "status"],
column_order: ["display_id", "status", "id"],
},
set_active: true,
},
{ headers: secondAdminHeader }
)
expect(viewConfig.status).toEqual(200)
// Verify the view is now active
const activeView = await api.get(
"/admin/views/orders/configurations/active",
{ headers: secondAdminHeader }
)
expect(activeView.status).toEqual(200)
expect(activeView.data.view_configuration).toBeTruthy()
expect(activeView.data.view_configuration.id).toEqual(
viewConfig.data.view_configuration.id
)
expect(activeView.data.view_configuration.name).toEqual(
"Auto-Active View"
)
})
it("should set view as active when updated with set_active flag", async () => {
const container = getContainer()
const settingsService = container.resolve("settings")
// Create two views
const view1 = await settingsService.createViewConfigurations({
entity: "orders",
name: "View 1",
is_system_default: false,
user_id: secondAdminUserId,
configuration: {
visible_columns: ["id"],
column_order: ["id"],
},
})
const view2 = await settingsService.createViewConfigurations({
entity: "orders",
name: "View 2",
is_system_default: false,
user_id: secondAdminUserId,
configuration: {
visible_columns: ["id", "total"],
column_order: ["total", "id"],
},
})
// Set view1 as active initially
await settingsService.setActiveViewConfiguration(
"orders",
secondAdminUserId,
view1.id
)
// Update view2 with set_active flag
const updateResponse = await api.post(
`/admin/views/orders/configurations/${view2.id}`,
{
name: "Updated View 2",
set_active: true,
},
{ headers: secondAdminHeader }
)
expect(updateResponse.status).toEqual(200)
// Verify view2 is now the active view
const activeView = await api.get(
"/admin/views/orders/configurations/active",
{ headers: secondAdminHeader }
)
expect(activeView.status).toEqual(200)
expect(activeView.data.view_configuration.id).toEqual(view2.id)
expect(activeView.data.view_configuration.name).toEqual(
"Updated View 2"
)
})
it("should allow resetting system default to code-level defaults", async () => {
// Create a system default view
const systemDefaultView = await api.post(
"/admin/views/orders/configurations",
{
name: "Custom System Default",
is_system_default: true,
configuration: {
visible_columns: ["id", "status", "total"],
column_order: ["status", "total", "id"],
},
},
adminHeaders
)
expect(systemDefaultView.status).toEqual(200)
const viewId = systemDefaultView.data.view_configuration.id
// Verify it exists
let viewsList = await api.get(
"/admin/views/orders/configurations",
adminHeaders
)
expect(
viewsList.data.view_configurations.some((v: any) => v.id === viewId)
).toBe(true)
// Delete the system default view (reset to code defaults)
const deleteResponse = await api.delete(
`/admin/views/orders/configurations/${viewId}`,
adminHeaders
)
expect(deleteResponse.status).toEqual(200)
expect(deleteResponse.data.deleted).toBe(true)
// Verify it's gone
viewsList = await api.get(
"/admin/views/orders/configurations",
adminHeaders
)
expect(
viewsList.data.view_configurations.some((v: any) => v.id === viewId)
).toBe(false)
// Getting active view should return null (falls back to code defaults)
const activeView = await api.get(
"/admin/views/orders/configurations/active",
adminHeaders
)
expect(activeView.data.view_configuration).toBeNull()
})
it("should return system default view when created and no user view is active", async () => {
// Step 1: Create a system default view
const systemDefaultView = await api.post(
"/admin/views/orders/configurations",
{
name: "System Default Orders",
is_system_default: true,
configuration: {
visible_columns: [
"id",
"display_id",
"created_at",
"customer",
"total",
],
column_order: [
"display_id",
"customer",
"total",
"created_at",
"id",
],
filters: {},
sorting: { id: "created_at", desc: true },
search: "",
},
},
adminHeaders
)
expect(systemDefaultView.status).toEqual(200)
expect(
systemDefaultView.data.view_configuration.is_system_default
).toBe(true)
// Step 2: Retrieve active view - should return the system default
const activeView = await api.get(
"/admin/views/orders/configurations/active",
{ headers: secondAdminHeader }
)
expect(activeView.status).toEqual(200)
expect(activeView.data.view_configuration).toBeTruthy()
expect(activeView.data.view_configuration.id).toEqual(
systemDefaultView.data.view_configuration.id
)
expect(activeView.data.view_configuration.name).toEqual(
"System Default Orders"
)
expect(activeView.data.view_configuration.is_system_default).toBe(
true
)
expect(activeView.data.is_default_active).toBe(true)
expect(activeView.data.default_type).toEqual("system")
})
})
describe("Filter, Sorting, and Search Persistence", () => {
it("should save and restore filters, sorting, and search configuration", async () => {
// Create a view with filters, sorting, and search
const viewConfig = await api.post(
"/admin/views/orders/configurations",
{
name: "Filtered View",
configuration: {
visible_columns: ["id", "status", "total", "created_at"],
column_order: ["status", "total", "created_at", "id"],
filters: {
status: ["pending", "completed"],
total: { gte: 100 },
},
sorting: { id: "created_at", desc: true },
search: "test search",
},
},
{ headers: secondAdminHeader }
)
expect(viewConfig.status).toEqual(200)
expect(
viewConfig.data.view_configuration.configuration.filters
).toEqual({
status: ["pending", "completed"],
total: { gte: 100 },
})
expect(
viewConfig.data.view_configuration.configuration.sorting
).toEqual({
id: "created_at",
desc: true,
})
expect(
viewConfig.data.view_configuration.configuration.search
).toEqual("test search")
// Retrieve the view and verify filters are preserved
const getResponse = await api.get(
`/admin/views/orders/configurations/${viewConfig.data.view_configuration.id}`,
{ headers: secondAdminHeader }
)
expect(getResponse.status).toEqual(200)
expect(
getResponse.data.view_configuration.configuration.filters
).toEqual({
status: ["pending", "completed"],
total: { gte: 100 },
})
})
it("should remove filters when updating a view without filters", async () => {
// Create a view with filters
const viewConfig = await api.post(
"/admin/views/orders/configurations",
{
name: "View with Filters",
configuration: {
visible_columns: ["id", "status", "total"],
column_order: ["status", "total", "id"],
filters: {
status: ["pending", "completed"],
total: { gte: 100 },
},
sorting: { id: "total", desc: true },
search: "initial search",
},
},
{ headers: secondAdminHeader }
)
expect(viewConfig.status).toEqual(200)
const viewId = viewConfig.data.view_configuration.id
// Update the view to remove filters
const updateResponse = await api.post(
`/admin/views/orders/configurations/${viewId}`,
{
configuration: {
visible_columns: ["id", "status", "total"],
column_order: ["status", "total", "id"],
filters: {}, // Empty filters object
sorting: null, // Remove sorting
search: "", // Clear search
},
},
{ headers: secondAdminHeader }
)
expect(updateResponse.status).toEqual(200)
// Verify filters were removed
expect(
updateResponse.data.view_configuration.configuration.filters
).toEqual({})
expect(
updateResponse.data.view_configuration.configuration.sorting
).toBeNull()
expect(
updateResponse.data.view_configuration.configuration.search
).toEqual("")
// Retrieve again to double-check persistence
const getResponse = await api.get(
`/admin/views/orders/configurations/${viewId}`,
{ headers: secondAdminHeader }
)
expect(getResponse.status).toEqual(200)
expect(
getResponse.data.view_configuration.configuration.filters
).toEqual({})
expect(
getResponse.data.view_configuration.configuration.sorting
).toBeNull()
expect(
getResponse.data.view_configuration.configuration.search
).toEqual("")
})
it("should update only specific filters while keeping others", async () => {
// Create a view with multiple filters
const viewConfig = await api.post(
"/admin/views/orders/configurations",
{
name: "Multi-Filter View",
configuration: {
visible_columns: ["id", "status", "total", "created_at"],
column_order: ["status", "total", "created_at", "id"],
filters: {
status: ["pending", "completed"],
total: { gte: 100, lte: 1000 },
created_at: { gte: "2024-01-01" },
},
sorting: { id: "created_at", desc: true },
search: "customer",
},
},
{ headers: secondAdminHeader }
)
expect(viewConfig.status).toEqual(200)
const viewId = viewConfig.data.view_configuration.id
// Update to remove only the 'total' filter
const updateResponse = await api.post(
`/admin/views/orders/configurations/${viewId}`,
{
configuration: {
visible_columns: ["id", "status", "total", "created_at"],
column_order: ["status", "total", "created_at", "id"],
filters: {
status: ["pending", "completed"],
created_at: { gte: "2024-01-01" },
// 'total' filter removed
},
sorting: { id: "created_at", desc: true },
search: "customer",
},
},
{ headers: secondAdminHeader }
)
expect(updateResponse.status).toEqual(200)
expect(
updateResponse.data.view_configuration.configuration.filters
).toEqual({
status: ["pending", "completed"],
created_at: { gte: "2024-01-01" },
})
expect(
updateResponse.data.view_configuration.configuration.filters.total
).toBeUndefined()
})
})
})
},
})

View File

@@ -25,6 +25,7 @@ export * from "./region"
export * from "./reservation" export * from "./reservation"
export * from "./return-reason" export * from "./return-reason"
export * from "./sales-channel" export * from "./sales-channel"
export * from "./settings"
export * from "./shipping-options" export * from "./shipping-options"
export * from "./shipping-profile" export * from "./shipping-profile"
export * from "./stock-location" export * from "./stock-location"

View File

@@ -0,0 +1,2 @@
export * from "./steps"
export * from "./workflows"

View File

@@ -0,0 +1,27 @@
import {
CreateViewConfigurationDTO,
} from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
export type CreateViewConfigurationStepInput = CreateViewConfigurationDTO
export const createViewConfigurationStepId = "create-view-configuration"
export const createViewConfigurationStep = createStep(
createViewConfigurationStepId,
async (data: CreateViewConfigurationStepInput, { container }) => {
const service = container.resolve(Modules.SETTINGS)
const created = await service.createViewConfigurations(data)
return new StepResponse(created, { id: created.id })
},
async (compensateInput, { container }) => {
if (!compensateInput?.id) {
return
}
const service = container.resolve(Modules.SETTINGS)
await service.deleteViewConfigurations([compensateInput.id])
}
)

View File

@@ -0,0 +1,3 @@
export * from "./create-view-configuration"
export * from "./update-view-configuration"
export * from "./set-active-view-configuration"

View File

@@ -0,0 +1,59 @@
import { ISettingsModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
export type SetActiveViewConfigurationStepInput = {
id: string
entity: string
user_id: string
}
export const setActiveViewConfigurationStepId = "set-active-view-configuration"
export const setActiveViewConfigurationStep = createStep(
setActiveViewConfigurationStepId,
async (input: SetActiveViewConfigurationStepInput, { container }) => {
const service = container.resolve<ISettingsModuleService>(Modules.SETTINGS)
// Get the currently active view configuration for rollback
const currentActiveView = await service.getActiveViewConfiguration(
input.entity,
input.user_id
)
// Set the new view as active
await service.setActiveViewConfiguration(
input.entity,
input.user_id,
input.id
)
return new StepResponse(input.id, {
entity: input.entity,
user_id: input.user_id,
previousActiveViewId: currentActiveView?.id || null,
})
},
async (compensateInput, { container }) => {
if (!compensateInput) {
return
}
const service = container.resolve<ISettingsModuleService>(Modules.SETTINGS)
if (compensateInput.previousActiveViewId) {
// Restore the previous active view
await service.setActiveViewConfiguration(
compensateInput.entity,
compensateInput.user_id,
compensateInput.previousActiveViewId
)
} else {
// If there was no previous active view, clear the active view
await service.clearActiveViewConfiguration(
compensateInput.entity,
compensateInput.user_id
)
}
}
)

View File

@@ -0,0 +1,41 @@
import {
UpdateViewConfigurationDTO,
ISettingsModuleService,
ViewConfigurationDTO,
} from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
export type UpdateViewConfigurationStepInput = {
id: string
data: UpdateViewConfigurationDTO
}
export const updateViewConfigurationStepId = "update-view-configuration"
export const updateViewConfigurationStep = createStep(
updateViewConfigurationStepId,
async (input: UpdateViewConfigurationStepInput, { container }) => {
const service = container.resolve<ISettingsModuleService>(Modules.SETTINGS)
const currentState = await service.retrieveViewConfiguration(input.id)
const updated = await service.updateViewConfigurations(input.id, input.data)
return new StepResponse(updated, {
id: input.id,
previousState: currentState,
})
},
async (compensateInput, { container }) => {
if (!compensateInput?.id || !compensateInput?.previousState) {
return
}
const service = container.resolve<ISettingsModuleService>(Modules.SETTINGS)
const { id, created_at, updated_at, ...restoreData } =
compensateInput.previousState as ViewConfigurationDTO
await service.updateViewConfigurations(compensateInput.id, restoreData)
}
)

View File

@@ -0,0 +1,42 @@
import {
CreateViewConfigurationDTO,
ViewConfigurationDTO,
} from "@medusajs/framework/types"
import {
WorkflowData,
WorkflowResponse,
createWorkflow,
when,
} from "@medusajs/framework/workflows-sdk"
import {
createViewConfigurationStep,
setActiveViewConfigurationStep,
} from "../steps"
export type CreateViewConfigurationWorkflowInput =
CreateViewConfigurationDTO & {
set_active?: boolean
}
export const createViewConfigurationWorkflowId = "create-view-configuration"
export const createViewConfigurationWorkflow = createWorkflow(
createViewConfigurationWorkflowId,
(
input: WorkflowData<CreateViewConfigurationWorkflowInput>
): WorkflowResponse<ViewConfigurationDTO> => {
const viewConfig = createViewConfigurationStep(input)
when({ input, viewConfig }, ({ input }) => {
return !!input.set_active && !!input.user_id
}).then(() => {
setActiveViewConfigurationStep({
id: viewConfig.id,
entity: viewConfig.entity,
user_id: input.user_id as string,
})
})
return new WorkflowResponse(viewConfig)
}
)

View File

@@ -0,0 +1,2 @@
export * from "./create-view-configuration"
export * from "./update-view-configuration"

View File

@@ -0,0 +1,51 @@
import {
UpdateViewConfigurationDTO,
ViewConfigurationDTO,
} from "@medusajs/framework/types"
import {
WorkflowData,
WorkflowResponse,
createWorkflow,
when,
transform,
} from "@medusajs/framework/workflows-sdk"
import {
updateViewConfigurationStep,
setActiveViewConfigurationStep,
} from "../steps"
export type UpdateViewConfigurationWorkflowInput = {
id: string
set_active?: boolean
} & UpdateViewConfigurationDTO
export const updateViewConfigurationWorkflowId = "update-view-configuration"
export const updateViewConfigurationWorkflow = createWorkflow(
updateViewConfigurationWorkflowId,
(
input: WorkflowData<UpdateViewConfigurationWorkflowInput>
): WorkflowResponse<ViewConfigurationDTO> => {
const updateData = transform({ input }, ({ input }) => {
const { id, set_active, ...data } = input
return data
})
const viewConfig = updateViewConfigurationStep({
id: input.id,
data: updateData,
})
when({ input, viewConfig }, ({ input, viewConfig }) => {
return !!input.set_active && !!viewConfig.user_id
}).then(() => {
setActiveViewConfigurationStep({
id: viewConfig.id,
entity: viewConfig.entity,
user_id: viewConfig.user_id as string,
})
})
return new WorkflowResponse(viewConfig)
}
)

View File

@@ -21,6 +21,7 @@ import {
IPromotionModuleService, IPromotionModuleService,
IRegionModuleService, IRegionModuleService,
ISalesChannelModuleService, ISalesChannelModuleService,
ISettingsModuleService,
IStockLocationService, IStockLocationService,
IStoreModuleService, IStoreModuleService,
ITaxModuleService, ITaxModuleService,
@@ -74,6 +75,7 @@ declare module "@medusajs/types" {
[Modules.FILE]: IFileModuleService [Modules.FILE]: IFileModuleService
[Modules.NOTIFICATION]: INotificationModuleService [Modules.NOTIFICATION]: INotificationModuleService
[Modules.LOCKING]: ILockingModule [Modules.LOCKING]: ILockingModule
[Modules.SETTINGS]: ISettingsModuleService
} }
} }

View File

@@ -43,4 +43,5 @@ export * from "./tax-provider"
export * from "./tax-rate" export * from "./tax-rate"
export * from "./tax-region" export * from "./tax-region"
export * from "./user" export * from "./user"
export * from "./view-configuration"
export * from "./workflow-execution" export * from "./workflow-execution"

View File

@@ -0,0 +1,3 @@
export * from "./responses"
export * from "./queries"
export * from "./payloads"

View File

@@ -0,0 +1,108 @@
export interface AdminCreateViewConfiguration {
/**
* The entity this configuration is for (e.g., "order", "product").
*/
entity: string
/**
* The name of the view configuration.
*/
name?: string
/**
* Whether this is a system default configuration.
*/
is_system_default?: boolean
/**
* Whether to set this view as the active view after creation.
*/
set_active?: boolean
/**
* The view configuration settings.
*/
configuration: {
/**
* The list of visible column IDs.
*/
visible_columns: string[]
/**
* The order of columns.
*/
column_order: string[]
/**
* Custom column widths.
*/
column_widths?: Record<string, number>
/**
* Active filters for the view.
*/
filters?: Record<string, any>
/**
* Sorting configuration.
*/
sorting?: {
id: string
desc: boolean
} | null
/**
* Search query for the view.
*/
search?: string
}
}
export interface AdminUpdateViewConfiguration {
/**
* The name of the view configuration.
*/
name?: string
/**
* Whether this is a system default configuration.
*/
is_system_default?: boolean
/**
* Whether to set this view as the active view after update.
*/
set_active?: boolean
/**
* The view configuration settings.
*/
configuration?: {
/**
* The list of visible column IDs.
*/
visible_columns?: string[]
/**
* The order of columns.
*/
column_order?: string[]
/**
* Custom column widths.
*/
column_widths?: Record<string, number>
/**
* Active filters for the view.
*/
filters?: Record<string, any>
/**
* Sorting configuration.
*/
sorting?: {
id: string
desc: boolean
} | null
/**
* Search query for the view.
*/
search?: string
}
}
export interface AdminSetActiveViewConfiguration {
/**
* The entity to set the active view for.
*/
entity: string
/**
* The ID of the view configuration to set as active, or null to clear the active view.
*/
view_configuration_id: string | null
}

View File

@@ -0,0 +1,37 @@
import { BaseFilterable, OperatorMap } from "../../../dal"
import { FindParams, SelectParams } from "../../common"
export interface AdminGetViewConfigurationParams extends SelectParams {}
export interface AdminGetViewConfigurationsParams
extends FindParams,
BaseFilterable<AdminGetViewConfigurationsParams> {
/**
* IDs to filter view configurations by.
*/
id?: string | string[]
/**
* Entity to filter by.
*/
entity?: string | string[]
/**
* Name to filter by.
*/
name?: string | string[]
/**
* User ID to filter by.
*/
user_id?: string | string[] | null
/**
* Filter by system default status.
*/
is_system_default?: boolean
/**
* Date filters for when the view configuration was created.
*/
created_at?: OperatorMap<string>
/**
* Date filters for when the view configuration was updated.
*/
updated_at?: OperatorMap<string>
}

View File

@@ -0,0 +1,80 @@
import { DeleteResponse, PaginatedResponse } from "../../common"
interface AdminViewConfiguration {
/**
* The view configuration's ID.
*/
id: string
/**
* The entity this configuration is for (e.g., "order", "product").
*/
entity: string
/**
* The name of the view configuration.
*/
name: string | null
/**
* The ID of the user who owns this configuration, or null for system defaults.
*/
user_id: string | null
/**
* Whether this is a system default configuration.
*/
is_system_default: boolean
/**
* The view configuration settings.
*/
configuration: {
/**
* The list of visible column IDs.
*/
visible_columns: string[]
/**
* The order of columns.
*/
column_order: string[]
/**
* Custom column widths.
*/
column_widths?: Record<string, number>
/**
* Active filters for the view.
*/
filters?: Record<string, any>
/**
* Sorting configuration.
*/
sorting?: {
id: string
desc: boolean
} | null
/**
* Search query for the view.
*/
search?: string
}
/**
* The date the view configuration was created.
*/
created_at: Date
/**
* The date the view configuration was updated.
*/
updated_at: Date
}
export interface AdminViewConfigurationResponse {
/**
* The view configuration's details.
*/
view_configuration: AdminViewConfiguration | null
}
export type AdminViewConfigurationListResponse = PaginatedResponse<{
/**
* The list of view configurations.
*/
view_configurations: AdminViewConfiguration[]
}>
export type AdminViewConfigurationDeleteResponse = DeleteResponse<"view_configuration">

View File

@@ -0,0 +1 @@
export * from "./admin"

View File

@@ -111,9 +111,9 @@ export interface UserPreferenceDTO {
} }
/** /**
* The filters to apply on the retrieved view configurations. * Partial filters for view configuration fields.
*/ */
export interface FilterableViewConfigurationProps extends BaseFilterable<ViewConfigurationDTO> { export interface ViewConfigurationFilterableFields {
/** /**
* The IDs to filter by. * The IDs to filter by.
*/ */
@@ -140,6 +140,21 @@ export interface FilterableViewConfigurationProps extends BaseFilterable<ViewCon
name?: string | string[] name?: string | string[]
} }
/**
* The filters to apply on the retrieved view configurations.
*/
export interface FilterableViewConfigurationProps extends ViewConfigurationFilterableFields {
/**
* An array of filters to apply on the entity, where each item in the array is joined with an "and" condition.
*/
$and?: (ViewConfigurationFilterableFields | FilterableViewConfigurationProps)[]
/**
* An array of filters to apply on the entity, where each item in the array is joined with an "or" condition.
*/
$or?: (ViewConfigurationFilterableFields | FilterableViewConfigurationProps)[]
}
/** /**
* The filters to apply on the retrieved user preferences. * The filters to apply on the retrieved user preferences.
*/ */

View File

@@ -8,14 +8,14 @@ export interface CreateViewConfigurationDTO {
entity: string entity: string
/** /**
* The name of the configuration. * The name of the configuration. Required unless creating a system default.
*/ */
name: string name?: string
/** /**
* The user ID this configuration belongs to. * The user ID this configuration belongs to. Can be null for system defaults.
*/ */
user_id?: string user_id?: string | null
/** /**
* Whether this is a system default configuration. * Whether this is a system default configuration.
@@ -131,4 +131,4 @@ export interface UpdateUserPreferenceDTO {
* The preference value. * The preference value.
*/ */
value: any value: any
} }

View File

@@ -0,0 +1,92 @@
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import { AdminUpdateViewConfigurationType } from "../validators"
import { HttpTypes } from "@medusajs/framework/types"
import { MedusaError, Modules } from "@medusajs/framework/utils"
import { updateViewConfigurationWorkflow } from "@medusajs/core-flows"
export const GET = async (
req: AuthenticatedMedusaRequest<HttpTypes.AdminGetViewConfigurationParams>,
res: MedusaResponse<HttpTypes.AdminViewConfigurationResponse>
) => {
const settingsService = req.scope.resolve(Modules.SETTINGS)
const viewConfiguration = await settingsService.retrieveViewConfiguration(
req.params.id,
req.queryConfig
)
if (
viewConfiguration.user_id &&
viewConfiguration.user_id !== req.auth_context.actor_id &&
!req.auth_context.app_metadata?.admin
) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"You don't have access to this view configuration"
)
}
res.json({ view_configuration: viewConfiguration })
}
export const POST = async (
req: AuthenticatedMedusaRequest<AdminUpdateViewConfigurationType>,
res: MedusaResponse<HttpTypes.AdminViewConfigurationResponse>
) => {
const settingsService = req.scope.resolve(Modules.SETTINGS)
// Single retrieval for permission check
const existing = await settingsService.retrieveViewConfiguration(
req.params.id,
{ select: ["id", "user_id", "is_system_default"] }
)
if (existing.user_id && existing.user_id !== req.auth_context.actor_id) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"You can only update your own view configurations"
)
}
const input = {
id: req.params.id,
...req.validatedBody,
}
const { result } = await updateViewConfigurationWorkflow(req.scope).run({
input,
})
res.json({ view_configuration: result })
}
export const DELETE = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse<HttpTypes.AdminViewConfigurationDeleteResponse>
) => {
const settingsService = req.scope.resolve(Modules.SETTINGS)
// Retrieve existing to check permissions
const existing = await settingsService.retrieveViewConfiguration(
req.params.id,
{ select: ["id", "user_id", "is_system_default", "entity", "name"] }
)
if (existing.user_id && existing.user_id !== req.auth_context.actor_id) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"You can only delete your own view configurations"
)
}
await settingsService.deleteViewConfigurations(req.params.id)
res.status(200).json({
id: req.params.id,
object: "view_configuration",
deleted: true,
})
}

View File

@@ -0,0 +1,79 @@
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import {
AdminSetActiveViewConfigurationType,
AdminGetActiveViewConfigurationParamsType,
} from "../validators"
import { HttpTypes } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
export const GET = async (
req: AuthenticatedMedusaRequest<AdminGetActiveViewConfigurationParamsType>,
res: MedusaResponse<
HttpTypes.AdminViewConfigurationResponse & {
is_default_active?: boolean
default_type?: "system" | "code"
}
>
) => {
const settingsService = req.scope.resolve(Modules.SETTINGS)
const viewConfiguration = await settingsService.getActiveViewConfiguration(
req.params.entity,
req.auth_context.actor_id
)
if (!viewConfiguration) {
// No active view set or explicitly cleared - return null
res.json({
view_configuration: null,
is_default_active: true,
default_type: "code",
})
} else {
// Check if the user has an explicit preference
const activeViewPref = await settingsService.getUserPreference(
req.auth_context.actor_id,
`active_view.${req.params.entity}`
)
// If there's no preference and the view is a system default, it means we're falling back to system default
const isDefaultActive =
!activeViewPref && viewConfiguration.is_system_default
res.json({
view_configuration: viewConfiguration,
is_default_active: isDefaultActive,
default_type:
isDefaultActive && viewConfiguration.is_system_default
? "system"
: undefined,
})
}
}
export const POST = async (
req: AuthenticatedMedusaRequest<AdminSetActiveViewConfigurationType>,
res: MedusaResponse<{ success: boolean }>
) => {
const settingsService = req.scope.resolve(Modules.SETTINGS)
if (req.body.view_configuration_id === null) {
// Clear the active view configuration
await settingsService.clearActiveViewConfiguration(
req.params.entity,
req.auth_context.actor_id
)
} else {
// Set a specific view as active
await settingsService.setActiveViewConfiguration(
req.params.entity,
req.auth_context.actor_id,
req.body.view_configuration_id
)
}
res.json({ success: true })
}

View File

@@ -0,0 +1,27 @@
import {
MedusaRequest,
MedusaResponse,
MedusaNextFunction
} from "@medusajs/framework/http"
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
import ViewConfigurationsFeatureFlag from "../../../../../loaders/feature-flags/view-configurations"
export const ensureViewConfigurationsEnabled = async (
req: MedusaRequest,
res: MedusaResponse,
next: MedusaNextFunction
) => {
const flagRouter = req.scope.resolve(
ContainerRegistrationKeys.FEATURE_FLAG_ROUTER
) as any
if (!flagRouter.isFeatureEnabled(ViewConfigurationsFeatureFlag.key)) {
res.status(404).json({
type: "not_found",
message: "Route not found"
})
return
}
next()
}

View File

@@ -0,0 +1,72 @@
import { validateAndTransformBody, validateAndTransformQuery } from "@medusajs/framework"
import { MiddlewareRoute } from "@medusajs/framework/http"
import * as QueryConfig from "./query-config"
import {
AdminCreateViewConfiguration,
AdminUpdateViewConfiguration,
AdminSetActiveViewConfiguration,
AdminGetViewConfigurationParams,
AdminGetActiveViewConfigurationParams,
AdminGetViewConfigurationsParams,
} from "./validators"
import { ensureViewConfigurationsEnabled } from "./middleware"
export const viewConfigurationRoutesMiddlewares: MiddlewareRoute[] = [
// Apply feature flag check to all view configuration routes
{
method: ["GET", "POST", "DELETE"],
matcher: "/admin/views/*/configurations*",
middlewares: [ensureViewConfigurationsEnabled],
},
{
method: ["GET"],
matcher: "/admin/views/:entity/configurations",
middlewares: [
validateAndTransformQuery(
AdminGetViewConfigurationsParams,
QueryConfig.retrieveViewConfigurationList
),
],
},
{
method: ["POST"],
matcher: "/admin/views/:entity/configurations",
middlewares: [
validateAndTransformBody(AdminCreateViewConfiguration),
],
},
{
method: ["GET"],
matcher: "/admin/views/:entity/configurations/:id",
middlewares: [
validateAndTransformQuery(
AdminGetViewConfigurationParams,
QueryConfig.retrieveViewConfiguration
),
],
},
{
method: ["POST"],
matcher: "/admin/views/:entity/configurations/:id",
middlewares: [
validateAndTransformBody(AdminUpdateViewConfiguration),
],
},
{
method: ["GET"],
matcher: "/admin/views/:entity/configurations/active",
middlewares: [
validateAndTransformQuery(
AdminGetActiveViewConfigurationParams,
QueryConfig.retrieveViewConfiguration
),
],
},
{
method: ["POST"],
matcher: "/admin/views/:entity/configurations/active",
middlewares: [
validateAndTransformBody(AdminSetActiveViewConfiguration),
],
},
]

View File

@@ -0,0 +1,20 @@
export const defaultViewConfigurationFields = [
"id",
"entity",
"name",
"user_id",
"is_system_default",
"configuration",
"created_at",
"updated_at",
]
export const retrieveViewConfigurationList = {
defaults: defaultViewConfigurationFields,
isList: true,
}
export const retrieveViewConfiguration = {
defaults: defaultViewConfigurationFields,
isList: false,
}

View File

@@ -0,0 +1,59 @@
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import { AdminCreateViewConfigurationType } from "./validators"
import { HttpTypes } from "@medusajs/framework/types"
import { MedusaError, Modules } from "@medusajs/framework/utils"
import { createViewConfigurationWorkflow } from "@medusajs/core-flows"
export const GET = async (
req: AuthenticatedMedusaRequest<HttpTypes.AdminGetViewConfigurationsParams>,
res: MedusaResponse<HttpTypes.AdminViewConfigurationListResponse>
) => {
const settingsService = req.scope.resolve(Modules.SETTINGS)
const filters = {
...req.filterableFields,
entity: req.params.entity,
$or: [{ user_id: req.auth_context.actor_id }, { is_system_default: true }],
}
const [viewConfigurations, count] =
await settingsService.listAndCountViewConfigurations(
filters,
req.queryConfig
)
res.json({
view_configurations: viewConfigurations,
count,
offset: req.queryConfig.pagination?.skip || 0,
limit: req.queryConfig.pagination?.take || 20,
})
}
export const POST = async (
req: AuthenticatedMedusaRequest<AdminCreateViewConfigurationType>,
res: MedusaResponse<HttpTypes.AdminViewConfigurationResponse>
) => {
// Validate: name is required unless creating a system default
if (!req.body.is_system_default && !req.body.name) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Name is required unless creating a system default view"
)
}
const input = {
...req.body,
entity: req.params.entity,
user_id: req.body.is_system_default ? null : req.auth_context.actor_id,
}
const { result } = await createViewConfigurationWorkflow(req.scope).run({
input,
})
return res.json({ view_configuration: result })
}

View File

@@ -0,0 +1,71 @@
import { z } from "zod"
import {
createFindParams,
createOperatorMap,
createSelectParams,
} from "../../../../utils/validators"
import { applyAndAndOrOperators } from "../../../../utils/common-validators"
export const AdminGetViewConfigurationParams = createSelectParams()
export type AdminGetActiveViewConfigurationParamsType = z.infer<typeof AdminGetActiveViewConfigurationParams>
export const AdminGetActiveViewConfigurationParams = createSelectParams()
export const AdminGetViewConfigurationsParamsFields = z.object({
id: z.union([z.string(), z.array(z.string())]).optional(),
entity: z.union([z.string(), z.array(z.string())]).optional(),
name: z.union([z.string(), z.array(z.string())]).optional(),
user_id: z.union([z.string(), z.array(z.string()), z.null()]).optional(),
is_system_default: z.boolean().optional(),
created_at: createOperatorMap().optional(),
updated_at: createOperatorMap().optional(),
})
export type AdminGetViewConfigurationsParamsType = z.infer<typeof AdminGetViewConfigurationsParams>
export const AdminGetViewConfigurationsParams = createFindParams({
offset: 0,
limit: 20,
})
.merge(AdminGetViewConfigurationsParamsFields)
.merge(applyAndAndOrOperators(AdminGetViewConfigurationsParamsFields))
export type AdminCreateViewConfigurationType = z.infer<typeof AdminCreateViewConfiguration>
export const AdminCreateViewConfiguration = z.object({
name: z.string().optional(),
is_system_default: z.boolean().optional().default(false),
set_active: z.boolean().optional().default(false),
configuration: z.object({
visible_columns: z.array(z.string()),
column_order: z.array(z.string()),
column_widths: z.record(z.string(), z.number()).optional(),
filters: z.record(z.string(), z.any()).optional(),
sorting: z.object({
id: z.string(),
desc: z.boolean(),
}).nullable().optional(),
search: z.string().optional(),
}),
})
export type AdminUpdateViewConfigurationType = z.infer<typeof AdminUpdateViewConfiguration>
export const AdminUpdateViewConfiguration = z.object({
name: z.string().optional(),
is_system_default: z.boolean().optional(),
set_active: z.boolean().optional().default(false),
configuration: z.object({
visible_columns: z.array(z.string()).optional(),
column_order: z.array(z.string()).optional(),
column_widths: z.record(z.string(), z.number()).optional(),
filters: z.record(z.string(), z.any()).optional(),
sorting: z.object({
id: z.string(),
desc: z.boolean(),
}).nullable().optional(),
search: z.string().optional(),
}).optional(),
})
export type AdminSetActiveViewConfigurationType = z.infer<typeof AdminSetActiveViewConfiguration>
export const AdminSetActiveViewConfiguration = z.object({
view_configuration_id: z.union([z.string(), z.null()]),
})

View File

@@ -41,6 +41,7 @@ import { adminTaxRegionRoutesMiddlewares } from "./admin/tax-regions/middlewares
import { adminTaxProviderRoutesMiddlewares } from "./admin/tax-providers/middlewares" import { adminTaxProviderRoutesMiddlewares } from "./admin/tax-providers/middlewares"
import { adminUploadRoutesMiddlewares } from "./admin/uploads/middlewares" import { adminUploadRoutesMiddlewares } from "./admin/uploads/middlewares"
import { adminUserRoutesMiddlewares } from "./admin/users/middlewares" import { adminUserRoutesMiddlewares } from "./admin/users/middlewares"
import { viewConfigurationRoutesMiddlewares } from "./admin/views/[entity]/configurations/middlewares"
import { adminWorkflowsExecutionsMiddlewares } from "./admin/workflows-executions/middlewares" import { adminWorkflowsExecutionsMiddlewares } from "./admin/workflows-executions/middlewares"
import { authRoutesMiddlewares } from "./auth/middlewares" import { authRoutesMiddlewares } from "./auth/middlewares"
@@ -126,4 +127,5 @@ export default defineMiddlewares([
...adminTaxProviderRoutesMiddlewares, ...adminTaxProviderRoutesMiddlewares,
...adminOrderEditRoutesMiddlewares, ...adminOrderEditRoutesMiddlewares,
...adminPaymentCollectionsMiddlewares, ...adminPaymentCollectionsMiddlewares,
...viewConfigurationRoutesMiddlewares,
]) ])