Skip to content

Commit

Permalink
Allow Image generation without login (#137)
Browse files Browse the repository at this point in the history
* Don't generate intro if there are literally no cards to show

* Update prebuilthooks message for no images

* Add GLOBAL_GH_PAT

* Fetch user by login

* Update download.ts to use logic to fetch data

* Update readme

* Update crud logic based on public private account

* Update fetchGithubUnwrappedData

* Update APIs to handle public users

* Support public profiles on download.ts

* Update get-all-images api to support public profiles

* Update default get-cover to fetch private images

* make updated get-cover api for public profiles

* Update default card sharing api to fetch from pvt bucket

* Replace

* update protected route

* Enable public routes

* Show share actions on public view UI

* Move file to be right under public

* Handle case sensitive for get-all-images

* Small fix

* Remove code

* Push to username based page upon public username entry

* Route to public stats page after username is submitted

* Fix isPublic Logic

* Add a basic loader when username data is being fetched

* --wip-- [skip ci]

* Multiple refactors to support public access stats

* Change public view stats CTA

---------

Co-authored-by: Jayant Bhawal <bhawal.jayant@gmail.com>
  • Loading branch information
samad-yar-khan and jayantbh authored Dec 14, 2023
1 parent 4244abb commit f784fa0
Show file tree
Hide file tree
Showing 28 changed files with 568 additions and 248 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ Welcome to **Unwrapped - by Middleware**! This application is designed to provid
NEXT_PUBLIC_MIXPANEL=NEXT_PUBLIC_MIXPANEL
TOKEN_ENC_PUB_KEY=TOKEN_ENC_PUB_KEY
TOKEN_ENC_PRI_KEY=TOKEN_ENC_PRI_KEY
GLOBAL_GH_PAT=GLOBAL_GH_PAT
```

## Optional Keys and Behaviors
Expand Down
2 changes: 2 additions & 0 deletions libdefs/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ declare type DateString = string;
declare namespace NodeJS {
export interface ProcessEnv {
NEXT_PUBLIC_APP_ENVIRONMENT: 'production' | 'development';
NEXT_PUBLIC_APP_URL: string;
NEXTAUTH_URL: string;
NEXTAUTH_SECRET: string;
GITHUB_ID: string;
Expand All @@ -23,5 +24,6 @@ declare namespace NodeJS {
NEXT_PUBLIC_MIXPANEL: string;
ZAPIER_WEBHOOK_URL: string;
NEXT_PUBLIC_GA: string;
GLOBAL_GH_PAT: string;
}
}
6 changes: 4 additions & 2 deletions src/api-helpers/archive.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import archiver from 'archiver';
import { ImageFile } from '../types/images';
import chalk from 'chalk';
import { ImagesWithBuffers } from '@/types/images';

export async function archiveFiles(fileBuffers: ImageFile[]): Promise<Buffer> {
export async function archiveFiles(
fileBuffers: Omit<ImagesWithBuffers, 'image'>[]
): Promise<Buffer> {
console.info(chalk.yellow('Archiving images...'));
return new Promise((resolve, reject) => {
const archive = archiver('zip', { zlib: { level: 9 } });
Expand Down
49 changes: 31 additions & 18 deletions src/api-helpers/card-data-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,20 @@ import { Username } from '@/components/templates/index';
import { DependantsData } from '@/components/templates/Dependants';

export const getDataFromGithubResponse = (data: GitHubDataResponse) => {
const intro: IntroCardProps | null = {
username: data.user.login,
year: new Date().getFullYear()
};

const guardian: GuardianData | null =
data.reviewed_prs_with_requested_changes_count > 2
? {
numberOfTimes: data.reviewed_prs_with_requested_changes_count
}
: null;

const contributions: ContributionsData | null = {
contributions: data.total_contributions,
percentile: data.contribution_percentile
};
const contributions: ContributionsData | null =
data.total_contributions > 0
? {
contributions: data.total_contributions,
percentile: data.contribution_percentile
}
: null;

const totalAuthored = data.authored_monthly_pr_counts.reduce(
(acc, curr) => acc + curr,
Expand All @@ -54,12 +52,14 @@ export const getDataFromGithubResponse = (data: GitHubDataResponse) => {
}
: null;

const timeBasedData: TimeOfTheDayData | null = {
prsDuringDay: data.prs_opened_during_day,
totalPrs: data.prs_opened_during_day + data.prs_opened_during_night,
productiveDay: data.weekday_with_max_opened_prs as string,
productiveHour: data.hour_with_max_opened_prs as number
};
const timeBasedData: TimeOfTheDayData | null = data.total_contributions
? {
prsDuringDay: data.prs_opened_during_day,
totalPrs: data.prs_opened_during_day + data.prs_opened_during_night,
productiveDay: data.weekday_with_max_opened_prs as string,
productiveHour: data.hour_with_max_opened_prs as number
}
: null;

const zenOrNinja: ZenNinjaData | null =
data.total_contributions > 50
Expand Down Expand Up @@ -100,9 +100,7 @@ export const getDataFromGithubResponse = (data: GitHubDataResponse) => {
}
: null;

return {
username: data.user.login,
[CardTypes.UNWRAPPED_INTRO]: intro,
const nonIntroCardData = {
[CardTypes.GUARDIAN_OF_PROD]: guardian,
[CardTypes.YOUR_CONTRIBUTIONS]: contributions,
[CardTypes.PR_REVIEWED_VS_AUTHORED]: authoredVsReviewedPRs,
Expand All @@ -112,6 +110,21 @@ export const getDataFromGithubResponse = (data: GitHubDataResponse) => {
[CardTypes.TOP_REVIEWERS]: codeReviewerStats,
[CardTypes.OSS_CONTRIBUTION]: ossContribsData,
[CardTypes.IT_TAKES_A_VILLAGE]: userReviewers
};

const hasAnyData = !!Object.values(nonIntroCardData).filter(Boolean).length;

const intro: IntroCardProps | null = hasAnyData
? {
username: data.user.login,
year: new Date().getFullYear()
}
: null;

return {
username: data.user.login,
[CardTypes.UNWRAPPED_INTRO]: intro,
...nonIntroCardData
} as Record<
CardTypes,
| IntroCardProps
Expand Down
20 changes: 20 additions & 0 deletions src/api-helpers/exapi-sdk/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,26 @@ export async function fetchUser(token: string) {
}
}

export async function fetchUserByLogin(token: string, userLogin: string) {
try {
const response = await axios.get<GithubUser>(
`https://api.github.com/users/${userLogin}`,
{
headers: {
Authorization: `Bearer ${token}`
}
}
);
const userData: GithubUser = response.data;
return userData;
} catch (error: any) {
logException(`Error fetching user data: ${error.message}`, {
originalException: error
});
throw new Error(`Error fetching user data: ${error.message}`);
}
}

async function fetchReviewedPRsForMonth(
author: string,
month: number,
Expand Down
6 changes: 3 additions & 3 deletions src/api-helpers/image-gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import chalk from 'chalk';
import { createImageUsingVercel } from './vercel-generator';
import { getDataFromGithubResponse } from '@/api-helpers/card-data-adapter';
import { CardTypes, sequence } from '../types/cards';
import { ImageFile } from '@/types/images';
import { ImagesWithBuffers } from '@/types/images';
import { logException } from '@/utils/logger';

export const generateImages = async (
data: GitHubDataResponse,
customSequence?: CardTypes[]
): Promise<ImageFile[]> => {
): Promise<ImagesWithBuffers[]> => {
try {
console.info(chalk.yellow('Generating images...'));

Expand All @@ -29,7 +29,7 @@ export const generateImages = async (
)
);

return imageFileBuffers.filter(Boolean) as ImageFile[];
return imageFileBuffers.filter(Boolean) as ImagesWithBuffers[];
} catch (error) {
console.error('Error in generateImages:', error);
logException('Error in generateImages', { originalException: error });
Expand Down
66 changes: 39 additions & 27 deletions src/api-helpers/persistance.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ImageFile } from '@/types/images';
import { ImagesWithBuffers } from '@/types/images';
import {
deleteS3Directory,
fetchFileFromS3Directory,
Expand All @@ -21,52 +21,64 @@ const bucketName = process.env.UNWRAPPED_PERSISTENCE_BUCKET_NAME;

export const saveCards = async (
userLogin: string,
imageFiles: ImageFile[]
imageFiles: ImagesWithBuffers[],
isPublic: boolean = true
): Promise<void> => {
if (awsCredentialExists && bucketName) {
await uploadImagesToS3(bucketName, userLogin, imageFiles);
const prefix = isPublic ? `public/${userLogin}` : `${userLogin}`;
await uploadImagesToS3(bucketName, prefix, imageFiles);
} else {
await saveImagesToLocalDirectory(
`${process.cwd()}/unwrapped-cards/${userLogin}/`,
imageFiles
);
const prefix = isPublic
? `${process.cwd()}/unwrapped-cards/public/${userLogin}/`
: `${process.cwd()}/unwrapped-cards/${userLogin}/`;
await saveImagesToLocalDirectory(prefix, imageFiles);
}
};

export const fetchSavedCards = async (
userLogin: string
): Promise<ImageFile[]> => {
userLogin: string,
isPublic: boolean = true
): Promise<ImagesWithBuffers[]> => {
if (awsCredentialExists && bucketName) {
return await fetchImagesFromS3Directory(bucketName, userLogin);
const prefix = isPublic ? `public/${userLogin}` : `${userLogin}`;
return await fetchImagesFromS3Directory(bucketName, prefix);
} else {
return await fetchImagesFromLocalDirectory(
`${process.cwd()}/unwrapped-cards/${userLogin}/`
);
const prefix = isPublic
? `${process.cwd()}/unwrapped-cards/public/${userLogin}/`
: `${process.cwd()}/unwrapped-cards/${userLogin}/`;
return await fetchImagesFromLocalDirectory(prefix);
}
};

export const deleteSaveCards = async (userLogin: string): Promise<void> => {
export const deleteSaveCards = async (
userLogin: string,
isPublic: boolean = true
): Promise<void> => {
if (awsCredentialExists && bucketName) {
await deleteS3Directory(bucketName, userLogin);
const prefix = isPublic ? `public/${userLogin}` : `${userLogin}`;
await deleteS3Directory(bucketName, prefix);
} else {
await deleteLocalDirectory(
`${process.cwd()}/unwrapped-cards/${userLogin}/`
);
const prefix = isPublic
? `${process.cwd()}/unwrapped-cards/public/${userLogin}/`
: `${process.cwd()}/unwrapped-cards/${userLogin}/`;
await deleteLocalDirectory(prefix);
}
};

export const fetchSavedCard = async (
userLogin: string,
cardName: string
): Promise<ImageFile> => {
cardName: string,
isPublic: boolean = true
): Promise<ImagesWithBuffers> => {
if (awsCredentialExists && bucketName) {
return await fetchFileFromS3Directory(
bucketName,
`${userLogin}/${cardName}.png`
);
const prefix = isPublic
? `public/${userLogin}/${cardName}.png`
: `${userLogin}/${cardName}.png`;
return await fetchFileFromS3Directory(bucketName, prefix);
} else {
return await fetchImageFromLocalDirectory(
`${process.cwd()}/unwrapped-cards/${userLogin}/${cardName}.png`
);
const prefix = isPublic
? `${process.cwd()}/unwrapped-cards/public/${userLogin}/${cardName}.png`
: `${process.cwd()}/unwrapped-cards/${userLogin}/${cardName}.png`;
return await fetchImageFromLocalDirectory(prefix);
}
};
6 changes: 1 addition & 5 deletions src/api-helpers/unrwrapped-aggregator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,8 @@ const remove_users_login = (list: Array<string>, user_login: string) => {
export const fetchGithubUnwrappedData = async (
token: string,
timezone: string,
username?: string
user: GithubUser
): Promise<GitHubDataResponse> => {
const user = username
? ({ login: username } as GithubUser)
: await fetchUser(token);

const [
pr_authored_data,
pr_reviewed_data,
Expand Down
4 changes: 2 additions & 2 deletions src/api-helpers/vercel-generator.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ImageFile } from '@/types/images';
import { ImagesWithBuffers } from '@/types/images';
import { ImageResponse } from '@vercel/og';
import { arrayBufferToBuffer } from '@/api-helpers/general';

Expand All @@ -17,7 +17,7 @@ export const createImageUsingVercel = async (
data: CardTemplateData['data'],
cardType: CardTypes,
env: 'node' | 'browser' = 'node'
): Promise<ImageFile> => {
): Promise<ImagesWithBuffers> => {
const fileName = `${cardType.toLowerCase()}.png`;
try {
const interFonts = await getInterFonts(env);
Expand Down
Loading

0 comments on commit f784fa0

Please sign in to comment.