mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-12 15:52:56 +10:00
feat(homepage): add new sections to homepage
This commit is contained in:
@ -8,6 +8,7 @@ import { join } from "path";
|
||||
import { AuthModule } from "./auth/auth.module";
|
||||
import { CacheModule } from "./cache/cache.module";
|
||||
import { ConfigModule } from "./config/config.module";
|
||||
import { ContributorsModule } from "./contributors/contributors.module";
|
||||
import { DatabaseModule } from "./database/database.module";
|
||||
import { HealthModule } from "./health/health.module";
|
||||
import { MailModule } from "./mail/mail.module";
|
||||
@ -36,6 +37,7 @@ import { UtilsModule } from "./utils/utils.module";
|
||||
StorageModule,
|
||||
PrinterModule,
|
||||
TranslationModule,
|
||||
ContributorsModule,
|
||||
|
||||
// Static Assets
|
||||
ServeStaticModule.forRoot({
|
||||
|
||||
@ -166,7 +166,7 @@ export class AuthController {
|
||||
response.status(200).send(data);
|
||||
}
|
||||
|
||||
// Two Factor Authentication Flows
|
||||
// Two-Factor Authentication Flows
|
||||
@ApiTags("Two-Factor Auth")
|
||||
@Post("2fa/setup")
|
||||
@UseGuards(JwtGuard)
|
||||
|
||||
@ -210,7 +210,7 @@ export class AuthService {
|
||||
});
|
||||
}
|
||||
|
||||
// Two Factor Authentication Flows
|
||||
// Two-Factor Authentication Flows
|
||||
async setup2FASecret(email: string) {
|
||||
// If the user already has 2FA enabled, throw an error
|
||||
const user = await this.userService.findOneByIdentifier(email);
|
||||
|
||||
@ -43,7 +43,9 @@ export const configSchema = z.object({
|
||||
SENTRY_DSN: z.string().url().startsWith("https://").optional(),
|
||||
|
||||
// Crowdin (Optional)
|
||||
CROWDIN_PROJECT_ID: z.coerce.number().optional(),
|
||||
CROWDIN_DISTRIBUTION_HASH: z.string().optional(),
|
||||
CROWDIN_ACCESS_TOKEN: z.string().optional(),
|
||||
|
||||
// GitHub (OAuth)
|
||||
GITHUB_CLIENT_ID: z.string().optional(),
|
||||
|
||||
30
apps/server/src/contributors/contributors.controller.ts
Normal file
30
apps/server/src/contributors/contributors.controller.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { Controller, Get } from "@nestjs/common";
|
||||
|
||||
import { UtilsService } from "../utils/utils.service";
|
||||
import { ContributorsService } from "./contributors.service";
|
||||
|
||||
@Controller("contributors")
|
||||
export class ContributorsController {
|
||||
constructor(
|
||||
private readonly contributorsService: ContributorsService,
|
||||
private readonly utils: UtilsService,
|
||||
) {}
|
||||
|
||||
@Get("/github")
|
||||
async githubContributors() {
|
||||
return this.utils.getCachedOrSet(
|
||||
`contributors:github`,
|
||||
async () => this.contributorsService.fetchGitHubContributors(),
|
||||
1000 * 60 * 60 * 24, // 24 hours
|
||||
);
|
||||
}
|
||||
|
||||
@Get("/crowdin")
|
||||
async crowdinContributors() {
|
||||
return this.utils.getCachedOrSet(
|
||||
`contributors:crowdin`,
|
||||
async () => this.contributorsService.fetchCrowdinContributors(),
|
||||
1000 * 60 * 60 * 24, // 24 hours
|
||||
);
|
||||
}
|
||||
}
|
||||
12
apps/server/src/contributors/contributors.module.ts
Normal file
12
apps/server/src/contributors/contributors.module.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { HttpModule } from "@nestjs/axios";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
import { ContributorsController } from "./contributors.controller";
|
||||
import { ContributorsService } from "./contributors.service";
|
||||
|
||||
@Module({
|
||||
imports: [HttpModule],
|
||||
controllers: [ContributorsController],
|
||||
providers: [ContributorsService],
|
||||
})
|
||||
export class ContributorsModule {}
|
||||
55
apps/server/src/contributors/contributors.service.ts
Normal file
55
apps/server/src/contributors/contributors.service.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { HttpService } from "@nestjs/axios";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { ContributorDto } from "@reactive-resume/dto";
|
||||
|
||||
import { Config } from "../config/schema";
|
||||
|
||||
type GitHubResponse = { id: number; login: string; html_url: string; avatar_url: string }[];
|
||||
type CrowdinContributorsResponse = {
|
||||
data: { data: { id: number; username: string; avatarUrl: string } }[];
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class ContributorsService {
|
||||
constructor(
|
||||
private readonly httpService: HttpService,
|
||||
private readonly configService: ConfigService<Config>,
|
||||
) {}
|
||||
|
||||
async fetchGitHubContributors() {
|
||||
const response = await this.httpService.axiosRef.get(
|
||||
`https://api.github.com/repos/AmruthPillai/Reactive-Resume/contributors`,
|
||||
);
|
||||
const data = response.data as GitHubResponse;
|
||||
|
||||
return data.map((user) => {
|
||||
return {
|
||||
id: user.id,
|
||||
name: user.login,
|
||||
url: user.html_url,
|
||||
avatar: user.avatar_url,
|
||||
} satisfies ContributorDto;
|
||||
});
|
||||
}
|
||||
|
||||
async fetchCrowdinContributors() {
|
||||
const projectId = this.configService.get("CROWDIN_PROJECT_ID");
|
||||
const accessToken = this.configService.get("CROWDIN_ACCESS_TOKEN");
|
||||
|
||||
const response = await this.httpService.axiosRef.get(
|
||||
`https://api.crowdin.com/api/v2/projects/${projectId}/members`,
|
||||
{ headers: { Authorization: `Bearer ${accessToken}` } },
|
||||
);
|
||||
const { data } = response.data as CrowdinContributorsResponse;
|
||||
|
||||
return data.map(({ data }) => {
|
||||
return {
|
||||
id: data.id,
|
||||
name: data.username,
|
||||
url: `https://crowdin.com/profile/${data.username}`,
|
||||
avatar: data.avatarUrl,
|
||||
} satisfies ContributorDto;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -133,7 +133,7 @@ export class PrinterService {
|
||||
return tempHtml;
|
||||
}, pageElement);
|
||||
|
||||
pagesBuffer.push(await page.pdf({ width, height }));
|
||||
pagesBuffer.push(await page.pdf({ width, height, printBackground: true }));
|
||||
|
||||
await page.evaluate((tempHtml: string) => {
|
||||
document.body.innerHTML = tempHtml;
|
||||
|
||||
@ -126,7 +126,6 @@ export class ResumeController {
|
||||
|
||||
return { url };
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
Logger.error(error);
|
||||
throw new InternalServerErrorException(error);
|
||||
}
|
||||
|
||||
@ -1,34 +1,31 @@
|
||||
import { HttpService } from "@nestjs/axios";
|
||||
import { Controller, Get, Header, Param } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
|
||||
import { Config } from "../config/schema";
|
||||
import { UtilsService } from "../utils/utils.service";
|
||||
import { TranslationService } from "./translation.service";
|
||||
|
||||
@Controller("translation")
|
||||
export class TranslationController {
|
||||
constructor(
|
||||
private readonly httpService: HttpService,
|
||||
private readonly configService: ConfigService<Config>,
|
||||
private readonly translationService: TranslationService,
|
||||
private readonly utils: UtilsService,
|
||||
) {}
|
||||
|
||||
private async fetchTranslations(locale: string) {
|
||||
const distributionHash = this.configService.get("CROWDIN_DISTRIBUTION_HASH");
|
||||
const response = await this.httpService.axiosRef.get(
|
||||
`https://distributions.crowdin.net/${distributionHash}/content/${locale}/messages.json`,
|
||||
@Get("/languages")
|
||||
async languages() {
|
||||
return this.utils.getCachedOrSet(
|
||||
`translation:languages`,
|
||||
async () => this.translationService.fetchLanguages(),
|
||||
1000 * 60 * 60 * 24, // 24 hours
|
||||
);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@Get("/:locale")
|
||||
@Header("Content-Type", "application/octet-stream")
|
||||
@Header("Content-Disposition", 'attachment; filename="messages.po"')
|
||||
async getTranslation(@Param("locale") locale: string) {
|
||||
async translation(@Param("locale") locale: string) {
|
||||
return this.utils.getCachedOrSet(
|
||||
`translation:${locale}`,
|
||||
async () => this.fetchTranslations(locale),
|
||||
async () => this.translationService.fetchTranslations(locale),
|
||||
1000 * 60 * 60 * 24, // 24 hours
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,9 +2,11 @@ import { HttpModule } from "@nestjs/axios";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
import { TranslationController } from "./translation.controller";
|
||||
import { TranslationService } from "./translation.service";
|
||||
|
||||
@Module({
|
||||
imports: [HttpModule],
|
||||
controllers: [TranslationController],
|
||||
providers: [TranslationService],
|
||||
})
|
||||
export class TranslationModule {}
|
||||
|
||||
53
apps/server/src/translation/translation.service.ts
Normal file
53
apps/server/src/translation/translation.service.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { HttpService } from "@nestjs/axios";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { LanguageDto } from "@reactive-resume/dto";
|
||||
|
||||
import { Config } from "../config/schema";
|
||||
|
||||
type CrowdinResponse = {
|
||||
data: {
|
||||
data: {
|
||||
language: { id: string; name: string; locale: string; editorCode: string };
|
||||
translationProgress: number;
|
||||
};
|
||||
}[];
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class TranslationService {
|
||||
constructor(
|
||||
private readonly httpService: HttpService,
|
||||
private readonly configService: ConfigService<Config>,
|
||||
) {}
|
||||
|
||||
async fetchTranslations(locale: string) {
|
||||
const distributionHash = this.configService.get("CROWDIN_DISTRIBUTION_HASH");
|
||||
const response = await this.httpService.axiosRef.get(
|
||||
`https://distributions.crowdin.net/${distributionHash}/content/${locale}/messages.json`,
|
||||
);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async fetchLanguages() {
|
||||
const projectId = this.configService.get("CROWDIN_PROJECT_ID");
|
||||
const accessToken = this.configService.get("CROWDIN_ACCESS_TOKEN");
|
||||
|
||||
const response = await this.httpService.axiosRef.get(
|
||||
`https://api.crowdin.com/api/v2/projects/${projectId}/languages/progress?limit=100`,
|
||||
{ headers: { Authorization: `Bearer ${accessToken}` } },
|
||||
);
|
||||
const { data } = response.data as CrowdinResponse;
|
||||
|
||||
return data.map(({ data }) => {
|
||||
return {
|
||||
id: data.language.id,
|
||||
name: data.language.name,
|
||||
progress: data.translationProgress,
|
||||
editorCode: data.language.editorCode,
|
||||
locale: data.language.locale,
|
||||
} satisfies LanguageDto;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user