feat(homepage): add new sections to homepage

This commit is contained in:
Amruth Pillai
2023-11-13 17:03:41 +01:00
parent 4b1e33db80
commit d18b258761
79 changed files with 3096 additions and 313 deletions

View File

@ -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({

View File

@ -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)

View File

@ -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);

View File

@ -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(),

View 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
);
}
}

View 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 {}

View 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;
});
}
}

View File

@ -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;

View File

@ -126,7 +126,6 @@ export class ResumeController {
return { url };
} catch (error) {
console.log(error);
Logger.error(error);
throw new InternalServerErrorException(error);
}

View File

@ -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
);
}

View File

@ -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 {}

View 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;
});
}
}