src/user/services/user.service.ts
Properties |
|
Methods |
|
constructor(userRepository: Repository<DbUser>, userHighlightReactionRepository: Repository<DbUserHighlightReaction>, userHighlightRepository: Repository<DbUserHighlight>, userInsightsRepository: Repository<DbInsight>, userCollaborationRepository: Repository<DbUserCollaboration>, userListRepository: Repository<DbUserList>, starSearchUserThreadRepository: Repository<DbStarSearchUserThread>, workspaceRepository: Repository<DbWorkspace>, workspaceMemberRepository: Repository
|
|||||||||||||||||||||||||||||||||||||||
Defined in src/user/services/user.service.ts:39
|
|||||||||||||||||||||||||||||||||||||||
Parameters :
|
Async acceptTerms | ||||||
acceptTerms(id: number)
|
||||||
Defined in src/user/services/user.service.ts:571
|
||||||
Parameters :
Returns :
any
|
baseQueryBuilder |
baseQueryBuilder()
|
Defined in src/user/services/user.service.ts:67
|
Returns :
SelectQueryBuilder<DbUser>
|
Async checkAddUser | ||||||
checkAddUser(user: User)
|
||||||
Defined in src/user/services/user.service.ts:307
|
||||||
Parameters :
Returns :
Promise<DbUser>
|
Private Async createStubUser | ||||||
createStubUser(username: string)
|
||||||
Defined in src/user/services/user.service.ts:464
|
||||||
Parameters :
Returns :
Promise<DbUser>
|
Async deleteUser | ||||||
deleteUser(id: number)
|
||||||
Defined in src/user/services/user.service.ts:610
|
||||||
Parameters :
Returns :
unknown
|
Async filterGivenContributors | |||||||||
filterGivenContributors(contributors: string[], options: RepoContributorsDto)
|
|||||||||
Defined in src/user/services/user.service.ts:294
|
|||||||||
Parameters :
Returns :
Promise<DbUser[]>
|
Async findContributorDevstats | |||||||||
findContributorDevstats(username: string, options: UserDevstatsDto)
|
|||||||||
Defined in src/user/services/user.service.ts:434
|
|||||||||
Parameters :
Returns :
Promise<DbUserDevstats>
|
Async findManyByUsernames | ||||||
findManyByUsernames(usernames: string[])
|
||||||
Defined in src/user/services/user.service.ts:250
|
||||||
Parameters :
Returns :
Promise<DbUser[]>
|
Async findOneByEmail | ||||||
findOneByEmail(email: string)
|
||||||
Defined in src/user/services/user.service.ts:593
|
||||||
Parameters :
Returns :
Promise<DbUser | null>
|
Private Async findOneById | |||||
findOneById(undefined: literal type)
|
|||||
Defined in src/user/services/user.service.ts:108
|
|||||
Parameters :
Returns :
Promise<DbUser>
|
Private Async findOneByUsername | |||||
findOneByUsername(undefined: literal type)
|
|||||
Defined in src/user/services/user.service.ts:185
|
|||||
Parameters :
Returns :
Promise<DbUser>
|
Async findTopUsers | ||||||
findTopUsers(pageOptionsDto: TopUsersDto)
|
||||||
Defined in src/user/services/user.service.ts:79
|
||||||
Parameters :
Returns :
Promise<PageDto<DbTopUser>>
|
Async findUsersByFilter | ||||||
findUsersByFilter(pageOptionsDto: FilteredUsersDto)
|
||||||
Defined in src/user/services/user.service.ts:270
|
||||||
Parameters :
Returns :
Promise<PageDto<DbFilteredUser>>
|
Async getNumWaitlisted |
getNumWaitlisted()
|
Defined in src/user/services/user.service.ts:554
|
Returns :
unknown
|
reactionsQueryBuilder |
reactionsQueryBuilder()
|
Defined in src/user/services/user.service.ts:73
|
Async refreshOneDevstatsByUsername | ||||||
refreshOneDevstatsByUsername(username: string)
|
||||||
Defined in src/user/services/user.service.ts:167
|
||||||
Parameters :
Returns :
Promise<DbUser>
|
Async tryFindUserOrMakeStub | |||||
tryFindUserOrMakeStub(undefined: literal type)
|
|||||
Defined in src/user/services/user.service.ts:395
|
|||||
Parameters :
Returns :
Promise<DbUser>
|
Async tryUpdateContributorOSCR | ||||||
tryUpdateContributorOSCR(user: DbUser)
|
||||||
Defined in src/user/services/user.service.ts:691
|
||||||
Parameters :
Returns :
unknown
|
Async updateEmailPreferences | |||||||||
updateEmailPreferences(id: number, user: UpdateUserEmailPreferencesDto)
|
|||||||||
Defined in src/user/services/user.service.ts:585
|
|||||||||
Parameters :
Returns :
unknown
|
Async updateInterests | |||||||||
updateInterests(id: number, user: UpdateUserProfileInterestsDto)
|
|||||||||
Defined in src/user/services/user.service.ts:581
|
|||||||||
Parameters :
Returns :
unknown
|
Async updateOnboarding | |||||||||
updateOnboarding(id: number, user: UserOnboardingDto)
|
|||||||||
Defined in src/user/services/user.service.ts:529
|
|||||||||
Parameters :
Returns :
any
|
Async updateUser | |||||||||
updateUser(id: number, user: UpdateUserDto)
|
|||||||||
Defined in src/user/services/user.service.ts:496
|
|||||||||
Parameters :
Returns :
unknown
|
Async updateWaitlistStatus | ||||||
updateWaitlistStatus(id: number)
|
||||||
Defined in src/user/services/user.service.ts:544
|
||||||
Parameters :
Returns :
any
|
Private logger |
Default value : new Logger("UserService")
|
Defined in src/user/services/user.service.ts:39
|
import { BadRequestException, Inject, Injectable, Logger, NotFoundException, forwardRef } from "@nestjs/common";
import { Repository, SelectQueryBuilder } from "typeorm";
import { InjectRepository } from "@nestjs/typeorm";
import { User } from "@supabase/supabase-js";
import { Octokit } from "@octokit/rest";
import { ConfigService } from "@nestjs/config";
import { DbStarSearchUserThread } from "../../star-search/entities/user-thread.entity";
import { WaitlistedUsersDto } from "../../auth/dtos/waitlisted.dto";
import { opensaucedEmailRegex, validEmailRegex } from "../../common/util/email";
import { PullRequestGithubEventsService } from "../../timescale/pull_request_github_events.service";
import { DbUser } from "../user.entity";
import { UpdateUserDto } from "../dtos/update-user.dto";
import { UpdateUserProfileInterestsDto } from "../dtos/update-user-interests.dto";
import { UpdateUserEmailPreferencesDto } from "../dtos/update-user-email-prefs.dto";
import { UserOnboardingDto } from "../../auth/dtos/user-onboarding.dto";
import { userNotificationTypes } from "../entities/user-notification.constants";
import { DbUserHighlightReaction } from "../entities/user-highlight-reaction.entity";
import { DbTopUser } from "../entities/top-users.entity";
import { TopUsersDto } from "../dtos/top-users.dto";
import { PageDto } from "../../common/dtos/page.dto";
import { PageMetaDto } from "../../common/dtos/page-meta.dto";
import { DbFilteredUser } from "../entities/filtered-users.entity";
import { FilteredUsersDto } from "../dtos/filtered-users.dto";
import { DbUserHighlight } from "../entities/user-highlight.entity";
import { DbInsight } from "../../insight/entities/insight.entity";
import { DbUserCollaboration } from "../entities/user-collaboration.entity";
import { DbUserList } from "../../user-lists/entities/user-list.entity";
import { DbWorkspace } from "../../workspace/entities/workspace.entity";
import { DbWorkspaceMember, WorkspaceMemberRoleEnum } from "../../workspace/entities/workspace-member.entity";
import { UserDto } from "../dtos/user.dto";
import { ContributorDevstatsService } from "../../timescale/contrib-stats.service";
import { RepoContributorsDto } from "../../repo/dtos/repo-contributors.dto";
import { DbUserDevstats } from "../entities/user-devstats.entity";
import { UserDevstatsDto } from "../dtos/user-devstats.dto";
@Injectable()
export class UserService {
private logger = new Logger("UserService");
constructor(
@InjectRepository(DbUser, "ApiConnection")
private userRepository: Repository<DbUser>,
@InjectRepository(DbUserHighlightReaction, "ApiConnection")
private userHighlightReactionRepository: Repository<DbUserHighlightReaction>,
@InjectRepository(DbUserHighlight, "ApiConnection")
private userHighlightRepository: Repository<DbUserHighlight>,
@InjectRepository(DbInsight, "ApiConnection")
private userInsightsRepository: Repository<DbInsight>,
@InjectRepository(DbUserCollaboration, "ApiConnection")
private userCollaborationRepository: Repository<DbUserCollaboration>,
@InjectRepository(DbUserList, "ApiConnection")
private userListRepository: Repository<DbUserList>,
@InjectRepository(DbStarSearchUserThread, "ApiConnection")
private starSearchUserThreadRepository: Repository<DbStarSearchUserThread>,
@InjectRepository(DbWorkspace, "ApiConnection")
private workspaceRepository: Repository<DbWorkspace>,
@InjectRepository(DbWorkspaceMember, "ApiConnection")
private workspaceMemberRepository: Repository<DbWorkspaceMember>,
@Inject(forwardRef(() => PullRequestGithubEventsService))
private pullRequestGithubEventsService: PullRequestGithubEventsService,
@Inject(forwardRef(() => ContributorDevstatsService))
private contribDevstatsService: ContributorDevstatsService,
private configService: ConfigService
) {}
baseQueryBuilder(): SelectQueryBuilder<DbUser> {
const builder = this.userRepository.createQueryBuilder("users");
return builder;
}
reactionsQueryBuilder(): SelectQueryBuilder<DbUserHighlightReaction> {
const builder = this.userHighlightReactionRepository.createQueryBuilder("user_highlight_reactions");
return builder;
}
async findTopUsers(pageOptionsDto: TopUsersDto): Promise<PageDto<DbTopUser>> {
const queryBuilder = this.reactionsQueryBuilder();
const { userId } = pageOptionsDto;
queryBuilder
.select("users.login as login")
.from(DbUser, "users")
.innerJoin("user_highlights", "user_highlights", "user_highlights.user_id = users.id")
.innerJoin("user_highlight_reactions", "reactions", "reactions.highlight_id = user_highlights.id")
.where("reactions.deleted_at IS NULL");
if (userId) {
queryBuilder.andWhere(
"users.id NOT IN (SELECT following_user_id FROM users_to_users_followers WHERE user_id = :userId AND deleted_at IS NULL)",
{ userId }
);
}
queryBuilder.groupBy("users.login").orderBy("COUNT(reactions.user_id)", "DESC");
queryBuilder.offset(pageOptionsDto.skip).limit(pageOptionsDto.limit);
const [itemCount, entities] = await Promise.all([queryBuilder.getCount(), queryBuilder.getRawMany()]);
const pageMetaDto = new PageMetaDto({ itemCount, pageOptionsDto });
return new PageDto(entities, pageMetaDto);
}
private async findOneById({
id,
withDeleted = false,
includeEmail = false,
}: {
id: number;
withDeleted?: boolean;
includeEmail?: boolean;
}): Promise<DbUser> {
const queryBuilder = this.baseQueryBuilder();
if (withDeleted) {
queryBuilder.withDeleted();
queryBuilder.addSelect("users.deleted_at", "users_deleted_at");
}
queryBuilder
.addSelect(
`(
SELECT COALESCE(COUNT("user_notifications"."id"), 0)
FROM user_notifications
WHERE user_id = :userId
AND user_notifications.type IN (:...userNotificationTypes)
AND user_notifications.read_at IS NULL
)::INTEGER`,
"users_notification_count"
)
.addSelect(
`(
SELECT COALESCE(COUNT("insights"."id"), 0)
FROM insights
LEFT JOIN insight_members ON insights.id = insight_members.insight_id
WHERE insight_members.user_id = :userId
)::INTEGER`,
"users_insights_count"
)
.where("id = :id", { id });
if (includeEmail) {
queryBuilder.addSelect("users.email", "users_email");
}
let item: DbUser | null;
try {
queryBuilder.setParameters({ userId: id, userNotificationTypes });
item = await queryBuilder.getOne();
} catch (e) {
// handle error
item = null;
}
if (!item) {
throw new NotFoundException();
}
return item;
}
async refreshOneDevstatsByUsername(username: string): Promise<DbUser> {
const queryBuilder = this.baseQueryBuilder().where("LOWER(login) = :username", {
username: username.toLowerCase(),
});
const user: DbUser | null = await queryBuilder.getOne();
if (!user) {
throw new NotFoundException();
}
const oscr = await this.tryUpdateContributorOSCR(user);
user.oscr = oscr;
return user;
}
private async findOneByUsername({
username,
withDeleted = false,
options,
}: {
username: string;
withDeleted?: boolean;
options?: UserDto;
}): Promise<DbUser> {
const maintainerRepoIds = options?.maintainerRepoIds?.split(",");
const queryBuilder = this.baseQueryBuilder();
if (withDeleted) {
queryBuilder.withDeleted();
}
queryBuilder
.addSelect(
`(
SELECT COALESCE(COUNT("user_highlights"."id"), 0)
FROM user_highlights
WHERE user_id = users.id
AND user_highlights.deleted_at IS NULL
)::INTEGER`,
"users_highlights_count"
)
.addSelect(
`(
SELECT COALESCE(COUNT("user_follows"."id"), 0)
FROM users_to_users_followers user_follows
WHERE user_id = users.id
AND user_follows.deleted_at IS NULL
)::INTEGER`,
"users_following_count"
)
.addSelect(
`(
SELECT COALESCE(COUNT("user_follows"."id"), 0)
FROM users_to_users_followers user_follows
WHERE following_user_id = users.id
AND user_follows.deleted_at IS NULL
)::INTEGER`,
"users_followers_count"
)
.where("LOWER(login) = :username", { username: username.toLowerCase() });
const item: DbUser | null = await queryBuilder.getOne();
if (!item) {
throw new NotFoundException();
}
const oscr = await this.tryUpdateContributorOSCR(item);
const recentPrCount = await this.pullRequestGithubEventsService.findCountByPrAuthor(username, 30, 0);
const userVelocity = await this.pullRequestGithubEventsService.findVelocityByPrAuthor(username, 30, 0);
const isMaintainer = await this.pullRequestGithubEventsService.isMaintainer(username, maintainerRepoIds);
item.oscr = oscr;
item.recent_pull_request_velocity_count = userVelocity;
item.recent_pull_requests_count = recentPrCount;
item.is_maintainer = isMaintainer;
return item;
}
async findManyByUsernames(usernames: string[]): Promise<DbUser[]> {
const queryBuilder = this.baseQueryBuilder();
const lowerCaseUsernames = usernames.map((username) => username.toLowerCase());
queryBuilder
.where("LOWER(login) IN (:...usernames)", { usernames: lowerCaseUsernames })
.setParameters({ usernames: lowerCaseUsernames });
const items: DbUser[] = await queryBuilder.getMany();
const foundUsernames = items.map((user) => user.login.toLowerCase());
const notFoundUsernames = lowerCaseUsernames.filter((username) => !foundUsernames.includes(username));
if (notFoundUsernames.length > 0) {
throw new NotFoundException(notFoundUsernames);
}
return items;
}
async findUsersByFilter(pageOptionsDto: FilteredUsersDto): Promise<PageDto<DbFilteredUser>> {
const queryBuilder = this.baseQueryBuilder();
const { username, limit } = pageOptionsDto;
if (!username) {
throw new BadRequestException();
}
queryBuilder
.select(["users.login as login", "users.name as full_name"])
.where(`LOWER(users.login) LIKE :username`)
.andWhere("users.type = 'User'")
.setParameters({ username: `%${username.toLowerCase()}%` })
.limit(limit);
queryBuilder.offset(pageOptionsDto.skip).limit(pageOptionsDto.limit);
const [itemCount, entities] = await Promise.all([queryBuilder.getCount(), queryBuilder.getRawMany()]);
const pageMetaDto = new PageMetaDto({ itemCount, pageOptionsDto });
return new PageDto(entities, pageMetaDto);
}
async filterGivenContributors(contributors: string[], options: RepoContributorsDto): Promise<DbUser[]> {
const queryBuilder = this.baseQueryBuilder();
queryBuilder
.where(`LOWER(users.login) IN (:...users)`, { users: contributors })
.andWhere("users.type = 'User'")
.orderBy(options.filter!, options.orderDirection)
.offset(options.skip)
.limit(options.limit);
return queryBuilder.getMany();
}
async checkAddUser(user: User): Promise<DbUser> {
const {
user_metadata: { user_name, email, name },
identities,
confirmed_at,
} = user;
const github = identities!.filter((identity) => identity.provider === "github")[0];
const id = parseInt(github.id, 10);
try {
const user = await this.findOneById({ id, includeEmail: true, withDeleted: true });
if (!user.is_open_sauced_member) {
// create new workspace for user
const newWorkspace = await this.workspaceRepository.save({
name: "Your workspace",
description: "Your personal workspace",
});
if (user.deleted_at) {
await this.userRepository.restore(user.id);
}
await this.userRepository.update(user.id, {
is_open_sauced_member: true,
connected_at: new Date(),
campaign_start_date: new Date(),
personal_workspace_id: newWorkspace.id,
});
await this.workspaceMemberRepository.save({
role: WorkspaceMemberRoleEnum.Owner,
workspace: newWorkspace,
member: user,
});
}
return user;
} catch (e) {
try {
// if we got here but the user exists
const user = await this.tryFindUserOrMakeStub({ username: user_name as string });
if (user) {
this.logger.error(`Found existing user in UserService.checkAddUser fallback: ${user_name as string}`);
return user;
}
} catch (e) {
this.logger.error(`User ${user_name as string} not found, creating new user/personal workspace`);
}
// create new workspace for user
const newWorkspace = await this.workspaceRepository.save({
name: "Your workspace",
description: "Your personal workspace",
});
// create new user
const newUser = await this.userRepository.save({
id,
name: name as string,
is_open_sauced_member: true,
login: user_name as string,
email: email as string,
created_at: new Date(github.created_at),
updated_at: new Date(github.updated_at ?? github.created_at),
connected_at: confirmed_at ? new Date(confirmed_at) : new Date(),
campaign_start_date: confirmed_at ? new Date(confirmed_at) : new Date(),
personal_workspace_id: newWorkspace.id,
});
await this.workspaceMemberRepository.save({
role: WorkspaceMemberRoleEnum.Owner,
workspace: newWorkspace,
member: newUser,
});
return newUser;
}
}
/*
* this function is a special "escape" hatch function that will return the
* queried user by ID or username if they exist in our database. If not, we use
* github's octokit to go stub them out. This should be used in any paths
* where there is a critical possibility of encountering a user that requires
* that requires github metadata in our database.
*/
async tryFindUserOrMakeStub({
userId,
username,
dto,
withDeleted = false,
}: {
userId?: number;
username?: string;
dto?: UserDto;
withDeleted?: boolean;
}): Promise<DbUser> {
if (!userId && !username) {
throw new BadRequestException("either user id or username must be provided");
}
let user;
try {
if (userId) {
user = await this.findOneById({ id: userId, withDeleted });
} else if (username) {
user = await this.findOneByUsername({ username, options: dto, withDeleted });
}
} catch (e) {
// could not find user being added to workspace in our database. Add it.
if (userId) {
throw new BadRequestException("no user by user ID found: must provide user owner/name to create stub user");
} else if (username) {
user = await this.createStubUser(username);
}
}
if (!user) {
throw new NotFoundException("could not find nor create user");
}
return user;
}
async findContributorDevstats(username: string, options: UserDevstatsDto): Promise<DbUserDevstats> {
const range = options.range!;
const user = await this.tryFindUserOrMakeStub({ username, withDeleted: true });
const devstats = await this.contribDevstatsService.findAllContributorStats(
{
range,
skip: 0,
},
[user.login]
);
if (devstats.length !== 1) {
throw new BadRequestException("Error fetching user:", username);
}
return {
...user,
commits: devstats[0].commits,
prs_created: devstats[0].prs_created,
prs_reviewed: devstats[0].prs_reviewed,
issues_created: devstats[0].issues_created,
commit_comments: devstats[0].commit_comments,
issue_comments: devstats[0].issue_comments,
pr_review_comments: devstats[0].pr_review_comments,
comments: devstats[0].comments,
total_contributions: devstats[0].total_contributions,
} as DbUserDevstats;
}
private async createStubUser(username: string): Promise<DbUser> {
const ghAuthToken: string = this.configService.get("github.authToken")!;
// using octokit and GitHub's API, go fetch the user
const octokit = new Octokit({
auth: ghAuthToken,
});
let octoResponse;
try {
octoResponse = await octokit.users.getByUsername({ username });
} catch (error: unknown) {
console.error(error);
throw new BadRequestException("Error fetching user:", username);
}
/*
* create a new, minimum partial user based on GitHub data (primarily, the ID).
* Because our first party databases uses the GitHub IDs as primary keys,
* we can't use an auto generated ID for a stub user.
* This stub user will eventually get picked up by the ETL and more data will get backfilled on them.
*/
return this.userRepository.save({
id: octoResponse.data.id,
type: octoResponse.data.type,
is_open_sauced_member: false,
login: octoResponse.data.login,
});
}
async updateUser(id: number, user: UpdateUserDto) {
try {
if (!validEmailRegex.test(user.email) || opensaucedEmailRegex.test(user.email)) {
throw new BadRequestException("user provided email is not valid");
}
await this.findOneById({ id });
await this.userRepository.update(id, {
name: user.name,
email: user.email,
bio: user.bio ?? "",
url: user.url ?? "",
twitter_username: user.twitter_username ?? "",
company: user.company ?? "",
location: user.location ?? "",
display_local_time: !!user.display_local_time,
timezone: user.timezone,
github_sponsors_url: user.github_sponsors_url ?? "",
linkedin_url: user.linkedin_url ?? "",
discord_url: user.discord_url ?? "",
});
return this.findOneById({ id });
} catch (e) {
if (e instanceof Error) {
throw e;
}
throw new NotFoundException("Unable to update user");
}
}
async updateOnboarding(id: number, user: UserOnboardingDto) {
try {
await this.findOneById({ id });
await this.userRepository.update(id, {
is_onboarded: true,
is_waitlisted: false,
timezone: user.timezone,
interests: user.interests.join(","),
});
} catch (e) {
throw new NotFoundException("Unable to update user onboarding status");
}
}
async updateWaitlistStatus(id: number) {
try {
await this.findOneById({ id });
await this.userRepository.update(id, { is_waitlisted: true });
} catch (e) {
throw new NotFoundException("Unable to update user waitlist status");
}
}
async getNumWaitlisted() {
const queryBuilder = this.userRepository.manager
.createQueryBuilder()
.select("COUNT(*)", "waitlisted_users")
.from("users", "users")
.where("is_waitlisted = true")
.andWhere("is_open_sauced_member = true");
const item = await queryBuilder.getRawOne<WaitlistedUsersDto>();
if (!item) {
throw new NotFoundException("could not derive number of waitlisted_users");
}
return item;
}
async acceptTerms(id: number) {
try {
await this.findOneById({ id });
await this.userRepository.update(id, { accepted_usage_terms: true });
} catch (e) {
throw new NotFoundException("Unable to update accepting usage terms and conditions");
}
}
async updateInterests(id: number, user: UpdateUserProfileInterestsDto) {
return this.userRepository.update(id, { interests: user.interests.join(",") });
}
async updateEmailPreferences(id: number, user: UpdateUserEmailPreferencesDto) {
return this.userRepository.update(id, {
display_email: user.display_email,
receive_collaboration: user.receive_collaboration,
receive_product_updates: user.receive_product_updates,
});
}
async findOneByEmail(email: string): Promise<DbUser | null> {
const queryBuilder = this.baseQueryBuilder();
queryBuilder.where(`users.email = :email`, { email: email.toLowerCase() });
let item: DbUser | null;
try {
item = await queryBuilder.getOne();
} catch (e) {
// handle error
item = null;
}
return item;
}
async deleteUser(id: number) {
try {
const user = await this.findOneById({ id });
/*
* typeORM doesn't play well with soft deletes and foreign key constraints.
* so, we capture all the users's relations and soft delete them manually
* without disrupting the foreign key constraint back to the user
*/
const userAndRelations = await this.userRepository.findOneOrFail({
where: {
id,
},
relations: [
"highlights",
"insights",
"collaborations",
"request_collaborations",
"from_user_notifications",
"lists",
"starsearch_thread",
],
});
await this.userRepository.softDelete(id);
// need to reset these as we're only doing a soft delete.
await this.userRepository.update(id, {
is_onboarded: false,
is_open_sauced_member: false,
});
await Promise.all([
// soft delete the user's highlights
Promise.all(
userAndRelations.highlights.map(async (highlight) => {
await this.userHighlightRepository.softDelete(highlight.id);
})
),
// soft delete the user's insight pages
Promise.all(
userAndRelations.insights.map(async (insight) => {
await this.userInsightsRepository.softDelete(insight.id);
})
),
// soft delete the user's collaborations
Promise.all(
userAndRelations.collaborations.map(async (collab) => {
await this.userCollaborationRepository.softDelete(collab.id);
})
),
// soft delete the user's collaboration requests
Promise.all(
userAndRelations.request_collaborations.map(async (req_collab) => {
await this.userCollaborationRepository.softDelete(req_collab.id);
})
),
// soft delete the user's lists
Promise.all(
userAndRelations.lists.map(async (list) => {
await this.userListRepository.softDelete(list.id);
})
),
// soft delete the user's StarSearch threads
Promise.all(
userAndRelations.starsearch_thread.map(async (thread) => {
await this.starSearchUserThreadRepository.softDelete(thread.id);
})
),
]);
return user;
} catch (e) {
throw new NotFoundException("Unable to delete user");
}
}
async tryUpdateContributorOSCR(user: DbUser) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if ((user as DbUser | undefined)?.login?.endsWith("[bot]")) {
return 0;
}
const now = new Date(
Date.UTC(
new Date().getUTCFullYear(),
new Date().getUTCMonth(),
new Date().getUTCDate(),
new Date().getUTCHours(),
new Date().getUTCMinutes(),
new Date().getUTCSeconds()
)
);
const twentyFourHoursAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
/*
* returns the OSRC early if the last time the devstats was updated was less than 24 hours ago.
* dates are expected to be in UTC, without a time zone
*/
if (user.devstats_updated_at! > twentyFourHoursAgo) {
return user.oscr;
}
const oscr = await this.contribDevstatsService.calculateOpenSourceContributorRating(user.login);
await this.userRepository.update(user.id, {
oscr,
devstats_updated_at: now.toUTCString(),
});
return oscr;
}
}