diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3501deab4..d192d3d50 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -82,4 +82,14 @@ git commit -m "fix(homepage): fix typo on homepage in the faq section" It helps to be as descriptive as possible in commit messages so that users can be aware of the changes made by you. +Then, you can run the basic checks, as to make sure the CI has the most chance of being successful: + +``` +pnpm run lint + +pnpm run test + +pnpm run build +``` + Finally, create a pull request to merge the changes on your forked repository to the original repository hosted on AmruthPillai/Reactive-Resume. I can take a look at the changes you've made when I have the time and have it merged onto the app. diff --git a/apps/client/src/pages/builder/sidebars/right/sections/sharing.tsx b/apps/client/src/pages/builder/sidebars/right/sections/sharing.tsx index b048a5432..f8ebe472e 100644 --- a/apps/client/src/pages/builder/sidebars/right/sections/sharing.tsx +++ b/apps/client/src/pages/builder/sidebars/right/sections/sharing.tsx @@ -4,6 +4,7 @@ import { Button, Input, Label, Switch, Tooltip } from "@reactive-resume/ui"; import { AnimatePresence, motion } from "framer-motion"; import { useToast } from "@/client/hooks/use-toast"; +import { axios } from "@/client/libs/axios" import { useUser } from "@/client/services/user"; import { useResumeStore } from "@/client/stores/resume"; @@ -20,6 +21,7 @@ export const SharingSection = () => { // Constants const url = `${window.location.origin}/${username}/${slug}`; + const rawJsonUrl = `${window.location.origin}${axios.defaults.baseURL}/resume/public/${username}/${slug}/stealthily`; const onCopy = async () => { await navigator.clipboard.writeText(url); @@ -31,6 +33,16 @@ export const SharingSection = () => { }); }; + const onRawJsonCopy = async () => { + await navigator.clipboard.writeText(rawJsonUrl); + + toast({ + variant: "success", + title: t`A link has been copied to your clipboard.`, + description: t`Anyone with this link can view and download the json data of the resume. This link will not count towards the resume view count.`, + }); + }; + return (
@@ -65,21 +77,50 @@ export const SharingSection = () => { {isPublic && ( - +
+ -
- +
+ - - - + + + +
+
+ +
+ + +
+ + + + + +
)} diff --git a/apps/server/src/resume/resume.controller.ts b/apps/server/src/resume/resume.controller.ts index 29d93266f..e962400dc 100644 --- a/apps/server/src/resume/resume.controller.ts +++ b/apps/server/src/resume/resume.controller.ts @@ -20,7 +20,7 @@ import { ResumeDto, UpdateResumeDto, } from "@reactive-resume/dto"; -import { ResumeData, resumeDataSchema } from "@reactive-resume/schema"; +import { resumeDataSchema } from "@reactive-resume/schema"; import { ErrorMessage } from "@reactive-resume/utils"; import set from "lodash.set"; import { zodToJsonSchema } from "zod-to-json-schema"; @@ -92,6 +92,19 @@ export class ResumeController { return this.resumeService.findOneStatistics(id); } + @Get("/public/:username/:slug/stealthily") + @UseGuards(OptionalGuard) + async findOneByUsernameSlugStealthily( + @Param("username") username: string, + @Param("slug") slug: string, + ) { + const resume = await this.resumeService.findOneByUsernameSlug(username, slug); + + redactPrivateNotes(resume as ResumeDto); + + return resume; + } + @Get("/public/:username/:slug") @UseGuards(OptionalGuard) async findOneByUsernameSlug( @@ -99,10 +112,13 @@ export class ResumeController { @Param("slug") slug: string, @User("id") userId: string, ) { - const resume = await this.resumeService.findOneByUsernameSlug(username, slug, userId); + const resume = await this.resumeService.findOneByUsernameSlug(username, slug); - // Hide private notes from public resume API responses - set(resume.data as ResumeData, "metadata.notes", undefined); + redactPrivateNotes(resume as ResumeDto); + + if (!userId) { + await this.resumeService.incrementViewCountForOne(resume.id); + } return resume; } @@ -155,3 +171,7 @@ export class ResumeController { } } } + +function redactPrivateNotes(resume: ResumeDto) { + set(resume.data, "metadata.notes", undefined); +} diff --git a/apps/server/src/resume/resume.service.ts b/apps/server/src/resume/resume.service.ts index 53d4ab243..3a4669a7d 100644 --- a/apps/server/src/resume/resume.service.ts +++ b/apps/server/src/resume/resume.service.ts @@ -84,21 +84,19 @@ export class ResumeService { }; } - async findOneByUsernameSlug(username: string, slug: string, userId?: string) { - const resume = await this.prisma.resume.findFirstOrThrow({ + async findOneByUsernameSlug(username: string, slug: string) { + return await this.prisma.resume.findFirstOrThrow({ where: { user: { username }, slug, visibility: "public" }, }); + } + async incrementViewCountForOne(resumeId: string) { // Update statistics: increment the number of views by 1 - if (!userId) { - await this.prisma.statistics.upsert({ - where: { resumeId: resume.id }, - create: { views: 1, downloads: 0, resumeId: resume.id }, - update: { views: { increment: 1 } }, - }); - } - - return resume; + await this.prisma.statistics.upsert({ + where: { resumeId }, + create: { views: 1, downloads: 0, resumeId }, + update: { views: { increment: 1 } }, + }); } async update(userId: string, id: string, updateResumeDto: UpdateResumeDto) {