Skip to content
Merged
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
71 changes: 60 additions & 11 deletions packages/core/src/modules/opportunities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ import { id } from '@oyster/utils';

import { getChatCompletion } from '@/infrastructure/ai';
import { job, registerWorker } from '@/infrastructure/bull';
import { OpportunityBullJob } from '@/infrastructure/bull.types';
import {
type GetBullJobData,
OpportunityBullJob,
} from '@/infrastructure/bull.types';
import { track } from '@/infrastructure/mixpanel';
import { getPageContent } from '@/infrastructure/puppeteer';
import { redis } from '@/infrastructure/redis';
import { getMostRelevantCompany } from '@/modules/employment/companies';
import { saveCompanyIfNecessary } from '@/modules/employment/use-cases/save-company-if-necessary';
import { STUDENT_PROFILE_URL } from '@/shared/env';
Expand Down Expand Up @@ -95,6 +99,49 @@ export async function bookmarkOpportunity({
return success({});
}

// "Check for Deleted Opportunity (in Slack)"

type CheckForDeletedOpportunityInput = Pick<
GetBullJobData<'slack.message.change'>,
'channelId' | 'deletedAt' | 'id'
>;

/**
* Checks for a soft-deleted opportunity. This is the case when somebody
* posts a message in an opportunity channel, someone else replies to it, and
* then the original message is soft-deleted (but still exists because there
* are replies).
*/
export async function checkForDeletedOpportunity({
channelId,
deletedAt,
id: messageId,
}: CheckForDeletedOpportunityInput): Promise<void> {
if (!deletedAt) {
return;
}

const isOpportunityChannel = await redis.sismember(
'slack:opportunity_channels',
channelId
);

if (!isOpportunityChannel) {
return;
}

const opportunity = await db
.selectFrom('opportunities')
.select('id')
.where('slackChannelId', '=', channelId)
.where('slackMessageId', '=', messageId)
.executeTakeFirst();

if (opportunity) {
await deleteOpportunity({ opportunityId: opportunity.id });
}
}

// "Create Opportunity"

type CreateOpportunityInput = {
Expand Down Expand Up @@ -251,7 +298,7 @@ export async function createOpportunityTag(
// "Delete Opportunity"

type DeleteOpportunityInput = {
memberId: string;
memberId?: string;
opportunityId: string;
};

Expand All @@ -267,16 +314,18 @@ export async function deleteOpportunity({
memberId,
opportunityId,
}: DeleteOpportunityInput): Promise<Result> {
const hasPermission = await hasOpportunityWritePermission({
memberId,
opportunityId,
});

if (!hasPermission) {
return fail({
code: 403,
error: 'You do not have permission to delete this opportunity.',
if (memberId) {
const hasPermission = await hasOpportunityWritePermission({
memberId,
opportunityId,
});

if (!hasPermission) {
return fail({
code: 403,
error: 'You do not have permission to delete this opportunity.',
});
}
}

await db.transaction().execute(async (trx) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { db } from '@oyster/db';

import { job } from '@/infrastructure/bull';
import { type GetBullJobData } from '@/infrastructure/bull.types';
import { checkForDeletedOpportunity } from '@/modules/opportunities';

export async function changeSlackMessage({
channelId,
Expand All @@ -23,4 +24,6 @@ export async function changeSlackMessage({
threadId: message.threadId || message.id,
});
}

checkForDeletedOpportunity({ channelId, deletedAt, id });
}
Loading