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
54 changes: 54 additions & 0 deletions backend/src/auth/providers/setup-totp.provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { RefreshToken } from '../entities/refreshToken.entity';
import { User } from '../../users/entities/user.entity';

@Injectable()
export class RefreshTokenRepositoryOperations {
constructor(
@InjectRepository(RefreshToken)
private readonly repo: Repository<RefreshToken>,
) {}

async saveRefreshToken(user: User, token: string): Promise<RefreshToken> {
const expiresAt = this.computeExpiryFromEnv();

const rt = this.repo.create({
userId: user.id,
token,
expiresAt,
revoked: false,
});

return this.repo.save(rt);
}

async revokeToken(token: string): Promise<void> {
await this.repo.update({ token }, { revoked: true });
}

async findValidToken(token: string): Promise<RefreshToken | null> {
const rt = await this.repo.findOne({ where: { token } });
if (!rt) return null;
if (rt.revoked) return null;
if (rt.expiresAt && rt.expiresAt < new Date()) return null;
return rt;
}

private computeExpiryFromEnv(): Date | undefined {
// supports ms number or '7d' etc? We'll keep ms for now.
const raw = process.env.JWT_REFRESH_EXPIRATION;
if (!raw) return undefined;

const ms = Number(raw);
if (Number.isFinite(ms) && ms > 0) {
return new Date(Date.now() + ms);
}
return undefined;
}

async revokeAllRefreshTokens(userId: string): Promise<void> {
await this.repo.update({ userId }, { revoked: true });
}
}
58 changes: 58 additions & 0 deletions backend/src/users/providers/update-member-status.provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Injectable, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from '../entities/user.entity';
import { UserRole } from '../enums/userRoles.enum';
import { CloudinaryService } from '../../cloudinary/cloudinary.service';

@Injectable()
export class UploadProfilePictureProvider {
constructor(
@InjectRepository(User)
private readonly usersRepository: Repository<User>,
private readonly cloudinaryService: CloudinaryService,
) {}

async uploadProfilePicture(
targetUserId: string,
file: Express.Multer.File,
currentUserId: string,
currentUserRole: UserRole,
): Promise<{ id: string; profilePicture: string }> {
if (currentUserId !== targetUserId && currentUserRole !== UserRole.ADMIN) {
throw new BadRequestException(
'You can only update your own profile picture',
);
}

try {
const targetUser = await this.usersRepository.findOne({
where: { id: targetUserId },
});
if (!targetUser) {
throw new BadRequestException('User not found');
}

const uploadResult: any = await this.cloudinaryService.uploadImage(
file,
'profile-pictures',
);

if (targetUser.profilePicture) {
try {
const publicId = this.cloudinaryService.extractPublicIdFromUrl(
targetUser.profilePicture,
);
await this.cloudinaryService.deleteImage(publicId);
} catch {}
}

targetUser.profilePicture = uploadResult.secure_url;
const saved = await this.usersRepository.save(targetUser);

return { id: saved.id, profilePicture: saved.profilePicture };
} catch (error) {
throw new BadRequestException('Failed to upload profile picture');
}
}
}
Loading
Loading