Skip to content

Commit

Permalink
implement entitlements
Browse files Browse the repository at this point in the history
  • Loading branch information
emilwidlund committed Jan 19, 2025
1 parent 1b19260 commit 438d9de
Show file tree
Hide file tree
Showing 6 changed files with 466 additions and 456 deletions.
176 changes: 89 additions & 87 deletions packages/adapter-utils/src/entitlement/entitlement.test.ts
Original file line number Diff line number Diff line change
@@ -1,104 +1,106 @@
import { describe, it, expect, vi } from "vitest";
import { Entitlement } from "./entitlement";
import type {
Benefit,
WebhookBenefitGrantCreatedPayload,
WebhookBenefitGrantRevokedPayload,
Benefit,
WebhookBenefitGrantCreatedPayload,
WebhookBenefitGrantRevokedPayload,
} from "@polar-sh/sdk/models/components";
import { EntitlementStrategy } from "./entitlement";

describe("Entitlement", () => {
it("should run grant on handler", () => {
const onGrant = vi.fn();
const onRevoke = vi.fn();
describe("EntitlementStrategy", () => {
it("should run grant on handler", () => {
const onGrant = vi.fn();
const onRevoke = vi.fn();

const entitlement = Entitlement<{ test: string }>().grant(onGrant);
const entitlement = new EntitlementStrategy<{ test: string }>().grant(
onGrant,
);

expect(entitlement).toBeDefined();
expect(entitlement).toBeDefined();

const payload = {
type: "benefit_grant.created",
data: {
id: "123",
createdAt: new Date(),
modifiedAt: new Date(),
isGranted: true,
benefitId: "123",
customerId: "123",
subscriptionId: "123",
orderId: "123",
userId: "123",
isRevoked: false,
properties: { test: "test" },
customer: {
email: "[email protected]",
id: "123",
createdAt: new Date(),
modifiedAt: new Date(),
metadata: {},
emailVerified: true,
billingAddress: {
line1: "123",
line2: "123",
city: "123",
state: "123",
postalCode: "123",
country: "123",
},
name: "Test",
taxId: ["123"],
organizationId: "123",
avatarUrl: "123",
},
benefit: {
id: "123",
createdAt: new Date(),
modifiedAt: new Date(),
description: "Test",
selectable: true,
slug: "test",
} as unknown as Benefit,
},
} as WebhookBenefitGrantCreatedPayload;
const payload = {
type: "benefit_grant.created",
data: {
id: "123",
createdAt: new Date(),
modifiedAt: new Date(),
isGranted: true,
benefitId: "123",
customerId: "123",
subscriptionId: "123",
orderId: "123",
userId: "123",
isRevoked: false,
properties: { test: "test" },
customer: {
email: "[email protected]",
id: "123",
createdAt: new Date(),
modifiedAt: new Date(),
metadata: {},
emailVerified: true,
billingAddress: {
line1: "123",
line2: "123",
city: "123",
state: "123",
postalCode: "123",
country: "123",
},
name: "Test",
taxId: ["123"],
organizationId: "123",
avatarUrl: "123",
},
benefit: {
id: "123",
createdAt: new Date(),
modifiedAt: new Date(),
selectable: true,
description: "test",
} as unknown as Benefit,
},
} as WebhookBenefitGrantCreatedPayload;

entitlement.handler("test")(payload);
entitlement.handler("test")(payload);

expect(onGrant).toHaveBeenCalledWith({
payload,
customer: payload.data.customer,
properties: payload.data.properties,
});
expect(onGrant).toHaveBeenCalledWith({
payload,
customer: payload.data.customer,
properties: payload.data.properties,
});

expect(onRevoke).not.toHaveBeenCalled();
});
expect(onRevoke).not.toHaveBeenCalled();
});

it("should run revoke on handler", () => {
const onGrant = vi.fn();
const onRevoke = vi.fn();
it("should run revoke on handler", () => {
const onGrant = vi.fn();
const onRevoke = vi.fn();

const entitlement = Entitlement<{ test: string }>()
.grant(onGrant)
.revoke(onRevoke);
const entitlement = new EntitlementStrategy<{ test: string }>()
.grant(onGrant)
.revoke(onRevoke);

const payload = {
type: "benefit_grant.revoked",
data: {
id: "123",
createdAt: new Date(),
modifiedAt: new Date(),
isGranted: false,
benefitId: "123",
customerId: "123",
},
} as WebhookBenefitGrantRevokedPayload;
const payload = {
type: "benefit_grant.revoked",
data: {
id: "123",
createdAt: new Date(),
modifiedAt: new Date(),
isGranted: false,
benefitId: "123",
customerId: "123",
benefit: { description: "test" },
},
} as WebhookBenefitGrantRevokedPayload;

entitlement.handler("test")(payload);
entitlement.handler("test")(payload);

expect(onGrant).not.toHaveBeenCalled();
expect(onGrant).not.toHaveBeenCalled();

expect(onRevoke).toHaveBeenCalledWith({
payload,
customer: payload.data.customer,
properties: payload.data.properties,
});
});
expect(onRevoke).toHaveBeenCalledWith({
payload,
customer: payload.data.customer,
properties: payload.data.properties,
});
});
});
136 changes: 72 additions & 64 deletions packages/adapter-utils/src/entitlement/entitlement.ts
Original file line number Diff line number Diff line change
@@ -1,81 +1,89 @@
import type {
Customer,
WebhookBenefitGrantCreatedPayload,
WebhookBenefitGrantRevokedPayload,
Customer,
WebhookBenefitGrantCreatedPayload,
WebhookBenefitGrantRevokedPayload,
} from "@polar-sh/sdk/models/components";

export type EntitlementProperties = Record<string, string>;

export type EntitlementHandler = (
payload:
| WebhookBenefitGrantCreatedPayload
| WebhookBenefitGrantRevokedPayload,
payload:
| WebhookBenefitGrantCreatedPayload
| WebhookBenefitGrantRevokedPayload,
) => Promise<void>;

export interface EntitlementContext<T extends EntitlementProperties> {
customer: Customer;
properties: T;
payload:
| WebhookBenefitGrantCreatedPayload
| WebhookBenefitGrantRevokedPayload;
customer: Customer;
properties: T;
payload:
| WebhookBenefitGrantCreatedPayload
| WebhookBenefitGrantRevokedPayload;
}

export class EntitlementStrategy<T extends EntitlementProperties> {
private grantCallbacks: ((
context: EntitlementContext<T>,
) => Promise<void>)[] = [];
private revokeCallbacks: ((
context: EntitlementContext<T>,
) => Promise<void>)[] = [];
private grantCallbacks: ((
context: EntitlementContext<T>,
) => Promise<void>)[] = [];

public grant(callback: (context: EntitlementContext<T>) => Promise<void>) {
this.grantCallbacks.push(callback);
return this;
}
private revokeCallbacks: ((
context: EntitlementContext<T>,
) => Promise<void>)[] = [];

public revoke(callback: (context: EntitlementContext<T>) => Promise<void>) {
this.revokeCallbacks.push(callback);
return this;
}
public grant(callback: (context: EntitlementContext<T>) => Promise<void>) {
this.grantCallbacks.push(callback);
return this;
}

public handler(slug: string): EntitlementHandler {
return async (
payload:
| WebhookBenefitGrantCreatedPayload
| WebhookBenefitGrantRevokedPayload,
) => {
if (payload.data.benefit.slug === slug) {
switch (payload.type) {
case "benefit_grant.created":
await Promise.all(
this.grantCallbacks.map((callback) =>
callback({
customer: payload.data.customer,
properties: payload.data.properties as T,
payload,
}),
),
);
break;
case "benefit_grant.revoked":
await Promise.all(
this.revokeCallbacks.map((callback) =>
callback({
customer: payload.data.customer,
properties: payload.data.properties as T,
payload,
}),
),
);
break;
}
}
};
}
public revoke(callback: (context: EntitlementContext<T>) => Promise<void>) {
this.revokeCallbacks.push(callback);
return this;
}

public handler(slug: string): EntitlementHandler {
return async (
payload:
| WebhookBenefitGrantCreatedPayload
| WebhookBenefitGrantRevokedPayload,
) => {
if (payload.data.benefit.description === slug) {
switch (payload.type) {
case "benefit_grant.created":
await Promise.all(
this.grantCallbacks.map((callback) =>
callback({
customer: payload.data.customer,
properties: payload.data.properties as T,
payload,
}),
),
);
break;
case "benefit_grant.revoked":
await Promise.all(
this.revokeCallbacks.map((callback) =>
callback({
customer: payload.data.customer,
properties: payload.data.properties as T,
payload,
}),
),
);
break;
}
}
};
}
}

export const Entitlement = <
T extends EntitlementProperties = EntitlementProperties,
>() => {
return new EntitlementStrategy<T>();
};
export class Entitlements {
static handlers = [] as EntitlementHandler[];

static use<T extends EntitlementProperties = EntitlementProperties>(
slug: string,
strategy: EntitlementStrategy<T>,
) {
this.handlers.push(strategy.handler(slug));

return this;
}
}
41 changes: 0 additions & 41 deletions packages/adapter-utils/src/entitlements/Figma/Figma.test.ts

This file was deleted.

Loading

0 comments on commit 438d9de

Please sign in to comment.