Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions prisma/migrations/20241106125304_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "FarcasterConnection" ADD COLUMN "thankYouCastSent" BOOLEAN DEFAULT FALSE;
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ model FarcasterConnection {
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id])
thankYouCastSent Boolean @default(false)
}

model WorldIdConnection {
Expand Down
8 changes: 6 additions & 2 deletions src/cronJobs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { schedule } from 'node-cron';
import { sendDailyCasts } from './neynar/utils';
import {
sendDailyDelegationCasts,
sendDailyThankYouCast,
} from './neynar/utils';

const sendCastsCronJobTime = '21 1 22 * * *'; // at 22:01 Tehran time every day

Expand All @@ -12,7 +15,8 @@ const sendCastsCronJob = () => {
sendCastsCronJobTime,
async () => {
try {
await sendDailyCasts();
await sendDailyDelegationCasts();
await sendDailyThankYouCast();
} catch (e) {
console.error('sendCastsCronJob error', e);
}
Expand Down
15 changes: 15 additions & 0 deletions src/flow/flow.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,21 @@ export class FlowController {
return 'Success';
}

@UseGuards(AuthGuard)
@ApiOperation({
summary: 'Used to unmark a project as Conflict of Interest',
})
@ApiBody({
type: SetCoIDto,
description: 'Project id',
})
@UseGuards(AuthGuard)
@Post('/unmark-coI')
async unmarkCoI(@Req() { userId }: AuthedReq, @Body() { pid }: SetCoIDto) {
await this.flowService.unsetCoi(userId, pid);
return 'Success';
}

@UseGuards(AuthGuard)
@ApiOperation({
summary: 'Used for a pairwise vote between two projects',
Expand Down
34 changes: 31 additions & 3 deletions src/flow/flow.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,30 @@ export class FlowService {
});
};

unsetCoi = async (userId: number, projectId: number) => {
await this.prismaService.projectCoI.delete({
where: {
userId_projectId: {
projectId,
userId,
},
},
});
};

isCoi = async (userId: number, projectId: number) => {
const res = await this.prismaService.projectCoI.findUnique({
where: {
userId_projectId: {
projectId,
userId,
},
},
});

return !!res;
};

setRating = async (
projectId: number,
userId: number,
Expand Down Expand Up @@ -568,17 +592,20 @@ export class FlowService {
include: { project: true },
});

const withStars = await Promise.all(
const withMoreFields = await Promise.all(
ranking.map(async (el) => ({
...el,
stars: await this.getProjectStars(el.projectId, userId),
coi: await this.isCoi(userId, el.projectId),
})),
);

return withStars.sort((a, b) => b.share - a.share);
return withMoreFields.sort((a, b) => b.share - a.share);
};

undo = async (userId: number, parentCollection: number | null) => {
// TODO: Check if a colleciton is still wip and not finished

const lastVote = await this.prismaService.vote.findFirst({
where: {
userId,
Expand Down Expand Up @@ -758,6 +785,7 @@ export class FlowService {
projectStars,
allProjects,
);
// console.log('real progress:', realProgress);

const progress = Math.min(1, realProgress * 3);

Expand Down Expand Up @@ -808,7 +836,7 @@ export class FlowService {
new Set(allProjects.map((item) => item.implicitCategory)),
).map((cat, index) => ({ name: cat, priority: index * 2 }));

console.log(shuffleArraySeeded(implicitCategoryPriorities, userId));
// console.log(shuffleArraySeeded(implicitCategoryPriorities, userId));

const getImplicitCatScore = (cat: string) =>
shuffleArraySeeded(implicitCategoryPriorities, userId).find(
Expand Down
137 changes: 136 additions & 1 deletion src/neynar/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,141 @@ import { PrismaClient } from '@prisma/client';
import { FarcasterMetadata } from 'src/flow/types';
import neynarClient from './neynarClient';

const findMaxiUsers = async () => {
const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.POSTGRES_PRISMA_URL,
},
},
});

await prisma.$connect();

const collectionCategories = await prisma.project.findMany({
where: {
type: 'collection',
},
select: {
id: true,
},
});
const collectionCategoryIds = collectionCategories.map(
(category) => category.id,
);

const farcasterConnections = await prisma.farcasterConnection.findMany({
where: {
thankYouCastSent: false,
},
select: {
userId: true,
},
});

const userIdsWithFarcaster = farcasterConnections.map(
(connection) => connection.userId,
);

// Find users who have delegated or attested to all categories (Collection + Budget)
const maxiUsers = await prisma.user.findMany({
where: {
id: { in: userIdsWithFarcaster },
// Check that the user has attested or delegated to the Budget category
OR: [
{
budgetAttestations: {
some: {},
},
},
{
budgetDelegation: {
isNot: null,
},
},
],
// Ensure the user has attested or delegated to all Collection categories
AND: [
{
AND: collectionCategoryIds.map((categoryId) => ({
OR: [
{
attestations: {
some: {
collectionId: categoryId,
},
},
},
{
delegations: {
some: {
collectionId: categoryId,
},
},
},
],
})),
},
],
},
select: {
farcasterConnection: {
select: {
userId: true,
metadata: true,
},
},
},
});
return maxiUsers.map((user) => user.farcasterConnection);
};

const sendThankYouCast = async (username: string) => {
if (!farcasterSignerUUID) {
throw new Error(
'Make sure you set FARCASTER_SIGNER_UUID in your .env file',
);
}
await neynarClient.publishCast(
farcasterSignerUUID,
`@${username}! Thank you for playing with Pairwise!

YOU ROCK!`,
);
console.log(`The Thanks you Cast successfully sent to @${username}`);
};

const updateThankYouCastSent = async (userId?: number) => {
if (!userId) return;
const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.POSTGRES_PRISMA_URL,
},
},
});
await prisma.farcasterConnection.update({
where: { userId },
data: { thankYouCastSent: true },
});
await prisma.$disconnect();
};

export const sendDailyThankYouCast = async () => {
const maxiUsers = await findMaxiUsers();
if (!maxiUsers || maxiUsers.length === 0) return;
for (const user of maxiUsers) {
try {
await sendThankYouCast(
(user?.metadata?.valueOf() as FarcasterMetadata)['username'],
);
await updateThankYouCastSent(user?.userId);
} catch (e) {
console.error('Error sending Thank you cast for user ID: ', user?.userId);
}
}
};

/**
* Returns an array of `{fid, username, totalDelegates}` mapping in which `totalDelegates` is
* the number of times that another user has delegated to this specific username/fid
Expand Down Expand Up @@ -77,7 +212,7 @@ export const getDelegations = async (start: number, end?: number) => {
return result;
};

export const sendDailyCasts = async () => {
export const sendDailyDelegationCasts = async () => {
const endTimestamp = new Date();
endTimestamp.setMinutes(0, 0, 0); // set to xx:00:00
const delegations = await getDelegations(
Expand Down