@lodashventure/medusa-brand 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +95 -0
  2. package/dist/admin/components/brand-form.d.ts +19 -0
  3. package/dist/admin/components/brand-form.js +182 -0
  4. package/dist/admin/components/brand-image-uploader.d.ts +14 -0
  5. package/dist/admin/components/brand-image-uploader.js +217 -0
  6. package/dist/admin/lib/sdk.d.ts +1 -0
  7. package/dist/admin/lib/sdk.js +14 -0
  8. package/dist/admin/routes/brands/page.d.ts +4 -0
  9. package/dist/admin/routes/brands/page.js +253 -0
  10. package/dist/admin/widgets/product-brand-widget.d.ts +8 -0
  11. package/dist/admin/widgets/product-brand-widget.js +207 -0
  12. package/dist/api/admin/brands/[id]/image/route.d.ts +5 -0
  13. package/dist/api/admin/brands/[id]/image/route.js +118 -0
  14. package/dist/api/admin/brands/[id]/logo/route.d.ts +5 -0
  15. package/dist/api/admin/brands/[id]/logo/route.js +118 -0
  16. package/dist/api/admin/brands/[id]/products/route.d.ts +2 -0
  17. package/dist/api/admin/brands/[id]/products/route.js +51 -0
  18. package/dist/api/admin/brands/[id]/route.d.ts +5 -0
  19. package/dist/api/admin/brands/[id]/route.js +111 -0
  20. package/dist/api/admin/brands/route.d.ts +4 -0
  21. package/dist/api/admin/brands/route.js +75 -0
  22. package/dist/api/admin/products/[id]/brand/route.d.ts +5 -0
  23. package/dist/api/admin/products/[id]/brand/route.js +116 -0
  24. package/dist/api/middlewares/attach-brand-to-products.d.ts +2 -0
  25. package/dist/api/middlewares/attach-brand-to-products.js +104 -0
  26. package/dist/api/middlewares.d.ts +6 -0
  27. package/dist/api/middlewares.js +26 -0
  28. package/dist/api/store/brands/route.d.ts +2 -0
  29. package/dist/api/store/brands/route.js +50 -0
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.js +6 -0
  32. package/dist/modules/brand/index.d.ts +35 -0
  33. package/dist/modules/brand/index.js +12 -0
  34. package/dist/modules/brand/migrations/Migration20251021070648.d.ts +5 -0
  35. package/dist/modules/brand/migrations/Migration20251021070648.js +27 -0
  36. package/dist/modules/brand/models/brand.d.ts +16 -0
  37. package/dist/modules/brand/models/brand.js +42 -0
  38. package/dist/modules/brand/service.d.ts +21 -0
  39. package/dist/modules/brand/service.js +10 -0
  40. package/dist/services/gcs-direct-upload.d.ts +8 -0
  41. package/dist/services/gcs-direct-upload.js +54 -0
  42. package/dist/workflows/upload-brand-image.d.ts +15 -0
  43. package/dist/workflows/upload-brand-image.js +56 -0
  44. package/package.json +58 -0
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GET = void 0;
4
+ const utils_1 = require("@medusajs/framework/utils");
5
+ // GET - List all active brands for store
6
+ const GET = async (req, res) => {
7
+ const brandService = req.scope.resolve("brandCustom");
8
+ const logger = req.scope.resolve(utils_1.ContainerRegistrationKeys.LOGGER);
9
+ try {
10
+ const { q, limit = 20, offset = 0, order = "name", } = req.query;
11
+ const filters = {
12
+ is_active: true, // Only show active brands in store
13
+ };
14
+ if (q) {
15
+ filters.$or = [
16
+ { name: { $ilike: `%${q}%` } },
17
+ { description: { $ilike: `%${q}%` } },
18
+ ];
19
+ }
20
+ const [brands, count] = await Promise.all([
21
+ brandService.listBrands(filters, {
22
+ limit: Number(limit),
23
+ offset: Number(offset),
24
+ order,
25
+ select: [
26
+ "id",
27
+ "name",
28
+ "slug",
29
+ "description",
30
+ "image",
31
+ "logo",
32
+ "website",
33
+ "metadata"
34
+ ],
35
+ }),
36
+ brandService.listBrands(filters).then((result) => result.length)
37
+ ]);
38
+ return res.json({
39
+ brands,
40
+ count,
41
+ offset: Number(offset),
42
+ limit: Number(limit),
43
+ });
44
+ }
45
+ catch (error) {
46
+ logger.error("Error listing brands for store:", error);
47
+ return res.status(500).json({ error: "Failed to list brands" });
48
+ }
49
+ };
50
+ exports.GET = GET;
@@ -0,0 +1 @@
1
+ export { BRAND_MODULE } from "./modules/brand";
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BRAND_MODULE = void 0;
4
+ // Export the module name for use in medusa-config
5
+ var brand_1 = require("./modules/brand");
6
+ Object.defineProperty(exports, "BRAND_MODULE", { enumerable: true, get: function () { return brand_1.BRAND_MODULE; } });
@@ -0,0 +1,35 @@
1
+ import BrandModuleService from "./service";
2
+ export declare const BRAND_MODULE = "brandCustom";
3
+ declare const _default: import("@medusajs/types").ModuleExports<typeof BrandModuleService> & {
4
+ linkable: {
5
+ readonly brand: {
6
+ id: {
7
+ serviceName: "brandCustom";
8
+ field: "brand";
9
+ linkable: "brand_id";
10
+ primaryKey: "id";
11
+ };
12
+ toJSON: () => {
13
+ serviceName: "brandCustom";
14
+ field: "brand";
15
+ linkable: "brand_id";
16
+ primaryKey: "id";
17
+ };
18
+ };
19
+ readonly productBrand: {
20
+ id: {
21
+ serviceName: "brandCustom";
22
+ field: "productBrand";
23
+ linkable: "product_brand_id";
24
+ primaryKey: "id";
25
+ };
26
+ toJSON: () => {
27
+ serviceName: "brandCustom";
28
+ field: "productBrand";
29
+ linkable: "product_brand_id";
30
+ primaryKey: "id";
31
+ };
32
+ };
33
+ };
34
+ };
35
+ export default _default;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.BRAND_MODULE = void 0;
7
+ const utils_1 = require("@medusajs/framework/utils");
8
+ const service_1 = __importDefault(require("./service"));
9
+ exports.BRAND_MODULE = "brandCustom";
10
+ exports.default = (0, utils_1.Module)(exports.BRAND_MODULE, {
11
+ service: service_1.default,
12
+ });
@@ -0,0 +1,5 @@
1
+ import { Migration } from "@medusajs/framework/mikro-orm/migrations";
2
+ export declare class Migration20251021070648 extends Migration {
3
+ up(): Promise<void>;
4
+ down(): Promise<void>;
5
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Migration20251021070648 = void 0;
4
+ const migrations_1 = require("@medusajs/framework/mikro-orm/migrations");
5
+ class Migration20251021070648 extends migrations_1.Migration {
6
+ async up() {
7
+ this.addSql(`alter table if exists "product_brand" drop constraint if exists "product_brand_product_id_brand_id_unique";`);
8
+ this.addSql(`alter table if exists "brand" drop constraint if exists "brand_slug_unique";`);
9
+ this.addSql(`alter table if exists "brand" drop constraint if exists "brand_name_unique";`);
10
+ this.addSql(`create table if not exists "brand" ("id" text not null, "name" text not null, "slug" text not null, "description" text null, "image" text null, "logo" text null, "website" text null, "is_active" boolean not null default true, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "brand_pkey" primary key ("id"));`);
11
+ this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_brand_name_unique" ON "brand" (name) WHERE deleted_at IS NULL;`);
12
+ this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_brand_slug_unique" ON "brand" (slug) WHERE deleted_at IS NULL;`);
13
+ this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_brand_deleted_at" ON "brand" (deleted_at) WHERE deleted_at IS NULL;`);
14
+ this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_brand_slug" ON "brand" (slug) WHERE deleted_at IS NULL;`);
15
+ this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_brand_is_active" ON "brand" (is_active) WHERE deleted_at IS NULL;`);
16
+ this.addSql(`create table if not exists "product_brand" ("id" text not null, "product_id" text not null, "brand_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "product_brand_pkey" primary key ("id"));`);
17
+ this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_product_brand_deleted_at" ON "product_brand" (deleted_at) WHERE deleted_at IS NULL;`);
18
+ this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_product_brand_product_id" ON "product_brand" (product_id) WHERE deleted_at IS NULL;`);
19
+ this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_product_brand_brand_id" ON "product_brand" (brand_id) WHERE deleted_at IS NULL;`);
20
+ this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_product_brand_product_id_brand_id_unique" ON "product_brand" (product_id, brand_id) WHERE deleted_at IS NULL;`);
21
+ }
22
+ async down() {
23
+ this.addSql(`drop table if exists "brand" cascade;`);
24
+ this.addSql(`drop table if exists "product_brand" cascade;`);
25
+ }
26
+ }
27
+ exports.Migration20251021070648 = Migration20251021070648;
@@ -0,0 +1,16 @@
1
+ export declare const Brand: import("@medusajs/framework/utils").DmlEntity<import("@medusajs/framework/utils").DMLEntitySchemaBuilder<{
2
+ id: import("@medusajs/framework/utils").PrimaryKeyModifier<string, import("@medusajs/framework/utils").IdProperty>;
3
+ name: import("@medusajs/framework/utils").TextProperty;
4
+ slug: import("@medusajs/framework/utils").TextProperty;
5
+ description: import("@medusajs/framework/utils").NullableModifier<string, import("@medusajs/framework/utils").TextProperty>;
6
+ image: import("@medusajs/framework/utils").NullableModifier<string, import("@medusajs/framework/utils").TextProperty>;
7
+ logo: import("@medusajs/framework/utils").NullableModifier<string, import("@medusajs/framework/utils").TextProperty>;
8
+ website: import("@medusajs/framework/utils").NullableModifier<string, import("@medusajs/framework/utils").TextProperty>;
9
+ is_active: import("@medusajs/framework/utils").BooleanProperty;
10
+ metadata: import("@medusajs/framework/utils").NullableModifier<Record<string, unknown>, import("@medusajs/framework/utils").JSONProperty>;
11
+ }>, "brand">;
12
+ export declare const ProductBrand: import("@medusajs/framework/utils").DmlEntity<import("@medusajs/framework/utils").DMLEntitySchemaBuilder<{
13
+ id: import("@medusajs/framework/utils").PrimaryKeyModifier<string, import("@medusajs/framework/utils").IdProperty>;
14
+ product_id: import("@medusajs/framework/utils").TextProperty;
15
+ brand_id: import("@medusajs/framework/utils").TextProperty;
16
+ }>, "product_brand">;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProductBrand = exports.Brand = void 0;
4
+ const utils_1 = require("@medusajs/framework/utils");
5
+ exports.Brand = utils_1.model
6
+ .define("brand", {
7
+ id: utils_1.model.id().primaryKey(),
8
+ name: utils_1.model.text().unique(),
9
+ slug: utils_1.model.text().unique(),
10
+ description: utils_1.model.text().nullable(),
11
+ image: utils_1.model.text().nullable(),
12
+ logo: utils_1.model.text().nullable(),
13
+ website: utils_1.model.text().nullable(),
14
+ is_active: utils_1.model.boolean().default(true),
15
+ metadata: utils_1.model.json().nullable(),
16
+ })
17
+ .indexes([
18
+ {
19
+ on: ["slug"],
20
+ },
21
+ {
22
+ on: ["is_active"],
23
+ },
24
+ ]);
25
+ exports.ProductBrand = utils_1.model
26
+ .define("product_brand", {
27
+ id: utils_1.model.id().primaryKey(),
28
+ product_id: utils_1.model.text(),
29
+ brand_id: utils_1.model.text(),
30
+ })
31
+ .indexes([
32
+ {
33
+ on: ["product_id"],
34
+ },
35
+ {
36
+ on: ["brand_id"],
37
+ },
38
+ {
39
+ on: ["product_id", "brand_id"],
40
+ unique: true,
41
+ },
42
+ ]);
@@ -0,0 +1,21 @@
1
+ declare const BrandModuleService_base: import("@medusajs/framework/utils").MedusaServiceReturnType<import("@medusajs/framework/utils").ModelConfigurationsToConfigTemplate<{
2
+ readonly Brand: import("@medusajs/framework/utils").DmlEntity<import("@medusajs/framework/utils").DMLEntitySchemaBuilder<{
3
+ id: import("@medusajs/framework/utils").PrimaryKeyModifier<string, import("@medusajs/framework/utils").IdProperty>;
4
+ name: import("@medusajs/framework/utils").TextProperty;
5
+ slug: import("@medusajs/framework/utils").TextProperty;
6
+ description: import("@medusajs/framework/utils").NullableModifier<string, import("@medusajs/framework/utils").TextProperty>;
7
+ image: import("@medusajs/framework/utils").NullableModifier<string, import("@medusajs/framework/utils").TextProperty>;
8
+ logo: import("@medusajs/framework/utils").NullableModifier<string, import("@medusajs/framework/utils").TextProperty>;
9
+ website: import("@medusajs/framework/utils").NullableModifier<string, import("@medusajs/framework/utils").TextProperty>;
10
+ is_active: import("@medusajs/framework/utils").BooleanProperty;
11
+ metadata: import("@medusajs/framework/utils").NullableModifier<Record<string, unknown>, import("@medusajs/framework/utils").JSONProperty>;
12
+ }>, "brand">;
13
+ readonly ProductBrand: import("@medusajs/framework/utils").DmlEntity<import("@medusajs/framework/utils").DMLEntitySchemaBuilder<{
14
+ id: import("@medusajs/framework/utils").PrimaryKeyModifier<string, import("@medusajs/framework/utils").IdProperty>;
15
+ product_id: import("@medusajs/framework/utils").TextProperty;
16
+ brand_id: import("@medusajs/framework/utils").TextProperty;
17
+ }>, "product_brand">;
18
+ }>>;
19
+ export default class BrandModuleService extends BrandModuleService_base {
20
+ }
21
+ export {};
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("@medusajs/framework/utils");
4
+ const brand_1 = require("./models/brand");
5
+ class BrandModuleService extends (0, utils_1.MedusaService)({
6
+ Brand: brand_1.Brand,
7
+ ProductBrand: brand_1.ProductBrand,
8
+ }) {
9
+ }
10
+ exports.default = BrandModuleService;
@@ -0,0 +1,8 @@
1
+ export declare class GcsDirectUploadService {
2
+ private storage;
3
+ private bucketName;
4
+ private baseUrl;
5
+ constructor();
6
+ uploadFile(filename: string, buffer: Buffer, mimeType: string): Promise<string>;
7
+ deleteFile(filename: string): Promise<void>;
8
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GcsDirectUploadService = void 0;
4
+ const storage_1 = require("@google-cloud/storage");
5
+ class GcsDirectUploadService {
6
+ storage;
7
+ bucketName;
8
+ baseUrl;
9
+ constructor() {
10
+ // Initialize GCS client with credentials from env
11
+ this.storage = new storage_1.Storage({
12
+ credentials: {
13
+ client_email: process.env.CLIENT_EMAIL,
14
+ private_key: process.env.PRIVATE_KEY.replace(/\\n/g, '\n'),
15
+ },
16
+ });
17
+ this.bucketName = process.env.BUCKET_NAME || 'sangaroon';
18
+ this.baseUrl = process.env.GCP_STORAGE_BASE_PUBLIC_URL || 'https://storage.googleapis.com';
19
+ }
20
+ async uploadFile(filename, buffer, mimeType) {
21
+ try {
22
+ const bucket = this.storage.bucket(this.bucketName);
23
+ const file = bucket.file(filename);
24
+ await file.save(buffer, {
25
+ metadata: {
26
+ contentType: mimeType,
27
+ },
28
+ public: true,
29
+ resumable: false,
30
+ });
31
+ // Return the public URL
32
+ return `${this.baseUrl}/${this.bucketName}/${filename}`;
33
+ }
34
+ catch (error) {
35
+ console.error('Error uploading to GCS:', error);
36
+ throw new Error(`Failed to upload file to GCS: ${error.message}`);
37
+ }
38
+ }
39
+ async deleteFile(filename) {
40
+ try {
41
+ const bucket = this.storage.bucket(this.bucketName);
42
+ const file = bucket.file(filename);
43
+ const [exists] = await file.exists();
44
+ if (exists) {
45
+ await file.delete();
46
+ }
47
+ }
48
+ catch (error) {
49
+ console.error('Error deleting from GCS:', error);
50
+ // Don't throw on delete errors
51
+ }
52
+ }
53
+ }
54
+ exports.GcsDirectUploadService = GcsDirectUploadService;
@@ -0,0 +1,15 @@
1
+ interface UploadBrandImageInput {
2
+ brandId: string;
3
+ imageType: "image" | "logo";
4
+ fileData: {
5
+ filename: string;
6
+ mimeType: string;
7
+ content: string;
8
+ };
9
+ }
10
+ export declare const uploadBrandImageWorkflow: import("@medusajs/framework/workflows-sdk").ReturnWorkflow<UploadBrandImageInput, {
11
+ brandId: string;
12
+ imageType: "image" | "logo";
13
+ imageUrl: string;
14
+ }, []>;
15
+ export {};
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.uploadBrandImageWorkflow = void 0;
4
+ const workflows_sdk_1 = require("@medusajs/framework/workflows-sdk");
5
+ const gcs_direct_upload_1 = require("../services/gcs-direct-upload");
6
+ const uploadBrandImageStep = (0, workflows_sdk_1.createStep)("upload-brand-image-step", async ({ brandId, imageType, fileData }, { container }) => {
7
+ const brandService = container.resolve("brandCustom");
8
+ const gcsUploadService = new gcs_direct_upload_1.GcsDirectUploadService();
9
+ // Generate unique filename for direct upload
10
+ const timestamp = Date.now();
11
+ const ext = fileData.filename.split('.').pop() || 'jpg';
12
+ const filename = `brands/${imageType}-${brandId}-${timestamp}.${ext}`;
13
+ // Convert base64 to buffer
14
+ const buffer = Buffer.from(fileData.content, "base64");
15
+ // Upload directly to GCS
16
+ const publicUrl = await gcsUploadService.uploadFile(filename, buffer, fileData.mimeType);
17
+ // Retrieve existing brand
18
+ const brand = await brandService.retrieveBrand(brandId);
19
+ if (!brand) {
20
+ throw new Error(`Brand with id ${brandId} not found`);
21
+ }
22
+ // Delete old image if it exists
23
+ const oldImageUrl = brand[imageType];
24
+ if (oldImageUrl) {
25
+ try {
26
+ if (oldImageUrl.includes('storage.googleapis.com')) {
27
+ const parts = oldImageUrl.split('/');
28
+ const bucketIndex = parts.indexOf('sangaroon');
29
+ if (bucketIndex !== -1 && bucketIndex < parts.length - 1) {
30
+ const oldFilename = parts.slice(bucketIndex + 1).join('/');
31
+ await gcsUploadService.deleteFile(oldFilename);
32
+ }
33
+ }
34
+ }
35
+ catch (error) {
36
+ console.error(`Failed to delete old ${imageType}:`, error);
37
+ // Continue even if deletion fails
38
+ }
39
+ }
40
+ // Update brand with new image URL
41
+ const updateData = {};
42
+ updateData[imageType] = publicUrl;
43
+ await brandService.updateBrands([{
44
+ id: brandId,
45
+ ...updateData
46
+ }]);
47
+ return new workflows_sdk_1.StepResponse({
48
+ brandId,
49
+ imageType,
50
+ imageUrl: publicUrl,
51
+ });
52
+ });
53
+ exports.uploadBrandImageWorkflow = (0, workflows_sdk_1.createWorkflow)("upload-brand-image", (input) => {
54
+ const result = uploadBrandImageStep(input);
55
+ return new workflows_sdk_1.WorkflowResponse(result);
56
+ });
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@lodashventure/medusa-brand",
3
+ "version": "1.1.0",
4
+ "description": "Brand management plugin for Medusa v2",
5
+ "author": "Lodashventure",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "medusa",
9
+ "medusa-plugin",
10
+ "brand",
11
+ "ecommerce"
12
+ ],
13
+ "main": "dist/index.js",
14
+ "exports": {
15
+ ".": "./dist/index.js",
16
+ "./modules/brand": "./dist/modules/brand/index.js",
17
+ "./package.json": "./package.json"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/lodashventure/medusa-brand"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "admin"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsc --build",
29
+ "watch": "tsc --watch",
30
+ "dev": "tsc --watch"
31
+ },
32
+ "dependencies": {
33
+ "@google-cloud/storage": "^7.17.2",
34
+ "@medusajs/admin-sdk": "*",
35
+ "@medusajs/framework": "*",
36
+ "@medusajs/icons": "*",
37
+ "@medusajs/ui": "*"
38
+ },
39
+ "peerDependencies": {
40
+ "@medusajs/admin-sdk": "*",
41
+ "@medusajs/framework": "*",
42
+ "@medusajs/icons": "*",
43
+ "@medusajs/medusa": "*",
44
+ "@medusajs/ui": "*",
45
+ "react": "^18.0.0",
46
+ "react-dom": "^18.0.0"
47
+ },
48
+ "devDependencies": {
49
+ "@medusajs/types": "*",
50
+ "@types/multer": "^2.0.0",
51
+ "@types/node": "^20.19.23",
52
+ "@types/react": "^18.3.2",
53
+ "@types/react-dom": "^18.2.25",
54
+ "ts-node": "^10.9.2",
55
+ "typescript": "^5.6.2",
56
+ "yalc": "1.0.0-pre.53"
57
+ }
58
+ }