Skip to content

Commit

Permalink
Payment DB model and fetching (#143)
Browse files Browse the repository at this point in the history
* Adds payment address api

* Adds fetching current registered payment for project

* Create onboarding flow

* Fix lint issues

* Added sliders

* Lint issues

* Remove table header

* Add migrations for tables

* Update accrding to PR comments

* lint

---------

Co-authored-by: Alec <alecerasmus2@gmail.com>
  • Loading branch information
sudoFerraz and AlecErasmus authored May 6, 2024
1 parent 36f2846 commit 0835464
Show file tree
Hide file tree
Showing 15 changed files with 578 additions and 19 deletions.
19 changes: 19 additions & 0 deletions cred-manager/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ model Team {
ContributionRequest ContributionRequest[]
single_repository Boolean @default(false)
Attestation Attestation[]
PaymentAddress PaymentAddress?
}

model ContributionCalculation {
Expand Down Expand Up @@ -148,3 +149,21 @@ model UserWallet {
address String
created_at DateTime @default(now()) @db.Timestamp(6)
}

model PaymentAddress {
id String @id @default(uuid())
chain_id String @db.VarChar(255)
team_id String @unique
Team Team @relation(fields: [team_id], references: [id], onDelete: Cascade)
wallet_address String @db.VarChar(255)
created_at DateTime @default(now()) @db.Timestamp(6)
PaymentRecipient PaymentRecipient[]
}

model PaymentRecipient {
id String @id @default(uuid())
wallet_address String @db.VarChar(255)
payment_percentage Float
payment_address_id String
PaymentAddress PaymentAddress @relation(fields: [payment_address_id], references: [id], onDelete: Cascade)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-- CreateTable
CREATE TABLE "PaymentAddress" (
"id" TEXT NOT NULL,
"chain_id" VARCHAR(255) NOT NULL,
"team_id" TEXT NOT NULL,
"wallet_address" VARCHAR(255) NOT NULL,
"created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "PaymentAddress_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "PaymentRecipient" (
"id" TEXT NOT NULL,
"wallet_address" VARCHAR(255) NOT NULL,
"payment_percentage" DOUBLE PRECISION NOT NULL,
"payment_address_id" TEXT NOT NULL,

CONSTRAINT "PaymentRecipient_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "PaymentAddress_team_id_key" ON "PaymentAddress"("team_id");

-- AddForeignKey
ALTER TABLE "PaymentAddress" ADD CONSTRAINT "PaymentAddress_team_id_fkey" FOREIGN KEY ("team_id") REFERENCES "Team"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "PaymentRecipient" ADD CONSTRAINT "PaymentRecipient_payment_address_id_fkey" FOREIGN KEY ("payment_address_id") REFERENCES "PaymentAddress"("id") ON DELETE CASCADE ON UPDATE CASCADE;
19 changes: 19 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ model Team {
ContributionRequest ContributionRequest[]
single_repository Boolean @default(false)
Attestation Attestation[]
PaymentAddress PaymentAddress?
}

model ContributionCalculation {
Expand Down Expand Up @@ -148,3 +149,21 @@ model UserWallet {
address String
created_at DateTime @default(now()) @db.Timestamp(6)
}

model PaymentAddress {
id String @id @default(uuid())
chain_id String @db.VarChar(255)
team_id String @unique
Team Team @relation(fields: [team_id], references: [id], onDelete: Cascade)
wallet_address String @db.VarChar(255)
created_at DateTime @default(now()) @db.Timestamp(6)
PaymentRecipient PaymentRecipient[]
}

model PaymentRecipient {
id String @id @default(uuid())
wallet_address String @db.VarChar(255)
payment_percentage Float
payment_address_id String
PaymentAddress PaymentAddress @relation(fields: [payment_address_id], references: [id], onDelete: Cascade)
}
19 changes: 9 additions & 10 deletions src/app/(dashboard)/projects/[projectId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,21 +177,20 @@ export default function TeamDetailsPage({ params }: PageProps) {
team?.single_repository &&
!hasContributionRequest ? (
<>
<Button
className="mr-2"
variant={"destructive"}
onClick={() => {
router.push(`/projects/${teamId}/payments`);
}}
>
Payment Address
</Button>

<GenerateAttestationModal teamId={teamId} />
</>
) : (
<div></div>
)}
<Button
className="mr-2"
variant={"destructive"}
onClick={() => {
router.push(`/projects/${teamId}/payments`);
}}
>
Payment Address
</Button>
<Button
className="mr-2"
onClick={() => {
Expand Down
29 changes: 24 additions & 5 deletions src/app/(dashboard)/projects/[projectId]/payments/page.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,39 @@
import PaymentsOnboarding from "@/components/onboarding/onboardingTutorial";
"use client";

import { PaymentAddressDto } from "@/app/api/payments/service/paymentAddress";
import PaymentsOnboarding from "@/components/payments/paymentsOnboarding";
import axios from "axios";
import { useState } from "react";

interface PageProps {
params: { projectId: string };
}

export default function ProjectPaymentsPage({ params }: PageProps) {
const teamId = params.projectId;
const projectId = params.projectId;
const breadcrumbItems = [
{ title: "Projects", link: "/projects" },
{ title: "Project details", link: `/projects/${teamId}` },
{ title: "Project Payments", link: `/projects/${teamId}/payments` },
{ title: "Project details", link: `/projects/${projectId}` },
{ title: "Project Payments", link: `/projects/${projectId}/payments` },
];
const [projectPaymentAddress, setProjectPaymentAddress] = useState<
PaymentAddressDto | undefined
>();

const handleFetchProjectPaymentAddress = async () => {
const { data } = await axios.get("/api/payments?team_id=" + projectId);
if (data.success && data.paymentAddress) {
setProjectPaymentAddress(data.paymentAddress);
}
};

return (
<>
<PaymentsOnboarding></PaymentsOnboarding>
{projectPaymentAddress != null ? (
<>WIP: Payment coming soon</>
) : (
<PaymentsOnboarding projectId={projectId}></PaymentsOnboarding>
)}
</>
);
}
60 changes: 60 additions & 0 deletions src/app/api/contributors/fetchUserContributors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,31 @@ export async function fetchUserContributorsByTeam(
}
}

export async function fetchUserPaymentContributorsByTeam(
teamId: string,
): Promise<ContributorDto[]> {
try {
// fetch userScores where calculation is part of a team
// which the owner is the userId
const foundUserScores = await prisma.userScore.findMany({
where: {
user_type: "USER",
contribution_calculation: {
Team: {
id: teamId,
},
},
},
});
const userContributors =
transformUserScoresToPaymentContributors(foundUserScores);
return userContributors;
} catch (error) {
console.log(error);
return [];
}
}

export async function fetchUserContributorsInterval(
userId: string,
): Promise<any[]> {
Expand Down Expand Up @@ -147,3 +172,38 @@ export function transformUserScoresToContributors(
}
return contributorsArray;
}

export function transformUserScoresToPaymentContributors(
userScoresArray: UserScoreDto[],
): ContributorDto[] {
// Transforms userScore into contributorDto
// Sums up all contributions and divide the sum of a unique user name
// to reach average per unique user
const contributionScoreSumMap: Record<string, number> = {};
let allContributionsSum: number = 0;
const contributorsArray: ContributorDto[] = [];
userScoresArray.forEach((userScore) => {
if (!contributionScoreSumMap[userScore.username]) {
contributionScoreSumMap[userScore.username] = parseFloat(userScore.score);
} else {
contributionScoreSumMap[userScore.username] += parseFloat(
userScore.score,
);
}
allContributionsSum += parseFloat(userScore.score);
});

for (const [key, value] of Object.entries(contributionScoreSumMap)) {
contributorsArray.push({
userName: key,
contributionScore: value,
contributionScorePercentage: (value / allContributionsSum) * 100,
});
}
contributorsArray.push({
userName: "armitage-labs",
contributionScore: 0,
contributionScorePercentage: 3,
});
return contributorsArray;
}
17 changes: 17 additions & 0 deletions src/app/api/contributors/payments/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { getServerSession } from "next-auth";
import { NextRequest, NextResponse } from "next/server";
import { options } from "../../auth/[...nextauth]/options";
import { fetchUserPaymentContributorsByTeam } from "../fetchUserContributors";

export async function GET(req: NextRequest) {
const teamId = req.nextUrl.searchParams.get("team_id");
const session = await getServerSession(options);
if (session?.userId && teamId) {
const contributorsArray = await fetchUserPaymentContributorsByTeam(teamId);
return NextResponse.json({
success: true,
contributors: contributorsArray,
});
}
return NextResponse.json({ success: false, gitRepos: [] });
}
21 changes: 21 additions & 0 deletions src/app/api/payments/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { getServerSession } from "next-auth";
import { NextRequest, NextResponse } from "next/server";
import { options } from "../auth/[...nextauth]/options";
import { fetchTeamPaymentAddresses } from "./service/paymentAddress";

export async function GET(req: NextRequest) {
const session = await getServerSession(options);
const teamId = req.nextUrl.searchParams.get("team_id");
if (session?.userId && teamId) {
const paymentAddress = await fetchTeamPaymentAddresses(teamId);
return NextResponse.json({
success: true,
paymentAddress: paymentAddress,
});
} else {
return NextResponse.json({
success: false,
paymentAddress: [],
});
}
}
44 changes: 44 additions & 0 deletions src/app/api/payments/service/paymentAddress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import prisma from "db";

export type PaymentAddressDto = {
id: string;
chain_id: string;
team_id: string;
wallet_address: string;
created_at: Date;
payment_receipents: PaymentRecipientDto[];
};

export type PaymentRecipientDto = {
id: string;
wallet_address: string;
payment_percentage: number;
};

export async function fetchTeamPaymentAddresses(
teamId: string,
): Promise<PaymentAddressDto | undefined> {
try {
const foundPaymentAddresses = await prisma.paymentAddress.findUnique({
where: {
team_id: teamId,
},
});
if (!foundPaymentAddresses) {
return;
} else {
const paymentRecipients = await prisma.paymentRecipient.findMany({
where: {
payment_address_id: foundPaymentAddresses.id,
},
});
return {
...foundPaymentAddresses,
payment_receipents: paymentRecipients ? paymentRecipients : [],
};
}
} catch (error) {
console.log(error);
return;
}
}
70 changes: 70 additions & 0 deletions src/components/payments/contributors/contributorsColumns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"use client";

import { ColumnDef } from "@tanstack/react-table";
import { ContributorDto } from "@/app/api/contributors/fetchUserContributors";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import PaymentSlider from "./paymentSlider";

export const ProjectContributorsColumns: ColumnDef<ContributorDto>[] = [
{
id: "avatar",
cell: ({ row }) => {
const contributor = row.original;
return (
<div>
<Avatar className="h-8 w-8">
<AvatarImage
src={`https://github.com/${contributor.userName}.png?size=100`}
alt={contributor.userName}
/>
<AvatarFallback>{contributor.userName[0]}</AvatarFallback>
</Avatar>
</div>
);
},
},
{
accessorKey: "userName",
header: () => <></>,
},
{
accessorKey: "contributionScorePercentage",
header: () => {
return <></>;
},
cell: ({ row }) => {
const contributor = row.original;
return (
<div className="text-xl font-bold text-left pl-4">
{contributor.contributionScorePercentage.toFixed(2) + "%"}
</div>
);
},
},
{
accessorKey: "contributionScore",
header: () => {
return <></>;
},
cell: ({ row }) => {
const contributor = row.original;
return (
<div className="text-xl font-bold text-left pl-14">
{contributor.contributionScore.toFixed(2)}
</div>
);
},
},
{
id: "actions",
cell: ({ row }) => {
return (
<div className="flex ">
<PaymentSlider
onChange={() => console.log("I change")}
></PaymentSlider>
</div>
);
},
},
];
Loading

0 comments on commit 0835464

Please sign in to comment.