import axios from 'axios'; import { Queue } from 'bull'; import * as admin from 'firebase-admin'; import { Repository } from 'typeorm'; import { InjectQueue } from '@nestjs/bull'; import { BadRequestException, Inject, Injectable, InternalServerErrorException, NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { User } from './database/entities'; import { QuestType, RedditAuthorizeDto } from './funki-id.dto'; import { FunkiRedditConfiguration } from './funki-id.module'; type RedditUser = { id: string; username: string; total_karma: number }; @Injectable() export class FunkiIdRedditService { constructor( @Inject('REDDIT_CONFIG') private readonly redditConfig: FunkiRedditConfiguration, @InjectRepository(User, 'funki-id') private readonly userRepo: Repository, @Inject('FIREBASE_AUTH') private readonly firebaseAuth: admin.auth.Auth, @InjectQueue('validate-quest-by-type-queue') private readonly validateQueue: Queue, ) {} async authorize(dto: RedditAuthorizeDto, userData?: User): Promise<{ customToken: string }> { const url = `https://www.reddit.com/api/v1/access_token?grant_type=authorization_code&code=${dto.code}&redirect_uri=${this.redditConfig.redirectUrl}`; const tokenResponseData = await axios .post(url, {}, { auth: { username: this.redditConfig.clientId, password: this.redditConfig.clientSecret } }) .catch(error => { console.log({ error }); throw new BadRequestException(`Invalid authorization code ${dto?.code}`); }); if (tokenResponseData?.status !== 200) { if (tokenResponseData?.data?.error === 'invalid_grant') { throw new BadRequestException('Invalid code'); } throw new InternalServerErrorException(`Cannot get Reddit token ${tokenResponseData?.data?.error}`); } const tokenData = await tokenResponseData?.data; const me = await this.getMe(tokenData.token_type, tokenData.access_token); if (!me) { throw new NotFoundException('Reddit user not found'); } const user = await this.findOrCreateUserByRedditInfo(me, userData); const customToken = await this.firebaseAuth.createCustomToken(user.uid, { redditId: me.id }); await this.validateQueue.add('validate-quest-by-type-job', { funkiId: user.uid, type: QuestType.LINK_SOCIAL, }); return { customToken, }; } private async getMe(tokenType: string, token: string) { const response = await axios.get('https://oauth.reddit.com/api/v1/me', { headers: { Authorization: `${tokenType} ${token}`, }, }); if (response.status !== 200) { return null; } const me: RedditUser = await response.data; return me; } public async findOrCreateUserByRedditInfo(redditUser: RedditUser, userData?: User) { let user = await this.userRepo.findOne({ where: { redditId: redditUser.id } }); if (!!userData?.uid) { if (user) throw new BadRequestException('Reddit Id already linked'); return await this.userRepo.save({ uid: userData.uid, redditId: redditUser.id, reddit: { karma: redditUser?.total_karma, }, }); } if (!user) { const firebaseUser = await this.firebaseAuth.createUser({ displayName: redditUser.username, }); user = await this.userRepo.save({ uid: firebaseUser.uid, name: firebaseUser?.displayName, email: firebaseUser?.email, redditId: redditUser.id, reddit: { karma: redditUser?.total_karma, }, }); } return user; } }