Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: save permits to database #20

Merged
5 changes: 3 additions & 2 deletions .github/workflows/jest-testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ jobs:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }}
X25519_PRIVATE_KEY: ${{ secrets.X25519_PRIVATE_KEY }}
# Dummy values, not production ones!
X25519_PRIVATE_KEY: "wrQ9wTI1bwdAHbxk2dfsvoK1yRwDc0CEenmMXFvGYgY"
EVM_PRIVATE_ENCRYPTED: "kmpTKq5Wh9r9x5j3U9GqZr3NYnjK2g0HtbzeUBOuLC2y3x8ja_SKBNlB2AZ6LigXHP_HeMitftVUtzmoj8CFfVP9SqjWoL6IPku1hVTWkdTn97g1IxzmjydFxjdcf0wuDW1hvVtoq3Uw5yALABqxcQ"
NFT_MINTER_PRIVATE_KEY: ${{ secrets.NFT_MINTER_PRIVATE_KEY }}
NFT_CONTRACT_ADDRESS: ${{ secrets.NFT_CONTRACT_ADDRESS }}
EVM_PRIVATE_ENCRYPTED: ${{ secrets.EVM_PRIVATE_ENCRYPTED }}

steps:
- name: Checkout code
Expand Down
68 changes: 67 additions & 1 deletion src/parser/permit-generation-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Database,
encodePermits,
generatePayoutPermit,
Permit,
SupportedEvents,
TokenType,
} from "@ubiquibot/permit-generation/core";
Expand All @@ -17,6 +18,7 @@ import {
} from "../configuration/permit-generation-configuration";
import { getOctokitInstance } from "../get-authentication-token";
import { IssueActivity } from "../issue-activity";
import { getRepo, parseGitHubUrl } from "../start";
import envConfigSchema, { EnvConfigType } from "../types/env-type";
import program from "./command-line";
import { Module, Result } from "./processor";
Expand All @@ -39,7 +41,7 @@ export class PermitGenerationModule implements Module {
evmPrivateEncrypted: configuration.evmPrivateEncrypted,
evmNetworkId: configuration.evmNetworkId,
};
const issueId = Number(payload.issueUrl.match(/[0-9]+$/)?.[1]);
const issueId = Number(payload.issueUrl.match(/[0-9]+$/)?.[0]);
payload.issue = {
id: issueId,
};
Expand Down Expand Up @@ -100,13 +102,77 @@ export class PermitGenerationModule implements Module {
config.permitRequests
);
result[key].permitUrl = `https://pay.ubq.fi?claim=${encodePermits(permits)}`;
await this._savePermitsToDatabase(result[key].userId, { issueUrl: payload.issueUrl, issueId }, permits);
} catch (e) {
console.error(e);
}
}
return result;
}

async _getOrCreateIssueLocation(issue: { issueId: number; issueUrl: string }) {
let locationId: number | null = null;

const { data: locationData } = await this._supabase
.from("locations")
.select("id")
.eq("issue_id", issue.issueId)
.eq("node_url", issue.issueUrl)
.single();

if (!locationData) {
const issueItem = await getRepo(parseGitHubUrl(issue.issueUrl));
const { data: newLocationData, error } = await this._supabase
.from("locations")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gentlementlegen Isn't the locations table deprecated? Shouldn't we just delete it or at least don't use at all?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah we should probably stop using that table and instead just add a new URL column wherever it makes sense in the other tables.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used it because it is still there and we didn't update it to something different but indeed it should be changed. Might wanna have a different task for this so we can also migrate all the current values as well (there are 43 records)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't worry about data migrations. Basically none of the old data is valuable. No need to do extra work to save it.

.insert({
node_url: issue.issueUrl,
issue_id: issue.issueId,
node_type: "Issue",
repository_id: issueItem.id,
})
.select("id")
.single();
if (!newLocationData || error) {
console.error("Failed to create a new location", error);
} else {
locationId = newLocationData.id;
}
} else {
locationId = locationData.id;
}
if (!locationId) {
throw new Error(`Failed to retrieve the related location from issue ${issue}`);
}
return locationId;
}

async _savePermitsToDatabase(userId: number, issue: { issueId: number; issueUrl: string }, permits: Permit[]) {
for (const permit of permits) {
try {
const { data: userData } = await this._supabase.from("users").select("id").eq("id", userId).single();
const locationId = await this._getOrCreateIssueLocation(issue);

if (userData) {
const { error } = await this._supabase.from("permits").insert({
amount: permit.amount.toString(),
nonce: permit.nonce,
deadline: permit.deadline,
signature: permit.signature,
beneficiary_id: userData.id,
location_id: locationId,
});
if (error) {
console.error("Failed to insert a new permit", error);
}
} else {
console.error(`Failed to save the permit: could not find user ${userId}`);
}
} catch (e) {
console.error("Failed to save permits to the database", e);
}
}
}

get enabled(): boolean {
if (!Value.Check(permitGenerationConfigurationType, this._configuration)) {
console.warn("Invalid configuration detected for PermitGenerationModule, disabling.");
Expand Down
6 changes: 6 additions & 0 deletions src/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
GitHubPullRequest,
GitHubPullRequestReviewComment,
GitHubPullRequestReviewState,
GitHubRepository,
GitHubTimelineEvent,
GitHubUser,
} from "./github-types";
Expand Down Expand Up @@ -57,6 +58,11 @@ import {
export type IssueParams = ReturnType<typeof parseGitHubUrl>;
export type PullParams = { owner: string; repo: string; pull_number: number };

export async function getRepo(params: IssueParams): Promise<GitHubRepository> {
const octokit = getOctokitInstance();
return (await octokit.repos.get(params)).data;
}

export async function getIssue(params: IssueParams): Promise<GitHubIssue> {
const octokit = getOctokitInstance();
return (await octokit.issues.get(params)).data;
Expand Down
9 changes: 9 additions & 0 deletions tests/__mocks__/db-seed.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,5 +119,14 @@
"userId": 88761781,
"address": "0x4D0704f400D57Ba93eEa88765C3FcDBD826dCFc4"
}
],
"locations": [
{
"id": 1,
"issue_id": 22,
"node_url": "https://github.com/ubiquibot/comment-incentives/issues/22",
"node_type": "Issue",
"repository_id": 1
}
]
}
16 changes: 16 additions & 0 deletions tests/__mocks__/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,20 @@ export const db = factory({
userId: Number,
address: String,
},
locations: {
id: primaryKey(Number),
issue_id: Number,
node_url: String,
node_type: String,
repository_id: Number,
},
permits: {
id: primaryKey(Number),
amount: String,
nonce: String,
deadline: String,
signature: String,
beneficiary_id: Number,
location_id: Number,
},
});
60 changes: 60 additions & 0 deletions tests/__mocks__/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export const handlers = [
http.get("https://api.github.com/repos/ubiquibot/comment-incentives/issues/22", () => {
return HttpResponse.json(issueGet);
}),
http.get("https://api.github.com/repos/ubiquibot/comment-incentives", () => {
return HttpResponse.json(issueGet);
}),
http.get("https://api.github.com/repos/ubiquibot/comment-incentives/issues/22/events", ({ params: { page } }) => {
return HttpResponse.json(!page ? issueEventsGet : issueEvents2Get);
}),
Expand Down Expand Up @@ -54,4 +57,61 @@ export const handlers = [
http.post("https://api.github.com/app/installations/48381972/access_tokens", () => {
return HttpResponse.json({});
}),
http.get("https://wfzpewmlyiozupulbuur.supabase.co/rest/v1/users", ({ request }) => {
const url = new URL(request.url);
const id = url.searchParams.get("id");
const userId = Number((id as string).match(/\d+/)?.[0]);
const user = db.users.findFirst({
where: {
id: {
equals: userId,
},
},
});
if (!user) {
return HttpResponse.json("User not found", { status: 404 });
}
return HttpResponse.json(user);
}),
http.get("https://wfzpewmlyiozupulbuur.supabase.co/rest/v1/locations", ({ request }) => {
const url = new URL(request.url);
const issue = url.searchParams.get("issue_id");
const node = url.searchParams.get("node_url");
if (!issue) {
return HttpResponse.json(db.locations.findMany({}));
}
const issueId = Number((issue as string).match(/\d+/)?.[0]);
const nodeUrl = (node as string).match(/https.+/)?.[0];
const location = db.locations.findFirst({
where: {
node_url: {
equals: nodeUrl,
},
issue_id: {
equals: issueId,
},
},
});
if (!location) {
return HttpResponse.json("Location not found", { status: 404 });
}
return HttpResponse.json(location);
}),
http.post("https://wfzpewmlyiozupulbuur.supabase.co/rest/v1/locations", async ({ request }) => {
const data = await request.json();
if (!data) {
return HttpResponse.error();
}
const createdLocation = db.locations.create(data as Record<string, string>);
return HttpResponse.json(createdLocation);
}),
http.post("https://wfzpewmlyiozupulbuur.supabase.co/rest/v1/permits", async ({ request }) => {
const data = (await request.json()) as Record<string, string | number>;
if (!data) {
return HttpResponse.error();
}
data.id = db.permits.count() + 1;
const createdPermit = db.permits.create(data);
return HttpResponse.json(createdPermit);
}),
];
Loading
Loading