Nothing found.
diff --git a/apps/portal/src/server/router/offers/offers-profile-router.ts b/apps/portal/src/server/router/offers/offers-profile-router.ts
index d395c9ec..9cac7feb 100644
--- a/apps/portal/src/server/router/offers/offers-profile-router.ts
+++ b/apps/portal/src/server/router/offers/offers-profile-router.ts
@@ -1,4 +1,4 @@
-import crypto, { randomUUID } from 'crypto';
+import crypto from 'crypto';
import { z } from 'zod';
import { JobType } from '@prisma/client';
import * as trpc from '@trpc/server';
@@ -10,6 +10,7 @@ import {
} from '~/mappers/offers-mappers';
import { baseCurrencyString } from '~/utils/offers/currency';
import { convert } from '~/utils/offers/currency/currencyExchange';
+import { generateRandomName, generateRandomStringForToken } from '~/utils/offers/randomGenerator';
import { createValidationRegex } from '~/utils/offers/zodRegex';
import { createRouter } from '../context';
@@ -263,9 +264,12 @@ export const offersProfileRouter = createRouter()
// TODO: add more
const token = crypto
.createHash('sha256')
- .update(Date.now().toString())
+ .update(Date.now().toString() + generateRandomStringForToken())
.digest('hex');
+ // Generate random name until unique
+ const uniqueName: string = await generateRandomName();
+
const profile = await ctx.prisma.offersProfile.create({
data: {
background: {
@@ -538,7 +542,7 @@ export const offersProfileRouter = createRouter()
}),
),
},
- profileName: randomUUID().substring(0, 10),
+ profileName: uniqueName,
},
});
diff --git a/apps/portal/src/server/router/offers/offers.ts b/apps/portal/src/server/router/offers/offers.ts
index 1a1bd354..b333fa66 100644
--- a/apps/portal/src/server/router/offers/offers.ts
+++ b/apps/portal/src/server/router/offers/offers.ts
@@ -103,22 +103,32 @@ export const offersRouter = createRouter().query('list', {
monthYearReceived: order,
}
: sortingKey === sortingKeysMap.totalCompensation
- ? {
- offersIntern: {
- monthlySalary: {
- baseValue: order,
+ ? [
+ {
+ offersIntern: {
+ monthlySalary: {
+ baseValue: order,
+ },
},
},
- }
+ {
+ monthYearReceived: 'desc',
+ },
+ ]
: sortingKey === sortingKeysMap.totalYoe
- ? {
- profile: {
- background: {
- totalYoe: order,
+ ? [
+ {
+ profile: {
+ background: {
+ totalYoe: order,
+ },
},
},
- }
- : undefined,
+ {
+ monthYearReceived: 'desc',
+ },
+ ]
+ : { monthYearReceived: 'desc' },
where: {
AND: [
{
@@ -207,22 +217,32 @@ export const offersRouter = createRouter().query('list', {
monthYearReceived: order,
}
: sortingKey === sortingKeysMap.totalCompensation
- ? {
- offersFullTime: {
- totalCompensation: {
- baseValue: order,
+ ? [
+ {
+ offersIntern: {
+ monthlySalary: {
+ baseValue: order,
+ },
},
},
- }
+ {
+ monthYearReceived: 'desc',
+ },
+ ]
: sortingKey === sortingKeysMap.totalYoe
- ? {
- profile: {
- background: {
- totalYoe: order,
+ ? [
+ {
+ profile: {
+ background: {
+ totalYoe: order,
+ },
},
},
- }
- : undefined,
+ {
+ monthYearReceived: 'desc',
+ },
+ ]
+ : { monthYearReceived: 'desc' },
where: {
AND: [
{
diff --git a/apps/portal/src/server/router/questions-answer-comment-router.ts b/apps/portal/src/server/router/questions-answer-comment-router.ts
index 75977b41..63a9ede4 100644
--- a/apps/portal/src/server/router/questions-answer-comment-router.ts
+++ b/apps/portal/src/server/router/questions-answer-comment-router.ts
@@ -166,13 +166,29 @@ export const questionsAnswerCommentRouter = createProtectedRouter()
const { answerCommentId, vote } = input;
- return await ctx.prisma.questionsAnswerCommentVote.create({
- data: {
- answerCommentId,
- userId,
- vote,
- },
- });
+ const incrementValue = vote === Vote.UPVOTE ? 1 : -1;
+
+ const [answerCommentVote] = await ctx.prisma.$transaction([
+ ctx.prisma.questionsAnswerCommentVote.create({
+ data: {
+ answerCommentId,
+ userId,
+ vote,
+ },
+ }),
+ ctx.prisma.questionsAnswerComment.update({
+ data: {
+ upvotes: {
+ increment: incrementValue,
+ },
+ },
+ where: {
+ id: answerCommentId,
+ },
+ }),
+ ]);
+
+ return answerCommentVote;
},
})
.mutation('updateVote', {
@@ -198,14 +214,30 @@ export const questionsAnswerCommentRouter = createProtectedRouter()
});
}
- return await ctx.prisma.questionsAnswerCommentVote.update({
- data: {
- vote,
- },
- where: {
- id,
- },
- });
+ const incrementValue = vote === Vote.UPVOTE ? 2 : -2;
+
+ const [answerCommentVote] = await ctx.prisma.$transaction([
+ ctx.prisma.questionsAnswerCommentVote.update({
+ data: {
+ vote,
+ },
+ where: {
+ id,
+ },
+ }),
+ ctx.prisma.questionsAnswerComment.update({
+ data: {
+ upvotes: {
+ increment: incrementValue,
+ },
+ },
+ where: {
+ id: voteToUpdate.answerCommentId,
+ },
+ }),
+ ]);
+
+ return answerCommentVote;
},
})
.mutation('deleteVote', {
@@ -229,10 +261,26 @@ export const questionsAnswerCommentRouter = createProtectedRouter()
});
}
- return await ctx.prisma.questionsAnswerCommentVote.delete({
- where: {
- id: input.id,
- },
- });
+ const incrementValue = voteToDelete.vote === Vote.UPVOTE ? -1 : 1;
+
+ const [answerCommentVote] = await ctx.prisma.$transaction([
+ ctx.prisma.questionsAnswerCommentVote.delete({
+ where: {
+ id: input.id,
+ },
+ }),
+ ctx.prisma.questionsAnswerComment.update({
+ data: {
+ upvotes: {
+ increment: incrementValue,
+ },
+ },
+ where: {
+ id: voteToDelete.answerCommentId,
+ },
+ }),
+ ]);
+ return answerCommentVote;
+
},
});
diff --git a/apps/portal/src/server/router/questions-answer-router.ts b/apps/portal/src/server/router/questions-answer-router.ts
index 5d386854..e2318ba7 100644
--- a/apps/portal/src/server/router/questions-answer-router.ts
+++ b/apps/portal/src/server/router/questions-answer-router.ts
@@ -229,13 +229,28 @@ export const questionsAnswerRouter = createProtectedRouter()
const { answerId, vote } = input;
- return await ctx.prisma.questionsAnswerVote.create({
- data: {
- answerId,
- userId,
- vote,
- },
- });
+ const incrementValue = vote === Vote.UPVOTE ? 1 : -1;
+
+ const [answerVote] = await ctx.prisma.$transaction([
+ ctx.prisma.questionsAnswerVote.create({
+ data: {
+ answerId,
+ userId,
+ vote,
+ },
+ }),
+ ctx.prisma.questionsAnswer.update({
+ data: {
+ upvotes: {
+ increment: incrementValue,
+ },
+ },
+ where: {
+ id: answerId,
+ },
+ }),
+ ]);
+ return answerVote;
},
})
.mutation('updateVote', {
@@ -260,14 +275,30 @@ export const questionsAnswerRouter = createProtectedRouter()
});
}
- return await ctx.prisma.questionsAnswerVote.update({
- data: {
- vote,
- },
- where: {
- id,
- },
- });
+ const incrementValue = vote === Vote.UPVOTE ? 2 : -2;
+
+ const [questionsAnswerVote] = await ctx.prisma.$transaction([
+ ctx.prisma.questionsAnswerVote.update({
+ data: {
+ vote,
+ },
+ where: {
+ id,
+ },
+ }),
+ ctx.prisma.questionsAnswer.update({
+ data: {
+ upvotes: {
+ increment: incrementValue,
+ },
+ },
+ where: {
+ id: voteToUpdate.answerId,
+ },
+ }),
+ ]);
+
+ return questionsAnswerVote;
},
})
.mutation('deleteVote', {
@@ -290,10 +321,26 @@ export const questionsAnswerRouter = createProtectedRouter()
});
}
- return await ctx.prisma.questionsAnswerVote.delete({
- where: {
- id: input.id,
- },
- });
+ const incrementValue = voteToDelete.vote === Vote.UPVOTE ? -1 : 1;
+
+ const [questionsAnswerVote] = await ctx.prisma.$transaction([
+ ctx.prisma.questionsAnswerVote.delete({
+ where: {
+ id: input.id,
+ },
+ }),
+ ctx.prisma.questionsAnswer.update({
+ data: {
+ upvotes: {
+ increment: incrementValue,
+ },
+ },
+ where: {
+ id: voteToDelete.answerId,
+ },
+ }),
+ ]);
+ return questionsAnswerVote;
+
},
});
diff --git a/apps/portal/src/server/router/questions-question-comment-router.ts b/apps/portal/src/server/router/questions-question-comment-router.ts
index e2f786f9..28cf3b9d 100644
--- a/apps/portal/src/server/router/questions-question-comment-router.ts
+++ b/apps/portal/src/server/router/questions-question-comment-router.ts
@@ -166,13 +166,28 @@ export const questionsQuestionCommentRouter = createProtectedRouter()
const userId = ctx.session?.user?.id;
const { questionCommentId, vote } = input;
- return await ctx.prisma.questionsQuestionCommentVote.create({
- data: {
- questionCommentId,
- userId,
- vote,
- },
- });
+ const incrementValue: number = vote === Vote.UPVOTE ? 1 : -1;
+
+ const [ questionCommentVote ] = await ctx.prisma.$transaction([
+ ctx.prisma.questionsQuestionCommentVote.create({
+ data: {
+ questionCommentId,
+ userId,
+ vote,
+ },
+ }),
+ ctx.prisma.questionsQuestionComment.update({
+ data: {
+ upvotes: {
+ increment: incrementValue,
+ },
+ },
+ where: {
+ id: questionCommentId,
+ },
+ }),
+ ]);
+ return questionCommentVote;
},
})
.mutation('updateVote', {
@@ -198,14 +213,30 @@ export const questionsQuestionCommentRouter = createProtectedRouter()
});
}
- return await ctx.prisma.questionsQuestionCommentVote.update({
- data: {
- vote,
- },
- where: {
- id,
- },
- });
+ const incrementValue = vote === Vote.UPVOTE ? 2 : -2;
+
+ const [questionCommentVote] = await ctx.prisma.$transaction([
+ ctx.prisma.questionsQuestionCommentVote.update({
+ data: {
+ vote,
+ },
+ where: {
+ id,
+ },
+ }),
+ ctx.prisma.questionsQuestionComment.update({
+ data: {
+ upvotes: {
+ increment: incrementValue,
+ },
+ },
+ where: {
+ id: voteToUpdate.questionCommentId,
+ },
+ }),
+ ]);
+
+ return questionCommentVote;
},
})
.mutation('deleteVote', {
@@ -229,10 +260,25 @@ export const questionsQuestionCommentRouter = createProtectedRouter()
});
}
- return await ctx.prisma.questionsQuestionCommentVote.delete({
- where: {
- id: input.id,
- },
- });
+ const incrementValue = voteToDelete.vote === Vote.UPVOTE ? -1 : 1;
+
+ const [questionCommentVote] = await ctx.prisma.$transaction([
+ ctx.prisma.questionsQuestionCommentVote.delete({
+ where: {
+ id: input.id,
+ },
+ }),
+ ctx.prisma.questionsQuestionComment.update({
+ data: {
+ upvotes: {
+ increment: incrementValue,
+ },
+ },
+ where: {
+ id: voteToDelete.questionCommentId,
+ },
+ }),
+ ]);
+ return questionCommentVote;
},
});
diff --git a/apps/portal/src/server/router/questions-question-encounter-router.ts b/apps/portal/src/server/router/questions-question-encounter-router.ts
index 9612d076..75265f85 100644
--- a/apps/portal/src/server/router/questions-question-encounter-router.ts
+++ b/apps/portal/src/server/router/questions-question-encounter-router.ts
@@ -27,9 +27,13 @@ export const questionsQuestionEncounterRouter = createProtectedRouter()
const locationCounts: Record
= {};
const roleCounts: Record = {};
+ let latestSeenAt = questionEncountersData[0].seenAt;
+
for (let i = 0; i < questionEncountersData.length; i++) {
const encounter = questionEncountersData[i];
+ latestSeenAt = latestSeenAt < encounter.seenAt ? encounter.seenAt : latestSeenAt;
+
if (!(encounter.company!.name in companyCounts)) {
companyCounts[encounter.company!.name] = 1;
}
@@ -48,6 +52,7 @@ export const questionsQuestionEncounterRouter = createProtectedRouter()
const questionEncounter: AggregatedQuestionEncounter = {
companyCounts,
+ latestSeenAt,
locationCounts,
roleCounts,
};
diff --git a/apps/portal/src/server/router/questions-question-router.ts b/apps/portal/src/server/router/questions-question-router.ts
index 3768b852..3c4f8f8b 100644
--- a/apps/portal/src/server/router/questions-question-router.ts
+++ b/apps/portal/src/server/router/questions-question-router.ts
@@ -11,9 +11,16 @@ export const questionsQuestionRouter = createProtectedRouter()
.query('getQuestionsByFilter', {
input: z.object({
companyNames: z.string().array(),
+ cursor: z
+ .object({
+ idCursor: z.string().optional(),
+ lastSeenCursor: z.date().optional(),
+ upvoteCursor: z.number().optional(),
+ })
+ .nullish(),
endDate: z.date().default(new Date()),
+ limit: z.number().min(1).default(50),
locations: z.string().array(),
- pageSize: z.number().default(50),
questionTypes: z.nativeEnum(QuestionsQuestionType).array(),
roles: z.string().array(),
sortOrder: z.nativeEnum(SortOrder),
@@ -21,16 +28,36 @@ export const questionsQuestionRouter = createProtectedRouter()
startDate: z.date().optional(),
}),
async resolve({ ctx, input }) {
+ const { cursor } = input;
+
const sortCondition =
input.sortType === SortType.TOP
- ? {
- upvotes: input.sortOrder,
- }
- : {
- lastSeenAt: input.sortOrder,
- };
+ ? [
+ {
+ upvotes: input.sortOrder,
+ },
+ {
+ id: input.sortOrder,
+ },
+ ]
+ : [
+ {
+ lastSeenAt: input.sortOrder,
+ },
+ {
+ id: input.sortOrder,
+ },
+ ];
+
+ const toSkip = cursor ? 1 : 0;
const questionsData = await ctx.prisma.questionsQuestion.findMany({
+ cursor:
+ cursor !== undefined
+ ? {
+ id: cursor ? cursor!.idCursor : undefined,
+ }
+ : undefined,
include: {
_count: {
select: {
@@ -53,9 +80,9 @@ export const questionsQuestionRouter = createProtectedRouter()
},
votes: true,
},
- orderBy: {
- ...sortCondition,
- },
+ orderBy: sortCondition,
+ skip: toSkip,
+ take: input.limit + 1,
where: {
...(input.questionTypes.length > 0
? {
@@ -98,7 +125,7 @@ export const questionsQuestionRouter = createProtectedRouter()
},
});
- return questionsData.map((data) => {
+ const processedQuestionsData = questionsData.map((data) => {
const votes: number = data.votes.reduce(
(previousValue: number, currentValue) => {
let result: number = previousValue;
@@ -116,23 +143,78 @@ export const questionsQuestionRouter = createProtectedRouter()
0,
);
+ const companyCounts: Record = {};
+ const locationCounts: Record = {};
+ const roleCounts: Record = {};
+
+ let latestSeenAt = data.encounters[0].seenAt;
+
+ for (let i = 0; i < data.encounters.length; i++) {
+ const encounter = data.encounters[i];
+
+ latestSeenAt =
+ latestSeenAt < encounter.seenAt ? encounter.seenAt : latestSeenAt;
+
+ if (!(encounter.company!.name in companyCounts)) {
+ companyCounts[encounter.company!.name] = 1;
+ }
+ companyCounts[encounter.company!.name] += 1;
+
+ if (!(encounter.location in locationCounts)) {
+ locationCounts[encounter.location] = 1;
+ }
+ locationCounts[encounter.location] += 1;
+
+ if (!(encounter.role in roleCounts)) {
+ roleCounts[encounter.role] = 1;
+ }
+ roleCounts[encounter.role] += 1;
+ }
+
const question: Question = {
- company: data.encounters[0].company!.name ?? 'Unknown company',
+ aggregatedQuestionEncounters: {
+ companyCounts,
+ latestSeenAt,
+ locationCounts,
+ roleCounts,
+ },
content: data.content,
id: data.id,
- location: data.encounters[0].location ?? 'Unknown location',
numAnswers: data._count.answers,
numComments: data._count.comments,
numVotes: votes,
receivedCount: data.encounters.length,
- role: data.encounters[0].role ?? 'Unknown role',
- seenAt: data.encounters[0].seenAt,
+ seenAt: latestSeenAt,
type: data.questionType,
updatedAt: data.updatedAt,
user: data.user?.name ?? '',
};
return question;
});
+
+ let nextCursor: typeof cursor | undefined = undefined;
+
+ if (questionsData.length > input.limit) {
+ const nextItem = questionsData.pop()!;
+ processedQuestionsData.pop();
+
+ const nextIdCursor: string | undefined = nextItem.id;
+ const nextLastSeenCursor =
+ input.sortType === SortType.NEW ? nextItem.lastSeenAt : undefined;
+ const nextUpvoteCursor =
+ input.sortType === SortType.TOP ? nextItem.upvotes : undefined;
+
+ nextCursor = {
+ idCursor: nextIdCursor,
+ lastSeenCursor: nextLastSeenCursor,
+ upvoteCursor: nextUpvoteCursor,
+ };
+ }
+
+ return {
+ data: processedQuestionsData,
+ nextCursor,
+ };
},
})
.query('getQuestionById', {
@@ -190,16 +272,45 @@ export const questionsQuestionRouter = createProtectedRouter()
0,
);
+ const companyCounts: Record = {};
+ const locationCounts: Record = {};
+ const roleCounts: Record = {};
+
+ let latestSeenAt = questionData.encounters[0].seenAt;
+
+ for (const encounter of questionData.encounters) {
+ latestSeenAt =
+ latestSeenAt < encounter.seenAt ? encounter.seenAt : latestSeenAt;
+
+ if (!(encounter.company!.name in companyCounts)) {
+ companyCounts[encounter.company!.name] = 1;
+ }
+ companyCounts[encounter.company!.name] += 1;
+
+ if (!(encounter.location in locationCounts)) {
+ locationCounts[encounter.location] = 1;
+ }
+ locationCounts[encounter.location] += 1;
+
+ if (!(encounter.role in roleCounts)) {
+ roleCounts[encounter.role] = 1;
+ }
+ roleCounts[encounter.role] += 1;
+ }
+
const question: Question = {
- company: questionData.encounters[0].company!.name ?? 'Unknown company',
+ aggregatedQuestionEncounters: {
+ companyCounts,
+ latestSeenAt,
+ locationCounts,
+ roleCounts,
+ },
content: questionData.content,
id: questionData.id,
- location: questionData.encounters[0].location ?? 'Unknown location',
numAnswers: questionData._count.answers,
numComments: questionData._count.comments,
numVotes: votes,
receivedCount: questionData.encounters.length,
- role: questionData.encounters[0].role ?? 'Unknown role',
seenAt: questionData.encounters[0].seenAt,
type: questionData.questionType,
updatedAt: questionData.updatedAt,
diff --git a/apps/portal/src/types/questions.d.ts b/apps/portal/src/types/questions.d.ts
index 4157eb37..aea8d31e 100644
--- a/apps/portal/src/types/questions.d.ts
+++ b/apps/portal/src/types/questions.d.ts
@@ -1,16 +1,13 @@
import type { QuestionsQuestionType } from '@prisma/client';
export type Question = {
- // TODO: company, location, role maps
- company: string;
+ aggregatedQuestionEncounters: AggregatedQuestionEncounter;
content: string;
id: string;
- location: string;
numAnswers: number;
numComments: number;
numVotes: number;
receivedCount: number;
- role: string;
seenAt: Date;
type: QuestionsQuestionType;
updatedAt: Date;
@@ -19,6 +16,7 @@ export type Question = {
export type AggregatedQuestionEncounter = {
companyCounts: Record;
+ latestSeenAt: Date;
locationCounts: Record;
roleCounts: Record;
};
diff --git a/apps/portal/src/utils/offers/randomGenerator.ts b/apps/portal/src/utils/offers/randomGenerator.ts
new file mode 100644
index 00000000..c0a05ac9
--- /dev/null
+++ b/apps/portal/src/utils/offers/randomGenerator.ts
@@ -0,0 +1,44 @@
+import type { Config} from 'unique-names-generator';
+import { countries, names } from 'unique-names-generator';
+import { adjectives, animals,colors, uniqueNamesGenerator } from 'unique-names-generator';
+import { PrismaClient } from '@prisma/client';
+
+const prisma = new PrismaClient()
+
+const customConfig: Config = {
+ dictionaries: [adjectives, colors, animals],
+ length: 3,
+ separator: '-',
+};
+
+export async function generateRandomName(): Promise {
+ let uniqueName: string = uniqueNamesGenerator(customConfig);
+
+ let sameNameProfiles = await prisma.offersProfile.findMany({
+ where: {
+ profileName: uniqueName
+ }
+ })
+
+ while (sameNameProfiles.length !== 0) {
+ uniqueName = uniqueNamesGenerator(customConfig);
+ sameNameProfiles = await prisma.offersProfile.findMany({
+ where: {
+ profileName: uniqueName
+ }
+ })
+ }
+
+ return uniqueName
+}
+
+const tokenConfig: Config = {
+ dictionaries: [adjectives, colors, animals, countries, names]
+ .sort((_a, _b) => 0.5 - Math.random()),
+ length: 5,
+ separator: '-',
+};
+
+export function generateRandomStringForToken(): string {
+ return uniqueNamesGenerator(tokenConfig)
+}
\ No newline at end of file
diff --git a/apps/portal/src/utils/offers/time.tsx b/apps/portal/src/utils/offers/time.tsx
index 4f68adeb..ffa3c545 100644
--- a/apps/portal/src/utils/offers/time.tsx
+++ b/apps/portal/src/utils/offers/time.tsx
@@ -25,9 +25,11 @@ export function timeSinceNow(date: Date | number | string) {
}
interval = seconds / 60;
if (interval > 1) {
- return `${Math.floor(interval)} minutes`;
+ const time: number = Math.floor(interval);
+ return time === 1 ? `${time} minute` : `${time} minutes`;
}
- return `${Math.floor(interval)} seconds`;
+ const time: number = Math.floor(interval);
+ return time === 1 ? `${time} second` : `${time} seconds`;
}
export function formatDate(value: Date | number | string) {
diff --git a/yarn.lock b/yarn.lock
index f5517ea1..f2dd81c2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -14405,6 +14405,11 @@ unique-filename@^1.1.1:
dependencies:
unique-slug "^2.0.0"
+unique-names-generator@^4.7.1:
+ version "4.7.1"
+ resolved "https://registry.yarnpkg.com/unique-names-generator/-/unique-names-generator-4.7.1.tgz#966407b12ba97f618928f77322cfac8c80df5597"
+ integrity sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow==
+
unique-slug@^2.0.0:
version "2.0.2"
resolved "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz"