@rahul_vendure/vendure-plugin-wishlist 1.0.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.
- package/README.md +168 -0
- package/dist/api/api-extensions.d.ts +1 -0
- package/dist/api/api-extensions.js +47 -0
- package/dist/api/product-variant-entity.resolver.d.ts +8 -0
- package/dist/api/product-variant-entity.resolver.js +50 -0
- package/dist/api/wishlist.resolver.d.ts +20 -0
- package/dist/api/wishlist.resolver.js +106 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +5 -0
- package/dist/entities/wishlist-item.entity.d.ts +8 -0
- package/dist/entities/wishlist-item.entity.js +43 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +9 -0
- package/dist/services/wishlist.service.d.ts +16 -0
- package/dist/services/wishlist.service.js +143 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.js +2 -0
- package/dist/wishlist.plugin.d.ts +6 -0
- package/dist/wishlist.plugin.js +39 -0
- package/package.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# @rahul_vendure/vendure-plugin-wishlist
|
|
2
|
+
|
|
3
|
+
A Vendure plugin that adds wishlist functionality — customers can save product variants to their wishlist with single and bulk add/remove operations.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Add/remove single items** to wishlist
|
|
8
|
+
- **Bulk add/remove** multiple items in one mutation
|
|
9
|
+
- **Idempotent** — adding an already-wishlisted item returns the existing entry
|
|
10
|
+
- **`isInWishlist` field** on `ProductVariant` — easy to check in storefront queries
|
|
11
|
+
- **Paginated list** — uses Vendure's built-in `ListQueryBuilder` for filtering/sorting/pagination
|
|
12
|
+
- **Unique constraint** — a customer can only wishlist a variant once
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @rahul_vendure/vendure-plugin-wishlist
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { WishlistPlugin } from '@rahul_vendure/vendure-plugin-wishlist';
|
|
24
|
+
|
|
25
|
+
const config: VendureConfig = {
|
|
26
|
+
plugins: [
|
|
27
|
+
WishlistPlugin.init(),
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
> **Important:** This plugin creates a `wishlist_item` table. If your project has
|
|
33
|
+
> `synchronize: false`, you need to generate and run a database migration after
|
|
34
|
+
> adding this plugin:
|
|
35
|
+
>
|
|
36
|
+
> ```bash
|
|
37
|
+
> npx vendure migrate
|
|
38
|
+
> ```
|
|
39
|
+
|
|
40
|
+
## GraphQL API
|
|
41
|
+
|
|
42
|
+
All operations are on the **Shop API** and require authentication (`Permission.Owner`).
|
|
43
|
+
|
|
44
|
+
### Queries
|
|
45
|
+
|
|
46
|
+
#### `wishlists`
|
|
47
|
+
|
|
48
|
+
Get the current customer's wishlist with pagination.
|
|
49
|
+
|
|
50
|
+
```graphql
|
|
51
|
+
query {
|
|
52
|
+
wishlists(options: { take: 10, skip: 0 }) {
|
|
53
|
+
items {
|
|
54
|
+
id
|
|
55
|
+
createdAt
|
|
56
|
+
productVariant {
|
|
57
|
+
id
|
|
58
|
+
name
|
|
59
|
+
priceWithTax
|
|
60
|
+
featuredAsset {
|
|
61
|
+
preview
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
totalItems
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Mutations
|
|
71
|
+
|
|
72
|
+
#### `addToWishlist`
|
|
73
|
+
|
|
74
|
+
Add a single product variant. Returns the existing item if already wishlisted.
|
|
75
|
+
|
|
76
|
+
```graphql
|
|
77
|
+
mutation {
|
|
78
|
+
addToWishlist(productVariantId: "42") {
|
|
79
|
+
id
|
|
80
|
+
productVariant {
|
|
81
|
+
id
|
|
82
|
+
name
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### `removeFromWishlist`
|
|
89
|
+
|
|
90
|
+
Remove by wishlist item ID or product variant ID (at least one required).
|
|
91
|
+
|
|
92
|
+
```graphql
|
|
93
|
+
mutation {
|
|
94
|
+
removeFromWishlist(productVariantId: "42") {
|
|
95
|
+
result
|
|
96
|
+
message
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### `addMultipleToWishlist`
|
|
102
|
+
|
|
103
|
+
Bulk add multiple variants at once. Skips duplicates.
|
|
104
|
+
|
|
105
|
+
```graphql
|
|
106
|
+
mutation {
|
|
107
|
+
addMultipleToWishlist(productVariantIds: ["42", "43", "44"]) {
|
|
108
|
+
id
|
|
109
|
+
productVariant {
|
|
110
|
+
id
|
|
111
|
+
name
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
#### `removeMultipleFromWishlist`
|
|
118
|
+
|
|
119
|
+
Bulk remove by product variant IDs.
|
|
120
|
+
|
|
121
|
+
```graphql
|
|
122
|
+
mutation {
|
|
123
|
+
removeMultipleFromWishlist(productVariantIds: ["42", "43"]) {
|
|
124
|
+
result
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Extended Fields
|
|
130
|
+
|
|
131
|
+
#### `ProductVariant.isInWishlist`
|
|
132
|
+
|
|
133
|
+
Returns `true` if the current customer has this variant in their wishlist.
|
|
134
|
+
Returns `false` for unauthenticated users.
|
|
135
|
+
|
|
136
|
+
```graphql
|
|
137
|
+
query {
|
|
138
|
+
product(id: "1") {
|
|
139
|
+
variants {
|
|
140
|
+
id
|
|
141
|
+
name
|
|
142
|
+
isInWishlist
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Database
|
|
149
|
+
|
|
150
|
+
This plugin creates a `wishlist_item` table with:
|
|
151
|
+
|
|
152
|
+
| Column | Type | Description |
|
|
153
|
+
| --- | --- | --- |
|
|
154
|
+
| `id` | `ID` | Primary key |
|
|
155
|
+
| `customerId` | `ID` | Foreign key to `customer` |
|
|
156
|
+
| `productVariantId` | `ID` | Foreign key to `product_variant` |
|
|
157
|
+
| `createdAt` | `DateTime` | Auto-set on creation |
|
|
158
|
+
| `updatedAt` | `DateTime` | Auto-set on update |
|
|
159
|
+
|
|
160
|
+
A unique composite index on `(customerId, productVariantId)` prevents duplicates.
|
|
161
|
+
|
|
162
|
+
## Compatibility
|
|
163
|
+
|
|
164
|
+
- Vendure `^3.0.0`
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const shopApiExtensions: import("graphql").DocumentNode;
|
|
@@ -0,0 +1,47 @@
|
|
|
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.shopApiExtensions = void 0;
|
|
7
|
+
const graphql_tag_1 = __importDefault(require("graphql-tag"));
|
|
8
|
+
exports.shopApiExtensions = (0, graphql_tag_1.default) `
|
|
9
|
+
type WishlistItem implements Node {
|
|
10
|
+
id: ID!
|
|
11
|
+
createdAt: DateTime!
|
|
12
|
+
updatedAt: DateTime!
|
|
13
|
+
productVariant: ProductVariant!
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type WishlistItemList implements PaginatedList {
|
|
17
|
+
items: [WishlistItem!]!
|
|
18
|
+
totalItems: Int!
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
input WishlistItemListOptions
|
|
22
|
+
|
|
23
|
+
extend type Query {
|
|
24
|
+
wishlists(options: WishlistItemListOptions): WishlistItemList!
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
extend type Mutation {
|
|
28
|
+
addToWishlist(productVariantId: ID!): WishlistItem!
|
|
29
|
+
"""
|
|
30
|
+
Removes a wishlist item by its id or product variant id.
|
|
31
|
+
At least one must be specified.
|
|
32
|
+
"""
|
|
33
|
+
removeFromWishlist(id: ID, productVariantId: ID): DeletionResponse!
|
|
34
|
+
"""
|
|
35
|
+
Adds multiple product variants to wishlist at once.
|
|
36
|
+
"""
|
|
37
|
+
addMultipleToWishlist(productVariantIds: [ID!]!): [WishlistItem!]!
|
|
38
|
+
"""
|
|
39
|
+
Removes multiple wishlist items by their product variant ids.
|
|
40
|
+
"""
|
|
41
|
+
removeMultipleFromWishlist(productVariantIds: [ID!]!): DeletionResponse!
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
extend type ProductVariant {
|
|
45
|
+
isInWishlist: Boolean!
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { CustomerService, ProductVariant, RequestContext } from '@vendure/core';
|
|
2
|
+
import { WishlistService } from '../services/wishlist.service';
|
|
3
|
+
export declare class ProductVariantEntityResolver {
|
|
4
|
+
private customerService;
|
|
5
|
+
private wishlistService;
|
|
6
|
+
constructor(customerService: CustomerService, wishlistService: WishlistService);
|
|
7
|
+
isInWishlist(ctx: RequestContext, productVariant: ProductVariant): Promise<boolean>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.ProductVariantEntityResolver = void 0;
|
|
16
|
+
const graphql_1 = require("@nestjs/graphql");
|
|
17
|
+
const core_1 = require("@vendure/core");
|
|
18
|
+
const wishlist_service_1 = require("../services/wishlist.service");
|
|
19
|
+
let ProductVariantEntityResolver = class ProductVariantEntityResolver {
|
|
20
|
+
constructor(customerService, wishlistService) {
|
|
21
|
+
this.customerService = customerService;
|
|
22
|
+
this.wishlistService = wishlistService;
|
|
23
|
+
}
|
|
24
|
+
async isInWishlist(ctx, productVariant) {
|
|
25
|
+
const userId = ctx.activeUserId;
|
|
26
|
+
if (!userId) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const customer = await this.customerService.findOneByUserId(ctx, userId);
|
|
30
|
+
if (!customer) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return this.wishlistService.isInWishlist(ctx, customer.id, productVariant.id);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
exports.ProductVariantEntityResolver = ProductVariantEntityResolver;
|
|
37
|
+
__decorate([
|
|
38
|
+
(0, graphql_1.ResolveField)(),
|
|
39
|
+
__param(0, (0, core_1.Ctx)()),
|
|
40
|
+
__param(1, (0, graphql_1.Parent)()),
|
|
41
|
+
__metadata("design:type", Function),
|
|
42
|
+
__metadata("design:paramtypes", [core_1.RequestContext,
|
|
43
|
+
core_1.ProductVariant]),
|
|
44
|
+
__metadata("design:returntype", Promise)
|
|
45
|
+
], ProductVariantEntityResolver.prototype, "isInWishlist", null);
|
|
46
|
+
exports.ProductVariantEntityResolver = ProductVariantEntityResolver = __decorate([
|
|
47
|
+
(0, graphql_1.Resolver)('ProductVariant'),
|
|
48
|
+
__metadata("design:paramtypes", [core_1.CustomerService,
|
|
49
|
+
wishlist_service_1.WishlistService])
|
|
50
|
+
], ProductVariantEntityResolver);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DeletionResponse } from '@vendure/common/lib/generated-types';
|
|
2
|
+
import { CustomerService, ID, PaginatedList, RequestContext } from '@vendure/core';
|
|
3
|
+
import { WishlistItem } from '../entities/wishlist-item.entity';
|
|
4
|
+
import { WishlistService } from '../services/wishlist.service';
|
|
5
|
+
export declare class WishlistShopResolver {
|
|
6
|
+
private customerService;
|
|
7
|
+
private wishlistService;
|
|
8
|
+
constructor(customerService: CustomerService, wishlistService: WishlistService);
|
|
9
|
+
wishlists(ctx: RequestContext, args: {
|
|
10
|
+
options?: any;
|
|
11
|
+
}): Promise<PaginatedList<WishlistItem>>;
|
|
12
|
+
addToWishlist(ctx: RequestContext, productVariantId: ID): Promise<WishlistItem>;
|
|
13
|
+
removeFromWishlist(ctx: RequestContext, args: {
|
|
14
|
+
id?: ID;
|
|
15
|
+
productVariantId?: ID;
|
|
16
|
+
}): Promise<DeletionResponse>;
|
|
17
|
+
addMultipleToWishlist(ctx: RequestContext, productVariantIds: ID[]): Promise<WishlistItem[]>;
|
|
18
|
+
removeMultipleFromWishlist(ctx: RequestContext, productVariantIds: ID[]): Promise<DeletionResponse>;
|
|
19
|
+
private getCustomerForOwner;
|
|
20
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.WishlistShopResolver = void 0;
|
|
16
|
+
const graphql_1 = require("@nestjs/graphql");
|
|
17
|
+
const core_1 = require("@vendure/core");
|
|
18
|
+
const wishlist_service_1 = require("../services/wishlist.service");
|
|
19
|
+
let WishlistShopResolver = class WishlistShopResolver {
|
|
20
|
+
constructor(customerService, wishlistService) {
|
|
21
|
+
this.customerService = customerService;
|
|
22
|
+
this.wishlistService = wishlistService;
|
|
23
|
+
}
|
|
24
|
+
async wishlists(ctx, args) {
|
|
25
|
+
const customer = await this.getCustomerForOwner(ctx);
|
|
26
|
+
return this.wishlistService.findAllByCustomerId(ctx, customer.id, args.options);
|
|
27
|
+
}
|
|
28
|
+
async addToWishlist(ctx, productVariantId) {
|
|
29
|
+
const customer = await this.getCustomerForOwner(ctx);
|
|
30
|
+
return this.wishlistService.create(ctx, customer.id, productVariantId);
|
|
31
|
+
}
|
|
32
|
+
async removeFromWishlist(ctx, args) {
|
|
33
|
+
const customer = await this.getCustomerForOwner(ctx);
|
|
34
|
+
return this.wishlistService.delete(ctx, customer.id, args.id, args.productVariantId);
|
|
35
|
+
}
|
|
36
|
+
async addMultipleToWishlist(ctx, productVariantIds) {
|
|
37
|
+
const customer = await this.getCustomerForOwner(ctx);
|
|
38
|
+
return this.wishlistService.createMultiple(ctx, customer.id, productVariantIds);
|
|
39
|
+
}
|
|
40
|
+
async removeMultipleFromWishlist(ctx, productVariantIds) {
|
|
41
|
+
const customer = await this.getCustomerForOwner(ctx);
|
|
42
|
+
return this.wishlistService.deleteMultiple(ctx, customer.id, productVariantIds);
|
|
43
|
+
}
|
|
44
|
+
async getCustomerForOwner(ctx) {
|
|
45
|
+
const userId = ctx.activeUserId;
|
|
46
|
+
if (!userId) {
|
|
47
|
+
throw new core_1.ForbiddenError();
|
|
48
|
+
}
|
|
49
|
+
const customer = await this.customerService.findOneByUserId(ctx, userId);
|
|
50
|
+
if (!customer) {
|
|
51
|
+
throw new core_1.InternalServerError('error.no-customer-found-for-current-user');
|
|
52
|
+
}
|
|
53
|
+
return customer;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
exports.WishlistShopResolver = WishlistShopResolver;
|
|
57
|
+
__decorate([
|
|
58
|
+
(0, graphql_1.Query)(),
|
|
59
|
+
(0, core_1.Allow)(core_1.Permission.Owner),
|
|
60
|
+
__param(0, (0, core_1.Ctx)()),
|
|
61
|
+
__param(1, (0, graphql_1.Args)()),
|
|
62
|
+
__metadata("design:type", Function),
|
|
63
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Object]),
|
|
64
|
+
__metadata("design:returntype", Promise)
|
|
65
|
+
], WishlistShopResolver.prototype, "wishlists", null);
|
|
66
|
+
__decorate([
|
|
67
|
+
(0, graphql_1.Mutation)(),
|
|
68
|
+
(0, core_1.Allow)(core_1.Permission.Owner),
|
|
69
|
+
__param(0, (0, core_1.Ctx)()),
|
|
70
|
+
__param(1, (0, graphql_1.Args)('productVariantId')),
|
|
71
|
+
__metadata("design:type", Function),
|
|
72
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Object]),
|
|
73
|
+
__metadata("design:returntype", Promise)
|
|
74
|
+
], WishlistShopResolver.prototype, "addToWishlist", null);
|
|
75
|
+
__decorate([
|
|
76
|
+
(0, graphql_1.Mutation)(),
|
|
77
|
+
(0, core_1.Allow)(core_1.Permission.Owner),
|
|
78
|
+
__param(0, (0, core_1.Ctx)()),
|
|
79
|
+
__param(1, (0, graphql_1.Args)()),
|
|
80
|
+
__metadata("design:type", Function),
|
|
81
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Object]),
|
|
82
|
+
__metadata("design:returntype", Promise)
|
|
83
|
+
], WishlistShopResolver.prototype, "removeFromWishlist", null);
|
|
84
|
+
__decorate([
|
|
85
|
+
(0, graphql_1.Mutation)(),
|
|
86
|
+
(0, core_1.Allow)(core_1.Permission.Owner),
|
|
87
|
+
__param(0, (0, core_1.Ctx)()),
|
|
88
|
+
__param(1, (0, graphql_1.Args)('productVariantIds')),
|
|
89
|
+
__metadata("design:type", Function),
|
|
90
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Array]),
|
|
91
|
+
__metadata("design:returntype", Promise)
|
|
92
|
+
], WishlistShopResolver.prototype, "addMultipleToWishlist", null);
|
|
93
|
+
__decorate([
|
|
94
|
+
(0, graphql_1.Mutation)(),
|
|
95
|
+
(0, core_1.Allow)(core_1.Permission.Owner),
|
|
96
|
+
__param(0, (0, core_1.Ctx)()),
|
|
97
|
+
__param(1, (0, graphql_1.Args)('productVariantIds')),
|
|
98
|
+
__metadata("design:type", Function),
|
|
99
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Array]),
|
|
100
|
+
__metadata("design:returntype", Promise)
|
|
101
|
+
], WishlistShopResolver.prototype, "removeMultipleFromWishlist", null);
|
|
102
|
+
exports.WishlistShopResolver = WishlistShopResolver = __decorate([
|
|
103
|
+
(0, graphql_1.Resolver)(),
|
|
104
|
+
__metadata("design:paramtypes", [core_1.CustomerService,
|
|
105
|
+
wishlist_service_1.WishlistService])
|
|
106
|
+
], WishlistShopResolver);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Customer, DeepPartial, ID, ProductVariant, VendureEntity } from '@vendure/core';
|
|
2
|
+
export declare class WishlistItem extends VendureEntity {
|
|
3
|
+
constructor(input?: DeepPartial<WishlistItem>);
|
|
4
|
+
customer: Customer;
|
|
5
|
+
customerId: ID;
|
|
6
|
+
productVariant: ProductVariant;
|
|
7
|
+
productVariantId: ID;
|
|
8
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.WishlistItem = void 0;
|
|
13
|
+
const core_1 = require("@vendure/core");
|
|
14
|
+
const typeorm_1 = require("typeorm");
|
|
15
|
+
let WishlistItem = class WishlistItem extends core_1.VendureEntity {
|
|
16
|
+
constructor(input) {
|
|
17
|
+
super(input);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
exports.WishlistItem = WishlistItem;
|
|
21
|
+
__decorate([
|
|
22
|
+
(0, typeorm_1.ManyToOne)(() => core_1.Customer, { eager: false }),
|
|
23
|
+
(0, typeorm_1.JoinColumn)({ name: 'customerId' }),
|
|
24
|
+
__metadata("design:type", core_1.Customer)
|
|
25
|
+
], WishlistItem.prototype, "customer", void 0);
|
|
26
|
+
__decorate([
|
|
27
|
+
(0, typeorm_1.Column)(),
|
|
28
|
+
__metadata("design:type", Object)
|
|
29
|
+
], WishlistItem.prototype, "customerId", void 0);
|
|
30
|
+
__decorate([
|
|
31
|
+
(0, typeorm_1.ManyToOne)(() => core_1.ProductVariant, { eager: true }),
|
|
32
|
+
(0, typeorm_1.JoinColumn)({ name: 'productVariantId' }),
|
|
33
|
+
__metadata("design:type", core_1.ProductVariant)
|
|
34
|
+
], WishlistItem.prototype, "productVariant", void 0);
|
|
35
|
+
__decorate([
|
|
36
|
+
(0, typeorm_1.Column)(),
|
|
37
|
+
__metadata("design:type", Object)
|
|
38
|
+
], WishlistItem.prototype, "productVariantId", void 0);
|
|
39
|
+
exports.WishlistItem = WishlistItem = __decorate([
|
|
40
|
+
(0, typeorm_1.Entity)(),
|
|
41
|
+
(0, typeorm_1.Index)(['customerId', 'productVariantId'], { unique: true }),
|
|
42
|
+
__metadata("design:paramtypes", [Object])
|
|
43
|
+
], WishlistItem);
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WishlistService = exports.WishlistItem = exports.WishlistPlugin = void 0;
|
|
4
|
+
var wishlist_plugin_1 = require("./wishlist.plugin");
|
|
5
|
+
Object.defineProperty(exports, "WishlistPlugin", { enumerable: true, get: function () { return wishlist_plugin_1.WishlistPlugin; } });
|
|
6
|
+
var wishlist_item_entity_1 = require("./entities/wishlist-item.entity");
|
|
7
|
+
Object.defineProperty(exports, "WishlistItem", { enumerable: true, get: function () { return wishlist_item_entity_1.WishlistItem; } });
|
|
8
|
+
var wishlist_service_1 = require("./services/wishlist.service");
|
|
9
|
+
Object.defineProperty(exports, "WishlistService", { enumerable: true, get: function () { return wishlist_service_1.WishlistService; } });
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DeletionResponse } from '@vendure/common/lib/generated-types';
|
|
2
|
+
import { ID, ListQueryBuilder, ListQueryOptions, PaginatedList, ProductVariantService, RequestContext, TransactionalConnection } from '@vendure/core';
|
|
3
|
+
import { WishlistItem } from '../entities/wishlist-item.entity';
|
|
4
|
+
export declare class WishlistService {
|
|
5
|
+
private connection;
|
|
6
|
+
private listQueryBuilder;
|
|
7
|
+
private productVariantService;
|
|
8
|
+
constructor(connection: TransactionalConnection, listQueryBuilder: ListQueryBuilder, productVariantService: ProductVariantService);
|
|
9
|
+
findOne(ctx: RequestContext, id: ID): Promise<WishlistItem>;
|
|
10
|
+
findAllByCustomerId(ctx: RequestContext, customerId: ID, options?: ListQueryOptions<WishlistItem>): Promise<PaginatedList<WishlistItem>>;
|
|
11
|
+
create(ctx: RequestContext, customerId: ID, productVariantId: ID): Promise<WishlistItem>;
|
|
12
|
+
createMultiple(ctx: RequestContext, customerId: ID, productVariantIds: ID[]): Promise<WishlistItem[]>;
|
|
13
|
+
delete(ctx: RequestContext, customerId: ID, id?: ID | null, productVariantId?: ID | null): Promise<DeletionResponse>;
|
|
14
|
+
deleteMultiple(ctx: RequestContext, customerId: ID, productVariantIds: ID[]): Promise<DeletionResponse>;
|
|
15
|
+
isInWishlist(ctx: RequestContext, customerId: ID, productVariantId: ID): Promise<boolean>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.WishlistService = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
const generated_types_1 = require("@vendure/common/lib/generated-types");
|
|
15
|
+
const core_1 = require("@vendure/core");
|
|
16
|
+
const typeorm_1 = require("typeorm");
|
|
17
|
+
const wishlist_item_entity_1 = require("../entities/wishlist-item.entity");
|
|
18
|
+
let WishlistService = class WishlistService {
|
|
19
|
+
constructor(connection, listQueryBuilder, productVariantService) {
|
|
20
|
+
this.connection = connection;
|
|
21
|
+
this.listQueryBuilder = listQueryBuilder;
|
|
22
|
+
this.productVariantService = productVariantService;
|
|
23
|
+
}
|
|
24
|
+
async findOne(ctx, id) {
|
|
25
|
+
return this.connection.getRepository(ctx, wishlist_item_entity_1.WishlistItem).findOneOrFail({
|
|
26
|
+
where: { id },
|
|
27
|
+
relations: ['productVariant', 'productVariant.featuredAsset'],
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async findAllByCustomerId(ctx, customerId, options) {
|
|
31
|
+
return this.listQueryBuilder
|
|
32
|
+
.build(wishlist_item_entity_1.WishlistItem, options, {
|
|
33
|
+
ctx,
|
|
34
|
+
where: { customerId },
|
|
35
|
+
relations: ['productVariant', 'productVariant.featuredAsset'],
|
|
36
|
+
})
|
|
37
|
+
.getManyAndCount()
|
|
38
|
+
.then(([items, totalItems]) => ({
|
|
39
|
+
items,
|
|
40
|
+
totalItems,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
async create(ctx, customerId, productVariantId) {
|
|
44
|
+
const productVariant = await this.productVariantService.findOne(ctx, productVariantId);
|
|
45
|
+
if (!productVariant) {
|
|
46
|
+
throw new core_1.UserInputError('Product variant not found');
|
|
47
|
+
}
|
|
48
|
+
const existing = await this.connection.getRepository(ctx, wishlist_item_entity_1.WishlistItem).findOne({
|
|
49
|
+
where: { customerId, productVariantId },
|
|
50
|
+
relations: ['productVariant', 'productVariant.featuredAsset'],
|
|
51
|
+
});
|
|
52
|
+
if (existing) {
|
|
53
|
+
return existing;
|
|
54
|
+
}
|
|
55
|
+
const wishlistItem = new wishlist_item_entity_1.WishlistItem({ customerId, productVariant });
|
|
56
|
+
const saved = await this.connection.getRepository(ctx, wishlist_item_entity_1.WishlistItem).save(wishlistItem);
|
|
57
|
+
return (0, core_1.assertFound)(this.findOne(ctx, saved.id));
|
|
58
|
+
}
|
|
59
|
+
async createMultiple(ctx, customerId, productVariantIds) {
|
|
60
|
+
if (!productVariantIds.length) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
const repo = this.connection.getRepository(ctx, wishlist_item_entity_1.WishlistItem);
|
|
64
|
+
// Fetch existing items and product variants in parallel
|
|
65
|
+
const [existingItems, productVariants] = await Promise.all([
|
|
66
|
+
repo.find({
|
|
67
|
+
where: { customerId, productVariantId: (0, typeorm_1.In)(productVariantIds) },
|
|
68
|
+
relations: ['productVariant', 'productVariant.featuredAsset'],
|
|
69
|
+
}),
|
|
70
|
+
Promise.all(productVariantIds.map((id) => this.productVariantService.findOne(ctx, id))),
|
|
71
|
+
]);
|
|
72
|
+
// Validate all variants exist
|
|
73
|
+
const missingIdx = productVariants.findIndex((pv) => !pv);
|
|
74
|
+
if (missingIdx !== -1) {
|
|
75
|
+
throw new core_1.UserInputError(`Product variant not found: ${productVariantIds[missingIdx]}`);
|
|
76
|
+
}
|
|
77
|
+
// Filter out already-existing ones
|
|
78
|
+
const existingVariantIds = new Set(existingItems.map((item) => String(item.productVariantId)));
|
|
79
|
+
const newItems = productVariants
|
|
80
|
+
.filter((pv) => !existingVariantIds.has(String(pv.id)))
|
|
81
|
+
.map((pv) => new wishlist_item_entity_1.WishlistItem({ customerId, productVariant: pv }));
|
|
82
|
+
if (newItems.length === 0) {
|
|
83
|
+
return existingItems;
|
|
84
|
+
}
|
|
85
|
+
// Bulk save and re-fetch with relations
|
|
86
|
+
const savedItems = await repo.save(newItems);
|
|
87
|
+
const newlyCreated = await repo.find({
|
|
88
|
+
where: { id: (0, typeorm_1.In)(savedItems.map((i) => i.id)) },
|
|
89
|
+
relations: ['productVariant', 'productVariant.featuredAsset'],
|
|
90
|
+
});
|
|
91
|
+
return [...existingItems, ...newlyCreated];
|
|
92
|
+
}
|
|
93
|
+
async delete(ctx, customerId, id, productVariantId) {
|
|
94
|
+
if (!id && !productVariantId) {
|
|
95
|
+
throw new core_1.UserInputError('Either id or productVariantId must be provided.');
|
|
96
|
+
}
|
|
97
|
+
const where = { customerId };
|
|
98
|
+
if (id)
|
|
99
|
+
where.id = id;
|
|
100
|
+
if (productVariantId)
|
|
101
|
+
where.productVariantId = productVariantId;
|
|
102
|
+
const entity = await this.connection.getRepository(ctx, wishlist_item_entity_1.WishlistItem).findOne({ where });
|
|
103
|
+
if (!entity) {
|
|
104
|
+
return { result: generated_types_1.DeletionResult.NOT_DELETED, message: 'Wishlist item not found' };
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
await this.connection.getRepository(ctx, wishlist_item_entity_1.WishlistItem).remove(entity);
|
|
108
|
+
return { result: generated_types_1.DeletionResult.DELETED };
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
return { result: generated_types_1.DeletionResult.NOT_DELETED, message: e.toString() };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async deleteMultiple(ctx, customerId, productVariantIds) {
|
|
115
|
+
if (!productVariantIds.length) {
|
|
116
|
+
throw new core_1.UserInputError('productVariantIds must be provided.');
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const result = await this.connection
|
|
120
|
+
.getRepository(ctx, wishlist_item_entity_1.WishlistItem)
|
|
121
|
+
.delete({ customerId, productVariantId: (0, typeorm_1.In)(productVariantIds) });
|
|
122
|
+
return {
|
|
123
|
+
result: (result.affected || 0) > 0 ? generated_types_1.DeletionResult.DELETED : generated_types_1.DeletionResult.NOT_DELETED,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
return { result: generated_types_1.DeletionResult.NOT_DELETED, message: e.toString() };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async isInWishlist(ctx, customerId, productVariantId) {
|
|
131
|
+
const count = await this.connection.getRepository(ctx, wishlist_item_entity_1.WishlistItem).count({
|
|
132
|
+
where: { customerId, productVariantId },
|
|
133
|
+
});
|
|
134
|
+
return count > 0;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
exports.WishlistService = WishlistService;
|
|
138
|
+
exports.WishlistService = WishlistService = __decorate([
|
|
139
|
+
(0, common_1.Injectable)(),
|
|
140
|
+
__metadata("design:paramtypes", [core_1.TransactionalConnection,
|
|
141
|
+
core_1.ListQueryBuilder,
|
|
142
|
+
core_1.ProductVariantService])
|
|
143
|
+
], WishlistService);
|
package/dist/types.d.ts
ADDED
package/dist/types.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var WishlistPlugin_1;
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.WishlistPlugin = void 0;
|
|
11
|
+
const core_1 = require("@vendure/core");
|
|
12
|
+
const api_extensions_1 = require("./api/api-extensions");
|
|
13
|
+
const product_variant_entity_resolver_1 = require("./api/product-variant-entity.resolver");
|
|
14
|
+
const wishlist_resolver_1 = require("./api/wishlist.resolver");
|
|
15
|
+
const constants_1 = require("./constants");
|
|
16
|
+
const wishlist_item_entity_1 = require("./entities/wishlist-item.entity");
|
|
17
|
+
const wishlist_service_1 = require("./services/wishlist.service");
|
|
18
|
+
let WishlistPlugin = WishlistPlugin_1 = class WishlistPlugin {
|
|
19
|
+
static init(options) {
|
|
20
|
+
this.options = options || {};
|
|
21
|
+
return WishlistPlugin_1;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
exports.WishlistPlugin = WishlistPlugin;
|
|
25
|
+
exports.WishlistPlugin = WishlistPlugin = WishlistPlugin_1 = __decorate([
|
|
26
|
+
(0, core_1.VendurePlugin)({
|
|
27
|
+
imports: [core_1.PluginCommonModule],
|
|
28
|
+
entities: [wishlist_item_entity_1.WishlistItem],
|
|
29
|
+
providers: [
|
|
30
|
+
{ provide: constants_1.WISHLIST_PLUGIN_OPTIONS, useFactory: () => WishlistPlugin.options },
|
|
31
|
+
wishlist_service_1.WishlistService,
|
|
32
|
+
],
|
|
33
|
+
shopApiExtensions: {
|
|
34
|
+
schema: api_extensions_1.shopApiExtensions,
|
|
35
|
+
resolvers: [wishlist_resolver_1.WishlistShopResolver, product_variant_entity_resolver_1.ProductVariantEntityResolver],
|
|
36
|
+
},
|
|
37
|
+
compatibility: '^3.0.0',
|
|
38
|
+
})
|
|
39
|
+
], WishlistPlugin);
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rahul_vendure/vendure-plugin-wishlist",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Vendure plugin for customer wishlists with bulk add/remove support",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist/**/*",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"watch": "tsc -w",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"@vendure/core": "^3.0.0"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"graphql-tag": "^2.12.6"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"vendure",
|
|
24
|
+
"vendure-plugin",
|
|
25
|
+
"wishlist",
|
|
26
|
+
"favorites"
|
|
27
|
+
],
|
|
28
|
+
"license": "MIT"
|
|
29
|
+
}
|