diff --git a/packages/page-builder-types/.gitignore b/packages/page-builder-types/.gitignore new file mode 100644 index 00000000..b512c09d --- /dev/null +++ b/packages/page-builder-types/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/packages/page-builder-types/package.json b/packages/page-builder-types/package.json new file mode 100644 index 00000000..8665e805 --- /dev/null +++ b/packages/page-builder-types/package.json @@ -0,0 +1,18 @@ +{ + "name": "@lambdacurry/medusa-page-builder-types", + "version": "0.0.4", + "packageManager": "yarn@4.6.0", + "types": "./src/index.d.ts", + "devDependencies": { + "typescript": "5.7.3" + }, + "exports": { + ".": "./src/index.d.ts" + }, + "installConfig": { + "hoistingLimits": "workspaces" + }, + "files": [ + "src" + ] +} diff --git a/packages/page-builder-types/src/admins.d.ts b/packages/page-builder-types/src/admins.d.ts new file mode 100644 index 00000000..1b44f093 --- /dev/null +++ b/packages/page-builder-types/src/admins.d.ts @@ -0,0 +1,78 @@ +import type { Post } from './models' + +// Response Types +export interface PaginatedResponse { + count: number + offset: number + limit: number +} + +export type AdminPageBuilderListPostsQuery = { + offset?: number + limit?: number + q?: string + id?: string | string[] + title?: string + handle?: string + status?: PostStatus | PostStatus[] + type?: PostType | PostType[] + content_mode?: PostContentMode | PostContentMode[] + is_home_page?: boolean + // biome-ignore lint/suspicious/noExplicitAny: medusa infered type + published_at?: any + // biome-ignore lint/suspicious/noExplicitAny: medusa infered type + archived_at?: any + // biome-ignore lint/suspicious/noExplicitAny: medusa infered type + created_at?: any + // biome-ignore lint/suspicious/noExplicitAny: medusa infered type + updated_at?: any + order?: string + fields?: string +} + +export interface AdminPageBuilderListPostsResponse extends PaginatedResponse { + posts: Post[] +} + +export type AdminPageBuilderCreatePostBody = { + title: string + handle?: string + excerpt?: string + content?: Record + status?: PostStatus + type?: PostType + content_mode?: PostContentMode + seo?: Record + is_home_page?: boolean +} + +export interface AdminPageBuilderCreatePostResponse { + post: Post +} + +export type AdminPageBuilderUpdatePostBody = { + id: string + title?: string + handle?: string + excerpt?: string + content?: Record + status?: PostStatus + type?: PostType + content_mode?: PostContentMode + seo?: Record + is_home_page?: boolean +} + +export interface AdminPageBuilderUpdatePostResponse { + post: Post +} + +export interface AdminPageBuilderDeletePostResponse { + id: string + object: string + deleted: boolean +} + +export interface AdminPageBuilderDuplicatePostResponse { + post: Post +} diff --git a/packages/page-builder-types/src/common.d.ts b/packages/page-builder-types/src/common.d.ts new file mode 100644 index 00000000..b57fef77 --- /dev/null +++ b/packages/page-builder-types/src/common.d.ts @@ -0,0 +1,39 @@ +/** + * Common type declarations for page builder + */ + +export type PostStatus = 'draft' | 'published' | 'archived' + +export type PostType = 'page' | 'post' + +export type PostContentMode = 'basic' | 'advanced' + +// These would be defined in the implementation file +export declare const postStatuses: readonly PostStatus[] +export declare const postTypes: readonly PostType[] +export declare const postContentModes: readonly PostContentMode[] + +export interface SortOptions { + sort?: string + order?: 'ASC' | 'DESC' +} + +export interface PaginationOptions { + limit?: number + offset?: number +} + +export interface FilterOptions { + q?: string + [key: string]: unknown +} + +export type QueryOptions = SortOptions & PaginationOptions & FilterOptions + +export interface FindConfig extends QueryOptions { + select?: (keyof T)[] + relations?: string[] + where?: { [K in keyof T]?: T[K] | T[K][] } & { + [key: string]: unknown + } +} diff --git a/packages/page-builder-types/src/index.d.ts b/packages/page-builder-types/src/index.d.ts new file mode 100644 index 00000000..cd043c0b --- /dev/null +++ b/packages/page-builder-types/src/index.d.ts @@ -0,0 +1,4 @@ +export * from './admins' +export * from './common' +export * from './models' +export * from './storefronts' diff --git a/packages/page-builder-types/src/models.d.ts b/packages/page-builder-types/src/models.d.ts new file mode 100644 index 00000000..1e22ad16 --- /dev/null +++ b/packages/page-builder-types/src/models.d.ts @@ -0,0 +1,92 @@ +/** + * Type declarations for page builder models + */ + +import type { PostContentMode, PostStatus, PostType } from './common' + +export interface Base { + id: string + created_at: string + updated_at: string | undefined +} + +export interface Post extends Base { + title: string + handle?: string | null + excerpt?: string | null + content?: Record | null + status: PostStatus + type: PostType + content_mode: PostContentMode + seo?: Record | null + is_home_page: boolean + published_at?: string | null + archived_at?: string | null + featured_image_id?: string + featured_image?: Image + authors?: PostAuthor[] + tags?: PostTag[] + sections?: PostSection[] + root_id?: string + root?: PostTemplate +} + +export interface Image extends Base { + url: string + alt?: string + width?: number + height?: number + mime_type?: string + file_size?: number + metadata?: Record +} + +export interface NavigationItem extends Base { + title: string + url: string + parent_id?: string + parent?: NavigationItem + children?: NavigationItem[] +} + +export interface PostAuthor extends Base { + name: string + bio?: string + posts?: Post[] +} + +export interface PostSection extends Base { + name: string + data?: Record + order: number + post_id?: string + post?: Post + parent_section_id?: string + parent_section?: PostSection + child_sections?: PostSection[] +} + +export interface PostTag extends Base { + name: string + posts?: Post[] +} + +export interface PostTemplate extends Base { + name: string + data?: Record + posts?: Post[] +} + +export interface SiteSettings extends Base { + site_name: string + site_url?: string + logo_id?: string + logo?: Image + favicon_id?: string + favicon?: Image + social_links?: Record + navigation?: Record + custom_css?: string + custom_js?: string + meta_defaults?: Record +} diff --git a/packages/page-builder-types/src/storefronts.d.ts b/packages/page-builder-types/src/storefronts.d.ts new file mode 100644 index 00000000..e6096dab --- /dev/null +++ b/packages/page-builder-types/src/storefronts.d.ts @@ -0,0 +1,49 @@ +/** + * Storefront type declarations for page builder + */ + +import type { Post, PostAuthor, PostTag, SiteSettings } from './models' + +export interface StorefrontGetPostParams { + handle: string +} + +export interface StorefrontGetPostResponse { + post: Post +} + +export interface StorefrontListPostsParams { + limit?: number + offset?: number + type?: string + tag?: string + author?: string + sort?: string + order?: 'asc' | 'desc' +} + +export interface StorefrontListPostsResponse { + posts: Post[] + count: number + offset: number + limit: number +} + +export interface StorefrontGetTagsResponse { + tags: PostTag[] +} + +export interface StorefrontGetAuthorsResponse { + authors: PostAuthor[] +} + +export interface StorefrontGetSiteSettingsResponse { + settings: SiteSettings +} + +export interface StorefrontRenderOptions { + cacheControl?: string + handle?: string + language?: string + preview?: boolean +} diff --git a/packages/plugins-sdk/package.json b/packages/plugins-sdk/package.json index 4ab7bfdd..6ecb7e29 100644 --- a/packages/plugins-sdk/package.json +++ b/packages/plugins-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@lambdacurry/medusa-plugins-sdk", - "version": "0.0.5", + "version": "0.0.6-beta.3", "description": "SDK for Medusa plugins", "author": "Lambda Curry (https://lambdacurry.dev)", "license": "MIT", @@ -38,7 +38,7 @@ "dev:publish": "yalc publish" }, "devDependencies": { - "@medusajs/types": "^2.5.0", + "@medusajs/types": "^2.6.1", "prettier": "^3.2.5", "rimraf": "^6.0.1", "tsup": "^8.0.2", @@ -46,7 +46,8 @@ "yalc": "^1.0.0-pre.53" }, "dependencies": { - "@medusajs/js-sdk": "^2.5.0", + "@lambdacurry/medusa-page-builder-types": "0.0.4", + "@medusajs/js-sdk": "^2.6.1", "@types/express": "^5.0.0", "@types/multer": "^1.4.12", "form-data": "^4.0.2" diff --git a/packages/plugins-sdk/src/sdk/admin/admin-page-builder.ts b/packages/plugins-sdk/src/sdk/admin/admin-page-builder.ts new file mode 100644 index 00000000..ccff26b4 --- /dev/null +++ b/packages/plugins-sdk/src/sdk/admin/admin-page-builder.ts @@ -0,0 +1,63 @@ +import type { Client } from '@medusajs/js-sdk' +import type { + AdminPageBuilderCreatePostBody, + AdminPageBuilderCreatePostResponse, + AdminPageBuilderListPostsQuery, + AdminPageBuilderListPostsResponse, + AdminPageBuilderDeletePostResponse, + AdminPageBuilderUpdatePostBody, + AdminPageBuilderUpdatePostResponse, + AdminPageBuilderDuplicatePostResponse, +} from '@lambdacurry/medusa-page-builder-types' + +export class AdminPageBuilderResource { + constructor(private client: Client) {} + + async listPosts(query: AdminPageBuilderListPostsQuery) { + return this.client.fetch( + '/admin/content/posts', + { + method: 'GET', + query, + }, + ) + } + + async createPost(data: AdminPageBuilderCreatePostBody) { + return this.client.fetch( + '/admin/content/posts', + { + method: 'POST', + body: data, + }, + ) + } + + async updatePost(id: string, data: AdminPageBuilderUpdatePostBody) { + return this.client.fetch( + `/admin/content/posts/${id}`, + { + method: 'PUT', + body: data, + }, + ) + } + + async deletePost(id: string) { + return this.client.fetch( + `/admin/content/posts/${id}`, + { + method: 'DELETE', + }, + ) + } + + async duplicatePost(id: string) { + return this.client.fetch( + `/admin/content/posts/${id}/duplicate`, + { + method: 'POST', + }, + ) + } +} diff --git a/packages/plugins-sdk/src/sdk/admin/admin-product-reviews.ts b/packages/plugins-sdk/src/sdk/admin/admin-product-reviews.ts index 6aac9951..52d7dc18 100644 --- a/packages/plugins-sdk/src/sdk/admin/admin-product-reviews.ts +++ b/packages/plugins-sdk/src/sdk/admin/admin-product-reviews.ts @@ -1,46 +1,70 @@ -import type { Client } from '@medusajs/js-sdk'; +import type { Client } from '@medusajs/js-sdk' import type { AdminCreateProductReviewResponseDTO, AdminListProductReviewsQuery, AdminProductReviewResponse, AdminUpdateProductReviewResponseDTO, AdminListProductReviewsResponse, -} from '../../types'; +} from '../../types' export class AdminProductReviewsResource { constructor(private client: Client) {} async list(query: AdminListProductReviewsQuery) { - return this.client.fetch(`/admin/product-reviews`, { - method: 'GET', - query, - }); + return this.client.fetch( + '/admin/product-reviews', + { + method: 'GET', + query, + }, + ) } - async updateStatus(productReviewId: string, status: 'pending' | 'approved' | 'flagged') { - return this.client.fetch(`/admin/product-reviews/${productReviewId}/status`, { - method: 'PUT', - body: { status }, - }); + async updateStatus( + productReviewId: string, + status: 'pending' | 'approved' | 'flagged', + ) { + return this.client.fetch( + `/admin/product-reviews/${productReviewId}/status`, + { + method: 'PUT', + body: { status }, + }, + ) } - async createResponse(productReviewId: string, data: AdminCreateProductReviewResponseDTO) { - return this.client.fetch(`/admin/product-reviews/${productReviewId}/response`, { - method: 'POST', - body: data, - }); + async createResponse( + productReviewId: string, + data: AdminCreateProductReviewResponseDTO, + ) { + return this.client.fetch( + `/admin/product-reviews/${productReviewId}/response`, + { + method: 'POST', + body: data, + }, + ) } - async updateResponse(productReviewId: string, data: AdminUpdateProductReviewResponseDTO) { - return this.client.fetch(`/admin/product-reviews/${productReviewId}/response`, { - method: 'PUT', - body: data, - }); + async updateResponse( + productReviewId: string, + data: AdminUpdateProductReviewResponseDTO, + ) { + return this.client.fetch( + `/admin/product-reviews/${productReviewId}/response`, + { + method: 'PUT', + body: data, + }, + ) } async deleteResponse(productReviewId: string) { - return this.client.fetch(`/admin/product-reviews/${productReviewId}/response`, { - method: 'DELETE', - }); + return this.client.fetch( + `/admin/product-reviews/${productReviewId}/response`, + { + method: 'DELETE', + }, + ) } } diff --git a/packages/plugins-sdk/src/sdk/admin/index.ts b/packages/plugins-sdk/src/sdk/admin/index.ts index 377d753b..fbb7288a 100644 --- a/packages/plugins-sdk/src/sdk/admin/index.ts +++ b/packages/plugins-sdk/src/sdk/admin/index.ts @@ -1,12 +1,15 @@ -import type { Client } from '@medusajs/js-sdk'; -import { Admin } from '@medusajs/js-sdk'; -import { AdminProductReviewsResource } from './admin-product-reviews'; - +import type { Client } from '@medusajs/js-sdk' +import { Admin } from '@medusajs/js-sdk' +import { AdminProductReviewsResource } from './admin-product-reviews' +import { AdminPageBuilderResource } from './admin-page-builder' export class ExtendedAdminSDK extends Admin { - public productReviews: AdminProductReviewsResource; + public productReviews: AdminProductReviewsResource + public pageBuilder: AdminPageBuilderResource constructor(client: Client) { - super(client); - this.productReviews = new AdminProductReviewsResource(client); + super(client) + + this.productReviews = new AdminProductReviewsResource(client) + this.pageBuilder = new AdminPageBuilderResource(client) } -} \ No newline at end of file +} diff --git a/packages/plugins-sdk/src/sdk/store/index.ts b/packages/plugins-sdk/src/sdk/store/index.ts index 8e1f69a5..e6bbe74e 100644 --- a/packages/plugins-sdk/src/sdk/store/index.ts +++ b/packages/plugins-sdk/src/sdk/store/index.ts @@ -1,11 +1,16 @@ -import type { Client } from '@medusajs/js-sdk'; -import { Store } from '@medusajs/js-sdk'; -import { StoreProductReviewsResource } from './store-product-reviews'; +import type { Client } from '@medusajs/js-sdk' +import { Store } from '@medusajs/js-sdk' +import { StoreProductReviewsResource } from './store-product-reviews' +import { StorePageBuilderResource } from './store-page-builder' export class ExtendedStorefrontSDK extends Store { - public productReviews: StoreProductReviewsResource; + public productReviews: StoreProductReviewsResource + public pageBuilder: StorePageBuilderResource + constructor(client: Client) { - super(client); - this.productReviews = new StoreProductReviewsResource(client); + super(client) + + this.productReviews = new StoreProductReviewsResource(client) + this.pageBuilder = new StorePageBuilderResource(client) } -} \ No newline at end of file +} diff --git a/packages/plugins-sdk/src/sdk/store/store-page-builder.ts b/packages/plugins-sdk/src/sdk/store/store-page-builder.ts new file mode 100644 index 00000000..0ad85f04 --- /dev/null +++ b/packages/plugins-sdk/src/sdk/store/store-page-builder.ts @@ -0,0 +1,12 @@ +import type { Client } from '@medusajs/js-sdk' + +export class StorePageBuilderResource { + constructor(private client: Client) {} + + async test() { + console.log(this.client) + return { + message: 'Hello, world!', + } + } +} diff --git a/packages/plugins-sdk/src/sdk/store/store-product-reviews.ts b/packages/plugins-sdk/src/sdk/store/store-product-reviews.ts index 51d829d1..a3c23c01 100644 --- a/packages/plugins-sdk/src/sdk/store/store-product-reviews.ts +++ b/packages/plugins-sdk/src/sdk/store/store-product-reviews.ts @@ -1,37 +1,49 @@ -import type { Client, ClientHeaders } from '@medusajs/js-sdk'; +import type { Client, ClientHeaders } from '@medusajs/js-sdk' import type { StoreListProductReviewsQuery, StoreListProductReviewsResponse, StoreListProductReviewStatsQuery, StoreListProductReviewStatsResponse, StoreUpsertProductReviewsDTO, - StoreUpsertProductReviewsResponse -} from '../../types'; + StoreUpsertProductReviewsResponse, +} from '../../types' export class StoreProductReviewsResource { constructor(private client: Client) {} async upsert(data: StoreUpsertProductReviewsDTO, headers?: ClientHeaders) { - return this.client.fetch(`/store/product-reviews`, { - method: 'POST', - body: data, - headers, - }); + return this.client.fetch( + '/store/product-reviews', + { + method: 'POST', + body: data, + headers, + }, + ) } async list(query: StoreListProductReviewsQuery, headers?: ClientHeaders) { - return this.client.fetch(`/store/product-reviews`, { - method: 'GET', - query, - headers, - }); + return this.client.fetch( + '/store/product-reviews', + { + method: 'GET', + query, + headers, + }, + ) } - async listStats(query: StoreListProductReviewStatsQuery, headers?: ClientHeaders) { - return this.client.fetch(`/store/product-review-stats`, { - method: 'GET', - query, - headers, - }); + async listStats( + query: StoreListProductReviewStatsQuery, + headers?: ClientHeaders, + ) { + return this.client.fetch( + '/store/product-review-stats', + { + method: 'GET', + query, + headers, + }, + ) } } diff --git a/plugins/page-builder/.gitignore b/plugins/page-builder/.gitignore new file mode 100644 index 00000000..7aa21691 --- /dev/null +++ b/plugins/page-builder/.gitignore @@ -0,0 +1,26 @@ +/dist +.env +.DS_Store +/uploads +/node_modules +yarn-error.log + +.idea + +coverage + +!src/** + +./tsconfig.tsbuildinfo +medusa-db.sql +build +.cache + +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +.medusa \ No newline at end of file diff --git a/plugins/page-builder/README.md b/plugins/page-builder/README.md new file mode 100644 index 00000000..2e18803e --- /dev/null +++ b/plugins/page-builder/README.md @@ -0,0 +1,176 @@ +# @lambdacurry/medusa-page-builder + +A plugin that adds visual page building capabilities to your Medusa application, with built-in components, layouts, and SEO management. + +> This plugin is part of the [Medusa Plugins Collection](https://github.com/lambda-curry/medusa-plugins). + +## Features +> See a demo in our [Medusa Starter](https://github.com/lambda-curry/medusa2-starter) + +- Visual drag-and-drop page builder interface +- Pre-built components library +- Customizable layouts and templates +- Dynamic content management +- SEO optimization tools +- Version control and publishing workflow +- SDK for Store and Admin operations + +## Prerequisites + +- [Medusa >=2.5.0 backend](https://docs.medusajs.com/development/backend/install) +- [PostgreSQL](https://docs.medusajs.com/development/backend/prepare-environment#postgresql) + +## Installation and Configuration + +1. Install the plugin: +```bash +yarn add @lambdacurry/medusa-page-builder + +# or, if you're using yarn workspaces +yarn workspace my-app add @lambdacurry/medusa-page-builder +``` + +2. Add to `medusa-config.ts`: +```js +module.exports = defineConfig({ + plugins: [ + { + resolve: '@lambdacurry/medusa-page-builder', + options: { + defaultPageStatus: 'draft', // OPTIONAL, default is 'published' + components: { + // Define your custom components here + }, + }, + }, + ], +}); +``` + +3. Run migrations: +```bash +yarn medusa db:migrate +``` + +## Using the Plugin SDK + +> For detailed SDK setup and configuration, refer to the [@lambdacurry/medusa-plugins-sdk README](../packages/plugins-sdk/README.md). + +### Store Operations + +```typescript +// List pages +const { pages, count } = await sdk.store.pages.list( + query: StoreListPagesQuery, + headers?: ClientHeaders +); + +// Get a single page +const page = await sdk.store.pages.retrieve( + pageId: string, + headers?: ClientHeaders +); + +// Get page components +const components = await sdk.store.pages.getComponents( + pageId: string, + headers?: ClientHeaders +); +``` + +### Admin Operations + +```typescript +// List pages +const { pages, count } = await sdk.admin.pages.list( + query: AdminListPagesQuery +); + +// Create/Update pages +const page = await sdk.admin.pages.create( + data: AdminCreatePageDTO +); + +const page = await sdk.admin.pages.update( + pageId: string, + data: AdminUpdatePageDTO +); + +// Manage page status +const page = await sdk.admin.pages.updateStatus( + pageId: string, + status: 'draft' | 'published' | 'archived' +); + +// Manage components +const page = await sdk.admin.pages.updateComponents( + pageId: string, + data: AdminUpdatePageComponentsDTO +); +``` + +## Page Workflow + +1. **Creation**: Pages are set to: + - `published` status by default + - `draft` status if `defaultPageStatus: 'draft'` is set in plugin options + +2. **Management**: Admins can: + - Create and edit pages using the visual builder + - Manage page status (draft/published/archived) + - Configure SEO settings + - Manage component layouts and content + +## Available Endpoints + +### Admin Endpoints +- `GET /admin/pages` - List all pages +- `POST /admin/pages` - Create a page +- `GET /admin/pages/:id` - Get a page +- `PUT /admin/pages/:id` - Update a page +- `PUT /admin/pages/:id/status` - Update status +- `PUT /admin/pages/:id/components` - Update components + +### Store Endpoints +- `GET /store/pages` - List published pages +- `GET /store/pages/:id` - Get a published page +- `GET /store/pages/:id/components` - Get page components + +## Local Development + +> **IMPORTANT**: A running PostgreSQL instance is required. The plugin expects `DB_USERNAME` and `DB_PASSWORD` environment variables to be set. If not provided, both default to "postgres". + +Available scripts: +```bash +# Build the plugin +yarn build + +# Development mode with hot-reload +yarn dev + +# Publish to local registry for testing +yarn dev:publish + +# Generate database migrations +yarn db:generate +``` + +### Installing the plugin in your Medusa project for local development +After publishing the plugin locally by running yarn dev:publish, go to the root of your Medusa project and run the following commands: + +```bash +cd path/to/your/medusa-application + +yarn medusa plugin:add @lambdacurry/medusa-page-builder + +# If you are using yarn with a monorepo, you may also need to run +yarn install +``` + +## Compatibility + +This plugin is compatible with versions `>= 2.5.0` of `@medusajs/medusa`. + +## License + +MIT License \ No newline at end of file diff --git a/plugins/page-builder/package.json b/plugins/page-builder/package.json new file mode 100644 index 00000000..93a94153 --- /dev/null +++ b/plugins/page-builder/package.json @@ -0,0 +1,94 @@ +{ + "name": "@lambdacurry/medusa-page-builder", + "version": "0.0.1", + "description": "Page Builder Plugin for Medusa", + "author": "Lambda Curry (https://lambdacurry.dev)", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/lambda-curry/medusa-plugins" + }, + "homepage": "https://github.com/lambda-curry/medusa-plugins/tree/main/plugins/page-builder", + "files": [ + ".medusa/server" + ], + "exports": { + "./package.json": "./package.json", + "./workflows": "./.medusa/server/src/workflows/index.js", + "./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js", + "./providers/*": "./.medusa/server/src/providers/*/index.js", + "./*": "./.medusa/server/src/*.js" + }, + "keywords": [ + "medusa", + "plugin", + "page-builder", + "medusa-plugin-page-builder", + "medusa-plugin", + "medusa-v2", + "lambdacurry" + ], + "scripts": { + "build": "medusa plugin:build", + "dev": "medusa plugin:develop", + "dev:publish": "medusa plugin:publish", + "prepublishOnly": "medusa plugin:build", + "db:generate": "DB_USERNAME=${DB_USERNAME:-postgres} DB_PASSWORD=${DB_PASSWORD:-postgres} medusa plugin:db:generate" + }, + "devDependencies": { + "@medusajs/admin-sdk": "^2.6.1", + "@medusajs/cli": "^2.6.1", + "@medusajs/framework": "^2.6.1", + "@medusajs/icons": "^2.6.1", + "@medusajs/medusa": "^2.6.1", + "@medusajs/test-utils": "^2.6.1", + "@medusajs/ui": "^4.0.7", + "@mikro-orm/cli": "6.4.3", + "@mikro-orm/core": "6.4.3", + "@mikro-orm/knex": "6.4.3", + "@mikro-orm/migrations": "6.4.3", + "@mikro-orm/postgresql": "6.4.3", + "@swc/core": "1.5.7", + "@types/express": "4.17.13", + "@types/node": "^20.0.0", + "@types/react": "^18.3.2", + "@types/react-dom": "^18.2.25", + "awilix": "^8.0.1", + "pg": "^8.13.0", + "prop-types": "^15.8.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "ts-node": "^10.9.2", + "typescript": "^5.6.2", + "vite": "^5.2.11", + "yalc": "^1.0.0-pre.53" + }, + "peerDependencies": { + "@medusajs/admin-sdk": "^2.6.0", + "@medusajs/cli": "^2.6.0", + "@medusajs/framework": "^2.6.0", + "@medusajs/icons": "^2.6.0", + "@medusajs/medusa": "^2.6.0", + "@medusajs/test-utils": "^2.6.0", + "@medusajs/ui": "^4.0.7", + "@mikro-orm/cli": "6.4.3", + "@mikro-orm/core": "6.4.3", + "@mikro-orm/knex": "6.4.3", + "@mikro-orm/migrations": "6.4.3", + "@mikro-orm/postgresql": "6.4.3", + "awilix": "^8.0.1", + "pg": "^8.13.0" + }, + "engines": { + "node": ">=20" + }, + "installConfig": { + "hoistingLimits": "workspaces" + }, + "dependencies": { + "@lambdacurry/medusa-page-builder-types": "0.0.4", + "@lambdacurry/medusa-plugins-sdk": "0.0.6-beta.3", + "@medusajs/js-sdk": "^2.6.1", + "@medusajs/workflows-sdk": "^2.6.1" + } +} diff --git a/plugins/page-builder/src/admin/components/action-menu.tsx b/plugins/page-builder/src/admin/components/action-menu.tsx new file mode 100644 index 00000000..3444f24b --- /dev/null +++ b/plugins/page-builder/src/admin/components/action-menu.tsx @@ -0,0 +1,100 @@ +import { + DropdownMenu, + IconButton, + clx, +} from "@medusajs/ui" +import { EllipsisHorizontal } from "@medusajs/icons" +import { Link } from "react-router-dom" + +export type Action = { + icon: React.ReactNode + label: string + disabled?: boolean +} & ( + | { + to: string + onClick?: never + } + | { + onClick: () => void + to?: never + } +) + +export type ActionGroup = { + actions: Action[] +} + +export type ActionMenuProps = { + groups: ActionGroup[] +} + +export const ActionMenu = ({ groups }: ActionMenuProps) => { + return ( + + + + + + + + {groups.map((group, index) => { + if (!group.actions.length) { + return null + } + + const isLast = index === groups.length - 1 + + return ( + + {group.actions.map((action, index) => { + if (action.onClick) { + return ( + { + e.stopPropagation() + action.onClick() + }} + className={clx( + "[&_svg]:text-ui-fg-subtle flex items-center gap-x-2", + { + "[&_svg]:text-ui-fg-disabled": action.disabled, + } + )} + > + {action.icon} + {action.label} + + ) + } + + return ( +
+ + e.stopPropagation()}> + {action.icon} + {action.label} + + +
+ ) + })} + {!isLast && } +
+ ) + })} +
+
+ ) +} \ No newline at end of file diff --git a/plugins/page-builder/src/admin/components/header.tsx b/plugins/page-builder/src/admin/components/header.tsx new file mode 100644 index 00000000..253b44bc --- /dev/null +++ b/plugins/page-builder/src/admin/components/header.tsx @@ -0,0 +1,66 @@ +import { Heading, Button, Text } from "@medusajs/ui" +import React, { Fragment } from "react" +import { Link, LinkProps } from "react-router-dom" +import { ActionMenu, ActionMenuProps } from "./action-menu" + +export type HeadingProps = { + title: string + subtitle?: string + actions?: ( + { + type: "button", + props: React.ComponentProps + link?: LinkProps + } | + { + type: "action-menu" + props: ActionMenuProps + } | + { + type: "custom" + children: React.ReactNode + } + )[] +} + +export const Header = ({ + title, + subtitle, + actions = [], +}: HeadingProps) => { + return ( +
+
+ {title} + {subtitle && ( + + {subtitle} + + )} +
+ {actions.length > 0 && ( +
+ {actions.map((action, index) => ( + + {action.type === "button" && ( + + )} + {action.type === "action-menu" && ( + + )} + {action.type === "custom" && action.children} + + ))} +
+ )} +
+ ) +} \ No newline at end of file diff --git a/plugins/page-builder/src/admin/hooks/posts-mutations.ts b/plugins/page-builder/src/admin/hooks/posts-mutations.ts new file mode 100644 index 00000000..031d427d --- /dev/null +++ b/plugins/page-builder/src/admin/hooks/posts-mutations.ts @@ -0,0 +1,73 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' +import type { + AdminPageBuilderCreatePostBody, + AdminPageBuilderCreatePostResponse, + AdminPageBuilderDeletePostResponse, + AdminPageBuilderDuplicatePostResponse, + AdminPageBuilderUpdatePostBody, + AdminPageBuilderUpdatePostResponse, +} from '@lambdacurry/medusa-page-builder-types' + +import { sdk } from '../sdk' + +const QUERY_KEY = ['posts'] + +export const useAdminCreatePost = () => { + const queryClient = useQueryClient() + return useMutation< + AdminPageBuilderCreatePostResponse, + Error, + AdminPageBuilderCreatePostBody + >({ + mutationFn: async (data) => { + return sdk.admin.pageBuilder.createPost(data) + }, + mutationKey: QUERY_KEY, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: QUERY_KEY }) + }, + }) +} + +export const useAdminUpdatePost = () => { + const queryClient = useQueryClient() + return useMutation< + AdminPageBuilderUpdatePostResponse, + Error, + AdminPageBuilderUpdatePostBody + >({ + mutationFn: async (data) => { + return sdk.admin.pageBuilder.updatePost(data.id, data) + }, + mutationKey: QUERY_KEY, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: QUERY_KEY }) + }, + }) +} + +export const useAdminDeletePost = () => { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: async (id: string) => { + return sdk.admin.pageBuilder.deletePost(id) + }, + mutationKey: QUERY_KEY, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: QUERY_KEY }) + }, + }) +} + +export const useAdminDuplicatePost = () => { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: async (id: string) => { + return sdk.admin.pageBuilder.duplicatePost(id) + }, + mutationKey: QUERY_KEY, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: QUERY_KEY }) + }, + }) +} diff --git a/plugins/page-builder/src/admin/hooks/posts-queries.ts b/plugins/page-builder/src/admin/hooks/posts-queries.ts new file mode 100644 index 00000000..0ac1fecf --- /dev/null +++ b/plugins/page-builder/src/admin/hooks/posts-queries.ts @@ -0,0 +1,35 @@ +import { useQuery } from '@tanstack/react-query' +import type { + AdminPageBuilderListPostsQuery, + AdminPageBuilderListPostsResponse, + Post, +} from '@lambdacurry/medusa-page-builder-types' + +import { sdk } from '../sdk' + +const QUERY_KEY = ['posts'] + +export const useAdminListPosts = (query: AdminPageBuilderListPostsQuery) => { + return useQuery< + AdminPageBuilderListPostsResponse, + AdminPageBuilderListPostsQuery + >({ + queryKey: [...QUERY_KEY, query], + queryFn: async () => { + return sdk.admin.pageBuilder.listPosts(query) + }, + }) +} + +export const useAdminFetchPost = (id: string) => { + return useQuery({ + queryKey: [...QUERY_KEY, id], + queryFn: async () => { + const post = await sdk.admin.pageBuilder.listPosts({ + id, + } as AdminPageBuilderListPostsQuery) + + return post?.posts?.[0] + }, + }) +} diff --git a/plugins/page-builder/src/admin/hooks/use-query-params.tsx b/plugins/page-builder/src/admin/hooks/use-query-params.tsx new file mode 100644 index 00000000..39ba4652 --- /dev/null +++ b/plugins/page-builder/src/admin/hooks/use-query-params.tsx @@ -0,0 +1,50 @@ +import { useSearchParams } from "react-router-dom"; +import { useMemo } from "react"; + +type QueryParams = { + [key in T]: string | undefined; +}; + +type SetQueryParams = ( + params: Partial>, +) => void; + +export function useQueryParams( + keys: T[], + prefix?: string, +): { queryObject: QueryParams; setQueryObject: SetQueryParams } { + const [params, setParams] = useSearchParams(); + + // Use useMemo to create the query object from URL params + const queryObject = useMemo(() => { + const result = {} as QueryParams; + + for (const key of keys) { + const prefixedKey = prefix ? `${prefix}_${key}` : key; + const value = params.get(prefixedKey) || undefined; + result[key] = value; + } + + return result; + }, [keys, params, prefix]); + + const setQueryObject = (newParams: Partial>) => { + const newSearchParams = new URLSearchParams(params); + + for (const [key, value] of Object.entries(newParams)) { + const prefixedKey = prefix ? `${prefix}_${key}` : key; + if (value === undefined || value === null) { + newSearchParams.delete(prefixedKey); + } else { + newSearchParams.set(prefixedKey, String(value)); + } + } + + setParams(newSearchParams); + }; + + return { + queryObject, + setQueryObject, + }; +} diff --git a/plugins/page-builder/src/admin/layouts/single-column.tsx b/plugins/page-builder/src/admin/layouts/single-column.tsx new file mode 100644 index 00000000..539ac146 --- /dev/null +++ b/plugins/page-builder/src/admin/layouts/single-column.tsx @@ -0,0 +1,16 @@ +import { Toaster } from "@medusajs/ui" + +export type SingleColumnLayoutProps = { + children: React.ReactNode +} + +export const SingleColumnLayout = ({ children }: SingleColumnLayoutProps) => { + return ( + <> + +
+ {children} +
+ + ) +} \ No newline at end of file diff --git a/plugins/page-builder/src/admin/routes/content/components/posts-data-table/index.tsx b/plugins/page-builder/src/admin/routes/content/components/posts-data-table/index.tsx new file mode 100644 index 00000000..81ae4824 --- /dev/null +++ b/plugins/page-builder/src/admin/routes/content/components/posts-data-table/index.tsx @@ -0,0 +1,154 @@ +import { + createDataTableFilterHelper, + DataTable, + toast, + useDataTable, +} from "@medusajs/ui"; +import { useMemo } from "react"; +import { useNavigate } from "react-router-dom"; +import { usePostsDataTableColumns } from "./use-post-data-table-columns"; +import { useAdminListPosts } from "../../../../hooks/posts-queries"; +import type { + Post, + PostStatus, + PostType, +} from "@lambdacurry/medusa-page-builder-types"; +import { + useAdminDeletePost, + useAdminDuplicatePost, +} from "../../../../hooks/posts-mutations"; +import { usePostTableQuery } from "./use-post-table-query"; + +// Create filter helper +const filterHelper = createDataTableFilterHelper(); + +// Define filters +const filters = [ + filterHelper.accessor("status", { + type: "select", + label: "Status", + options: [ + { + label: "Published", + value: "published", + }, + { + label: "Draft", + value: "draft", + }, + ], + }), + filterHelper.accessor("type", { + type: "select", + label: "Type", + options: [ + { + label: "Page", + value: "page", + }, + { + label: "Post", + value: "post", + }, + ], + }), +]; + +export const PostsDataTable = () => { + const navigate = useNavigate(); + const { mutateAsync: deletePost } = useAdminDeletePost(); + const { mutateAsync: duplicatePost } = useAdminDuplicatePost(); + const limit = 10; + + // Get all query state from the hook + const { + searchParams, + pagination, + filtering, + sorting, + search, + setPaginationState, + setFilteringState, + setSortingState, + setSearchState, + } = usePostTableQuery({ + pageSize: limit, + }); + + const statusFilters = useMemo(() => { + return (filtering.status || []) as PostStatus[]; + }, [filtering]); + + const typeFilters = useMemo(() => { + return (filtering.type || []) as PostType[]; + }, [filtering]); + + const handleEdit = (id: string) => { + navigate("editor/test"); // TODO: change to the actual content detail page + }; + + const handleDuplicate = async (id: string) => { + await duplicatePost(id); + toast.success("Page duplicated"); + }; + + const handleDelete = async (id: string) => { + await deletePost(id); + toast.success("Page deleted"); + }; + + const columns = usePostsDataTableColumns({ + onEdit: handleEdit, + onDuplicate: handleDuplicate, + onDelete: handleDelete, + }); + + const { data, isLoading } = useAdminListPosts({ + ...searchParams, + status: statusFilters.length > 0 ? statusFilters : undefined, + type: typeFilters.length > 0 ? typeFilters : undefined, + order: sorting ? `${sorting.desc ? "-" : ""}${sorting.id}` : undefined, + }); + + const table = useDataTable({ + onRowClick: (_, row) => handleEdit(row.id), + columns, + data: data?.posts || [], + getRowId: (row) => row.id, + rowCount: data?.count || 0, + isLoading, + pagination: { + state: pagination, + onPaginationChange: setPaginationState, + }, + search: { + state: search, + onSearchChange: setSearchState, + }, + filtering: { + state: filtering, + onFilteringChange: setFilteringState, + }, + filters, + sorting: { + state: sorting, + onSortingChange: setSortingState, + }, + }); + + return ( + + +
+ +
+ + +
+
+
+ + +
+ ); +}; diff --git a/plugins/page-builder/src/admin/routes/content/components/posts-data-table/use-post-data-table-columns.tsx b/plugins/page-builder/src/admin/routes/content/components/posts-data-table/use-post-data-table-columns.tsx new file mode 100644 index 00000000..4b6032ea --- /dev/null +++ b/plugins/page-builder/src/admin/routes/content/components/posts-data-table/use-post-data-table-columns.tsx @@ -0,0 +1,120 @@ +import React from "react"; +import { + createDataTableColumnHelper, + Text, + StatusBadge, + usePrompt, +} from "@medusajs/ui"; +import { PencilSquare, SquareTwoStackMini, Trash } from "@medusajs/icons"; +import type { Post } from "@lambdacurry/medusa-page-builder-types"; + +export type ColumnActions = { + onEdit?: (id: string) => void; + onDuplicate?: (id: string) => void; + onDelete?: (id: string) => void; +}; + +const columnHelper = createDataTableColumnHelper(); + +/** + * Hook that returns column definitions for the PostsDataTable + */ +export const usePostsDataTableColumns = (actions: ColumnActions = {}) => { + const { onEdit, onDuplicate, onDelete } = actions; + const prompt = usePrompt(); + + return React.useMemo( + () => [ + columnHelper.accessor("title", { + header: "Title", + enableSorting: true, + sortLabel: "Title", + }), + columnHelper.accessor("handle", { + header: "Handle", + enableSorting: true, + }), + columnHelper.accessor("status", { + header: "Status", + cell: ({ getValue }) => { + const status = getValue(); + return ( + + {status === "published" ? "Published" : "Draft"} + + ); + }, + enableSorting: true, + }), + columnHelper.accessor("type", { + header: "Type", + enableSorting: true, + }), + columnHelper.accessor("created_at", { + header: "Date Created", + cell: ({ getValue }) => { + const date = new Date(getValue()); + return {date.toLocaleDateString()}; + }, + enableSorting: true, + }), + columnHelper.accessor("updated_at", { + header: "Last Updated", + cell: ({ getValue }) => { + const date = new Date(getValue()); + return {date.toLocaleDateString()}; + }, + enableSorting: true, + }), + columnHelper.action({ + // @ts-ignore + actions: ({ row }) => [ + [ + { + icon: , + label: "Edit", + onClick: () => { + if (onEdit) { + onEdit(row.original.id); + } else { + console.log("edit", row.original.id); + } + }, + }, + ], + [ + { + icon: , + label: "Duplicate", + onClick: () => { + onDuplicate?.(row.original.id); + }, + }, + ], + [ + { + icon: , + label: "Delete", + onClick: async () => { + const res = await prompt({ + title: "Are you sure?", + description: `You are about to delete the page "${row.original.title}". This action cannot be undone.`, + confirmText: "Delete", + cancelText: "Cancel", + }); + + if (!res) return; + + onDelete?.(row.original.id); + }, + }, + ], + ], + }), + ], + [onEdit, onDuplicate, onDelete, prompt], + ); +}; diff --git a/plugins/page-builder/src/admin/routes/content/components/posts-data-table/use-post-table-query.tsx b/plugins/page-builder/src/admin/routes/content/components/posts-data-table/use-post-table-query.tsx new file mode 100644 index 00000000..0afca28b --- /dev/null +++ b/plugins/page-builder/src/admin/routes/content/components/posts-data-table/use-post-table-query.tsx @@ -0,0 +1,231 @@ +import { useMemo } from "react"; +import type { + DataTableFilteringState, + DataTablePaginationState, + DataTableSortingState, +} from "@medusajs/ui"; +import type { + PostContentMode, + PostType, + PostStatus, + AdminPageBuilderListPostsQuery, +} from "@lambdacurry/medusa-page-builder-types"; + +import { useQueryParams } from "../../../../hooks/use-query-params"; + +type UsePostTableQueryProps = { + prefix?: string; + pageSize?: number; +}; + +const DEFAULT_FIELDS = [ + "id", + "type", + "title", + "handle", + "excerpt", + "status", + "published_at", + "archived_at", + "is_home_page", + "created_at", + "updated_at", + "featured_image.*", +].join(","); + +export const usePostTableQuery = ({ + prefix, + pageSize = 10, +}: UsePostTableQueryProps) => { + const { queryObject, setQueryObject } = useQueryParams( + [ + "offset", + "order", + "q", + "title", + "handle", + "status", + "type", + "content_mode", + "is_home_page", + "published_at", + "archived_at", + "created_at", + "updated_at", + "id", + ], + prefix, + ); + + const { + offset, + title, + handle, + status, + type, + content_mode, + is_home_page, + published_at, + archived_at, + created_at, + updated_at, + order, + q, + } = queryObject; + + // Derived state for data table + const pagination = useMemo( + () => ({ + pageSize, + pageIndex: offset ? Math.floor(Number(offset) / pageSize) : 0, + }), + [offset, pageSize], + ); + + const filtering = useMemo(() => { + const filterState: DataTableFilteringState = {}; + + if (status) { + filterState.status = status.split(","); + } + + if (type) { + filterState.type = type.split(","); + } + + return filterState; + }, [status, type]); + + const sorting = useMemo(() => { + if (order) { + const isDesc = order.startsWith("-"); + const field = isDesc ? order.substring(1) : order; + return { + id: field, + desc: isDesc, + }; + } + return null; + }, [order]); + + const searchParams: AdminPageBuilderListPostsQuery = { + limit: pageSize, + offset: offset ? Number(offset) : 0, + title: title, + handle: handle, + status: status?.split(",") as PostStatus[], + type: type?.split(",") as PostType[], + content_mode: content_mode?.split(",") as PostContentMode[], + is_home_page: is_home_page ? is_home_page === "true" : undefined, + published_at: published_at ? JSON.parse(published_at) : undefined, + archived_at: archived_at ? JSON.parse(archived_at) : undefined, + created_at: created_at ? JSON.parse(created_at) : undefined, + updated_at: updated_at ? JSON.parse(updated_at) : undefined, + order: order, + q, + fields: DEFAULT_FIELDS, + }; + + // State updaters + const setSearchParams = (params: Partial) => { + const newQueryObject: Record = {}; + + if ("q" in params) { + newQueryObject.q = params.q || undefined; + } + + if ("status" in params) { + if ( + params.status && + Array.isArray(params.status) && + params.status.length > 0 + ) { + newQueryObject.status = params.status.join(","); + } else if (typeof params.status === "string") { + newQueryObject.status = params.status; + } else { + newQueryObject.status = undefined; + } + } + + if ("type" in params) { + if (params.type && Array.isArray(params.type) && params.type.length > 0) { + newQueryObject.type = params.type.join(","); + } else if (typeof params.type === "string") { + newQueryObject.type = params.type; + } else { + newQueryObject.type = undefined; + } + } + + if ("content_mode" in params) { + if ( + params.content_mode && + Array.isArray(params.content_mode) && + params.content_mode.length > 0 + ) { + newQueryObject.content_mode = params.content_mode.join(","); + } else if (typeof params.content_mode === "string") { + newQueryObject.content_mode = params.content_mode; + } else { + newQueryObject.content_mode = undefined; + } + } + + if (params.is_home_page !== undefined) { + newQueryObject.is_home_page = params.is_home_page?.toString(); + } + + if (params.order !== undefined) { + newQueryObject.order = params.order || undefined; + } + + if (params.offset !== undefined) { + newQueryObject.offset = params.offset?.toString(); + } + + setQueryObject(newQueryObject); + }; + + // Simplified handlers for common operations + const setPaginationState = (newPagination: DataTablePaginationState) => { + setSearchParams({ offset: newPagination.pageIndex * pageSize }); + }; + + const setFilteringState = (newFiltering: DataTableFilteringState) => { + setSearchParams({ + status: (newFiltering.status || []) as PostStatus[], + type: (newFiltering.type || []) as PostType[], + }); + }; + + const setSortingState = (newSorting: DataTableSortingState | null) => { + if (newSorting) { + setSearchParams({ + order: `${newSorting.desc ? "-" : ""}${newSorting.id}`, + }); + } else { + setSearchParams({ order: undefined }); + } + }; + + const setSearchState = (search: string) => { + setSearchParams({ q: search || undefined }); + }; + + return { + searchParams, + raw: queryObject, + setSearchParams, + // Derived state + pagination, + filtering, + sorting, + search: q || "", + // State updaters + setPaginationState, + setFilteringState, + setSortingState, + setSearchState, + }; +}; diff --git a/plugins/page-builder/src/admin/routes/content/editor/components/breadcrumbs.tsx b/plugins/page-builder/src/admin/routes/content/editor/components/breadcrumbs.tsx new file mode 100644 index 00000000..8cfd0b12 --- /dev/null +++ b/plugins/page-builder/src/admin/routes/content/editor/components/breadcrumbs.tsx @@ -0,0 +1,81 @@ +import { clx } from "@medusajs/ui" +import { TriangleRightMini } from "@medusajs/icons" +import { Link, useLoaderData } from "react-router-dom" + +type Crumb = { + label: string + path?: string +} + +export const Breadcrumbs = () => { + const data = useLoaderData() as any + + const crumbs: Crumb[] = [ + { + label: "Home", + path: "/", + }, + { + label: "Content", + path: "/content", + }, + ] + + if (data?.post) { + const post = data.post + const type = post.type?.charAt(0).toUpperCase() + post.type?.slice(1) as string + + crumbs.push(...[ + { + label: type, + }, + { + label: post.title as string, + path: post.id as string, + }, + ]) + } + + return ( +
    + {crumbs.map((crumb, index) => { + const isLast = index === crumbs.length - 1 + const isSingle = crumbs.length === 1 + + return ( +
  1. + {!isLast && crumb.path ? ( + + {crumb.label} + + ) : ( +
    + {!isSingle && isLast && ...} + + {crumb.label} + +
    + )} + {!isLast && ( + + + + )} +
  2. + ) + })} +
+ ) +} \ No newline at end of file diff --git a/plugins/page-builder/src/admin/routes/content/editor/components/editor-modal.tsx b/plugins/page-builder/src/admin/routes/content/editor/components/editor-modal.tsx new file mode 100644 index 00000000..d24b3d4a --- /dev/null +++ b/plugins/page-builder/src/admin/routes/content/editor/components/editor-modal.tsx @@ -0,0 +1,206 @@ +"use client" + +import { clx } from "@medusajs/ui" +import { Dialog as RadixDialog } from "radix-ui" +import * as React from "react" + +/** + * @prop defaultOpen - Whether the modal is opened by default. + * @prop open - Whether the modal is opened. + * @prop onOpenChange - A function to handle when the modal is opened or closed. + */ +interface EditorModalRootProps + extends React.ComponentPropsWithoutRef {} + +/** + * This component is based on the [Radix UI Dialog](https://www.radix-ui.com/primitives/docs/components/dialog) primitives. + */ +const EditorModalRoot = (props: EditorModalRootProps) => { + return +} +EditorModalRoot.displayName = "EditorModal" + +interface EditorModalTriggerProps extends React.ComponentPropsWithoutRef {} + +/** + * This component is used to create the trigger button that opens the modal. + * It accepts props from the [Radix UI Dialog Trigger](https://www.radix-ui.com/primitives/docs/components/dialog#trigger) component. + */ +const EditorModalTrigger = React.forwardRef< + React.ElementRef, + EditorModalTriggerProps +>((props: EditorModalTriggerProps, ref) => { + return +}) +EditorModalTrigger.displayName = "EditorModal.Trigger" + +// /** +// * This component is used to create the close button for the modal. +// * It accepts props from the [Radix UI Dialog Close](https://www.radix-ui.com/primitives/docs/components/dialog#close) component. +// */ +// const EditorModalClose = RadixDialog.Close +// EditorModalClose.displayName = "EditorModal.Close" + +interface EditorModalPortalProps extends RadixDialog.DialogPortalProps {} + +/** + * The `EditorModal.Content` component uses this component to wrap the modal content. + * It accepts props from the [Radix UI Dialog Portal](https://www.radix-ui.com/primitives/docs/components/dialog#portal) component. + */ +const EditorModalPortal = (props: EditorModalPortalProps) => { + return ( + + ) +} +EditorModalPortal.displayName = "EditorModal.Portal" + +/** + * This component is used to create the overlay for the modal. + * It accepts props from the [Radix UI Dialog Overlay](https://www.radix-ui.com/primitives/docs/components/dialog#overlay) component. + */ +const EditorModalOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + ) +}) +EditorModalOverlay.displayName = "EditorModal.Overlay" + +/** + * This component wraps the content of the modal. + * It accepts props from the [Radix UI Dialog Content](https://www.radix-ui.com/primitives/docs/components/dialog#content) component. + */ +const EditorModalContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + overlayProps?: React.ComponentPropsWithoutRef + portalProps?: React.ComponentPropsWithoutRef + } +>(({ className, overlayProps, portalProps, ...props }, ref) => { + return ( + + + + + ) +}) +EditorModalContent.displayName = "EditorModal.Content" + +/** + * This component is used to wrap the header content of the modal. + * This component is based on the `div` element and supports all of its props + */ +const EditorModalHeader = React.forwardRef< + HTMLDivElement, + React.ComponentPropsWithoutRef<"div"> +>(({ children, className, ...props }, ref) => { + return ( +
+ {/*
+ + + + + +
*/} + {children} +
+ ) +}) +EditorModalHeader.displayName = "EditorModal.Header" + +/** + * This component is used to wrap the footer content of the modal. + * This component is based on the `div` element and supports all of its props + */ +const EditorModalFooter = React.forwardRef< + HTMLDivElement, + React.ComponentPropsWithoutRef<"div"> +>(({ children, className, ...props }, ref) => { + return ( +
+ {children} +
+ ) +}) +EditorModalFooter.displayName = "EditorModal.Footer" + +interface EditorModalTitleProps extends React.ComponentPropsWithoutRef {} + +/** + * This component adds an accessible title to the modal. + * It accepts props from the [Radix UI Dialog Title](https://www.radix-ui.com/primitives/docs/components/dialog#title) component. + */ +const EditorModalTitle = React.forwardRef< + HTMLHeadingElement, + EditorModalTitleProps +>(({ className, ...props }: EditorModalTitleProps, ref) => { + return ( + + ) +}) +EditorModalTitle.displayName = "EditorModal.Title" + +/** + * This component adds accessible description to the modal. + * It accepts props from the [Radix UI Dialog Description](https://www.radix-ui.com/primitives/docs/components/dialog#description) component. + */ +const EditorModalDescription = RadixDialog.Description +EditorModalDescription.displayName = "EditorModal.Description" + +/** + * This component is used to wrap the body content of the modal. + * This component is based on the `div` element and supports all of its props + */ +const EditorModalBody = React.forwardRef< + HTMLDivElement, + React.ComponentPropsWithoutRef<"div"> +>(({ className, ...props }, ref) => { + return
+}) +EditorModalBody.displayName = "EditorModal.Body" + +const EditorModal = Object.assign(EditorModalRoot, { + Trigger: EditorModalTrigger, + Title: EditorModalTitle, + Description: EditorModalDescription, + Content: EditorModalContent, + Header: EditorModalHeader, + Body: EditorModalBody, + // Close: EditorModalClose, + Footer: EditorModalFooter, +}) + +export { EditorModal } \ No newline at end of file diff --git a/plugins/page-builder/src/admin/routes/content/editor/components/editor-sidebar.tsx b/plugins/page-builder/src/admin/routes/content/editor/components/editor-sidebar.tsx new file mode 100644 index 00000000..1ad9e60e --- /dev/null +++ b/plugins/page-builder/src/admin/routes/content/editor/components/editor-sidebar.tsx @@ -0,0 +1,61 @@ +import { SquareTwoStackSolid } from "@medusajs/icons" + +import { SidebarContainer } from "./sidebar-container" +import { INavItem, NavItem } from "./nav-item" + +export const EditorSidebar = () => { + const sidebarContent = ( + + ) + + return ( + <> + + {sidebarContent} + + + {sidebarContent} + + + ) +} + +const sections: INavItem[] = [ + { + icon: , + label: 'Section 1', + to: "sections/section_id_1", + }, + { + icon: , + label: 'Section 2', + to: "sections/section_id_2", + }, + { + icon: , + label: 'Section 3', + to: "sections/section_id_3", + }, + { + icon: , + label: 'Section 4', + to: "sections/section_id_4", + }, + { + icon: , + label: 'Section 5', + to: "sections/section_id_5", + }, +] + +const SectionsMenu = () => { + return ( + + ) +} \ No newline at end of file diff --git a/plugins/page-builder/src/admin/routes/content/editor/components/editor-top-bar.tsx b/plugins/page-builder/src/admin/routes/content/editor/components/editor-top-bar.tsx new file mode 100644 index 00000000..4b59d7fb --- /dev/null +++ b/plugins/page-builder/src/admin/routes/content/editor/components/editor-top-bar.tsx @@ -0,0 +1,18 @@ +import { Button } from "@medusajs/ui" +import { Breadcrumbs } from "./breadcrumbs" +import { SidebarToggle } from "./sidebar-toggle" + +export const EditorTopbar = () => { + return ( +
+
+ + +
+
+ + +
+
+ ) +} \ No newline at end of file diff --git a/plugins/page-builder/src/admin/routes/content/editor/components/main-content.tsx b/plugins/page-builder/src/admin/routes/content/editor/components/main-content.tsx new file mode 100644 index 00000000..50e802e0 --- /dev/null +++ b/plugins/page-builder/src/admin/routes/content/editor/components/main-content.tsx @@ -0,0 +1,23 @@ +import { clx } from "@medusajs/ui" +import { PropsWithChildren } from "react" + +import { useLoadingState } from "../hooks/use-loading-state" + +export const MainContent = ({ children }: PropsWithChildren) => { + const { isLoading } = useLoadingState() + + return ( +
+
+ {children} +
+
+ ) +} \ No newline at end of file diff --git a/plugins/page-builder/src/admin/routes/content/editor/components/nav-item.tsx b/plugins/page-builder/src/admin/routes/content/editor/components/nav-item.tsx new file mode 100644 index 00000000..9d4a61ee --- /dev/null +++ b/plugins/page-builder/src/admin/routes/content/editor/components/nav-item.tsx @@ -0,0 +1,37 @@ +import { Text } from '@medusajs/ui' +import { ReactNode } from 'react' +import { NavLink } from 'react-router-dom' + +export type INavItem = { + icon?: ReactNode + label: string + to: string + from?: string +} + +const BASE_NAV_LINK_CLASSES = + 'text-ui-fg-subtle transition-fg hover:bg-ui-bg-subtle-hover flex items-center gap-x-2 rounded-md py-0.5 pl-0.5 pr-2 outline-none [&>svg]:text-ui-fg-subtle focus-visible:shadow-borders-focus' + +export const NavItem = ({ icon, label, to, from }: INavItem) => { + return ( +
+ +
{icon}
+ + {label} + +
+
+ ) +} diff --git a/plugins/page-builder/src/admin/routes/content/editor/components/post-editor-layout.tsx b/plugins/page-builder/src/admin/routes/content/editor/components/post-editor-layout.tsx new file mode 100644 index 00000000..0941d5e3 --- /dev/null +++ b/plugins/page-builder/src/admin/routes/content/editor/components/post-editor-layout.tsx @@ -0,0 +1,20 @@ +import { PropsWithChildren } from "react" +import { TooltipProvider } from "@medusajs/ui" + +import { MainContent } from "./main-content" +import { EditorSidebar } from "./editor-sidebar" +import { PostSettingsSidebar } from "./post-settings-sidebar" + +type PostEditorLayoutProps = PropsWithChildren + +export const PostEditorLayout = ({ children }: PostEditorLayoutProps) => { + return ( + +
+ + {children} + +
+
+ ) +} \ No newline at end of file diff --git a/plugins/page-builder/src/admin/routes/content/editor/components/post-settings-sidebar.tsx b/plugins/page-builder/src/admin/routes/content/editor/components/post-settings-sidebar.tsx new file mode 100644 index 00000000..8e2dbb91 --- /dev/null +++ b/plugins/page-builder/src/admin/routes/content/editor/components/post-settings-sidebar.tsx @@ -0,0 +1,30 @@ +import { useLoaderData } from "react-router-dom" +import { SidebarContainer } from "./sidebar-container" + +export const PostSettingsSidebar = () => { + const data = useLoaderData() as any + const post = data?.post + const type = post?.type ? post.type?.charAt(0).toUpperCase() + post.type?.slice(1) as string : "Post" + + return ( + + + + ) +} + +type PostSettingsMenuProps = { + post: Record +} + +const PostSettingsMenu = ({ post }: PostSettingsMenuProps) => { + return ( +
+

+ Settings +

+
+ ) +} \ No newline at end of file diff --git a/plugins/page-builder/src/admin/routes/content/editor/components/sidebar-container.tsx b/plugins/page-builder/src/admin/routes/content/editor/components/sidebar-container.tsx new file mode 100644 index 00000000..10f4bbc2 --- /dev/null +++ b/plugins/page-builder/src/admin/routes/content/editor/components/sidebar-container.tsx @@ -0,0 +1,68 @@ +import { XMark } from "@medusajs/icons" +import { Drawer, IconButton, clx } from "@medusajs/ui" +import { PropsWithChildren } from "react" +import { useEditorSidebar } from "../hooks/use-editor-sidebar" + +type DrawerSidebarContainerProps = PropsWithChildren & { + title?: string + side?: "left" | "right" +} + +const DrawerSidebarContainer = ({ title, children, side = "left" }: DrawerSidebarContainerProps) => { + const { left, right, toggleLeft, toggleRight } = useEditorSidebar() + + const isOpen = side === "left" ? left.drawer : right.drawer + const toggle = side === "left" + ? () => toggleLeft("drawer") + : () => toggleRight("drawer") + + return ( + + +
+ {title} + + + +
+
+ {children} +
+
+
+ ) +} + +const StaticSidebarContainer = ({ children, side = "left" }: PropsWithChildren & { side?: "left" | "right" }) => { + const { left, right } = useEditorSidebar() + const isOpen = side === "left" ? left.static : right.static + + return ( +
+ {children} +
+ ) +} + +export const SidebarContainer = { + Drawer: DrawerSidebarContainer, + Static: StaticSidebarContainer +} \ No newline at end of file diff --git a/plugins/page-builder/src/admin/routes/content/editor/components/sidebar-toggle.tsx b/plugins/page-builder/src/admin/routes/content/editor/components/sidebar-toggle.tsx new file mode 100644 index 00000000..869341e2 --- /dev/null +++ b/plugins/page-builder/src/admin/routes/content/editor/components/sidebar-toggle.tsx @@ -0,0 +1,32 @@ +import { SidebarLeft, SidebarRight } from "@medusajs/icons" +import { IconButton, Tooltip } from "@medusajs/ui" +import { useEditorSidebar } from "../hooks/use-editor-sidebar" + +export const SidebarToggle = ({ side, drawerOnly = false }: { side: "left" | "right", drawerOnly?: boolean }) => { + const { toggleLeft, toggleRight } = useEditorSidebar() + const toggle = side === "left" ? toggleLeft : toggleRight + const Icon = side === "left" ? SidebarLeft : SidebarRight + + return ( + +
+ toggle(drawerOnly ? "drawer" : "static")} + size="small" + > + + + toggle("drawer")} + size="small" + > + + +
+
+ ) +} \ No newline at end of file diff --git a/plugins/page-builder/src/admin/routes/content/editor/hooks/use-editor-sidebar.tsx b/plugins/page-builder/src/admin/routes/content/editor/hooks/use-editor-sidebar.tsx new file mode 100644 index 00000000..321ef7bd --- /dev/null +++ b/plugins/page-builder/src/admin/routes/content/editor/hooks/use-editor-sidebar.tsx @@ -0,0 +1,12 @@ +import { useContext } from "react" +import { EditorSidebarContext, EditorSidebarContextType } from "../providers/editor-sidebar-context" + +export const useEditorSidebar = (): EditorSidebarContextType => { + const context = useContext(EditorSidebarContext) + + if (!context) { + throw new Error("useEditorSidebar must be used within a EditorSidebarProvider") + } + + return context +} diff --git a/plugins/page-builder/src/admin/routes/content/editor/hooks/use-loading-state.ts b/plugins/page-builder/src/admin/routes/content/editor/hooks/use-loading-state.ts new file mode 100644 index 00000000..9b4770f0 --- /dev/null +++ b/plugins/page-builder/src/admin/routes/content/editor/hooks/use-loading-state.ts @@ -0,0 +1,14 @@ +import { useNavigation } from 'react-router-dom' + +/** + * Hook to track the loading state of the application + * @returns Object containing the loading state + */ +export const useLoadingState = () => { + const navigation = useNavigation() + const isLoading = navigation.state === 'loading' + + return { + isLoading, + } +} diff --git a/plugins/page-builder/src/admin/routes/content/editor/providers/editor-sidebar-context.tsx b/plugins/page-builder/src/admin/routes/content/editor/providers/editor-sidebar-context.tsx new file mode 100644 index 00000000..3c2c06c6 --- /dev/null +++ b/plugins/page-builder/src/admin/routes/content/editor/providers/editor-sidebar-context.tsx @@ -0,0 +1,28 @@ +import { createContext } from "react" + +export type SidebarViewType = "drawer" | "static" + +export interface SidebarState { + drawer: boolean + static: boolean +} + +export interface EditorSidebarContextType { + left: SidebarState + right: SidebarState + toggleLeft: (viewType: SidebarViewType) => void + toggleRight: (viewType: SidebarViewType) => void +} + +export const EditorSidebarContext = createContext({ + left: { + drawer: false, + static: true + }, + right: { + drawer: false, + static: false + }, + toggleLeft: () => {}, + toggleRight: () => {}, +}) diff --git a/plugins/page-builder/src/admin/routes/content/editor/providers/editor-sidebar-provider.tsx b/plugins/page-builder/src/admin/routes/content/editor/providers/editor-sidebar-provider.tsx new file mode 100644 index 00000000..fbca84f2 --- /dev/null +++ b/plugins/page-builder/src/admin/routes/content/editor/providers/editor-sidebar-provider.tsx @@ -0,0 +1,46 @@ +import { PropsWithChildren, useState } from "react" +import { EditorSidebarContext, SidebarState, SidebarViewType } from "./editor-sidebar-context" + +/** + * Provider for the editor sidebar state + * Manages the visibility state of left and right sidebars + * with separate states for drawer and static views + */ +export const EditorSidebarProvider = ({ children }: PropsWithChildren) => { + const [leftSidebar, setLeftSidebar] = useState({ + drawer: false, + static: true + }) + + const [rightSidebar, setRightSidebar] = useState({ + drawer: false, + static: false + }) + + const toggleLeft = (viewType: SidebarViewType) => { + setLeftSidebar((prev) => ({ + ...prev, + [viewType]: !prev[viewType], + })) + } + + const toggleRight = (viewType: SidebarViewType) => { + setRightSidebar((prev) => ({ + ...prev, + [viewType]: !prev[viewType], + })) + } + + return ( + + {children} + + ) +} diff --git a/plugins/page-builder/src/admin/routes/content/editor/test/page.tsx b/plugins/page-builder/src/admin/routes/content/editor/test/page.tsx new file mode 100644 index 00000000..2a53159b --- /dev/null +++ b/plugins/page-builder/src/admin/routes/content/editor/test/page.tsx @@ -0,0 +1,51 @@ +import { LoaderFunctionArgs, useLoaderData, useParams } from "react-router-dom" +import { EditorTopbar } from "../components/editor-top-bar" +import { EditorModal } from "../components/editor-modal" +import { EditorSidebarProvider } from "../providers/editor-sidebar-provider" +import { PostEditorLayout } from "../components/post-editor-layout" + + +export async function loader({ params }: LoaderFunctionArgs) { + console.log("🚀 ~ loader ~ content/editor/:id ~ params:", params) + + return { + post: { + id: params.id, + title: "Test Page", + description: "Test Page Description", + content: "Test Page Content", + type: "page", + }, + } +} + +const PostDetailsPage = () => { + const { id } = useParams() + console.log("🚀 ~ PostDetailsPage ~ id:", id) + // const postData = useLoaderData() as Awaited> + // console.log("🚀 ~ PostDetailsPage ~ post:", postData) + const pageName = "Test Page" + + return ( + + + + + {/* EditorModal.Title is required by EditorModal.Content */} + + + + + + {/*

{postData.post?.title}

+

{postData.post?.description}

*/} +

{pageName}

+
+
+
+
+
+ ) +} + +export default PostDetailsPage diff --git a/plugins/page-builder/src/admin/routes/content/page.tsx b/plugins/page-builder/src/admin/routes/content/page.tsx new file mode 100644 index 00000000..edada810 --- /dev/null +++ b/plugins/page-builder/src/admin/routes/content/page.tsx @@ -0,0 +1,49 @@ +import { Button, Container } from "@medusajs/ui" +import { defineRouteConfig } from "@medusajs/admin-sdk" +import { DocumentText } from "@medusajs/icons" +import { useNavigate } from "react-router-dom" +import { SingleColumnLayout } from "../../layouts/single-column" +import { Header } from "../../components/header" +import { PostsDataTable } from "./components/posts-data-table" +import { useAdminCreatePost } from "../../hooks/posts-mutations" + +const ContentPage = () => { + const navigate = useNavigate() + + const { mutateAsync: createPost, isPending } = useAdminCreatePost() + + const handleCreatePost = async () => { + await createPost({ + title: 'New Page', + content: {}, + type: 'page' + }) + + navigate(`editor/test`) // TODO: change to the correct path + // navigate(`/editor/${type}/new`) + } + + return ( + + +
Create + } + ]} + /> + + + + ) +} + +export const config = defineRouteConfig({ + label: "Content", + icon: DocumentText, +}) + +export default ContentPage \ No newline at end of file diff --git a/plugins/page-builder/src/admin/sdk.ts b/plugins/page-builder/src/admin/sdk.ts new file mode 100644 index 00000000..39d900ed --- /dev/null +++ b/plugins/page-builder/src/admin/sdk.ts @@ -0,0 +1,12 @@ +import { MedusaPluginsSDK } from '@lambdacurry/medusa-plugins-sdk' + +declare const __BACKEND_URL__: string | undefined + +export const backendUrl = __BACKEND_URL__ ?? 'http://localhost:9000' + +export const sdk = new MedusaPluginsSDK({ + baseUrl: backendUrl, + auth: { + type: 'session', + }, +}) diff --git a/plugins/page-builder/src/admin/tsconfig.json b/plugins/page-builder/src/admin/tsconfig.json new file mode 100644 index 00000000..ea4bf122 --- /dev/null +++ b/plugins/page-builder/src/admin/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["."] +} \ No newline at end of file diff --git a/plugins/page-builder/src/admin/vite-env.d.ts b/plugins/page-builder/src/admin/vite-env.d.ts new file mode 100644 index 00000000..151aa685 --- /dev/null +++ b/plugins/page-builder/src/admin/vite-env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/plugins/page-builder/src/api/admin/content/posts/[id]/duplicate/route.ts b/plugins/page-builder/src/api/admin/content/posts/[id]/duplicate/route.ts new file mode 100644 index 00000000..bac143df --- /dev/null +++ b/plugins/page-builder/src/api/admin/content/posts/[id]/duplicate/route.ts @@ -0,0 +1,20 @@ +import type { + AuthenticatedMedusaRequest, + MedusaResponse, +} from '@medusajs/framework/http' +import { duplicatePostWorkflow } from '../../../../../../workflows/duplicate-post' + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse, +) => { + const id = req.params.id + + const { result } = await duplicatePostWorkflow(req.scope).run({ + input: { + id, + }, + }) + + res.status(200).json({ post: result }) +} diff --git a/plugins/page-builder/src/api/admin/content/posts/[id]/middlewares.ts b/plugins/page-builder/src/api/admin/content/posts/[id]/middlewares.ts new file mode 100644 index 00000000..0a1b7355 --- /dev/null +++ b/plugins/page-builder/src/api/admin/content/posts/[id]/middlewares.ts @@ -0,0 +1,18 @@ +import { + type MiddlewareRoute, + validateAndTransformBody, +} from '@medusajs/framework' +import { updatePostSchema } from '../../validations' + +export const adminPostItemRoutesMiddlewares: MiddlewareRoute[] = [ + { + matcher: '/admin/content/posts/:id', + method: 'PUT', + middlewares: [validateAndTransformBody(updatePostSchema)], + }, + { + matcher: '/admin/content/posts/:id', + method: 'DELETE', + middlewares: [], + }, +] diff --git a/plugins/page-builder/src/api/admin/content/posts/[id]/route.tsx b/plugins/page-builder/src/api/admin/content/posts/[id]/route.tsx new file mode 100644 index 00000000..4e5b5f38 --- /dev/null +++ b/plugins/page-builder/src/api/admin/content/posts/[id]/route.tsx @@ -0,0 +1,38 @@ +import type { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http"; +import { updatePostWorkflow } from "../../../../../workflows/update-post"; +import { deletePostWorkflow } from "../../../../../workflows/delete-post"; +import type { AdminPageBuilderUpdatePostBody } from "@lambdacurry/medusa-page-builder-types"; + +export const PUT = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse, +) => { + const id = req.params.id; + const data = { ...req.validatedBody, id }; + + const { result } = await updatePostWorkflow(req.scope).run({ + input: { + post: data, + }, + }); + + res.status(200).json({ post: result }); +}; + +export const DELETE = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse, +) => { + const id = req.params.id; + + const { result } = await deletePostWorkflow(req.scope).run({ + input: { + id, + }, + }); + + res.status(200).json({ id: result.id, object: "post", deleted: true }); +}; diff --git a/plugins/page-builder/src/api/admin/content/posts/middlewares.ts b/plugins/page-builder/src/api/admin/content/posts/middlewares.ts new file mode 100644 index 00000000..1671360d --- /dev/null +++ b/plugins/page-builder/src/api/admin/content/posts/middlewares.ts @@ -0,0 +1,52 @@ +import { + type MiddlewareRoute, + validateAndTransformQuery, + validateAndTransformBody, +} from '@medusajs/framework' +import { createPostSchema, listAdminPostsQuerySchema } from '../validations' + +export const defaultAdminPostFields = [ + 'id', + 'type', + 'title', + 'handle', + 'excerpt', + 'content', + 'status', + 'content_mode', + 'seo', + 'published_at', + 'archived_at', + 'is_home_page', + 'created_at', + 'updated_at', + 'featured_image.*', + 'authors.*', + 'root.*', + 'sections.*', + 'tags.*', +] + +export const defaultPostsQueryConfig = { + defaults: [...defaultAdminPostFields], + defaultLimit: 50, + isList: true, +} + +export const adminPostRoutesMiddlewares: MiddlewareRoute[] = [ + { + matcher: '/admin/content/posts', + method: 'GET', + middlewares: [ + validateAndTransformQuery( + listAdminPostsQuerySchema, + defaultPostsQueryConfig, + ), + ], + }, + { + matcher: '/admin/content/posts', + method: 'POST', + middlewares: [validateAndTransformBody(createPostSchema)], + }, +] diff --git a/plugins/page-builder/src/api/admin/content/posts/route.ts b/plugins/page-builder/src/api/admin/content/posts/route.ts new file mode 100644 index 00000000..0b3cf3de --- /dev/null +++ b/plugins/page-builder/src/api/admin/content/posts/route.ts @@ -0,0 +1,41 @@ +import type { + AuthenticatedMedusaRequest, + MedusaResponse, +} from '@medusajs/framework/http' +import { createPostWorkflow } from '../../../../workflows/create-post' +import type { AdminPageBuilderCreatePostBody } from '@lambdacurry/medusa-page-builder-types' + +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse, +) => { + const query = req.scope.resolve('query') + + const { data: posts, metadata = { count: 0, skip: 0, take: 0 } } = + await query.graph({ + entity: 'post', + fields: req.queryConfig?.fields || ['*'], + filters: req.filterableFields || {}, + pagination: req.queryConfig?.pagination || { skip: 0, take: 10 }, + }) + + res.status(200).json({ + posts: posts, + count: metadata.count, + offset: metadata.skip, + limit: metadata.take, + }) +} + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse, +) => { + const { result } = await createPostWorkflow(req.scope).run({ + input: { + post: req.validatedBody, + }, + }) + + res.status(200).json({ post: result }) +} diff --git a/plugins/page-builder/src/api/admin/content/validations.ts b/plugins/page-builder/src/api/admin/content/validations.ts new file mode 100644 index 00000000..ed0b4d1a --- /dev/null +++ b/plugins/page-builder/src/api/admin/content/validations.ts @@ -0,0 +1,66 @@ +import { z } from 'zod' +import { + createFindParams, + createOperatorMap, +} from '@medusajs/medusa/api/utils/validators' + +import { + postStatusValues, + postTypeValues, + postContentModeValues, +} from '../../../modules/page-builder/enum-values' + +export const postStatusesEnum = z.enum([...postStatusValues]) +export const postTypesEnum = z.enum([...postTypeValues]) +export const postContentModesEnum = z.enum([...postContentModeValues]) + +export const createPostSchema = z.object({ + title: z.string(), + handle: z.string().optional(), + excerpt: z.string().optional(), + content: z.record(z.string(), z.any()).optional(), + status: postStatusesEnum.optional(), + type: postTypesEnum.optional(), + content_mode: postContentModesEnum.optional(), + seo: z.record(z.string(), z.any()).optional(), + is_home_page: z.boolean().optional(), +}) + +export const updatePostSchema = z.object({ + id: z.string(), + title: z.string().optional(), + handle: z.string().optional(), + excerpt: z.string().optional(), + content: z.record(z.string(), z.any()).optional(), + status: postStatusesEnum.optional(), + type: postTypesEnum.optional(), + content_mode: postContentModesEnum.optional(), + seo: z.record(z.string(), z.any()).optional(), + is_home_page: z.boolean().optional(), +}) + +export const deletePostSchema = z.object({ + id: z.string(), +}) + +export const listAdminPostsQuerySchema = createFindParams({ + offset: 0, + limit: 50, +}).merge( + z.object({ + q: z.string().optional(), + id: z.union([z.string(), z.array(z.string())]).optional(), + title: z.string().optional(), + handle: z.string().optional(), + status: z.union([postStatusesEnum, z.array(postStatusesEnum)]).optional(), + type: z.union([postTypesEnum, z.array(postTypesEnum)]).optional(), + content_mode: z + .union([postContentModesEnum, z.array(postContentModesEnum)]) + .optional(), + is_home_page: z.boolean().optional(), + published_at: createOperatorMap().optional(), + archived_at: createOperatorMap().optional(), + created_at: createOperatorMap().optional(), + updated_at: createOperatorMap().optional(), + }), +) diff --git a/plugins/page-builder/src/api/middlewares.ts b/plugins/page-builder/src/api/middlewares.ts new file mode 100644 index 00000000..8d7f46b1 --- /dev/null +++ b/plugins/page-builder/src/api/middlewares.ts @@ -0,0 +1,8 @@ +import { defineMiddlewares } from '@medusajs/framework' +import { adminPostRoutesMiddlewares } from './admin/content/posts/middlewares' +import { adminPostItemRoutesMiddlewares } from './admin/content/posts/[id]/middlewares' + +export default defineMiddlewares([ + ...adminPostRoutesMiddlewares, + ...adminPostItemRoutesMiddlewares, +]) diff --git a/plugins/page-builder/src/links/post-author-user.ts b/plugins/page-builder/src/links/post-author-user.ts new file mode 100644 index 00000000..87cbaf27 --- /dev/null +++ b/plugins/page-builder/src/links/post-author-user.ts @@ -0,0 +1,14 @@ +import { defineLink } from '@medusajs/framework/utils' +import UserModule from '@medusajs/medusa/user' +import PageBuilderModule from '../modules/page-builder' + +export default defineLink( + { + ...PageBuilderModule.linkable.postAuthor.id, + field: 'medusa_user_id', + }, + UserModule.linkable.user, + { + readOnly: true, + }, +) diff --git a/plugins/page-builder/src/links/post-tag-user.ts b/plugins/page-builder/src/links/post-tag-user.ts new file mode 100644 index 00000000..86a515b3 --- /dev/null +++ b/plugins/page-builder/src/links/post-tag-user.ts @@ -0,0 +1,14 @@ +import { defineLink } from '@medusajs/framework/utils' +import UserModule from '@medusajs/medusa/user' +import PageBuilderModule from '../modules/page-builder' + +export default defineLink( + { + ...PageBuilderModule.linkable.postTag.id, + field: 'created_by_id', + }, + UserModule.linkable.user, + { + readOnly: true, + }, +) diff --git a/plugins/page-builder/src/modules/page-builder/enum-values.ts b/plugins/page-builder/src/modules/page-builder/enum-values.ts new file mode 100644 index 00000000..23e58f55 --- /dev/null +++ b/plugins/page-builder/src/modules/page-builder/enum-values.ts @@ -0,0 +1,3 @@ +export const postTypeValues = ['page', 'post'] as const +export const postStatusValues = ['draft', 'published', 'archived'] as const +export const postContentModeValues = ['basic', 'advanced'] as const diff --git a/plugins/page-builder/src/modules/page-builder/index.ts b/plugins/page-builder/src/modules/page-builder/index.ts new file mode 100644 index 00000000..42f96ad0 --- /dev/null +++ b/plugins/page-builder/src/modules/page-builder/index.ts @@ -0,0 +1,15 @@ +import { Module } from '@medusajs/framework/utils' + +import PageBuilderService from './service' + +export const PAGE_BUILDER_MODULE = 'page-builder' + +export const pageBuilderModuleEvents = Object.freeze({ + POST_CREATED: 'post.created', + POST_UPDATED: 'post.updated', + POST_DELETED: 'post.deleted', +}) + +export default Module(PAGE_BUILDER_MODULE, { + service: PageBuilderService, +}) diff --git a/plugins/page-builder/src/modules/page-builder/migrations/.snapshot-medusa-page-builder.json b/plugins/page-builder/src/modules/page-builder/migrations/.snapshot-medusa-page-builder.json new file mode 100644 index 00000000..66eeab37 --- /dev/null +++ b/plugins/page-builder/src/modules/page-builder/migrations/.snapshot-medusa-page-builder.json @@ -0,0 +1,1489 @@ +{ + "namespaces": [ + "public" + ], + "name": "public", + "tables": [ + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "label": { + "name": "label", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "location": { + "name": "location", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "enumItems": [ + "header", + "footer" + ], + "mappedType": "enum" + }, + "url": { + "name": "url", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "new_tab": { + "name": "new_tab", + "type": "boolean", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "false", + "mappedType": "boolean" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "0", + "mappedType": "integer" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "navigation_item", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_navigation_item_deleted_at", + "columnNames": [], + "composite": false, + "constraint": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_navigation_item_deleted_at\" ON \"navigation_item\" (deleted_at) WHERE deleted_at IS NULL" + }, + { + "keyName": "navigation_item_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {}, + "nativeEnums": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "type": { + "name": "type", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "enumItems": [ + "page", + "post" + ], + "mappedType": "enum" + }, + "title": { + "name": "title", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "handle": { + "name": "handle", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "excerpt": { + "name": "excerpt", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "content": { + "name": "content", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "status": { + "name": "status", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "'draft'", + "enumItems": [ + "draft", + "published", + "archived" + ], + "mappedType": "enum" + }, + "content_mode": { + "name": "content_mode", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "'advanced'", + "enumItems": [ + "basic", + "advanced" + ], + "mappedType": "enum" + }, + "seo": { + "name": "seo", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "published_at": { + "name": "published_at", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "archived_at": { + "name": "archived_at", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "is_home_page": { + "name": "is_home_page", + "type": "boolean", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "false", + "mappedType": "boolean" + }, + "root_id": { + "name": "root_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "post", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_post_handle_unique", + "columnNames": [], + "composite": false, + "constraint": false, + "primary": false, + "unique": false, + "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_post_handle_unique\" ON \"post\" (handle) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_post_root_id", + "columnNames": [], + "composite": false, + "constraint": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_post_root_id\" ON \"post\" (root_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_post_deleted_at", + "columnNames": [], + "composite": false, + "constraint": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_post_deleted_at\" ON \"post\" (deleted_at) WHERE deleted_at IS NULL" + }, + { + "keyName": "post_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "post_root_id_foreign": { + "constraintName": "post_root_id_foreign", + "columnNames": [ + "root_id" + ], + "localTableName": "public.post", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.post", + "deleteRule": "set null", + "updateRule": "cascade" + } + }, + "nativeEnums": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "name": { + "name": "name", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "medusa_user_id": { + "name": "medusa_user_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "post_author", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_post_author_medusa_user_id_unique", + "columnNames": [], + "composite": false, + "constraint": false, + "primary": false, + "unique": false, + "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_post_author_medusa_user_id_unique\" ON \"post_author\" (medusa_user_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_post_author_deleted_at", + "columnNames": [], + "composite": false, + "constraint": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_post_author_deleted_at\" ON \"post_author\" (deleted_at) WHERE deleted_at IS NULL" + }, + { + "keyName": "post_author_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {}, + "nativeEnums": {} + }, + { + "columns": { + "post_id": { + "name": "post_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "post_author_id": { + "name": "post_author_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + } + }, + "name": "post_post_authors", + "schema": "public", + "indexes": [ + { + "keyName": "post_post_authors_pkey", + "columnNames": [ + "post_id", + "post_author_id" + ], + "composite": true, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "post_post_authors_post_id_foreign": { + "constraintName": "post_post_authors_post_id_foreign", + "columnNames": [ + "post_id" + ], + "localTableName": "public.post_post_authors", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.post", + "deleteRule": "cascade", + "updateRule": "cascade" + }, + "post_post_authors_post_author_id_foreign": { + "constraintName": "post_post_authors_post_author_id_foreign", + "columnNames": [ + "post_author_id" + ], + "localTableName": "public.post_post_authors", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.post_author", + "deleteRule": "cascade", + "updateRule": "cascade" + } + }, + "nativeEnums": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "label": { + "name": "label", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "handle": { + "name": "handle", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "description": { + "name": "description", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "created_by_id": { + "name": "created_by_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "post_tag", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_post_tag_handle_unique", + "columnNames": [], + "composite": false, + "constraint": false, + "primary": false, + "unique": false, + "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_post_tag_handle_unique\" ON \"post_tag\" (handle) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_post_tag_deleted_at", + "columnNames": [], + "composite": false, + "constraint": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_post_tag_deleted_at\" ON \"post_tag\" (deleted_at) WHERE deleted_at IS NULL" + }, + { + "keyName": "post_tag_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {}, + "nativeEnums": {} + }, + { + "columns": { + "post_id": { + "name": "post_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "post_tag_id": { + "name": "post_tag_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + } + }, + "name": "post_post_tags", + "schema": "public", + "indexes": [ + { + "keyName": "post_post_tags_pkey", + "columnNames": [ + "post_id", + "post_tag_id" + ], + "composite": true, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "post_post_tags_post_id_foreign": { + "constraintName": "post_post_tags_post_id_foreign", + "columnNames": [ + "post_id" + ], + "localTableName": "public.post_post_tags", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.post", + "deleteRule": "cascade", + "updateRule": "cascade" + }, + "post_post_tags_post_tag_id_foreign": { + "constraintName": "post_post_tags_post_tag_id_foreign", + "columnNames": [ + "post_tag_id" + ], + "localTableName": "public.post_post_tags", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.post_tag", + "deleteRule": "cascade", + "updateRule": "cascade" + } + }, + "nativeEnums": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "title": { + "name": "title", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "status": { + "name": "status", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "'draft'", + "enumItems": [ + "draft", + "published", + "archived" + ], + "mappedType": "enum" + }, + "type": { + "name": "type", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "enumItems": [ + "page", + "post" + ], + "mappedType": "enum" + }, + "description": { + "name": "description", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "0", + "mappedType": "integer" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "post_template", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_post_template_deleted_at", + "columnNames": [], + "composite": false, + "constraint": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_post_template_deleted_at\" ON \"post_template\" (deleted_at) WHERE deleted_at IS NULL" + }, + { + "keyName": "post_template_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {}, + "nativeEnums": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "type": { + "name": "type", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "enumItems": [ + "button_list", + "cta", + "header", + "hero", + "product_carousel", + "product_grid", + "image_gallery", + "raw_html", + "rich_text", + "blog_list" + ], + "mappedType": "enum" + }, + "status": { + "name": "status", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "'draft'", + "enumItems": [ + "draft", + "published", + "archived" + ], + "mappedType": "enum" + }, + "name": { + "name": "name", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "content": { + "name": "content", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "json" + }, + "settings": { + "name": "settings", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "json" + }, + "styles": { + "name": "styles", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "is_reusable": { + "name": "is_reusable", + "type": "boolean", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "false", + "mappedType": "boolean" + }, + "usage_count": { + "name": "usage_count", + "type": "integer", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "1", + "mappedType": "integer" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "integer" + }, + "post_id": { + "name": "post_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "post_template_id": { + "name": "post_template_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "post_section", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_post_section_post_id", + "columnNames": [], + "composite": false, + "constraint": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_post_section_post_id\" ON \"post_section\" (post_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_post_section_post_template_id", + "columnNames": [], + "composite": false, + "constraint": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_post_section_post_template_id\" ON \"post_section\" (post_template_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_post_section_deleted_at", + "columnNames": [], + "composite": false, + "constraint": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_post_section_deleted_at\" ON \"post_section\" (deleted_at) WHERE deleted_at IS NULL" + }, + { + "keyName": "post_section_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "post_section_post_id_foreign": { + "constraintName": "post_section_post_id_foreign", + "columnNames": [ + "post_id" + ], + "localTableName": "public.post_section", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.post", + "deleteRule": "set null", + "updateRule": "cascade" + }, + "post_section_post_template_id_foreign": { + "constraintName": "post_section_post_template_id_foreign", + "columnNames": [ + "post_template_id" + ], + "localTableName": "public.post_section", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.post_template", + "deleteRule": "set null", + "updateRule": "cascade" + } + }, + "nativeEnums": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "description": { + "name": "description", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "header_code": { + "name": "header_code", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "footer_code": { + "name": "footer_code", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "storefront_url": { + "name": "storefront_url", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "primary_theme_colors": { + "name": "primary_theme_colors", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "accent_theme_colors": { + "name": "accent_theme_colors", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "highlight_theme_colors": { + "name": "highlight_theme_colors", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "display_font": { + "name": "display_font", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "body_font": { + "name": "body_font", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "include_site_name_beside_logo": { + "name": "include_site_name_beside_logo", + "type": "boolean", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "boolean" + }, + "social_instagram": { + "name": "social_instagram", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "social_youtube": { + "name": "social_youtube", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "social_facebook": { + "name": "social_facebook", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "social_twitter": { + "name": "social_twitter", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "social_linkedin": { + "name": "social_linkedin", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "social_pinterest": { + "name": "social_pinterest", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "social_tiktok": { + "name": "social_tiktok", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "social_snapchat": { + "name": "social_snapchat", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "global_css": { + "name": "global_css", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "ga_property_id": { + "name": "ga_property_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "shipping_sort": { + "name": "shipping_sort", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "site_settings", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_site_settings_deleted_at", + "columnNames": [], + "composite": false, + "constraint": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_site_settings_deleted_at\" ON \"site_settings\" (deleted_at) WHERE deleted_at IS NULL" + }, + { + "keyName": "site_settings_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {}, + "nativeEnums": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "url": { + "name": "url", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "post_id": { + "name": "post_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "site_settings_id": { + "name": "site_settings_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "image", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_image_post_id_unique", + "columnNames": [], + "composite": false, + "constraint": false, + "primary": false, + "unique": false, + "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_image_post_id_unique\" ON \"image\" (post_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_image_site_settings_id_unique", + "columnNames": [], + "composite": false, + "constraint": false, + "primary": false, + "unique": false, + "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_image_site_settings_id_unique\" ON \"image\" (site_settings_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_image_deleted_at", + "columnNames": [], + "composite": false, + "constraint": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_image_deleted_at\" ON \"image\" (deleted_at) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_product_image_url", + "columnNames": [], + "composite": false, + "constraint": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_product_image_url\" ON \"image\" (url) WHERE deleted_at IS NULL" + }, + { + "keyName": "image_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "image_post_id_foreign": { + "constraintName": "image_post_id_foreign", + "columnNames": [ + "post_id" + ], + "localTableName": "public.image", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.post", + "deleteRule": "set null", + "updateRule": "cascade" + }, + "image_site_settings_id_foreign": { + "constraintName": "image_site_settings_id_foreign", + "columnNames": [ + "site_settings_id" + ], + "localTableName": "public.image", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.site_settings", + "deleteRule": "set null", + "updateRule": "cascade" + } + }, + "nativeEnums": {} + } + ], + "nativeEnums": {} +} diff --git a/plugins/page-builder/src/modules/page-builder/migrations/Migration20250305164601.ts b/plugins/page-builder/src/modules/page-builder/migrations/Migration20250305164601.ts new file mode 100644 index 00000000..914a57ed --- /dev/null +++ b/plugins/page-builder/src/modules/page-builder/migrations/Migration20250305164601.ts @@ -0,0 +1,103 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20250305164601 extends Migration { + + override async up(): Promise { + this.addSql(`alter table if exists "image" drop constraint if exists "image_site_settings_id_unique";`); + this.addSql(`alter table if exists "image" drop constraint if exists "image_post_id_unique";`); + this.addSql(`alter table if exists "post_tag" drop constraint if exists "post_tag_handle_unique";`); + this.addSql(`alter table if exists "post_author" drop constraint if exists "post_author_medusa_user_id_unique";`); + this.addSql(`alter table if exists "post" drop constraint if exists "post_handle_unique";`); + this.addSql(`create table if not exists "navigation_item" ("id" text not null, "label" text not null, "location" text check ("location" in ('header', 'footer')) not null, "url" text not null, "new_tab" boolean not null default false, "sort_order" integer not null default 0, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "navigation_item_pkey" primary key ("id"));`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_navigation_item_deleted_at" ON "navigation_item" (deleted_at) WHERE deleted_at IS NULL;`); + + this.addSql(`create table if not exists "post" ("id" text not null, "type" text check ("type" in ('page', 'post')) not null, "title" text not null, "handle" text not null, "excerpt" text null, "content" jsonb null, "status" text check ("status" in ('draft', 'published', 'archived')) not null default 'draft', "content_mode" text check ("content_mode" in ('basic', 'advanced')) not null, "seo" jsonb null, "published_at" text null, "archived_at" text null, "is_home_page" boolean not null default false, "root_id" text null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "post_pkey" primary key ("id"));`); + this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_post_handle_unique" ON "post" (handle) WHERE deleted_at IS NULL;`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_root_id" ON "post" (root_id) WHERE deleted_at IS NULL;`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_deleted_at" ON "post" (deleted_at) WHERE deleted_at IS NULL;`); + + this.addSql(`create table if not exists "post_author" ("id" text not null, "name" text not null, "medusa_user_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "post_author_pkey" primary key ("id"));`); + this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_post_author_medusa_user_id_unique" ON "post_author" (medusa_user_id) WHERE deleted_at IS NULL;`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_author_deleted_at" ON "post_author" (deleted_at) WHERE deleted_at IS NULL;`); + + this.addSql(`create table if not exists "post_post_authors" ("post_id" text not null, "post_author_id" text not null, constraint "post_post_authors_pkey" primary key ("post_id", "post_author_id"));`); + + this.addSql(`create table if not exists "post_tag" ("id" text not null, "label" text not null, "handle" text not null, "description" text not null, "created_by_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "post_tag_pkey" primary key ("id"));`); + this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_post_tag_handle_unique" ON "post_tag" (handle) WHERE deleted_at IS NULL;`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_tag_deleted_at" ON "post_tag" (deleted_at) WHERE deleted_at IS NULL;`); + + this.addSql(`create table if not exists "post_post_tags" ("post_id" text not null, "post_tag_id" text not null, constraint "post_post_tags_pkey" primary key ("post_id", "post_tag_id"));`); + + this.addSql(`create table if not exists "post_template" ("id" text not null, "title" text not null, "status" text check ("status" in ('draft', 'published', 'archived')) not null default 'draft', "type" text check ("type" in ('page', 'post')) not null, "description" text null, "sort_order" integer not null default 0, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "post_template_pkey" primary key ("id"));`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_template_deleted_at" ON "post_template" (deleted_at) WHERE deleted_at IS NULL;`); + + this.addSql(`create table if not exists "post_section" ("id" text not null, "type" text check ("type" in ('button_list', 'cta', 'header', 'hero', 'product_carousel', 'product_grid', 'image_gallery', 'raw_html', 'rich_text', 'blog_list')) not null, "status" text check ("status" in ('draft', 'published', 'archived')) not null default 'draft', "name" text not null, "content" jsonb not null, "settings" jsonb not null, "styles" jsonb null, "is_reusable" boolean not null default false, "usage_count" integer not null default 1, "sort_order" integer not null, "post_id" text null, "post_template_id" text null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "post_section_pkey" primary key ("id"));`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_section_post_id" ON "post_section" (post_id) WHERE deleted_at IS NULL;`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_section_post_template_id" ON "post_section" (post_template_id) WHERE deleted_at IS NULL;`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_section_deleted_at" ON "post_section" (deleted_at) WHERE deleted_at IS NULL;`); + + this.addSql(`create table if not exists "site_settings" ("id" text not null, "description" text null, "header_code" text null, "footer_code" text null, "storefront_url" text null, "primary_theme_colors" jsonb null, "accent_theme_colors" jsonb null, "highlight_theme_colors" jsonb null, "display_font" jsonb null, "body_font" jsonb null, "include_site_name_beside_logo" boolean not null, "social_instagram" text null, "social_youtube" text null, "social_facebook" text null, "social_twitter" text null, "social_linkedin" text null, "social_pinterest" text null, "social_tiktok" text null, "social_snapchat" text null, "global_css" text null, "ga_property_id" text null, "shipping_sort" text null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "site_settings_pkey" primary key ("id"));`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_site_settings_deleted_at" ON "site_settings" (deleted_at) WHERE deleted_at IS NULL;`); + + this.addSql(`create table if not exists "image" ("id" text not null, "url" text not null, "metadata" jsonb null, "post_id" text null, "site_settings_id" text null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "image_pkey" primary key ("id"));`); + this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_image_post_id_unique" ON "image" (post_id) WHERE deleted_at IS NULL;`); + this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_image_site_settings_id_unique" ON "image" (site_settings_id) WHERE deleted_at IS NULL;`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_image_deleted_at" ON "image" (deleted_at) WHERE deleted_at IS NULL;`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_product_image_url" ON "image" (url) WHERE deleted_at IS NULL;`); + + this.addSql(`alter table if exists "post" add constraint "post_root_id_foreign" foreign key ("root_id") references "post" ("id") on update cascade on delete set null;`); + + this.addSql(`alter table if exists "post_post_authors" add constraint "post_post_authors_post_id_foreign" foreign key ("post_id") references "post" ("id") on update cascade on delete cascade;`); + this.addSql(`alter table if exists "post_post_authors" add constraint "post_post_authors_post_author_id_foreign" foreign key ("post_author_id") references "post_author" ("id") on update cascade on delete cascade;`); + + this.addSql(`alter table if exists "post_post_tags" add constraint "post_post_tags_post_id_foreign" foreign key ("post_id") references "post" ("id") on update cascade on delete cascade;`); + this.addSql(`alter table if exists "post_post_tags" add constraint "post_post_tags_post_tag_id_foreign" foreign key ("post_tag_id") references "post_tag" ("id") on update cascade on delete cascade;`); + + this.addSql(`alter table if exists "post_section" add constraint "post_section_post_id_foreign" foreign key ("post_id") references "post" ("id") on update cascade on delete set null;`); + this.addSql(`alter table if exists "post_section" add constraint "post_section_post_template_id_foreign" foreign key ("post_template_id") references "post_template" ("id") on update cascade on delete set null;`); + + this.addSql(`alter table if exists "image" add constraint "image_post_id_foreign" foreign key ("post_id") references "post" ("id") on update cascade on delete set null;`); + this.addSql(`alter table if exists "image" add constraint "image_site_settings_id_foreign" foreign key ("site_settings_id") references "site_settings" ("id") on update cascade on delete set null;`); + } + + override async down(): Promise { + this.addSql(`alter table if exists "post" drop constraint if exists "post_root_id_foreign";`); + + this.addSql(`alter table if exists "post_post_authors" drop constraint if exists "post_post_authors_post_id_foreign";`); + + this.addSql(`alter table if exists "post_post_tags" drop constraint if exists "post_post_tags_post_id_foreign";`); + + this.addSql(`alter table if exists "post_section" drop constraint if exists "post_section_post_id_foreign";`); + + this.addSql(`alter table if exists "image" drop constraint if exists "image_post_id_foreign";`); + + this.addSql(`alter table if exists "post_post_authors" drop constraint if exists "post_post_authors_post_author_id_foreign";`); + + this.addSql(`alter table if exists "post_post_tags" drop constraint if exists "post_post_tags_post_tag_id_foreign";`); + + this.addSql(`alter table if exists "post_section" drop constraint if exists "post_section_post_template_id_foreign";`); + + this.addSql(`alter table if exists "image" drop constraint if exists "image_site_settings_id_foreign";`); + + this.addSql(`drop table if exists "navigation_item" cascade;`); + + this.addSql(`drop table if exists "post" cascade;`); + + this.addSql(`drop table if exists "post_author" cascade;`); + + this.addSql(`drop table if exists "post_post_authors" cascade;`); + + this.addSql(`drop table if exists "post_tag" cascade;`); + + this.addSql(`drop table if exists "post_post_tags" cascade;`); + + this.addSql(`drop table if exists "post_template" cascade;`); + + this.addSql(`drop table if exists "post_section" cascade;`); + + this.addSql(`drop table if exists "site_settings" cascade;`); + + this.addSql(`drop table if exists "image" cascade;`); + } + +} diff --git a/plugins/page-builder/src/modules/page-builder/migrations/Migration20250313155933.ts b/plugins/page-builder/src/modules/page-builder/migrations/Migration20250313155933.ts new file mode 100644 index 00000000..4016e27f --- /dev/null +++ b/plugins/page-builder/src/modules/page-builder/migrations/Migration20250313155933.ts @@ -0,0 +1,15 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20250313155933 extends Migration { + + override async up(): Promise { + this.addSql(`alter table if exists "post" alter column "handle" type text using ("handle"::text);`); + this.addSql(`alter table if exists "post" alter column "handle" drop not null;`); + } + + override async down(): Promise { + this.addSql(`alter table if exists "post" alter column "handle" type text using ("handle"::text);`); + this.addSql(`alter table if exists "post" alter column "handle" set not null;`); + } + +} diff --git a/plugins/page-builder/src/modules/page-builder/models/image.ts b/plugins/page-builder/src/modules/page-builder/models/image.ts new file mode 100644 index 00000000..ab58e80a --- /dev/null +++ b/plugins/page-builder/src/modules/page-builder/models/image.ts @@ -0,0 +1,30 @@ +import { model } from '@medusajs/framework/utils' +import { PostModel } from './post' +import { SiteSettingsModel } from './site-settings' + +export const ImageModel = model + .define('image', { + id: model.id({ prefix: 'img' }).primaryKey(), + url: model.text(), + metadata: model.json().nullable(), + + // relations fields + post: model + .belongsTo(() => PostModel, { + mappedBy: 'featured_image', + }) + .nullable(), + site_settings: model + .belongsTo(() => SiteSettingsModel, { + mappedBy: 'favicon', + }) + .nullable(), + }) + .indexes([ + { + name: 'IDX_product_image_url', + on: ['url'], + unique: false, + where: 'deleted_at IS NULL', + }, + ]) diff --git a/plugins/page-builder/src/modules/page-builder/models/index.ts b/plugins/page-builder/src/modules/page-builder/models/index.ts new file mode 100644 index 00000000..e7009779 --- /dev/null +++ b/plugins/page-builder/src/modules/page-builder/models/index.ts @@ -0,0 +1,8 @@ +export * from './post-author' +export * from './post' +export * from './post-section' +export * from './post-tag' +export * from './post-template' +export * from './image' +export * from './site-settings' +export * from './navigation-item' diff --git a/plugins/page-builder/src/modules/page-builder/models/navigation-item.ts b/plugins/page-builder/src/modules/page-builder/models/navigation-item.ts new file mode 100644 index 00000000..db55b3ab --- /dev/null +++ b/plugins/page-builder/src/modules/page-builder/models/navigation-item.ts @@ -0,0 +1,10 @@ +import { model } from '@medusajs/framework/utils' + +export const NavigationItemModel = model.define('navigation_item', { + id: model.id({ prefix: 'nav_item' }).primaryKey(), + label: model.text(), + location: model.enum(['header', 'footer']), + url: model.text(), + new_tab: model.boolean().default(false), + sort_order: model.number().default(0), +}) diff --git a/plugins/page-builder/src/modules/page-builder/models/post-author.ts b/plugins/page-builder/src/modules/page-builder/models/post-author.ts new file mode 100644 index 00000000..f9897b73 --- /dev/null +++ b/plugins/page-builder/src/modules/page-builder/models/post-author.ts @@ -0,0 +1,13 @@ +import { model } from '@medusajs/framework/utils' +import { PostModel } from './post' + +export const PostAuthorModel = model.define('post_author', { + id: model.id({ prefix: 'post_author' }).primaryKey(), + name: model.text(), + + // relations fields + medusa_user_id: model.text().unique(), + posts: model.manyToMany(() => PostModel, { + mappedBy: 'authors', + }), +}) diff --git a/plugins/page-builder/src/modules/page-builder/models/post-section.ts b/plugins/page-builder/src/modules/page-builder/models/post-section.ts new file mode 100644 index 00000000..1a9823f3 --- /dev/null +++ b/plugins/page-builder/src/modules/page-builder/models/post-section.ts @@ -0,0 +1,39 @@ +import { model } from '@medusajs/framework/utils' +import { PostModel } from './post' +import { PostTemplateModel } from './post-template' + +export const PostSectionModel = model.define('post_section', { + id: model.id({ prefix: 'postsec' }).primaryKey(), + type: model.enum([ + 'button_list', + 'cta', + 'header', + 'hero', + 'product_carousel', + 'product_grid', + 'image_gallery', + 'raw_html', + 'rich_text', + 'blog_list', + ]), + status: model.enum(['draft', 'published', 'archived']).default('draft'), + name: model.text(), + content: model.json(), + settings: model.json(), + styles: model.json().nullable(), + is_reusable: model.boolean().default(false), + usage_count: model.number().default(1), + sort_order: model.number(), + + // relations fields + post: model + .belongsTo(() => PostModel, { + mappedBy: 'sections', + }) + .nullable(), + post_template: model + .belongsTo(() => PostTemplateModel, { + mappedBy: 'sections', + }) + .nullable(), +}) diff --git a/plugins/page-builder/src/modules/page-builder/models/post-tag.ts b/plugins/page-builder/src/modules/page-builder/models/post-tag.ts new file mode 100644 index 00000000..17e5e6c3 --- /dev/null +++ b/plugins/page-builder/src/modules/page-builder/models/post-tag.ts @@ -0,0 +1,15 @@ +import { model } from '@medusajs/framework/utils' +import { PostModel } from './post' + +export const PostTagModel = model.define('post_tag', { + id: model.id({ prefix: 'post_tag' }).primaryKey(), + label: model.text(), + handle: model.text().unique(), + description: model.text(), + + // relations fields + created_by_id: model.text(), + posts: model.manyToMany(() => PostModel, { + mappedBy: 'tags', + }), +}) diff --git a/plugins/page-builder/src/modules/page-builder/models/post-template.ts b/plugins/page-builder/src/modules/page-builder/models/post-template.ts new file mode 100644 index 00000000..bc312b53 --- /dev/null +++ b/plugins/page-builder/src/modules/page-builder/models/post-template.ts @@ -0,0 +1,14 @@ +import { model } from '@medusajs/framework/utils' +import { PostSectionModel } from './post-section' + +export const PostTemplateModel = model.define('post_template', { + id: model.id({ prefix: 'post_temp' }).primaryKey(), + title: model.text(), + status: model.enum(['draft', 'published', 'archived']).default('draft'), + type: model.enum(['page', 'post']), + description: model.text().nullable(), + sort_order: model.number().default(0), + + // relations fields + sections: model.hasMany(() => PostSectionModel), +}) diff --git a/plugins/page-builder/src/modules/page-builder/models/post.ts b/plugins/page-builder/src/modules/page-builder/models/post.ts new file mode 100644 index 00000000..d16f5169 --- /dev/null +++ b/plugins/page-builder/src/modules/page-builder/models/post.ts @@ -0,0 +1,41 @@ +import { model } from '@medusajs/framework/utils' +import { PostSectionModel } from './post-section' +import { PostTagModel } from './post-tag' +import { PostAuthorModel } from './post-author' +import { ImageModel } from './image' +import { + postContentModeValues, + postStatusValues, + postTypeValues, +} from '../enum-values' + +export const PostModel = model.define('post', { + id: model.id({ prefix: 'post' }).primaryKey(), + type: model.enum([...postTypeValues]), + title: model.text().searchable(), + handle: model.text().unique().searchable().nullable(), + excerpt: model.text().searchable().nullable(), + content: model.json().nullable(), + status: model.enum([...postStatusValues]).default('draft'), + content_mode: model.enum([...postContentModeValues]).default('advanced'), + seo: model.json().nullable(), + published_at: model.text().nullable(), + archived_at: model.text().nullable(), + is_home_page: model.boolean().default(false), + + // relations fields + featured_image: model.hasOne(() => ImageModel, { + mappedBy: 'post', + }), + authors: model.manyToMany(() => PostAuthorModel, { + mappedBy: 'posts', + }), + + root: model.belongsTo(() => PostModel).nullable(), + + sections: model.hasMany(() => PostSectionModel), + + tags: model.manyToMany(() => PostTagModel, { + mappedBy: 'posts', + }), +}) diff --git a/plugins/page-builder/src/modules/page-builder/models/site-settings.ts b/plugins/page-builder/src/modules/page-builder/models/site-settings.ts new file mode 100644 index 00000000..8f97c169 --- /dev/null +++ b/plugins/page-builder/src/modules/page-builder/models/site-settings.ts @@ -0,0 +1,32 @@ +import { model } from '@medusajs/framework/utils' +import { ImageModel } from './image' + +export const SiteSettingsModel = model.define('site_settings', { + id: model.id({ prefix: 'site_sett' }).primaryKey(), + description: model.text().nullable(), + header_code: model.text().nullable(), + footer_code: model.text().nullable(), + storefront_url: model.text().nullable(), + primary_theme_colors: model.json().nullable(), + accent_theme_colors: model.json().nullable(), + highlight_theme_colors: model.json().nullable(), + display_font: model.json().nullable(), + body_font: model.json().nullable(), + include_site_name_beside_logo: model.boolean(), + social_instagram: model.text().nullable(), + social_youtube: model.text().nullable(), + social_facebook: model.text().nullable(), + social_twitter: model.text().nullable(), + social_linkedin: model.text().nullable(), + social_pinterest: model.text().nullable(), + social_tiktok: model.text().nullable(), + social_snapchat: model.text().nullable(), + global_css: model.text().nullable(), + ga_property_id: model.text().nullable(), + shipping_sort: model.text().nullable(), + + // relations + favicon: model.hasOne(() => ImageModel, { + mappedBy: 'site_settings', + }), +}) diff --git a/plugins/page-builder/src/modules/page-builder/service.ts b/plugins/page-builder/src/modules/page-builder/service.ts new file mode 100644 index 00000000..fef52e9b --- /dev/null +++ b/plugins/page-builder/src/modules/page-builder/service.ts @@ -0,0 +1,24 @@ +import { MedusaService } from '@medusajs/framework/utils' +import { + PostAuthorModel, + PostModel, + PostTemplateModel, + PostTagModel, + PostSectionModel, + ImageModel, + SiteSettingsModel, + NavigationItemModel, +} from './models' + +class PageBuilderService extends MedusaService({ + PostAuthor: PostAuthorModel, + Post: PostModel, + PostTemplate: PostTemplateModel, + PostTag: PostTagModel, + PostSection: PostSectionModel, + Image: ImageModel, + SiteSettings: SiteSettingsModel, + NavigationItem: NavigationItemModel, +}) {} + +export default PageBuilderService diff --git a/plugins/page-builder/src/subscribers/README.md b/plugins/page-builder/src/subscribers/README.md new file mode 100644 index 00000000..0f5f58bf --- /dev/null +++ b/plugins/page-builder/src/subscribers/README.md @@ -0,0 +1,59 @@ +# Custom subscribers + +Subscribers handle events emitted in the Medusa application. + +The subscriber is created in a TypeScript or JavaScript file under the `src/subscribers` directory. + +For example, create the file `src/subscribers/product-created.ts` with the following content: + +```ts +import { + type SubscriberConfig, +} from "@medusajs/framework" + +// subscriber function +export default async function productCreateHandler() { + console.log("A product was created") +} + +// subscriber config +export const config: SubscriberConfig = { + event: "product.created", +} +``` + +A subscriber file must export: + +- The subscriber function that is an asynchronous function executed whenever the associated event is triggered. +- A configuration object defining the event this subscriber is listening to. + +## Subscriber Parameters + +A subscriber receives an object having the following properties: + +- `event`: An object holding the event's details. It has a `data` property, which is the event's data payload. +- `container`: The Medusa container. Use it to resolve modules' main services and other registered resources. + +```ts +import type { + SubscriberArgs, + SubscriberConfig, +} from "@medusajs/framework" + +export default async function productCreateHandler({ + event: { data }, + container, +}: SubscriberArgs<{ id: string }>) { + const productId = data.id + + const productModuleService = container.resolve("product") + + const product = await productModuleService.retrieveProduct(productId) + + console.log(`The product ${product.title} was created`) +} + +export const config: SubscriberConfig = { + event: "product.created", +} +``` \ No newline at end of file diff --git a/plugins/page-builder/src/workflows/create-post.ts b/plugins/page-builder/src/workflows/create-post.ts new file mode 100644 index 00000000..bb4604d9 --- /dev/null +++ b/plugins/page-builder/src/workflows/create-post.ts @@ -0,0 +1,25 @@ +import { transform } from '@medusajs/framework/workflows-sdk' +import { emitEventStep } from '@medusajs/medusa/core-flows' +import { WorkflowResponse, createWorkflow } from '@medusajs/workflows-sdk' + +import type { CreatePostWorkflowInput } from './types' +import { pageBuilderModuleEvents } from '../modules/page-builder' +import { createPostStep } from './steps/create-post' + +export const createPostWorkflow = createWorkflow( + 'create-post-workflow', + (input: CreatePostWorkflowInput) => { + const post = createPostStep(input.post) + + const emitData = transform({ post }, ({ post }) => { + return { + eventName: pageBuilderModuleEvents.POST_CREATED, + data: { id: post.id }, + } + }) + + emitEventStep(emitData) + + return new WorkflowResponse(post) + }, +) diff --git a/plugins/page-builder/src/workflows/delete-post.ts b/plugins/page-builder/src/workflows/delete-post.ts new file mode 100644 index 00000000..1d2c910d --- /dev/null +++ b/plugins/page-builder/src/workflows/delete-post.ts @@ -0,0 +1,26 @@ +import { transform } from '@medusajs/framework/workflows-sdk' +import { emitEventStep } from '@medusajs/medusa/core-flows' +import { WorkflowResponse, createWorkflow } from '@medusajs/workflows-sdk' + +import type { DeletePostWorkflowInput } from './types' +import { pageBuilderModuleEvents } from '../modules/page-builder' +import { deletePostStep } from './steps/delete-post' + +// Delete post workflow +export const deletePostWorkflow = createWorkflow( + 'delete-post-workflow', + (input: DeletePostWorkflowInput) => { + const result = deletePostStep({ id: input.id }) + + const emitData = transform({ result }, ({ result }) => { + return { + eventName: pageBuilderModuleEvents.POST_DELETED, + data: { id: result.id }, + } + }) + + emitEventStep(emitData) + + return new WorkflowResponse(result) + }, +) diff --git a/plugins/page-builder/src/workflows/duplicate-post.ts b/plugins/page-builder/src/workflows/duplicate-post.ts new file mode 100644 index 00000000..7dc64ea8 --- /dev/null +++ b/plugins/page-builder/src/workflows/duplicate-post.ts @@ -0,0 +1,32 @@ +import { transform } from '@medusajs/framework/workflows-sdk' +import { emitEventStep } from '@medusajs/medusa/core-flows' +import { WorkflowResponse, createWorkflow } from '@medusajs/workflows-sdk' + +import type { DuplicatePostWorkflowInput } from './types' +import { pageBuilderModuleEvents } from '../modules/page-builder' +import { duplicatePostStep } from './steps/duplicate-post' +import { duplicatePostRelationsStep } from './steps/duplicate-post-relations' + +// Duplicate post workflow +export const duplicatePostWorkflow = createWorkflow( + 'duplicate-post-workflow', + (input: DuplicatePostWorkflowInput) => { + const newPost = duplicatePostStep({ id: input.id }) + + const postWithRelations = duplicatePostRelationsStep({ + originalId: input.id, + newId: newPost.id, + }) + + const emitData = transform({ post: newPost }, ({ post }) => { + return { + eventName: pageBuilderModuleEvents.POST_CREATED, + data: { id: post.id }, + } + }) + + emitEventStep(emitData) + + return new WorkflowResponse(postWithRelations) + }, +) diff --git a/plugins/page-builder/src/workflows/steps/create-post.ts b/plugins/page-builder/src/workflows/steps/create-post.ts new file mode 100644 index 00000000..93507a03 --- /dev/null +++ b/plugins/page-builder/src/workflows/steps/create-post.ts @@ -0,0 +1,37 @@ +import { StepResponse, createStep } from '@medusajs/workflows-sdk' + +import type { CreatePostStepInput } from '../types' +import { PAGE_BUILDER_MODULE } from '../../modules/page-builder' +import type PageBuilderService from '../../modules/page-builder/service' + +export const createPostStepId = 'create-post-step' + +export const createPostStep = createStep( + createPostStepId, + async (data: CreatePostStepInput, { container }) => { + const pageBuilderService = + container.resolve(PAGE_BUILDER_MODULE) + + const createData: CreatePostStepInput = { + ...data, + status: data.status || 'draft', + content_mode: data.content_mode || 'advanced', + } + + const post = await pageBuilderService.createPosts(createData) + + return new StepResponse(post, { + postId: post.id, + }) + }, + async (data, { container }) => { + if (!data) return + + const { postId } = data + + const pageBuilderService = + container.resolve(PAGE_BUILDER_MODULE) + + await pageBuilderService.deletePosts(postId) + }, +) diff --git a/plugins/page-builder/src/workflows/steps/delete-post.ts b/plugins/page-builder/src/workflows/steps/delete-post.ts new file mode 100644 index 00000000..b292731a --- /dev/null +++ b/plugins/page-builder/src/workflows/steps/delete-post.ts @@ -0,0 +1,49 @@ +import { StepResponse, createStep } from '@medusajs/workflows-sdk' + +import type { CreatePostStepInput, DeletePostStepInput } from '../types' +import { PAGE_BUILDER_MODULE } from '../../modules/page-builder' +import type PageBuilderService from '../../modules/page-builder/service' + +export const deletePostStepId = 'delete-post-step' + +export const deletePostStep = createStep( + deletePostStepId, + async (data: DeletePostStepInput, { container }) => { + const pageBuilderService = + container.resolve(PAGE_BUILDER_MODULE) + + // Get the existing post before deleting for rollback + const existingPost = await pageBuilderService.retrievePost(data.id, { + relations: ['root', 'featured_image', 'authors', 'sections', 'tags'], + }) + + await pageBuilderService.deletePosts(data.id) + + return new StepResponse({ id: data.id }, existingPost) + }, + async (existingPost, { container }) => { + if (!existingPost) return + + const pageBuilderService = + container.resolve(PAGE_BUILDER_MODULE) + + // Restore the deleted post + const createPostInput: { + id: string + sections?: string[] + } & CreatePostStepInput = { + ...existingPost, + + // relations + root_id: existingPost.root?.root?.id ?? undefined, + featured_image_id: existingPost.featured_image?.id, + authors: existingPost.authors?.map((author) => author.id), + sections: existingPost.sections?.map((section) => section.id), + tags: existingPost.tags?.map((tag) => tag.id), + } + + const restoredPost = await pageBuilderService.createPosts(createPostInput) + + return restoredPost + }, +) diff --git a/plugins/page-builder/src/workflows/steps/duplicate-post-relations.ts b/plugins/page-builder/src/workflows/steps/duplicate-post-relations.ts new file mode 100644 index 00000000..2c0989dd --- /dev/null +++ b/plugins/page-builder/src/workflows/steps/duplicate-post-relations.ts @@ -0,0 +1,82 @@ +import { StepResponse, createStep } from '@medusajs/workflows-sdk' +import { PAGE_BUILDER_MODULE } from '../../modules/page-builder' +import type PageBuilderService from '../../modules/page-builder/service' + +export const duplicatePostRelationsStepId = 'duplicate-post-relations-step' + +type DuplicatePostRelationsStepInput = { + originalId: string + newId: string +} + +export const duplicatePostRelationsStep = createStep( + duplicatePostRelationsStepId, + async (data: DuplicatePostRelationsStepInput, { container }) => { + const pageBuilderService = + container.resolve(PAGE_BUILDER_MODULE) + + // Get the original post with all its relations + const originalPost = await pageBuilderService.retrievePost( + data.originalId, + { + relations: ['sections', 'featured_image', 'authors', 'tags'], + }, + ) + + const newPost = await pageBuilderService.retrievePost(data.newId) + const newPostSectionsIds: string[] = [] + + // Duplicate the sections since these should be unique to the post + if (originalPost.sections?.length > 0) { + type SectionInput = Parameters< + typeof pageBuilderService.createPostSections + >['0'][number] + + const sectionsToCreate: SectionInput[] = originalPost.sections.map( + (section) => ({ + ...section, + id: undefined, + post_template: undefined, + post_template_id: undefined, + + // Set the new post relation + post: newPost.id, + post_id: newPost.id, + }), + ) + + const newPostSections = + await pageBuilderService.createPostSections(sectionsToCreate) + + newPostSectionsIds.push(...newPostSections.map(({ id }) => id)) + } + + const relationsToUpdate = { + sections: newPostSectionsIds, + featured_image: originalPost.featured_image?.id, + authors: originalPost.authors?.map((author) => author.id) ?? [], + tags: originalPost.tags?.map((tag) => tag.id) ?? [], + } + + const updatedPost = await pageBuilderService.updatePosts({ + id: newPost.id, + ...relationsToUpdate, + }) + + return new StepResponse(updatedPost, { + relationsUpdated: relationsToUpdate, + }) + }, + async (data, { container }) => { + if (!data?.relationsUpdated) return + + const pageBuilderService = + container.resolve(PAGE_BUILDER_MODULE) + + const { sections, featured_image, authors, tags } = data.relationsUpdated + + if (sections?.length) { + await pageBuilderService.deletePostSections(sections) + } + }, +) diff --git a/plugins/page-builder/src/workflows/steps/duplicate-post.ts b/plugins/page-builder/src/workflows/steps/duplicate-post.ts new file mode 100644 index 00000000..5ded7251 --- /dev/null +++ b/plugins/page-builder/src/workflows/steps/duplicate-post.ts @@ -0,0 +1,51 @@ +import { StepResponse, createStep } from '@medusajs/workflows-sdk' + +import type { DuplicatePostStepInput } from '../types' +import { PAGE_BUILDER_MODULE } from '../../modules/page-builder' +import type PageBuilderService from '../../modules/page-builder/service' + +export const duplicatePostStepId = 'duplicate-post-step' + +export const duplicatePostStep = createStep( + duplicatePostStepId, + async (data: DuplicatePostStepInput, { container }) => { + const pageBuilderService = + container.resolve(PAGE_BUILDER_MODULE) + + // Get the existing post to duplicate + const existingPost = await pageBuilderService.retrievePost(data.id) + + // Create a new title with "(copy)" suffix + const newTitle = `${existingPost.title || 'Untitled'} (copy)` + + // Create a new handle or make it null to generate a new one + const handle = existingPost.handle ? `${existingPost.handle}-copy` : null + + // Create a new post with the copied data + const newPost = await pageBuilderService.createPosts({ + title: newTitle, + handle, + excerpt: existingPost.excerpt, + content: existingPost.content, + status: 'draft', // Always start as draft + type: existingPost.type, + content_mode: existingPost.content_mode, + seo: existingPost.seo ? { ...existingPost.seo } : undefined, + is_home_page: false, // Never copy home page status + }) + + return new StepResponse(newPost, { + originalId: existingPost.id, + newId: newPost.id, + }) + }, + async (data, { container }) => { + if (!data) return + + const pageBuilderService = + container.resolve(PAGE_BUILDER_MODULE) + + // Delete the created post if workflow fails + await pageBuilderService.deletePosts(data.newId) + }, +) diff --git a/plugins/page-builder/src/workflows/steps/update-post.ts b/plugins/page-builder/src/workflows/steps/update-post.ts new file mode 100644 index 00000000..24c77174 --- /dev/null +++ b/plugins/page-builder/src/workflows/steps/update-post.ts @@ -0,0 +1,42 @@ +import { StepResponse, createStep } from '@medusajs/workflows-sdk' + +import type { UpdatePostStepInput } from '../types' +import { PAGE_BUILDER_MODULE } from '../../modules/page-builder' +import type PageBuilderService from '../../modules/page-builder/service' + +export const updatePostStepId = 'update-post-step' + +export const updatePostStep = createStep( + updatePostStepId, + async (data: UpdatePostStepInput, { container }) => { + const pageBuilderService = + container.resolve(PAGE_BUILDER_MODULE) + + // Get the existing post before updating for rollback + const existingPost = await pageBuilderService.retrievePost(data.id, { + relations: ['root', 'featured_image', 'authors', 'sections', 'tags'], + }) + + const post = await pageBuilderService.updatePosts(data) + + return new StepResponse(post, { + ...existingPost, + + // relations + root_id: existingPost.root?.root?.id ?? undefined, + featured_image_id: existingPost.featured_image?.id, + authors: existingPost.authors?.map((author) => author.id), + sections: existingPost.sections?.map((section) => section.id), + tags: existingPost.tags?.map((tag) => tag.id), + } as UpdatePostStepInput) + }, + async (existingData, { container }) => { + if (!existingData) return + + const pageBuilderService = + container.resolve(PAGE_BUILDER_MODULE) + + // Rollback to the previous state with only the updatable fields + await pageBuilderService.updatePosts(existingData) + }, +) diff --git a/plugins/page-builder/src/workflows/types/index.ts b/plugins/page-builder/src/workflows/types/index.ts new file mode 100644 index 00000000..817e88fd --- /dev/null +++ b/plugins/page-builder/src/workflows/types/index.ts @@ -0,0 +1,49 @@ +import type { Post } from '@lambdacurry/medusa-page-builder-types' + +type PostInput = Omit< + Post, + | 'id' + | 'created_at' + | 'updated_at' + | 'featured_image' + | 'root' + | 'sections' + | 'authors' + | 'tags' +> + +// Post types +export type CreatePostStepInput = Partial & { + featured_image_id?: string + authors?: string[] + tags?: string[] +} + +export type UpdatePostStepInput = { + id: string + sections?: string[] +} & Partial + +export type DeletePostStepInput = { + id: string +} + +export type CreatePostWorkflowInput = { + post: CreatePostStepInput +} + +export type UpdatePostWorkflowInput = { + post: UpdatePostStepInput +} + +export type DeletePostWorkflowInput = { + id: string +} + +export type DuplicatePostStepInput = { + id: string +} + +export type DuplicatePostWorkflowInput = { + id: string +} diff --git a/plugins/page-builder/src/workflows/update-post.ts b/plugins/page-builder/src/workflows/update-post.ts new file mode 100644 index 00000000..bfedc505 --- /dev/null +++ b/plugins/page-builder/src/workflows/update-post.ts @@ -0,0 +1,25 @@ +import { transform } from '@medusajs/framework/workflows-sdk' +import { emitEventStep } from '@medusajs/medusa/core-flows' +import { WorkflowResponse, createWorkflow } from '@medusajs/workflows-sdk' + +import type { UpdatePostWorkflowInput } from './types' +import { pageBuilderModuleEvents } from '../modules/page-builder' +import { updatePostStep } from './steps/update-post' + +export const updatePostWorkflow = createWorkflow( + 'update-post-workflow', + (input: UpdatePostWorkflowInput) => { + const post = updatePostStep(input.post) + + const emitData = transform({ post }, ({ post }) => { + return { + eventName: pageBuilderModuleEvents.POST_UPDATED, + data: { id: post.id }, + } + }) + + emitEventStep(emitData) + + return new WorkflowResponse(post) + }, +) diff --git a/plugins/page-builder/tsconfig.json b/plugins/page-builder/tsconfig.json new file mode 100644 index 00000000..af817c80 --- /dev/null +++ b/plugins/page-builder/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "target": "ES2021", + "esModuleInterop": true, + "module": "Node16", + "moduleResolution": "Node16", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "declaration": true, + "sourceMap": false, + "inlineSourceMap": true, + "outDir": "./.medusa/server", + "rootDir": "./", + "baseUrl": ".", + "jsx": "react-jsx", + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "checkJs": false, + "strictNullChecks": true + }, + "ts-node": { + "swc": true + }, + "include": ["**/*"], + "exclude": [ + "node_modules", + ".medusa/server", + ".medusa/admin", + "src/admin", + ".cache" + ] +} diff --git a/yarn.lock b/yarn.lock index 1091e494..c2516b58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1854,12 +1854,85 @@ __metadata: languageName: node linkType: hard -"@lambdacurry/medusa-plugins-sdk@npm:0.0.5, @lambdacurry/medusa-plugins-sdk@workspace:packages/plugins-sdk": +"@lambdacurry/medusa-page-builder-types@npm:0.0.4, @lambdacurry/medusa-page-builder-types@workspace:packages/page-builder-types": version: 0.0.0-use.local - resolution: "@lambdacurry/medusa-plugins-sdk@workspace:packages/plugins-sdk" + resolution: "@lambdacurry/medusa-page-builder-types@workspace:packages/page-builder-types" + dependencies: + typescript: "npm:5.7.3" + languageName: unknown + linkType: soft + +"@lambdacurry/medusa-page-builder@workspace:plugins/page-builder": + version: 0.0.0-use.local + resolution: "@lambdacurry/medusa-page-builder@workspace:plugins/page-builder" + dependencies: + "@lambdacurry/medusa-page-builder-types": "npm:0.0.4" + "@lambdacurry/medusa-plugins-sdk": "npm:0.0.6-beta.3" + "@medusajs/admin-sdk": "npm:^2.6.1" + "@medusajs/cli": "npm:^2.6.1" + "@medusajs/framework": "npm:^2.6.1" + "@medusajs/icons": "npm:^2.6.1" + "@medusajs/js-sdk": "npm:^2.6.1" + "@medusajs/medusa": "npm:^2.6.1" + "@medusajs/test-utils": "npm:^2.6.1" + "@medusajs/ui": "npm:^4.0.7" + "@medusajs/workflows-sdk": "npm:^2.6.1" + "@mikro-orm/cli": "npm:6.4.3" + "@mikro-orm/core": "npm:6.4.3" + "@mikro-orm/knex": "npm:6.4.3" + "@mikro-orm/migrations": "npm:6.4.3" + "@mikro-orm/postgresql": "npm:6.4.3" + "@swc/core": "npm:1.5.7" + "@types/express": "npm:4.17.13" + "@types/node": "npm:^20.0.0" + "@types/react": "npm:^18.3.2" + "@types/react-dom": "npm:^18.2.25" + awilix: "npm:^8.0.1" + pg: "npm:^8.13.0" + prop-types: "npm:^15.8.1" + react: "npm:^18.2.0" + react-dom: "npm:^18.2.0" + ts-node: "npm:^10.9.2" + typescript: "npm:^5.6.2" + vite: "npm:^5.2.11" + yalc: "npm:^1.0.0-pre.53" + peerDependencies: + "@medusajs/admin-sdk": ^2.6.0 + "@medusajs/cli": ^2.6.0 + "@medusajs/framework": ^2.6.0 + "@medusajs/icons": ^2.6.0 + "@medusajs/medusa": ^2.6.0 + "@medusajs/test-utils": ^2.6.0 + "@medusajs/ui": ^4.0.7 + "@mikro-orm/cli": 6.4.3 + "@mikro-orm/core": 6.4.3 + "@mikro-orm/knex": 6.4.3 + "@mikro-orm/migrations": 6.4.3 + "@mikro-orm/postgresql": 6.4.3 + awilix: ^8.0.1 + pg: ^8.13.0 + languageName: unknown + linkType: soft + +"@lambdacurry/medusa-plugins-sdk@npm:0.0.5": + version: 0.0.5 + resolution: "@lambdacurry/medusa-plugins-sdk@npm:0.0.5" dependencies: "@medusajs/js-sdk": "npm:^2.5.0" - "@medusajs/types": "npm:^2.5.0" + "@types/express": "npm:^5.0.0" + "@types/multer": "npm:^1.4.12" + form-data: "npm:^4.0.2" + checksum: 10c0/988f9214b4f230f9ed2e843d5ad003097241a9709ad2c34fb773e9201b335e8a4cec620f9e78989f0e7174caa319ad0286c9b92e76c54292e0322716cad6e1e1 + languageName: node + linkType: hard + +"@lambdacurry/medusa-plugins-sdk@npm:0.0.6-beta.3, @lambdacurry/medusa-plugins-sdk@workspace:packages/plugins-sdk": + version: 0.0.0-use.local + resolution: "@lambdacurry/medusa-plugins-sdk@workspace:packages/plugins-sdk" + dependencies: + "@lambdacurry/medusa-page-builder-types": "npm:0.0.4" + "@medusajs/js-sdk": "npm:^2.6.1" + "@medusajs/types": "npm:^2.6.1" "@types/express": "npm:^5.0.0" "@types/multer": "npm:^1.4.12" form-data: "npm:^4.0.2" @@ -1983,13 +2056,13 @@ __metadata: languageName: unknown linkType: soft -"@medusajs/admin-bundler@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/admin-bundler@npm:2.5.1" +"@medusajs/admin-bundler@npm:2.6.1, @medusajs/admin-bundler@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/admin-bundler@npm:2.6.1" dependencies: - "@medusajs/admin-shared": "npm:2.5.1" - "@medusajs/admin-vite-plugin": "npm:2.5.1" - "@medusajs/dashboard": "npm:2.5.1" + "@medusajs/admin-shared": "npm:2.6.1" + "@medusajs/admin-vite-plugin": "npm:2.6.1" + "@medusajs/dashboard": "npm:2.6.1" "@rollup/plugin-node-resolve": "npm:^16.0.0" "@vitejs/plugin-react": "npm:^4.2.1" autoprefixer: "npm:^10.4.16" @@ -2002,7 +2075,7 @@ __metadata: vite: "npm:^5.4.14" peerDependencies: react-dom: ^18.0.0 - checksum: 10c0/18f263d8d8487c297b4b24e8639a29451b2aa49b08853fc61b31de90ad0dbc467e071eacc6b358e17f9ff748e8fc10c40a7c4c72632c1909a3308b0ea1231125 + checksum: 10c0/957d73bb6400fd2e1e384a480c5b8f38fc57b096555327bcee195f8283bbe4a262cf2570300c773258642fa10c253d13efa83239c44534d5cb6e577936506850 languageName: node linkType: hard @@ -2049,6 +2122,16 @@ __metadata: languageName: node linkType: hard +"@medusajs/admin-sdk@npm:^2.6.1": + version: 2.6.1 + resolution: "@medusajs/admin-sdk@npm:2.6.1" + dependencies: + "@medusajs/admin-shared": "npm:2.6.1" + zod: "npm:3.22.4" + checksum: 10c0/ab650456aaa15e3a8731af3f02c2663c8ff03ddb5b57333c4c03e14ef04b309970450a518b6e00a91ede403b0d4b86b8f6591ac98be614ea1afd8b3d6b56f48c + languageName: node + linkType: hard + "@medusajs/admin-shared@npm:2.4.0, @medusajs/admin-shared@npm:~2.4.0": version: 2.4.0 resolution: "@medusajs/admin-shared@npm:2.4.0" @@ -2056,10 +2139,10 @@ __metadata: languageName: node linkType: hard -"@medusajs/admin-shared@npm:2.5.1, @medusajs/admin-shared@npm:^2.5.0, @medusajs/admin-shared@npm:^2.5.1": - version: 2.5.1 - resolution: "@medusajs/admin-shared@npm:2.5.1" - checksum: 10c0/f5b8425cbd44a2dcc96ebe86d55d409cfa04a47923f4627b3a8882818662447c512a323d6e09ebfcb4996f6c83926ca54ab8faf7ab3c3c27123a34d0cb53ab4c +"@medusajs/admin-shared@npm:2.6.1, @medusajs/admin-shared@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/admin-shared@npm:2.6.1" + checksum: 10c0/35b822c028da1aac1aea48cacf2b629ce771a04f0d3bb1e6d3b44e5df7949fc10bbf1ff2f9bed27023a7cf0ed0afe1ba0150fac00b67e9f4eca1544b04eb94f7 languageName: node linkType: hard @@ -2082,14 +2165,14 @@ __metadata: languageName: node linkType: hard -"@medusajs/admin-vite-plugin@npm:2.5.1": - version: 2.5.1 - resolution: "@medusajs/admin-vite-plugin@npm:2.5.1" +"@medusajs/admin-vite-plugin@npm:2.6.1": + version: 2.6.1 + resolution: "@medusajs/admin-vite-plugin@npm:2.6.1" dependencies: "@babel/parser": "npm:7.25.6" "@babel/traverse": "npm:7.25.6" "@babel/types": "npm:7.25.6" - "@medusajs/admin-shared": "npm:2.5.1" + "@medusajs/admin-shared": "npm:2.6.1" chokidar: "npm:3.5.3" fdir: "npm:6.1.1" magic-string: "npm:0.30.5" @@ -2097,20 +2180,20 @@ __metadata: picocolors: "npm:^1.1.0" peerDependencies: vite: ^5.0.0 - checksum: 10c0/fbded1c97364457470f9a70aac7f78fc7b35d1d1a69414d43d47f0decd53e5003d789b2e420a629c11b8792b1ed9e52c542eb4a0883a44285cfec7cd4d447c1c + checksum: 10c0/355665d7afb32241d72929fecbfc214f409d579507528c3407af0289d863cd986f2d5970db405b10f95d46a3b76be0dfa408cbf03e3f4e36df80f301e75f677e languageName: node linkType: hard -"@medusajs/api-key@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/api-key@npm:2.5.1" +"@medusajs/api-key@npm:2.6.1, @medusajs/api-key@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/api-key@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/c0616181a90ffd2738f621446553b958f0b7e0ab2e1df58c86ef02d45121c87f3a0a8b97b85ec5807e94793e4dc0f8b1672d49cde96500a50f3081512b240de8 + checksum: 10c0/90c7b79aaf8c0a3be05e60fd1fbd380ee8a06ceddd7cefc8baa5d0b3dff1b6176f6fb22a4e2098124f846bb22f63528c4571902e05151e5d9cd3f830fc5fc8db languageName: node linkType: hard @@ -2127,14 +2210,14 @@ __metadata: languageName: node linkType: hard -"@medusajs/auth-emailpass@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/auth-emailpass@npm:2.5.1" +"@medusajs/auth-emailpass@npm:2.6.1, @medusajs/auth-emailpass@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/auth-emailpass@npm:2.6.1" dependencies: scrypt-kdf: "npm:^2.0.1" peerDependencies: - "@medusajs/framework": ^2.4.0 - checksum: 10c0/28953be54ab0f61cab8541ba721f577f9c64b44ede24e3fea0f7b87744a1feeb6d8f11eee2dd11a74a8fd2b7df783ddaa9eca613c32836ce1de8b207dc679483 + "@medusajs/framework": 2.6.1 + checksum: 10c0/f054377f46333a033717871839cd366f006ff17a831c5f7ea3d5484293fa9dec94e7a1280838086c96c1a9d23a360602e285fc988ed8be8659b0a3c18b50efb5 languageName: node linkType: hard @@ -2149,12 +2232,12 @@ __metadata: languageName: node linkType: hard -"@medusajs/auth-github@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/auth-github@npm:2.5.1" +"@medusajs/auth-github@npm:2.6.1, @medusajs/auth-github@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/auth-github@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 - checksum: 10c0/d7b28e10dfc154f50bed9c9bef084d0acbae05bc4d24d8e38a9ff4dd307822b68624dea690a81d3dda5d9ba3cc0abbd3a351c437c7aac26555ac10f9966391e4 + "@medusajs/framework": 2.6.1 + checksum: 10c0/e776f88e7e50713fd8c8ebb9df66a27aceaa2def3ce2e89eb1239515dc4ef195361d0d08415f0b48fad3c9910562fb5244e616a6f361defe667ada3ffce220bd languageName: node linkType: hard @@ -2167,14 +2250,14 @@ __metadata: languageName: node linkType: hard -"@medusajs/auth-google@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/auth-google@npm:2.5.1" +"@medusajs/auth-google@npm:2.6.1, @medusajs/auth-google@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/auth-google@npm:2.6.1" dependencies: jsonwebtoken: "npm:^9.0.2" peerDependencies: - "@medusajs/framework": ^2.4.0 - checksum: 10c0/3c980a6b4d66beee3564972f009580a7d27f98b8bd5d7f2fbf3372c27f510972a0d69188952d09c7779a71592f3e53ab24c11650c30e19522ebde01395caded8 + "@medusajs/framework": 2.6.1 + checksum: 10c0/d553907adbb9dbaa7751083e2e62f9808438e36b5dae7c26e935a057722854caa0eb5d55ae0badc9faf63a1569af72a1146c567fe954d646d45c5d4c2338fbb0 languageName: node linkType: hard @@ -2189,16 +2272,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/auth@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/auth@npm:2.5.1" +"@medusajs/auth@npm:2.6.1, @medusajs/auth@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/auth@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/528563e75129a703c35b7f6cdc3d74e23b2e4a16cfbb635ee19824a139f3d5e60bcf1aee7ab3a2925503026317c223658e89a5c03ee205fa7e273d93980d9278 + checksum: 10c0/00f9ed5a8bba868630628c28458d89967e0495a168675ab70766d7fccdbe680491ef5209e2698145770ec443c4da84360373069b4a291ee739052704cd6d42d5 languageName: node linkType: hard @@ -2215,12 +2298,12 @@ __metadata: languageName: node linkType: hard -"@medusajs/cache-inmemory@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/cache-inmemory@npm:2.5.1" +"@medusajs/cache-inmemory@npm:2.6.1, @medusajs/cache-inmemory@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/cache-inmemory@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 - checksum: 10c0/42f89dc5c4a9abfd536ca67945757c312d0cb2f1134fc31bc638be29c64658f8528a2711c9f3f01851271ae9f87cabc24dae96ef5bff98f5727015566bc7a12d + "@medusajs/framework": 2.6.1 + checksum: 10c0/2b7502cb661094abf8614afaa7ddbf1c91d7f61633271816777275338039f236088ce807df392e2afa5b5f93948928fdb8541b1ca440cca6b952d3d74431e9b8 languageName: node linkType: hard @@ -2233,15 +2316,15 @@ __metadata: languageName: node linkType: hard -"@medusajs/cache-redis@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/cache-redis@npm:2.5.1" +"@medusajs/cache-redis@npm:2.6.1, @medusajs/cache-redis@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/cache-redis@npm:2.6.1" dependencies: ioredis: "npm:^5.4.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 awilix: ^8.0.1 - checksum: 10c0/227040c4160fefad87a2646e20d2a83943e034fbaa50b809708e907e7747b77b67d5e3b8b6810c3a69026fa4e283fd091e1c7ab741e7729f61ca46bc4e25a5fb + checksum: 10c0/8f61658c161f49793021ad5989444334f4c0e678e94374f3546ee10bd28206a698ad3c1c31655b69466e8509528aa17a5e186c2fb1114cfd8eab7d2b7c2893d2 languageName: node linkType: hard @@ -2257,16 +2340,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/cart@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/cart@npm:2.5.1" +"@medusajs/cart@npm:2.6.1, @medusajs/cart@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/cart@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/2c4ee76defc157cbba73a68ec17be13f013595b91237545db4ffb2e1075bc5fab4ed51c5512ae76ccb0e1f4928b2d9073d44db20a9c1a43a1e9cb4955dc0aa7a + checksum: 10c0/9d952c1b417ac705f5955a10916728d8e113b3729c63fc84de6ca277c51064f8e5ca32372b8bf5ea42268f358c984106fa77ec0075bfca761104767e91b3a1fe languageName: node linkType: hard @@ -2365,15 +2448,56 @@ __metadata: languageName: node linkType: hard -"@medusajs/core-flows@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/core-flows@npm:2.5.1" +"@medusajs/cli@npm:^2.6.1": + version: 2.6.1 + resolution: "@medusajs/cli@npm:2.6.1" + dependencies: + "@medusajs/telemetry": "npm:2.6.1" + "@medusajs/utils": "npm:2.6.1" + "@types/express": "npm:^4.17.17" + chalk: "npm:^4.0.0" + configstore: "npm:5.0.1" + dotenv: "npm:^16.4.5" + execa: "npm:^5.1.1" + express: "npm:^4.21.0" + fs-exists-cached: "npm:^1.0.0" + fs-extra: "npm:^10.0.0" + glob: "npm:^10.3.10" + hosted-git-info: "npm:^4.0.2" + inquirer: "npm:^8.0.0" + is-valid-path: "npm:^0.1.1" + meant: "npm:^1.0.3" + ora: "npm:^5.4.1" + pg: "npm:^8.11.3" + pg-god: "npm:^1.0.12" + prompts: "npm:^2.4.2" + resolve-cwd: "npm:^3.0.0" + stack-trace: "npm:^0.0.10" + ulid: "npm:^2.3.0" + winston: "npm:^3.8.2" + yargs: "npm:^15.3.1" + peerDependencies: + "@mikro-orm/core": 6.4.3 + "@mikro-orm/knex": 6.4.3 + "@mikro-orm/migrations": 6.4.3 + "@mikro-orm/postgresql": 6.4.3 + awilix: ^8.0.1 + pg: ^8.13.0 + bin: + medusa: cli.js + checksum: 10c0/0420a36df6dcb7dbabe9ce8ba57a1ce21981b30019c01d60c2f1b4cb7c838d342c607c56865d99d359dfe1e0274d73ac7d6a478b72e48f10bd9d32ea13dfa5f1 + languageName: node + linkType: hard + +"@medusajs/core-flows@npm:2.6.1, @medusajs/core-flows@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/core-flows@npm:2.6.1" dependencies: json-2-csv: "npm:^5.5.4" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 awilix: ^8.0.1 - checksum: 10c0/03246c1aaabee8a9d632a98f64ce78964b15511d1b7e23649dd0cc37c661a1e0cae4935aba334608f32f51a60b2552c71c581537ca4ceeacf7b739138c794ba0 + checksum: 10c0/d7a5764d55ed9da7b5e049f4b579eca3ab6c57c21cc1905a40cf146d964177a523009435747bdb8b21e93efd56f86347bc8335351f1df28633ef019eefe99a11 languageName: node linkType: hard @@ -2389,16 +2513,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/currency@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/currency@npm:2.5.1" +"@medusajs/currency@npm:2.6.1, @medusajs/currency@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/currency@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/08a3857fc424b07fc0d98501acac84b54b79726b069fef6ba5d08fd2f85307f5a3502e2d845ae3c0ddf6077347e8f40b2587d5f6aab4586b63cb929797566e00 + checksum: 10c0/ad7884fd4615c89cfc46983e5d6efa1fa0889776dfa02664c88a0da7a8eb8b2367e9217f21c9adce14dcf704fd6d0bd4e105768377900daee842482ea3d8184f languageName: node linkType: hard @@ -2415,16 +2539,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/customer@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/customer@npm:2.5.1" +"@medusajs/customer@npm:2.6.1, @medusajs/customer@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/customer@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/a067a11c556dfe82d8f0487f6711964469fd3362b39e57d8fea042ffd5bad7d628acaf2299b010923d3def1d72dde6d48c541b38a09b264bc20e7d39ca7d019f + checksum: 10c0/d9ea80990e8a2b2be664f82f9d5d2d6c91e5e2dbbdf795bf826f4dcb9475c3fe37a2723ed5097f90aef80b69c77d8cc4ceed2900ce73b8b6b42bf0e5498ecd5e languageName: node linkType: hard @@ -2482,19 +2606,19 @@ __metadata: languageName: node linkType: hard -"@medusajs/dashboard@npm:2.5.1": - version: 2.5.1 - resolution: "@medusajs/dashboard@npm:2.5.1" +"@medusajs/dashboard@npm:2.6.1": + version: 2.6.1 + resolution: "@medusajs/dashboard@npm:2.6.1" dependencies: "@ariakit/react": "npm:^0.4.15" "@dnd-kit/core": "npm:^6.1.0" "@dnd-kit/sortable": "npm:^8.0.0" "@hookform/error-message": "npm:^2.0.1" "@hookform/resolvers": "npm:3.4.2" - "@medusajs/admin-shared": "npm:^2.5.1" - "@medusajs/icons": "npm:^2.5.1" - "@medusajs/js-sdk": "npm:^2.5.1" - "@medusajs/ui": "npm:~4.0.6" + "@medusajs/admin-shared": "npm:2.6.1" + "@medusajs/icons": "npm:2.6.1" + "@medusajs/js-sdk": "npm:2.6.1" + "@medusajs/ui": "npm:4.0.7" "@tanstack/react-query": "npm:5.64.2" "@tanstack/react-table": "npm:8.20.5" "@tanstack/react-virtual": "npm:^3.8.3" @@ -2519,18 +2643,18 @@ __metadata: react-jwt: "npm:^1.2.0" react-router-dom: "npm:6.20.1" zod: "npm:3.22.4" - checksum: 10c0/fecc60f991f2d973af5e7c76488c68083035a9a5100cee487de0c80c38de108c54bc47a58ce52511bda15e43f77a388c33ca4da5f914b981a17d7d5dd0d07624 + checksum: 10c0/1e4b533af93cd4441d435e641adac7a229f12188545d7758a5565dfc0f339ad1ebc2b530d7d5e594cf2a367840624df2156dd91dba986467c69debb8cdc57f3d languageName: node linkType: hard -"@medusajs/event-bus-local@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/event-bus-local@npm:2.5.1" +"@medusajs/event-bus-local@npm:2.6.1, @medusajs/event-bus-local@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/event-bus-local@npm:2.6.1" dependencies: ulid: "npm:^2.3.0" peerDependencies: - "@medusajs/framework": ^2.4.0 - checksum: 10c0/372aa3368a915e89b2c5afc5e377fde6e1c3c50db616a4348b3780e5f84bba464acdac29a6fb24736f35f7cff1042eb872dee45136899bba6c22e9a9927bfd4d + "@medusajs/framework": 2.6.1 + checksum: 10c0/9f70f81d8797fa35f0776f77f2ce10a7c4c0731514e153022990ef2219cc4c9f34ca54d8baba221703392f374af6f4d7b6e4218d4548ea52f55245356dbc09dc languageName: node linkType: hard @@ -2545,16 +2669,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/event-bus-redis@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/event-bus-redis@npm:2.5.1" +"@medusajs/event-bus-redis@npm:2.6.1, @medusajs/event-bus-redis@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/event-bus-redis@npm:2.6.1" dependencies: bullmq: "npm:5.13.0" ioredis: "npm:^5.4.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 awilix: ^8.0.1 - checksum: 10c0/ddaf2972d9a21551d13a9290e94e808c315872c76d1ac523d9e4e9a9544aa0ff8158d1ead04c0e1fde9eb0d69044b701e20ddd4f089b1a7f1aad7d63aa734617 + checksum: 10c0/c05147ccdabad3e3b33d4bd4e7748de8ad832b88bb7efc7d29cbebdda79b44ef97de099b1941a14954afd4d54d357d75a64530a241262ae69221ccb495baae7f languageName: node linkType: hard @@ -2571,12 +2695,12 @@ __metadata: languageName: node linkType: hard -"@medusajs/file-local@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/file-local@npm:2.5.1" +"@medusajs/file-local@npm:2.6.1, @medusajs/file-local@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/file-local@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 - checksum: 10c0/6b3025a4e99780259fc788d92722cb1f90a64e1598b5db61c99ef8e3b3d910e199f9a50dd9d76bde38bfa8972269ecce5cf421981bf29f4f1c47e0b849684304 + "@medusajs/framework": 2.6.1 + checksum: 10c0/7731abef423ef839e6ee9860752fec061c30cba0e46ecd5774074928d813ed885f3dc9b7636440c8d02e70733ea5e5bf205f81127475c828d9c528de45985a42 languageName: node linkType: hard @@ -2589,16 +2713,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/file-s3@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/file-s3@npm:2.5.1" +"@medusajs/file-s3@npm:2.6.1, @medusajs/file-s3@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/file-s3@npm:2.6.1" dependencies: "@aws-sdk/client-s3": "npm:^3.556.0" "@aws-sdk/s3-request-presigner": "npm:^3.556.0" ulid: "npm:^2.3.0" peerDependencies: - "@medusajs/framework": ^2.4.0 - checksum: 10c0/d9118040767a75904c3c74cb91d7d82bf11e4e77e7c804e466269cb10366a3b02c90ad561204659b3d17a13dc8a520920d3c281bbbdaa68306a70c43b3f47c8f + "@medusajs/framework": 2.6.1 + checksum: 10c0/1130dad21114cf3b5982d3f0cc9e8e67c77d3af247763c4a0840b5eeea0e73c4c1e887e39d002e54f1c2b320be5c94da80321b7185e03ba715be7d6fcf1d3386 languageName: node linkType: hard @@ -2615,13 +2739,13 @@ __metadata: languageName: node linkType: hard -"@medusajs/file@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/file@npm:2.5.1" +"@medusajs/file@npm:2.6.1, @medusajs/file@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/file@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 awilix: ^8.0.1 - checksum: 10c0/e2879d12201a055f47d6b334ab947de8b02ac01ab3fbb6bad837d4d8e4f810b61d844bf89dd9665dd52e9ca1c7c98d6c45316e545438ef913ec56f3fd3b7ba5a + checksum: 10c0/afe383fde591b7b2a074dbb1dbe40a05a524e669ff5283d24b7382f2cb1a705075f7b44f27221e4b2e37d6bfc59bb50697fd85b604ddfbe05c50331e274120ab languageName: node linkType: hard @@ -2735,12 +2859,63 @@ __metadata: languageName: node linkType: hard -"@medusajs/fulfillment-manual@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/fulfillment-manual@npm:2.5.1" +"@medusajs/framework@npm:^2.6.1": + version: 2.6.1 + resolution: "@medusajs/framework@npm:2.6.1" + dependencies: + "@jercle/yargonaut": "npm:^1.1.5" + "@medusajs/modules-sdk": "npm:2.6.1" + "@medusajs/orchestration": "npm:2.6.1" + "@medusajs/telemetry": "npm:2.6.1" + "@medusajs/types": "npm:2.6.1" + "@medusajs/utils": "npm:2.6.1" + "@medusajs/workflows-sdk": "npm:2.6.1" + "@opentelemetry/api": "npm:^1.9.0" + "@types/express": "npm:^4.17.17" + chokidar: "npm:^3.4.2" + compression: "npm:1.7.4" + connect-redis: "npm:5.2.0" + cookie-parser: "npm:^1.4.6" + cors: "npm:^2.8.5" + express: "npm:^4.21.0" + express-session: "npm:^1.17.3" + glob: "npm:7.2.3" + jsonwebtoken: "npm:^9.0.2" + lodash: "npm:4.17.21" + morgan: "npm:^1.9.1" + path-to-regexp: "npm:^0.1.10" + tsconfig-paths: "npm:^4.2.0" + zod: "npm:3.22.4" peerDependencies: - "@medusajs/framework": ^2.4.0 - checksum: 10c0/9bb243ab3eb27ac85b553f2b88b99c65d1cafb3efde597fbd87fc36f3ae69ad73e5a98e9ef1d868b8f9c96c2ce9647f58ccdd2785dbce80d8362a67ac412a9d0 + "@medusajs/cli": 2.6.1 + "@mikro-orm/cli": 6.4.3 + "@mikro-orm/core": 6.4.3 + "@mikro-orm/knex": 6.4.3 + "@mikro-orm/migrations": 6.4.3 + "@mikro-orm/postgresql": 6.4.3 + awilix: ^8.0.1 + ioredis: ^5.4.1 + pg: ^8.13.0 + vite: ^5.4.14 + peerDependenciesMeta: + "@mikro-orm/cli": + optional: true + ioredis: + optional: true + vite: + optional: true + bin: + medusa-mikro-orm: dist/mikro-orm-cli/bin.js + checksum: 10c0/091e262cc368be3c191e9742d0cc29bae7383e780a0b75d11e69eede778d799896e26184468175933a6d827a5b3a7246e57ad160efee0c2322261e7994a5616c + languageName: node + linkType: hard + +"@medusajs/fulfillment-manual@npm:2.6.1, @medusajs/fulfillment-manual@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/fulfillment-manual@npm:2.6.1" + peerDependencies: + "@medusajs/framework": 2.6.1 + checksum: 10c0/d381faa5546f0eec68eb9cf8526ff086b96b2abc9abe34b4b63a76750c9a946d60192168c8487381b6897198d707e638e5b6a8c3d87a66832344e1d02a02d0b4 languageName: node linkType: hard @@ -2753,16 +2928,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/fulfillment@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/fulfillment@npm:2.5.1" +"@medusajs/fulfillment@npm:2.6.1, @medusajs/fulfillment@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/fulfillment@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/c5efcdba9c7532409c6fae8285add8cdd10416918d9e5d716741b9c679cee9eaf2c7f8f338fdee55e71b1d5ab772505aa4fc70ab6e759351c050d890f39e15eb + checksum: 10c0/a00d2fd0e40beb84ee06945fc44d3f00767b75aeca63587e99ff5fb3c35edb31e1fbbfea6ba5ff72088bf57a4929e6b4ef8841fec485cb0a76a9f4968e78f779 languageName: node linkType: hard @@ -2797,26 +2972,26 @@ __metadata: languageName: node linkType: hard -"@medusajs/icons@npm:^2.5.1": - version: 2.5.1 - resolution: "@medusajs/icons@npm:2.5.1" +"@medusajs/icons@npm:2.6.1, @medusajs/icons@npm:^2.6.1": + version: 2.6.1 + resolution: "@medusajs/icons@npm:2.6.1" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - checksum: 10c0/f7fbe17475d8c39bd584103ec662f90aa5c8f07159f5820648eba8a0adf42a0d48057518da5a22503e5d5eaca552cbb7b26792613e2ec84827be1bbbafb64201 + checksum: 10c0/368e3101596244396d925ab4553eecd444804f20bc2e65f80ad138ddf91844eebf44671bee171af3e17ae3e4eb2aa6ef3c7c29c690e1eb61a1ba4f13e3021338 languageName: node linkType: hard -"@medusajs/index@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/index@npm:2.5.1" +"@medusajs/index@npm:2.6.1, @medusajs/index@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/index@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/knex": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/39e8c466fb487b0a113ef0e2865bd541f45577cee863d383e8637b3a1442dc95893fe865e917b52c18b45102d5bb74321891ff5f6053b3de99714f9206fbc01f + checksum: 10c0/8f930babcdf0c14eea3777d891e70f8ab178e6f5480ed783faa383f9b6e9cdd84ce5566237b0a744335d79277a2ac469081c2d1c505fa8131ed48ac071dd50f2 languageName: node linkType: hard @@ -2834,16 +3009,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/inventory@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/inventory@npm:2.5.1" +"@medusajs/inventory@npm:2.6.1, @medusajs/inventory@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/inventory@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/7639fc23d1ffa766207a6ce3e098ad9e5df57b15223ff24e1a02b9e3f26078404e2db8a6eec4303ad6d24771a4d55c96d0b6d53e80ea975eec9a68cb1342975c + checksum: 10c0/d455335ab3fa76c97a36043127d7b853e630859832903c778fe527b127ef4a92b681d2e12e1b235b3f6a10f0bda2444007abfe4ccba8e80a3eb9a62c348c3f69 languageName: node linkType: hard @@ -2871,38 +3046,38 @@ __metadata: languageName: node linkType: hard -"@medusajs/js-sdk@npm:^2.5.0": - version: 2.5.0 - resolution: "@medusajs/js-sdk@npm:2.5.0" +"@medusajs/js-sdk@npm:2.6.1, @medusajs/js-sdk@npm:^2.6.1": + version: 2.6.1 + resolution: "@medusajs/js-sdk@npm:2.6.1" dependencies: - "@medusajs/types": "npm:^2.5.0" + "@medusajs/types": "npm:2.6.1" fetch-event-stream: "npm:^0.1.5" qs: "npm:^6.12.1" - checksum: 10c0/d5c4a29590e1727882d4eb329292b64f8b4315ddb37dcea7bf9fb236e6ace20d5116c0fb6c64a33ac4db03b9f7c89a2e180880cd00dd10942580b659653c44ab + checksum: 10c0/afa2ec90b64c4e12d2723237f5880da9d05a5ae78b59590b9be01805c5657d8ad7a1c0ea63519e1f25c3c8f8cfb5d2140b33846b797621eb99007e0364be6636 languageName: node linkType: hard -"@medusajs/js-sdk@npm:^2.5.1": - version: 2.5.1 - resolution: "@medusajs/js-sdk@npm:2.5.1" +"@medusajs/js-sdk@npm:^2.5.0": + version: 2.5.0 + resolution: "@medusajs/js-sdk@npm:2.5.0" dependencies: - "@medusajs/types": "npm:^2.5.1" + "@medusajs/types": "npm:^2.5.0" fetch-event-stream: "npm:^0.1.5" qs: "npm:^6.12.1" - checksum: 10c0/dd78040f3a07fdb6cb4e4092630243f6fc7dc692dd230b318162f39882511081c2625e1b93ac79c37343410c6acc367afce7eb8494b7dada1de3f146dc37fd34 + checksum: 10c0/d5c4a29590e1727882d4eb329292b64f8b4315ddb37dcea7bf9fb236e6ace20d5116c0fb6c64a33ac4db03b9f7c89a2e180880cd00dd10942580b659653c44ab languageName: node linkType: hard -"@medusajs/link-modules@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/link-modules@npm:2.5.1" +"@medusajs/link-modules@npm:2.6.1, @medusajs/link-modules@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/link-modules@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/927bf05cab78aa8babf13ab14446fc9a97b92707dce2fb54aed21d95afbc9dc849a32af26aaa30b66e10bae963e3fe20df5c285c33f5670c66af56f84793f227 + checksum: 10c0/aadeefea1ab3133f92510d0477478a67cdb6c602d3634af9e0517f39ddc11c18645ede89c34a1bb6a503fd3e7395efbb3288c9d64a2510b283f1326efadd9934 languageName: node linkType: hard @@ -2919,12 +3094,12 @@ __metadata: languageName: node linkType: hard -"@medusajs/locking-postgres@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/locking-postgres@npm:2.5.1" +"@medusajs/locking-postgres@npm:2.6.1, @medusajs/locking-postgres@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/locking-postgres@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 - checksum: 10c0/63915b123c44b27b61279f716b34442a5b817b6c16ea6204d7916626365f0ee0d682117daf873e5b267268fd29a032437005bf68f508595f855c3cb26cf9d842 + "@medusajs/framework": 2.6.1 + checksum: 10c0/f21a984c155114ae52e46077a1af9efa263ad7766967bbf614ad43f5612f95f40112567aa655269b0a2e04c072ec9fca5cc0c515bed15b4f4194bc5c2026e093 languageName: node linkType: hard @@ -2937,14 +3112,14 @@ __metadata: languageName: node linkType: hard -"@medusajs/locking-redis@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/locking-redis@npm:2.5.1" +"@medusajs/locking-redis@npm:2.6.1, @medusajs/locking-redis@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/locking-redis@npm:2.6.1" dependencies: ioredis: "npm:^5.4.1" peerDependencies: - "@medusajs/framework": ^2.4.0 - checksum: 10c0/fb6261fc27ee9e15dea4ba56c222c7a0b5e574c18228ec49656c783ed9c60f37237ac8c09940c090910096326fe5706c83a35865a0733f8238b3b1b43861d2b1 + "@medusajs/framework": 2.6.1 + checksum: 10c0/48f37d56f39117914b4f303a853a2a82f6a4573b4bc224cf3463557aa39ea3c13c481716cf5c4ce1b0b19a18e53e7e24acdea68d209fd13dd5f9e899b1350d41 languageName: node linkType: hard @@ -2959,16 +3134,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/locking@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/locking@npm:2.5.1" +"@medusajs/locking@npm:2.6.1, @medusajs/locking@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/locking@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/d60879a872cb2e6ae456982e3478d4f3e724f6112e6f09e1b403c73a7888d21977a653fbea833441cb9bfc174e29113edf4eb89bbcb494add0fb1d079a2eac6a + checksum: 10c0/7368d54856aa391f257871ad46555b2e7efc907dde0af57693f54b730cc7dbe79219a99952be13428e698b9c86cc51a28d6ac2e70ff7e659d35e9f947bf57553 languageName: node linkType: hard @@ -3152,13 +3327,98 @@ __metadata: languageName: node linkType: hard -"@medusajs/modules-sdk@npm:^2.5.0": - version: 2.5.0 - resolution: "@medusajs/modules-sdk@npm:2.5.0" +"@medusajs/medusa@npm:^2.6.1": + version: 2.6.1 + resolution: "@medusajs/medusa@npm:2.6.1" dependencies: - "@medusajs/orchestration": "npm:^2.5.0" - "@medusajs/types": "npm:^2.5.0" - "@medusajs/utils": "npm:^2.5.0" + "@inquirer/checkbox": "npm:^2.3.11" + "@inquirer/input": "npm:^2.2.9" + "@medusajs/admin-bundler": "npm:2.6.1" + "@medusajs/api-key": "npm:2.6.1" + "@medusajs/auth": "npm:2.6.1" + "@medusajs/auth-emailpass": "npm:2.6.1" + "@medusajs/auth-github": "npm:2.6.1" + "@medusajs/auth-google": "npm:2.6.1" + "@medusajs/cache-inmemory": "npm:2.6.1" + "@medusajs/cache-redis": "npm:2.6.1" + "@medusajs/cart": "npm:2.6.1" + "@medusajs/core-flows": "npm:2.6.1" + "@medusajs/currency": "npm:2.6.1" + "@medusajs/customer": "npm:2.6.1" + "@medusajs/event-bus-local": "npm:2.6.1" + "@medusajs/event-bus-redis": "npm:2.6.1" + "@medusajs/file": "npm:2.6.1" + "@medusajs/file-local": "npm:2.6.1" + "@medusajs/file-s3": "npm:2.6.1" + "@medusajs/fulfillment": "npm:2.6.1" + "@medusajs/fulfillment-manual": "npm:2.6.1" + "@medusajs/index": "npm:2.6.1" + "@medusajs/inventory": "npm:2.6.1" + "@medusajs/link-modules": "npm:2.6.1" + "@medusajs/locking": "npm:2.6.1" + "@medusajs/locking-postgres": "npm:2.6.1" + "@medusajs/locking-redis": "npm:2.6.1" + "@medusajs/notification": "npm:2.6.1" + "@medusajs/notification-local": "npm:2.6.1" + "@medusajs/notification-sendgrid": "npm:2.6.1" + "@medusajs/order": "npm:2.6.1" + "@medusajs/payment": "npm:2.6.1" + "@medusajs/payment-stripe": "npm:2.6.1" + "@medusajs/pricing": "npm:2.6.1" + "@medusajs/product": "npm:2.6.1" + "@medusajs/promotion": "npm:2.6.1" + "@medusajs/region": "npm:2.6.1" + "@medusajs/sales-channel": "npm:2.6.1" + "@medusajs/stock-location": "npm:2.6.1" + "@medusajs/store": "npm:2.6.1" + "@medusajs/tax": "npm:2.6.1" + "@medusajs/telemetry": "npm:2.6.1" + "@medusajs/user": "npm:2.6.1" + "@medusajs/workflow-engine-inmemory": "npm:2.6.1" + "@medusajs/workflow-engine-redis": "npm:2.6.1" + boxen: "npm:^5.0.1" + chalk: "npm:^4.0.0" + chokidar: "npm:^3.4.2" + compression: "npm:^1.7.4" + express: "npm:^4.21.0" + fs-exists-cached: "npm:^1.0.0" + jsonwebtoken: "npm:^9.0.2" + lodash: "npm:^4.17.21" + multer: "npm:^1.4.5-lts.1" + node-schedule: "npm:^2.1.1" + qs: "npm:^6.11.2" + request-ip: "npm:^3.3.0" + slugify: "npm:^1.6.6" + uuid: "npm:^9.0.0" + zod: "npm:3.22.4" + peerDependencies: + "@medusajs/framework": 2.6.1 + "@mikro-orm/core": 6.4.3 + "@mikro-orm/knex": 6.4.3 + "@mikro-orm/migrations": 6.4.3 + "@mikro-orm/postgresql": 6.4.3 + "@swc/core": 1.5.7 + awilix: ^8.0.1 + react-dom: ^18.0.0 + yalc: 1.0.0-pre.53 + peerDependenciesMeta: + "@swc/core": + optional: true + react-dom: + optional: true + yalc: + optional: true + checksum: 10c0/d8095824c23b924ee68e53e22d17ea24dcb9917031c61f5a5f07db482e897bc1d837fa783ae331f47853b225b4f44ae145502992d2c3a0acf6b2bcf054155fb4 + languageName: node + linkType: hard + +"@medusajs/modules-sdk@npm:2.6.1, @medusajs/modules-sdk@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/modules-sdk@npm:2.6.1" + dependencies: + "@medusajs/orchestration": "npm:2.6.1" + "@medusajs/types": "npm:2.6.1" + "@medusajs/utils": "npm:2.6.1" peerDependencies: "@mikro-orm/core": 6.4.3 "@mikro-orm/knex": 6.4.3 @@ -3167,7 +3427,7 @@ __metadata: awilix: ^8.0.1 express: ^4.21.0 pg: ^8.13.0 - checksum: 10c0/34e20d3143f4b7e6e69355d121fa7e68d74e39e4117108efadb7b4400c1ce62963196270a2a0779b26f90a780f156e6aa9f8f1603f0c823d6f705b2806a26918 + checksum: 10c0/9f7831ff3fc0069ead6ed73ec97ee44f36966865a46a6a38202a1846b55b60faceb1770e3761a4df6e6f15c0c5210ce2859700a0555a0921e12688659d6b92cc languageName: node linkType: hard @@ -3190,12 +3450,12 @@ __metadata: languageName: node linkType: hard -"@medusajs/notification-local@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/notification-local@npm:2.5.1" +"@medusajs/notification-local@npm:2.6.1, @medusajs/notification-local@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/notification-local@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 - checksum: 10c0/a67163221e89f3c119e3681380f4adb63d2df4730bd071e509046811eee7e5a3b0fd93cb8cf96801d76c6bf3ba786be31ec7aeb7214d3922cbba8eacd55d2f0d + "@medusajs/framework": 2.6.1 + checksum: 10c0/d596b66d92b4c6bc0d50bf9699c14d8d08cd991ffce78b48c8c85c2e3a72e573ec21520227c401a7447840a3819b387ba8f8f44009b270913686ff10d992da95 languageName: node linkType: hard @@ -3208,14 +3468,14 @@ __metadata: languageName: node linkType: hard -"@medusajs/notification-sendgrid@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/notification-sendgrid@npm:2.5.1" +"@medusajs/notification-sendgrid@npm:2.6.1, @medusajs/notification-sendgrid@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/notification-sendgrid@npm:2.6.1" dependencies: "@sendgrid/mail": "npm:^8.1.3" peerDependencies: - "@medusajs/framework": ^2.4.0 - checksum: 10c0/1cf37bdd8f6982a0095012f6fac901e5028c67097e122ae4186943d7fd4a03b39ed02cee5f73d9e36a1a1daa3f2dfc755f0a69566954ce8d3c5b7dc9abb220a0 + "@medusajs/framework": 2.6.1 + checksum: 10c0/af631416e5cbe2edfe8b8f7b64521b272770e39330101b9625b25fa0ed8ed105565d14fa16491c8f4eaece376cc11f717f87111fb0fbd127994fe2b7162f4933 languageName: node linkType: hard @@ -3230,16 +3490,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/notification@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/notification@npm:2.5.1" +"@medusajs/notification@npm:2.6.1, @medusajs/notification@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/notification@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/a23f0d674929fb2bb71a8f634557adc8e1380d9e726707183010bc478ea8c4d52d5ed09df91fa2a763c0bec26a132c96bb92a52acb7a52e10f3321bef16d48c1 + checksum: 10c0/c7e7402ad06a3af423e8ba9a39d7071daa5662cced67f4ee3e1721f883c7c52fc84fc428d3f4aac6daad9751294f91f17998b4985223c3b84f4e0b3579184eba languageName: node linkType: hard @@ -3256,12 +3516,12 @@ __metadata: languageName: node linkType: hard -"@medusajs/orchestration@npm:^2.5.0": - version: 2.5.0 - resolution: "@medusajs/orchestration@npm:2.5.0" +"@medusajs/orchestration@npm:2.6.1, @medusajs/orchestration@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/orchestration@npm:2.6.1" dependencies: - "@medusajs/types": "npm:^2.5.0" - "@medusajs/utils": "npm:^2.5.0" + "@medusajs/types": "npm:2.6.1" + "@medusajs/utils": "npm:2.6.1" peerDependencies: "@mikro-orm/core": 6.4.3 "@mikro-orm/knex": 6.4.3 @@ -3270,7 +3530,7 @@ __metadata: awilix: ^8.0.1 express: ^4.21.0 pg: ^8.13.0 - checksum: 10c0/e84453544ec170eddf7dcd1e0aadbac83127296b694225e1ea3b9111dd2ae95542adc30c881275bc896ac94a8f48eaaed8f369db3453d33ce108977b88191275 + checksum: 10c0/7d79e20e757dc7589b906cecb8e6a2abf494f3295f249280f99728e56cdbd98e21317beb7621016f0d94b12c38dcce579ada1476f99f5f6b7c2034ba2b2a8e9f languageName: node linkType: hard @@ -3292,16 +3552,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/order@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/order@npm:2.5.1" +"@medusajs/order@npm:2.6.1, @medusajs/order@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/order@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/87e0cc1dc471e8099c210c80dc8cc53ee4f4478f59ef45267189901ff9fcceee8bbc21c09871cfde3aafb7a3d21544b9598b11cc937a793b36086949325490f6 + checksum: 10c0/d97c9c160f6146ab927a7c9318f22b3ae53ef8d6ec71144c17c629cad0b9de017d3fcd2307a33be2b44c8fc7d390d30a973ade38b47fb19116b679be470d13e6 languageName: node linkType: hard @@ -3318,15 +3578,15 @@ __metadata: languageName: node linkType: hard -"@medusajs/payment-stripe@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/payment-stripe@npm:2.5.1" +"@medusajs/payment-stripe@npm:2.6.1, @medusajs/payment-stripe@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/payment-stripe@npm:2.6.1" dependencies: stripe: "npm:^15.5.0" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 awilix: ^8.0.1 - checksum: 10c0/d0c39b59cc4d753d77cbaa878d6a0934eb9c9a75c6976b4b18445d9e8b3f246a994631bd0ae185e598e7c6ed7c1d3603e8b94956a87cc43dc74f128c0f32711d + checksum: 10c0/d87a73c848286ec546a34be5abec63b689ae278e5411805c69874ed6dd116335e317c0e2dbd696b9e7019bf74f688c629af2ad6fb83aa56544d9a2e1d76886c4 languageName: node linkType: hard @@ -3342,16 +3602,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/payment@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/payment@npm:2.5.1" +"@medusajs/payment@npm:2.6.1, @medusajs/payment@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/payment@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/4c7144e474ec586550c932554940dda236c83d722ac9c483b6c806f5917f9d57566269f84fab1c87f99c0d25dca6837406e56f3ec820ff19c8c9e9f168b8a929 + checksum: 10c0/24b23c9719458e20589e55a8c74e2d566592bc63cb2184501f6290d81b0b417f43520817f689618c60d99328cc6f2a6fdcbc49c670064a08d1491309f4c9228c languageName: node linkType: hard @@ -3368,16 +3628,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/pricing@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/pricing@npm:2.5.1" +"@medusajs/pricing@npm:2.6.1, @medusajs/pricing@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/pricing@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/826427039c699d41d37d906586dbb5cee15d97e087ece1c3da7c387b358068bc44b52d5e28539e5d0f009723795c00ab0fe7a9213b9acecf0d172f98448637a4 + checksum: 10c0/0c10daf3f225aa562e54c7f31b34c5e253b0359717a6e84a5d88d0cf4f0c69c0685dc5faa85d62e0db3271ff81871c0e2e35fc90a4e2b4c1f0c5b2dc15b0a56d languageName: node linkType: hard @@ -3394,16 +3654,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/product@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/product@npm:2.5.1" +"@medusajs/product@npm:2.6.1, @medusajs/product@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/product@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/403c1eb7b0e490aa1bd844d81f85160be92961a78efb5cd3d729eb03e543c2b2152f92ab95e6287548ed63d1ddedb2aec8a2ef49ab18f8da05676c7032e7a64e + checksum: 10c0/fc866831654c540678f0190055a8f427c9e8ffceeb997fbf865dbf46f1f986937d3e7730f74328c937a86b556e25471a4fecc43d1801377a08f9bf6b87c1e05b languageName: node linkType: hard @@ -3420,16 +3680,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/promotion@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/promotion@npm:2.5.1" +"@medusajs/promotion@npm:2.6.1, @medusajs/promotion@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/promotion@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/4f003f73c4ea79a62316268b600146b7782f0b6307f11b5220d83c40d05b66c1d1be66fdcd46336256ef24f2bd84df7c5babcb3e7f27675c7172d50e8ff93161 + checksum: 10c0/7035184c6de8b06300955d00afc1f1c6afca2e7fd3bb84ed137219251834a226b25427eb6d1ee392506d92202da31324701e75b3dbf679602a6991ac9605dbfa languageName: node linkType: hard @@ -3446,16 +3706,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/region@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/region@npm:2.5.1" +"@medusajs/region@npm:2.6.1, @medusajs/region@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/region@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/0ea10a66e34a58fa12c7fc481bbe99a3e6b943a786098f210f5817bf5198f643a2379492bd8939a5f275b3f5b2095d4e87f5f1f2adb7836ed88efd7faefd5cc4 + checksum: 10c0/91ae83a75d2d6a7eb57ee0c4ca0c2b045313af710f9e91b56d4c9a066e360e50a75b10bcf75e4fc1ab5646172734a54a1e70a762353b7ba6e28fb74ae2922b17 languageName: node linkType: hard @@ -3472,16 +3732,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/sales-channel@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/sales-channel@npm:2.5.1" +"@medusajs/sales-channel@npm:2.6.1, @medusajs/sales-channel@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/sales-channel@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/a92e3bc079c9d51ce7436046312fb3d7d91fc70b9a54a63d7fca5196ddb86c5c1f738fde52e2081c2acc471b23024cdc1a9cc644866f483a8b78a696664cacf9 + checksum: 10c0/54e2438ab7c27acf40da994fe35291b956427fe5ce76bbc818ca2fe0b3920aa8ebba321c7722413572283cee833698389d02e506e6841203a9d68e6fcb9a7fc8 languageName: node linkType: hard @@ -3498,16 +3758,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/stock-location@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/stock-location@npm:2.5.1" +"@medusajs/stock-location@npm:2.6.1, @medusajs/stock-location@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/stock-location@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/a22fa2fec398c71fd7aef0047ad65aff3761f20f3709ed77987d731e0c54078f8004effd06ba9690c014ef4a0a27619ae7b4ffe8c2ef970a19c8da950253e5bd + checksum: 10c0/af6923d032d749da33d993e974f3d74068e261a98d33e09c9e11456cfa6789ef9b1fe10494cfcd82c72846954d8aa20850b9fcde0ddabd5af3221e3b79091e52 languageName: node linkType: hard @@ -3524,16 +3784,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/store@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/store@npm:2.5.1" +"@medusajs/store@npm:2.6.1, @medusajs/store@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/store@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/a65f5ca518c98cf9bd9a912ef5cfdaaf06e06d63b4c86091dd89c6c723e2b9065fe14fbf64acbb5a1cf3db73285f3d8bcfeb4fc464e21ddcce0d9f5745e4b86f + checksum: 10c0/e2b3f1ad9475ccddb013e634bdc2a944f45413943079e782fe03674c28a173098ebf6fc2e5738983569e9208f6127d9f0f1b05aa10e753648cc98742258816bc languageName: node linkType: hard @@ -3550,16 +3810,16 @@ __metadata: languageName: node linkType: hard -"@medusajs/tax@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/tax@npm:2.5.1" +"@medusajs/tax@npm:2.6.1, @medusajs/tax@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/tax@npm:2.6.1" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/03fd5b99f24e2405a20f2181b3416416bc9bd53ec2492827e3216fcbe65775e30adffd7fc6e0d2250bf91ff0bf0c554ce0444ce3dafeb9e67e4f58d49adce11e + checksum: 10c0/631d921d76ff3b9fa7e8cf4f224defffdd9f4b0c3434a2f83e63aac7072c495c58cc382331c1fff8edfebe66d34578acaba37b04e40cb48842cc4d9249cad7ff languageName: node linkType: hard @@ -3576,9 +3836,9 @@ __metadata: languageName: node linkType: hard -"@medusajs/telemetry@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/telemetry@npm:2.5.1" +"@medusajs/telemetry@npm:2.6.1, @medusajs/telemetry@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/telemetry@npm:2.6.1" dependencies: "@babel/runtime": "npm:^7.22.10" axios: "npm:^0.21.4" @@ -3590,7 +3850,7 @@ __metadata: is-docker: "npm:^2.2.1" remove-trailing-slash: "npm:^0.1.1" uuid: "npm:^8.3.2" - checksum: 10c0/94cbd43b89265415c753fbdd35b3769454e7c7e1478514ce5b59dee631143ba45951fd0dbeecc6f9fc6ca55b30770cd2a42c9f4b806c680deb0b1be6f91f6a81 + checksum: 10c0/37448e2bd9214ca1005929a9738bbaa96a672d5d8db2b4d3aaa602e7dadf89f45fe7fa74ddd37729838f8c9b829099c8f865f1fdd2d5dba0b6db0d9ba4f8a27a languageName: node linkType: hard @@ -3654,39 +3914,60 @@ __metadata: languageName: node linkType: hard -"@medusajs/types@npm:^2.5.0": - version: 2.5.0 - resolution: "@medusajs/types@npm:2.5.0" +"@medusajs/test-utils@npm:^2.6.1": + version: 2.6.1 + resolution: "@medusajs/test-utils@npm:2.6.1" + dependencies: + "@types/express": "npm:^4.17.17" + axios: "npm:^0.21.4" + express: "npm:^4.21.0" + get-port: "npm:^5.1.0" + randomatic: "npm:^3.1.1" + peerDependencies: + "@medusajs/framework": 2.6.1 + "@medusajs/medusa": 2.6.1 + "@mikro-orm/postgresql": 6.4.3 + awilix: ^8.0.1 + peerDependenciesMeta: + "@medusajs/medusa": + optional: true + checksum: 10c0/bf8454d9389fbc354af084248e95b044e571bdfaf04588aed4c3f679e3596fef9123a3cb3c3e9e6979f5809400e0c0f9081c91cea10073dc2070051b1480c77f + languageName: node + linkType: hard + +"@medusajs/types@npm:2.6.1, @medusajs/types@npm:^2.6.1": + version: 2.6.1 + resolution: "@medusajs/types@npm:2.6.1" dependencies: bignumber.js: "npm:^9.1.2" peerDependencies: awilix: ^8.0.1 ioredis: ^5.4.1 - vite: ^5.2.11 + vite: ^5 || ^6 peerDependenciesMeta: ioredis: optional: true vite: optional: true - checksum: 10c0/0f37fb37bbb216f98aaf6af1abea0bd7ee41754bd89859eba4b00c119dd3fd3b5c9691dacf68ea4cfea1ef3803eaa72445d1bb19d5609ab82fd0c481ec296ba4 + checksum: 10c0/356819ba354cc3538779848601c6d09d7c3c171c5516098a060c63256dc28b55e23f48a4afa6c64fc61fcd6b2d6e8f8d4307e86990665b978bcb1fa16d7ea3d3 languageName: node linkType: hard -"@medusajs/types@npm:^2.5.1": - version: 2.5.1 - resolution: "@medusajs/types@npm:2.5.1" +"@medusajs/types@npm:^2.5.0": + version: 2.5.0 + resolution: "@medusajs/types@npm:2.5.0" dependencies: bignumber.js: "npm:^9.1.2" peerDependencies: awilix: ^8.0.1 ioredis: ^5.4.1 - vite: ^5.4.14 + vite: ^5.2.11 peerDependenciesMeta: ioredis: optional: true vite: optional: true - checksum: 10c0/98315c2aca0a3d3e12e9a3bc86a56eea58dcb5b8a1a8e7cd035a0688e8cb02881ddcf76e38dc83115f4eac96bce0e229bed542ce2c001a7f6c2ef8b501702646 + checksum: 10c0/0f37fb37bbb216f98aaf6af1abea0bd7ee41754bd89859eba4b00c119dd3fd3b5c9691dacf68ea4cfea1ef3803eaa72445d1bb19d5609ab82fd0c481ec296ba4 languageName: node linkType: hard @@ -3746,11 +4027,11 @@ __metadata: languageName: node linkType: hard -"@medusajs/ui@npm:^4.0.6, @medusajs/ui@npm:~4.0.6": - version: 4.0.6 - resolution: "@medusajs/ui@npm:4.0.6" +"@medusajs/ui@npm:4.0.7, @medusajs/ui@npm:^4.0.6, @medusajs/ui@npm:^4.0.7": + version: 4.0.7 + resolution: "@medusajs/ui@npm:4.0.7" dependencies: - "@medusajs/icons": "npm:^2.5.1" + "@medusajs/icons": "npm:2.6.1" "@tanstack/react-table": "npm:8.20.5" clsx: "npm:^1.2.1" copy-to-clipboard: "npm:^3.3.3" @@ -3766,22 +4047,22 @@ __metadata: peerDependencies: react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc - checksum: 10c0/ff45b909b3573fd6b443eee0aac47a2f13dbd145ed161e0485f7e22e1e963345e2e38dba236d1b927715feaa79d3dc79671bdeb2075c6f83682dcf8827e6f962 + checksum: 10c0/c320d7b610a50ca7483584cc84865ea7120cb74588173c747ad3ee27aefe575eddf3db2a1d558e791b66ffadc962c8fdfe39fe9fdc71f162e88e057bad480117 languageName: node linkType: hard -"@medusajs/user@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/user@npm:2.5.1" +"@medusajs/user@npm:2.6.1, @medusajs/user@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/user@npm:2.6.1" dependencies: jsonwebtoken: "npm:^9.0.2" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/2167f315cdfce7c58869e2299e9816a6172bf556ff28ad708ec118b413e55c56a40e554fe03db3394f05dffab1ea4c9d7dd96432d02d50101767b211ca797160 + checksum: 10c0/8bf924caec453ccc7c99554465319259b07dd1ca656790740581f655b2c9bdc2e99095f5dc4bc2654110ff275c95db565bfc908b2a0f2037ec96d8e9e6258b40 languageName: node linkType: hard @@ -3830,7 +4111,7 @@ __metadata: languageName: node linkType: hard -"@medusajs/utils@npm:2.5.0, @medusajs/utils@npm:^2.5.0": +"@medusajs/utils@npm:2.5.0": version: 2.5.0 resolution: "@medusajs/utils@npm:2.5.0" dependencies: @@ -3860,19 +4141,49 @@ __metadata: languageName: node linkType: hard -"@medusajs/workflow-engine-inmemory@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/workflow-engine-inmemory@npm:2.5.1" +"@medusajs/utils@npm:2.6.1, @medusajs/utils@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/utils@npm:2.6.1" + dependencies: + "@graphql-codegen/core": "npm:^4.0.2" + "@graphql-codegen/typescript": "npm:^4.0.9" + "@graphql-tools/merge": "npm:^9.0.7" + "@graphql-tools/schema": "npm:^10.0.6" + "@medusajs/types": "npm:2.6.1" + "@types/pluralize": "npm:^0.0.33" + bignumber.js: "npm:^9.1.2" + dotenv: "npm:^16.4.5" + dotenv-expand: "npm:^11.0.6" + graphql: "npm:^16.9.0" + jsonwebtoken: "npm:^9.0.2" + pg-connection-string: "npm:^2.7.0" + pluralize: "npm:^8.0.0" + ulid: "npm:^2.3.0" + peerDependencies: + "@mikro-orm/core": 6.4.3 + "@mikro-orm/knex": 6.4.3 + "@mikro-orm/migrations": 6.4.3 + "@mikro-orm/postgresql": 6.4.3 + awilix: ^8.0.1 + express: ^4.21.0 + pg: ^8.13.0 + checksum: 10c0/d423a396a3a1a821e94853a0afad9e6662c66542aa41a6021dae20f0c5bb3e53f2c68252e13d970b6da3d00589cf483f6146ee52662fecc888e0dd1a8b71f2a2 + languageName: node + linkType: hard + +"@medusajs/workflow-engine-inmemory@npm:2.6.1, @medusajs/workflow-engine-inmemory@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/workflow-engine-inmemory@npm:2.6.1" dependencies: cron-parser: "npm:^4.9.0" ulid: "npm:^2.3.0" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/d09e6a912385bbac77bbf0ec968acf171a9115c6db0486d33d1478d0d4e6984bc2c836afaaaa2f90702968bc0c0f60c9664afe718554c3f092946fcadd9f72a1 + checksum: 10c0/011029f9f2e78eda39befd3907f25a098296a7bad7510a8cf75aac268642556d35d8daf12b5e15933b0df4aa1414b2e648da9a380bef95a9f1f9d86fb0fab749 languageName: node linkType: hard @@ -3892,20 +4203,20 @@ __metadata: languageName: node linkType: hard -"@medusajs/workflow-engine-redis@npm:^2.5.0": - version: 2.5.1 - resolution: "@medusajs/workflow-engine-redis@npm:2.5.1" +"@medusajs/workflow-engine-redis@npm:2.6.1, @medusajs/workflow-engine-redis@npm:^2.5.0": + version: 2.6.1 + resolution: "@medusajs/workflow-engine-redis@npm:2.6.1" dependencies: bullmq: "npm:5.13.0" ioredis: "npm:^5.4.1" ulid: "npm:^2.3.0" peerDependencies: - "@medusajs/framework": ^2.4.0 + "@medusajs/framework": 2.6.1 "@mikro-orm/core": 6.4.3 "@mikro-orm/migrations": 6.4.3 "@mikro-orm/postgresql": 6.4.3 awilix: ^8.0.1 - checksum: 10c0/cf74adf11f811d2999b6d7024d0e39c39bc8413c1e87c476958e4c6781b1259d93fda8fd23a6beafcb1034fecbd6e549d46777eb550d4c3fd81381fa0006c0eb + checksum: 10c0/79be56cd50d7a3c2e6934f35a3f31cf244d4d126f8994354f29ec509a4b5a65fb607b929de8cd03d1e0f7c0a264f3441ed5b333d5ff51fa848e8b0c12ac2effe languageName: node linkType: hard @@ -3926,14 +4237,14 @@ __metadata: languageName: node linkType: hard -"@medusajs/workflows-sdk@npm:^2.5.0": - version: 2.5.0 - resolution: "@medusajs/workflows-sdk@npm:2.5.0" +"@medusajs/workflows-sdk@npm:2.6.1, @medusajs/workflows-sdk@npm:^2.5.0, @medusajs/workflows-sdk@npm:^2.6.1": + version: 2.6.1 + resolution: "@medusajs/workflows-sdk@npm:2.6.1" dependencies: - "@medusajs/modules-sdk": "npm:^2.5.0" - "@medusajs/orchestration": "npm:^2.5.0" - "@medusajs/types": "npm:^2.5.0" - "@medusajs/utils": "npm:^2.5.0" + "@medusajs/modules-sdk": "npm:2.6.1" + "@medusajs/orchestration": "npm:2.6.1" + "@medusajs/types": "npm:2.6.1" + "@medusajs/utils": "npm:2.6.1" ulid: "npm:^2.3.0" peerDependencies: "@mikro-orm/core": 6.4.3 @@ -3943,7 +4254,7 @@ __metadata: awilix: ^8.0.1 express: ^4.21.0 pg: ^8.13.0 - checksum: 10c0/d77747e99425e0c886579a994e783d4a7cc010fce45a3190ed780d10bd1dfb7aa0be6240ecbce07e0e9fd38a3fd5c26dc3952161b747b9c6de699960273be883 + checksum: 10c0/8588af3e36cc8eb69e4d7fcb546cdaa5a80b8b73e4750f3d141ba03300e4e1dd9907ee171a9d7a551e478df143e0a4aced8eb7cfeb3e1ec56ba3aa13ae4a66a2 languageName: node linkType: hard @@ -13819,7 +14130,7 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:0.1.12": +"path-to-regexp@npm:0.1.12, path-to-regexp@npm:^0.1.10": version: 0.1.12 resolution: "path-to-regexp@npm:0.1.12" checksum: 10c0/1c6ff10ca169b773f3bba943bbc6a07182e332464704572962d277b900aeee81ac6aa5d060ff9e01149636c30b1f63af6e69dd7786ba6e0ddb39d4dee1f0645b @@ -16109,7 +16420,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.6.2, typescript@npm:^5.7.2, typescript@npm:^5.7.3": +"typescript@npm:5.7.3, typescript@npm:^5.6.2, typescript@npm:^5.7.2, typescript@npm:^5.7.3": version: 5.7.3 resolution: "typescript@npm:5.7.3" bin: @@ -16119,7 +16430,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.6.2#optional!builtin, typescript@patch:typescript@npm%3A^5.7.2#optional!builtin, typescript@patch:typescript@npm%3A^5.7.3#optional!builtin": +"typescript@patch:typescript@npm%3A5.7.3#optional!builtin, typescript@patch:typescript@npm%3A^5.6.2#optional!builtin, typescript@patch:typescript@npm%3A^5.7.2#optional!builtin, typescript@patch:typescript@npm%3A^5.7.3#optional!builtin": version: 5.7.3 resolution: "typescript@patch:typescript@npm%3A5.7.3#optional!builtin::version=5.7.3&hash=5786d5" bin: