-
Notifications
You must be signed in to change notification settings - Fork 577
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1688c25
commit 76a80ab
Showing
30 changed files
with
1,260 additions
and
118 deletions.
There are no files selected for viewing
32 changes: 32 additions & 0 deletions
32
packages/bsky/src/api/app/bsky/purchase/getActiveSubscriptions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { Server } from '../../../../lexicon' | ||
import AppContext from '../../../../context' | ||
import { Subscription as ProtoSubscription } from '../../../../proto/bsync_pb' | ||
import { Subscription as XrpcSubscription } from '../../../../lexicon/types/app/bsky/purchase/getActiveSubscriptions' | ||
|
||
export default function (server: Server, ctx: AppContext) { | ||
server.app.bsky.purchase.getActiveSubscriptions({ | ||
auth: ctx.authVerifier.standard, | ||
handler: async ({ auth }) => { | ||
const viewer = auth.credentials.iss | ||
|
||
const { subscriptions } = await ctx.bsyncClient.getActiveSubscriptions({ | ||
actorDid: viewer, | ||
}) | ||
return { | ||
encoding: 'application/json', | ||
body: { | ||
subscriptions: subscriptions.map(subscriptionProtoToXrpc), | ||
}, | ||
} | ||
}, | ||
}) | ||
} | ||
|
||
const subscriptionProtoToXrpc = ( | ||
subscription: ProtoSubscription, | ||
): XrpcSubscription => ({ | ||
...subscription, | ||
periodEndsAt: subscription.periodEndsAt?.toDate().toISOString(), | ||
periodStartsAt: subscription.periodStartsAt?.toDate().toISOString(), | ||
purchasedAt: subscription.purchasedAt?.toDate().toISOString(), | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { Server } from '../../../../lexicon' | ||
import AppContext from '../../../../context' | ||
import { Features } from '@atproto/api/dist/client/types/app/bsky/purchase/getFeatures' | ||
|
||
export default function (server: Server, ctx: AppContext) { | ||
server.app.bsky.purchase.getFeatures({ | ||
auth: ctx.authVerifier.standard, | ||
handler: async ({ auth }) => { | ||
const viewer = auth.credentials.iss | ||
|
||
const features = await getFeaturesForViewerEntitlements(viewer, ctx) | ||
|
||
return { | ||
encoding: 'application/json', | ||
body: { | ||
features, | ||
}, | ||
} | ||
}, | ||
}) | ||
} | ||
|
||
const defaultFeatures: Features = { | ||
customProfileColor: false, | ||
} | ||
|
||
const coreEntitlementFeatures: Features = { | ||
customProfileColor: true, | ||
} | ||
|
||
const getFeaturesForViewerEntitlements = async ( | ||
viewerDid: string, | ||
ctx: AppContext, | ||
): Promise<Features> => { | ||
const { purchaseEntitlements } = await ctx.dataplane.getPurchaseEntitlements({ | ||
dids: [viewerDid], | ||
}) | ||
|
||
if (purchaseEntitlements?.length === 0) { | ||
return defaultFeatures | ||
} | ||
|
||
const { entitlements } = purchaseEntitlements[0] | ||
|
||
if (entitlements.includes('core')) { | ||
return coreEntitlementFeatures | ||
} else { | ||
return defaultFeatures | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
packages/bsky/src/api/app/bsky/purchase/getSubscriptionGroup.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { Server } from '../../../../lexicon' | ||
import AppContext from '../../../../context' | ||
|
||
export default function (server: Server, ctx: AppContext) { | ||
server.app.bsky.purchase.getSubscriptionGroup({ | ||
handler: async ({ params }) => { | ||
const { group, platform } = params | ||
|
||
const { offerings } = await ctx.bsyncClient.getSubscriptionGroup({ | ||
group, | ||
platform, | ||
}) | ||
|
||
return { | ||
encoding: 'application/json', | ||
body: { | ||
group, | ||
offerings: offerings.map(({ id, product }) => ({ | ||
id, | ||
platform, | ||
product, | ||
})), | ||
}, | ||
} | ||
}, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { Server } from '../../../../lexicon' | ||
import AppContext from '../../../../context' | ||
import { AuthRequiredError } from '@atproto/xrpc-server' | ||
import { RoleOutput, StandardOutput } from '../../../../auth-verifier' | ||
|
||
export default function (server: Server, ctx: AppContext) { | ||
server.app.bsky.purchase.refreshCache({ | ||
auth: ctx.authVerifier.standardOrRole, | ||
handler: async ({ auth, input }) => { | ||
const { did } = input.body | ||
validateCredentials(did, auth) | ||
|
||
await ctx.bsyncClient.addPurchaseOperation({ | ||
actorDid: did, | ||
}) | ||
|
||
return { | ||
encoding: 'application/json', | ||
body: {}, | ||
} | ||
}, | ||
}) | ||
} | ||
|
||
const validateCredentials = ( | ||
did: string, | ||
auth: StandardOutput | RoleOutput, | ||
) => { | ||
// admins can refresh any user's subscription cache | ||
if (auth.credentials.type === 'role') { | ||
return | ||
} | ||
|
||
// users can only refresh their own subscription cache | ||
if (auth.credentials.iss !== did) { | ||
throw new AuthRequiredError('bad issuer') | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
packages/bsky/src/data-plane/server/db/migrations/20241206T231908523Z-purchase.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { Kysely } from 'kysely' | ||
|
||
export async function up(db: Kysely<unknown>): Promise<void> { | ||
await db.schema | ||
.createTable('purchase') | ||
.addColumn('did', 'varchar', (col) => col.primaryKey()) | ||
.addColumn('entitlements', 'jsonb', (col) => col.notNull()) | ||
.addColumn('createdAt', 'varchar', (col) => col.notNull()) | ||
.addColumn('updatedAt', 'varchar', (col) => col.notNull()) | ||
.execute() | ||
} | ||
|
||
export async function down(db: Kysely<unknown>): Promise<void> { | ||
await db.schema.dropTable('purchase').execute() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { ColumnType } from 'kysely' | ||
|
||
export const tableName = 'purchase' | ||
|
||
export interface Purchase { | ||
did: string | ||
// https://github.com/kysely-org/kysely/issues/137 | ||
entitlements: ColumnType<string[], string, string> | ||
createdAt: string | ||
updatedAt: string | ||
} | ||
|
||
export type PartialDB = { [tableName]: Purchase } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { ServiceImpl } from '@connectrpc/connect' | ||
import { Service } from '../../../proto/bsky_connect' | ||
import { Database } from '../db' | ||
import { keyBy } from '@atproto/common' | ||
import { Timestamp } from '@bufbuild/protobuf' | ||
|
||
export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({ | ||
async getPurchaseEntitlements(req) { | ||
const { dids } = req | ||
|
||
if (dids.length === 0) { | ||
return { purchaseEntitlements: [] } | ||
} | ||
|
||
const res = await db.db | ||
.selectFrom('purchase') | ||
.select(['did', 'entitlements', 'createdAt']) | ||
.where('did', 'in', dids ?? []) | ||
.execute() | ||
|
||
const byDid = keyBy(res, 'did') | ||
const purchaseEntitlements = res.map((row) => { | ||
const purchase = byDid[row.did] ?? {} | ||
|
||
return { | ||
entitlements: purchase.entitlements ?? [], | ||
createdAt: Timestamp.fromDate(new Date(purchase.createdAt)), | ||
} | ||
}) | ||
|
||
return { purchaseEntitlements } | ||
}, | ||
}) |
Oops, something went wrong.