Skip to content

Commit f1c6a7a

Browse files
committed
CRUD endpoints
1 parent b13728b commit f1c6a7a

File tree

21 files changed

+374
-332
lines changed

21 files changed

+374
-332
lines changed

src/db/contractSubscriptions/createContractSubscription.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,22 @@ import { prisma } from "../client";
33
interface CreateContractSubscriptionParams {
44
chainId: number;
55
contractAddress: string;
6+
webhookId?: number;
67
}
78

89
export const createContractSubscription = async ({
910
chainId,
1011
contractAddress,
12+
webhookId,
1113
}: CreateContractSubscriptionParams) => {
1214
return prisma.contractSubscriptions.create({
13-
data: { chainId, contractAddress },
15+
data: {
16+
chainId,
17+
contractAddress,
18+
webhookId,
19+
},
20+
include: {
21+
webhook: true,
22+
},
1423
});
1524
};

src/db/contractSubscriptions/deleteContractSubscription.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,8 @@
11
import { prisma } from "../client";
22

3-
interface RemoveContractSubscriptionParams {
4-
chainId: number;
5-
contractAddress: string;
6-
}
7-
8-
export const deleteContractSubscription = async ({
9-
chainId,
10-
contractAddress,
11-
}: RemoveContractSubscriptionParams) => {
12-
return prisma.contractSubscriptions.updateMany({
13-
where: {
14-
chainId,
15-
contractAddress,
16-
deletedAt: null,
17-
},
3+
export const deleteContractSubscription = async (id: string) => {
4+
return prisma.contractSubscriptions.update({
5+
where: { id },
186
data: {
197
deletedAt: new Date(),
208
},

src/db/contractSubscriptions/getContractSubscriptions.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,14 @@ export const isContractSubscribed = async ({
99
chainId,
1010
contractAddress,
1111
}: GetContractSubscriptionsParams) => {
12-
const subscribedContract = await prisma.contractSubscriptions.findMany({
12+
const contractSubscription = await prisma.contractSubscriptions.findFirst({
1313
where: {
1414
chainId,
1515
contractAddress,
1616
deletedAt: null,
1717
},
1818
});
19-
20-
if (subscribedContract.length === 0) return false;
21-
else return true;
19+
return contractSubscription !== null;
2220
};
2321

2422
export const getContractSubscriptionsByChainId = async (
@@ -41,6 +39,9 @@ export const getAllContractSubscriptions = async () => {
4139
where: {
4240
deletedAt: null,
4341
},
42+
include: {
43+
webhook: true,
44+
},
4445
});
4546
};
4647

src/db/webhooks/getWebhook.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { prisma } from "../client";
2+
3+
export const getWebhook = async (id: number) => {
4+
return await prisma.webhooks.findUnique({
5+
where: { id },
6+
});
7+
};

src/db/webhooks/revokeWebhook.ts

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,13 @@
1-
import { StatusCodes } from "http-status-codes";
2-
import { createCustomError } from "../../server/middleware/error";
31
import { prisma } from "../client";
42

5-
interface RevokeWebhooksParams {
6-
id: number;
7-
}
8-
9-
export const markWebhookAsRevoked = async ({ id }: RevokeWebhooksParams) => {
10-
const currentTimestamp = new Date();
11-
12-
const exists = await prisma.webhooks.findUnique({
13-
where: {
14-
id,
15-
},
16-
});
17-
18-
if (!exists)
19-
throw createCustomError(
20-
`Webhook with id ${id} does not exist`,
21-
StatusCodes.BAD_REQUEST,
22-
"BAD_REQUEST",
23-
);
3+
export const deleteWebhook = async (id: number) => {
4+
const now = new Date();
245

256
return prisma.webhooks.update({
26-
where: {
27-
id,
28-
},
7+
where: { id },
298
data: {
30-
revokedAt: currentTimestamp,
31-
updatedAt: currentTimestamp,
9+
revokedAt: now,
10+
updatedAt: now,
3211
},
3312
});
3413
};

src/server/middleware/auth.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { THIRDWEB_DASHBOARD_ISSUER, handleSiwe } from "../../utils/auth";
1717
import { getAccessToken } from "../../utils/cache/accessToken";
1818
import { getAuthWallet } from "../../utils/cache/authWallet";
1919
import { getConfig } from "../../utils/cache/getConfig";
20-
import { getWebhook } from "../../utils/cache/getWebhook";
20+
import { getWebhooksByEventType } from "../../utils/cache/getWebhook";
2121
import { getKeypair } from "../../utils/cache/keypair";
2222
import { env } from "../../utils/env";
2323
import { logger } from "../../utils/logger";
@@ -397,7 +397,7 @@ const handleSecretKey = async (req: FastifyRequest): Promise<AuthResponse> => {
397397
const handleAuthWebhooks = async (
398398
req: FastifyRequest,
399399
): Promise<AuthResponse> => {
400-
const authWebhooks = await getWebhook(WebhooksEventTypes.AUTH);
400+
const authWebhooks = await getWebhooksByEventType(WebhooksEventTypes.AUTH);
401401
if (authWebhooks.length > 0) {
402402
const authResponses = await Promise.all(
403403
authWebhooks.map(async (webhook) => {

src/server/routes/configuration/cors/remove.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ export async function removeUrlToCorsConfiguration(fastify: FastifyInstance) {
2929
method: "DELETE",
3030
url: "/configuration/cors",
3131
schema: {
32-
summary: "Remove Url from CORS configuration",
33-
description: "Remove Url from CORS configuration",
32+
summary: "Remove CORS URLs",
33+
description: "Remove URLs from CORS configuration",
3434
tags: ["Configuration"],
3535
operationId: "removeUrlToCorsConfiguration",
3636
body: BodySchema,

src/server/routes/contract/subscriptions/addContractSubscription.ts

Lines changed: 68 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,91 +3,112 @@ import { FastifyInstance } from "fastify";
33
import { StatusCodes } from "http-status-codes";
44
import { upsertChainIndexer } from "../../../../db/chainIndexers/upsertChainIndexer";
55
import { createContractSubscription } from "../../../../db/contractSubscriptions/createContractSubscription";
6-
import {
7-
getContractSubscriptionsUniqueChainIds,
8-
isContractSubscribed,
9-
} from "../../../../db/contractSubscriptions/getContractSubscriptions";
6+
import { getContractSubscriptionsUniqueChainIds } from "../../../../db/contractSubscriptions/getContractSubscriptions";
7+
import { insertWebhook } from "../../../../db/webhooks/createWebhook";
8+
import { WebhooksEventTypes } from "../../../../schema/webhooks";
109
import { getSdk } from "../../../../utils/cache/getSdk";
10+
import { createCustomError } from "../../../middleware/error";
1111
import {
12-
contractParamSchema,
13-
standardResponseSchema,
14-
} from "../../../schemas/sharedApiSchemas";
12+
contractSubscriptionSchema,
13+
toContractSubscriptionSchema,
14+
} from "../../../schemas/contractSubscription";
15+
import { standardResponseSchema } from "../../../schemas/sharedApiSchemas";
1516
import { getChainIdFromChain } from "../../../utils/chain";
17+
import { isValidHttpUrl } from "../../../utils/validator";
1618

17-
const responseSchema = Type.Object({
18-
result: Type.Object({
19-
chain: Type.String(),
20-
contractAddress: Type.String(),
21-
status: Type.String(),
19+
const bodySchema = Type.Object({
20+
chain: Type.String({
21+
description: "The chain for the contract.",
22+
}),
23+
contractAddress: Type.String({
24+
description: "The address for the contract.",
2225
}),
26+
webhookUrl: Type.Optional(
27+
Type.String({
28+
description: "Webhook URL",
29+
examples: ["https://example.com/webhook"],
30+
}),
31+
),
32+
});
33+
34+
const responseSchema = Type.Object({
35+
result: contractSubscriptionSchema,
2336
});
2437

2538
responseSchema.example = {
2639
result: {
27-
chain: "ethereum",
40+
chain: 1,
2841
contractAddress: "0x....",
2942
status: "success",
3043
},
3144
};
3245

3346
export async function addContractSubscription(fastify: FastifyInstance) {
3447
fastify.route<{
35-
Params: Static<typeof contractParamSchema>;
48+
Body: Static<typeof bodySchema>;
3649
Reply: Static<typeof responseSchema>;
3750
}>({
3851
method: "POST",
39-
url: "/contract/:chain/:contractAddress/subscriptions/subscribe",
52+
url: "/contract-subscriptions/add",
4053
schema: {
41-
summary: "Subscribe to contract events and transactions",
42-
description: "Subscribe to contract events and transactions",
54+
summary: "Add contract subscription",
55+
description:
56+
"Subscribe to event logs and transaction receipts for a contract.",
4357
tags: ["Contract-Subscriptions"],
4458
operationId: "addContractSubscription",
45-
params: contractParamSchema,
59+
body: bodySchema,
4660
response: {
4761
...standardResponseSchema,
4862
[StatusCodes.OK]: responseSchema,
4963
},
5064
},
5165
handler: async (request, reply) => {
52-
const { chain, contractAddress } = request.params;
66+
const { chain, contractAddress, webhookUrl } = request.body;
5367

54-
const standardizedContractAddress = contractAddress.toLowerCase();
5568
const chainId = await getChainIdFromChain(chain);
69+
const standardizedContractAddress = contractAddress.toLowerCase();
5670

57-
const isAlreadySubscribed = await isContractSubscribed({
58-
chainId,
59-
contractAddress: standardizedContractAddress,
60-
});
61-
62-
if (!isAlreadySubscribed) {
63-
const subscribedChainIds =
64-
await getContractSubscriptionsUniqueChainIds();
71+
// If not currently indexed, upsert the latest block number.
72+
const subscribedChainIds = await getContractSubscriptionsUniqueChainIds();
73+
if (!subscribedChainIds.includes(chainId)) {
74+
try {
75+
const sdk = await getSdk({ chainId });
76+
const provider = sdk.getProvider();
77+
const currentBlockNumber = await provider.getBlockNumber();
78+
await upsertChainIndexer({ chainId, currentBlockNumber });
79+
} catch (error) {
80+
// this is fine, must be already locked, so don't need to update current block as this will be recent
81+
}
82+
}
6583

66-
// if not currently indexed, upsert the latest block number
67-
if (!subscribedChainIds.includes(chainId)) {
68-
try {
69-
const sdk = await getSdk({ chainId });
70-
const provider = sdk.getProvider();
71-
const currentBlockNumber = await provider.getBlockNumber();
72-
await upsertChainIndexer({ chainId, currentBlockNumber });
73-
} catch (error) {
74-
// this is fine, must be already locked, so don't need to update current block as this will be recent
75-
}
84+
// Create the webhook (if provided).
85+
let webhookId: number | undefined;
86+
if (webhookUrl) {
87+
if (!isValidHttpUrl(webhookUrl)) {
88+
throw createCustomError(
89+
"Invalid webhook URL. Make sure it starts with 'https://'.",
90+
StatusCodes.BAD_REQUEST,
91+
"BAD_REQUEST",
92+
);
7693
}
7794

78-
// upsert indexed contract, this will be picked up
79-
await createContractSubscription({
80-
chainId,
81-
contractAddress: standardizedContractAddress,
95+
const webhook = await insertWebhook({
96+
eventType: WebhooksEventTypes.CONTRACT_SUBSCRIPTION,
97+
name: "Auto-generated for contract subscription",
98+
url: webhookUrl,
8299
});
100+
webhookId = webhook.id;
83101
}
84102

103+
// Create the contract subscription.
104+
const contractSubscription = await createContractSubscription({
105+
chainId,
106+
contractAddress: standardizedContractAddress,
107+
webhookId,
108+
});
109+
85110
reply.status(StatusCodes.OK).send({
86-
result: {
87-
chain,
88-
contractAddress: standardizedContractAddress,
89-
status: "success",
90-
},
111+
result: toContractSubscriptionSchema(contractSubscription),
91112
});
92113
},
93114
});

src/server/routes/contract/subscriptions/getContractSubscriptions.ts

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,37 @@ import { Static, Type } from "@sinclair/typebox";
22
import { FastifyInstance } from "fastify";
33
import { StatusCodes } from "http-status-codes";
44
import { getAllContractSubscriptions } from "../../../../db/contractSubscriptions/getContractSubscriptions";
5+
import {
6+
contractSubscriptionSchema,
7+
toContractSubscriptionSchema,
8+
} from "../../../schemas/contractSubscription";
59
import { standardResponseSchema } from "../../../schemas/sharedApiSchemas";
610

711
const responseSchema = Type.Object({
8-
result: Type.Object({
9-
contracts: Type.Array(
10-
Type.Object({
11-
chainId: Type.Number(),
12-
contractAddress: Type.String(),
13-
}),
14-
),
15-
status: Type.String(),
16-
}),
12+
result: Type.Array(contractSubscriptionSchema),
1713
});
1814

1915
responseSchema.example = {
20-
result: {
21-
contracts: [
22-
{
23-
chain: "ethereum",
24-
contractAddress: "0x....",
16+
result: [
17+
{
18+
chain: "ethereum",
19+
contractAddress: "0x....",
20+
webhook: {
21+
url: "https://...",
2522
},
26-
],
27-
status: "success",
28-
},
23+
},
24+
],
2925
};
3026

3127
export async function getContractSubscriptions(fastify: FastifyInstance) {
3228
fastify.route<{
3329
Reply: Static<typeof responseSchema>;
3430
}>({
3531
method: "GET",
36-
url: "/contract/subscriptions/get-all",
32+
url: "/contract-subscriptions/get-all",
3733
schema: {
38-
summary: "Get all subscribed contracts",
39-
description: "Get all subscribed contracts",
34+
summary: "Get contract subscriptions",
35+
description: "Get all contract subscriptions.",
4036
tags: ["Contract-Subscriptions"],
4137
operationId: "getContractSubscriptions",
4238
response: {
@@ -47,16 +43,8 @@ export async function getContractSubscriptions(fastify: FastifyInstance) {
4743
handler: async (request, reply) => {
4844
const contractSubscriptions = await getAllContractSubscriptions();
4945

50-
const contracts = contractSubscriptions.map((val) => ({
51-
chainId: val.chainId,
52-
contractAddress: val.contractAddress,
53-
}));
54-
5546
reply.status(StatusCodes.OK).send({
56-
result: {
57-
contracts,
58-
status: "success",
59-
},
47+
result: contractSubscriptions.map(toContractSubscriptionSchema),
6048
});
6149
},
6250
});

0 commit comments

Comments
 (0)