diff --git a/apps/web/package.json b/apps/web/package.json index f8cc79ac..be7b4655 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -26,6 +26,7 @@ "@trpc/server": "11.8.0", "bad-words": "^4.0.0", "dayjs": "^1.11.13", + "echarts": "^6.0.0", "fuse.js": "^7.0.0", "geist": "^1.3.0", "lucide-react": "^0.436.0", diff --git a/apps/web/src/app/(pages)/(protected)/profile/page.tsx b/apps/web/src/app/(pages)/(protected)/profile/page.tsx index d00e3c23..e5cc71a9 100644 --- a/apps/web/src/app/(pages)/(protected)/profile/page.tsx +++ b/apps/web/src/app/(pages)/(protected)/profile/page.tsx @@ -13,6 +13,7 @@ import ProfileCardHeader from "~/app/_components/profile/profile-card-header"; import ProfileTabs from "~/app/_components/profile/profile-tabs"; import { ReviewCard } from "~/app/_components/reviews/review-card"; import { api } from "~/trpc/react"; +import { DraftReviewCard } from "~/app/_components/reviews/draft-review-card"; export default function Profile() { const searchParams = useSearchParams(); @@ -138,15 +139,29 @@ export default function Profile() { -
+
{reviews.length > 0 && - reviews.map((review) => ( - - ))} + reviews + .sort( + (a, b) => + (a.status === "DRAFT" ? -1 : 1) - + (b.status === "DRAFT" ? -1 : 1), + ) + .map((review) => + review.status === "DRAFT" ? ( + + ) : ( + + ), + )}
)} diff --git a/apps/web/src/app/(pages)/(protected)/review-form/page.tsx b/apps/web/src/app/(pages)/(protected)/review-form/page.tsx index a1d304fe..65918f38 100644 --- a/apps/web/src/app/(pages)/(protected)/review-form/page.tsx +++ b/apps/web/src/app/(pages)/(protected)/review-form/page.tsx @@ -1,8 +1,9 @@ "use client"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import { zodResolver } from "@hookform/resolvers/zod"; +import { Filter } from "bad-words"; import { useForm } from "react-hook-form"; import { Button } from "@cooper/ui/button"; @@ -13,10 +14,10 @@ import { InterviewSection, ReviewSection, } from "~/app/_components/form/sections"; +import Popup from "~/app/_components/form/sections/popup"; import { z } from "zod"; import { useCustomToast } from "@cooper/ui"; -import { WorkEnvironment, WorkTerm, JobType } from "@cooper/db/schema"; -import { Filter } from "bad-words"; +import { WorkEnvironment, WorkTerm, JobType, Status } from "@cooper/db/schema"; import dayjs from "dayjs"; import { Form } from "node_modules/@cooper/ui/src/form"; import { PaySection } from "~/app/_components/form/sections/pay-section"; @@ -91,14 +92,16 @@ const formSchema = z.object({ }) .min(1, { message: "You need to enter a company.", - }), + }) + .nullable(), roleName: z .string({ required_error: "You need to enter a company.", }) .min(1, { message: "You need to enter a company.", - }), + }) + .nullable(), locationId: z.string().min(1, { message: "You need to select a location.", }), @@ -133,12 +136,11 @@ const formSchema = z.object({ }) .transform((x) => x === "true") .pipe(z.boolean()), - federalHolidays: z.boolean().default(false), - freeLunch: z.boolean().default(false), - travelBenefits: z.boolean().default(false), - freeMerch: z.boolean().default(false), - snackBar: z.boolean().default(false), - employeeLounge: z.boolean().default(false), + federalHolidays: z.boolean(), + freeLunch: z.boolean(), + travelBenefits: z.boolean(), + freeMerch: z.boolean(), + snackBar: z.boolean(), otherBenefits: z.string().nullable(), }); @@ -160,6 +162,7 @@ export default function ReviewForm() { const { toast } = useCustomToast(); const [roleId, setRoleId] = useState(""); const [companyId, setCompanyId] = useState(""); + const [showModal, setShowModal] = useState(false); const form = useForm>({ resolver: zodResolver(formSchema), @@ -181,17 +184,31 @@ export default function ReviewForm() { pto: undefined, federalHolidays: false, freeLunch: false, + travelBenefits: false, freeMerch: false, + snackBar: false, otherBenefits: "", roleName: "", companyName: "", }, }); - // Watch form values and update roleId and companyId const roleName = form.watch("roleName"); const companyName = form.watch("companyName"); + const isDirty = form.formState.isDirty; + const isDirtyRef = useRef(isDirty); + + useEffect(() => { + isDirtyRef.current = isDirty; + }, [isDirty]); + + useEffect(() => { + form.reset(); + setShowModal(false); + isDirtyRef.current = false; + }, [form]); + useEffect(() => { if (roleName) { setRoleId(roleName); @@ -208,6 +225,21 @@ export default function ReviewForm() { } }, [companyName]); + useEffect(() => { + const handleLeave: EventListener = () => { + if (isDirtyRef.current) { + setShowModal(true); + } else { + router.push("/roles"); + } + }; + window.addEventListener("review-form:leave-attempt", handleLeave); + + return () => { + window.removeEventListener("review-form:leave-attempt", handleLeave); + }; + }, [router]); + const profileId = profile?.id; const reviews = api.review.getByProfile.useQuery( @@ -225,6 +257,7 @@ export default function ReviewForm() { const reviewsForCurrentTerm = reviews.data.filter( (review) => + review.status === Status.PUBLISHED && String(review.workTerm) === currentTerm && review.workYear === Number(currentYear), ); @@ -241,6 +274,16 @@ export default function ReviewForm() { }, }); + const draftMutation = api.review.saveDraft.useMutation({ + onSuccess: () => { + router.push("/roles"); + setShowModal(false); + }, + onError: (error) => { + toast.error(error.message || "Failed to save draft. Please try again."); + }, + }); + async function onSubmit(values: z.infer) { try { await mutation.mutateAsync({ @@ -250,6 +293,7 @@ export default function ReviewForm() { ...values, interviewRating: 1, reviewHeadline: "", + status: Status.PUBLISHED, }); } catch (error) { // Error is already handled by onError callback @@ -258,13 +302,65 @@ export default function ReviewForm() { } if (!sessionLoading && !profileLoading && (!session || !profile)) { - router.push("/"); + router.push("/roles"); } if (!session || !profile) { return null; } + const discardDraft = () => { + router.push("/roles"); + }; + + const normalizeRadios = (v: unknown) => + v === true || v === "yes" ? true : v === false || v === "no" ? false : null; + + async function onSaveDraft() { + try { + const values = form.getValues(); + + const draftPayload: Record = { + roleId: roleId, + profileId: profileId, + companyId: companyId, + workTerm: values.workTerm, + workYear: values.workYear, + overallRating: values.overallRating, + cultureRating: values.cultureRating, + supervisorRating: values.supervisorRating, + interviewRating: 1, + interviewDifficulty: +values.interviewDifficulty || null, + interviewReview: values.interviewReview ?? null, + reviewHeadline: "", + textReview: values.textReview || null, + locationId: values.locationId || null, + jobType: values.jobType, + hourlyPay: values.hourlyPay === "" ? null : values.hourlyPay, + workEnvironment: values.workEnvironment, + drugTest: normalizeRadios(values.drugTest), + pto: normalizeRadios(values.pto), + overtimeNormal: normalizeRadios(values.overtimeNormal), + federalHolidays: values.federalHolidays || null, + freeLunch: values.freeLunch || null, + travelBenefits: values.travelBenefits || null, + freeMerch: values.freeMerch || null, + snackBar: values.snackBar || null, + otherBenefits: values.otherBenefits ?? null, + status: Status.DRAFT, + }; + + await draftMutation.mutateAsync( + draftPayload as Parameters[0], + ); + + form.reset(values); + isDirtyRef.current = false; + toast.success("This draft has been saved."); + } catch (error) { + console.error("Draft save failed:", error); + } + } if ( session.user.role && session.user.role !== UserRole.STUDENT && @@ -283,7 +379,9 @@ export default function ReviewForm() { return (
-
+
Basic information
@@ -317,30 +415,56 @@ export default function ReviewForm() {
- - {/* Submit Button */} -
- +
+ {/* Save Draft Button */} +
+ +
+ {/* Submit Button */} +
+ +
) : (
You already submitted too many reviews for this term
)}
+ {isDirty && showModal && ( +
+
+ setShowModal(false)} + onDiscard={discardDraft} + onSave={onSaveDraft} + /> +
+
+ )}
); diff --git a/apps/web/src/app/_components/admin/dashboard-table.tsx b/apps/web/src/app/_components/admin/dashboard-table.tsx index cce7ed36..8a945c9a 100644 --- a/apps/web/src/app/_components/admin/dashboard-table.tsx +++ b/apps/web/src/app/_components/admin/dashboard-table.tsx @@ -22,7 +22,7 @@ interface DashboardItem { category: DashboardCategory; title: string; subtitle?: string; - description?: string; + description?: string | null; company?: string; location?: string | null; createdAt: Date; diff --git a/apps/web/src/app/_components/companies/company-reviews.tsx b/apps/web/src/app/_components/companies/company-reviews.tsx index e62c8ad5..cd79f13b 100644 --- a/apps/web/src/app/_components/companies/company-reviews.tsx +++ b/apps/web/src/app/_components/companies/company-reviews.tsx @@ -35,8 +35,8 @@ export function CompanyReview({ companyObj }: CompanyReviewProps) { const averages = api.review.list .useQuery({}) - .data?.filter((r) => r.overallRating != 0) - .map((review) => review.overallRating); + .data?.map((review) => review.overallRating) + .filter((rating): rating is number => rating != null && rating !== 0); const cooperAvg: number = Math.round( ((averages ?? []).reduce((accumulator, currentValue) => { diff --git a/apps/web/src/app/_components/form/sections/company-details-section.tsx b/apps/web/src/app/_components/form/sections/company-details-section.tsx index 5d94a9e5..cec617bf 100644 --- a/apps/web/src/app/_components/form/sections/company-details-section.tsx +++ b/apps/web/src/app/_components/form/sections/company-details-section.tsx @@ -20,14 +20,16 @@ export function CompanyDetailsSection() { const form = useFormContext(); const benefits = [ - { field: "pto", label: "PTO" }, { field: "federalHolidays", label: "Federal holidays off" }, { field: "freeLunch", label: "Free lunch" }, { field: "travelBenefits", label: "Travel benefits" }, { field: "freeMerch", label: "Free merchandise" }, { field: "snackBar", label: "Snack bar" }, - { field: "employeeLounge", label: "Employee lounge" }, - ]; + ] as const; + + const selectedBenefits = benefits + .filter((benefit) => Boolean(form.watch(benefit.field))) + .map((benefit) => benefit.field); return ( @@ -179,40 +181,32 @@ export function CompanyDetailsSection() { ); }} /> - ( - - - Benefits - - - ({ - id: benefit.field, - label: benefit.label, - }))} - selectedOptions={ - Array.isArray(field.value) - ? field.value - : field.value - ? [field.value] - : [] - } - placeholder="Select benefits" - onSelectionChange={(selected) => { - field.onChange(selected.length > 0 ? selected : undefined); - }} - isInMenuContent={true} - /> - - - - )} - /> + + + Benefits + + + ({ + id: benefit.field, + label: benefit.label, + }))} + selectedOptions={selectedBenefits} + placeholder="Select benefits" + onSelectionChange={(selected) => { + benefits.forEach((benefit) => { + form.setValue(benefit.field, selected.includes(benefit.field), { + shouldDirty: true, + shouldValidate: true, + }); + }); + }} + isInMenuContent={true} + /> + + ); } diff --git a/apps/web/src/app/_components/form/sections/popup.tsx b/apps/web/src/app/_components/form/sections/popup.tsx new file mode 100644 index 00000000..20427218 --- /dev/null +++ b/apps/web/src/app/_components/form/sections/popup.tsx @@ -0,0 +1,139 @@ +import React from "react"; + +interface PopupProps { + onSave: () => void; + onCancel: () => void; + onDiscard: () => void; + showModal: boolean; +} + +const Popup: React.FC = ({ + showModal, + onCancel, + onDiscard, + onSave, +}) => { + if (!showModal) { + return null; + } + + return ( + <> + {/* Non Mobile Popup */} +
+
+
+ +
+
+ Save as Draft? +
+
+ We'll save what you've written so far, and you can continue editing + your review anytime. +
+
+ +
+ + +
+
+
+
+ + {/* Mobile Popup */} +
+
+
+ Save as Draft? +
+
+ We'll save what you've written so far, and you can continue editing + your review anytime. +
+ +
+ +
+ + +
+ + +
+
+ +
+ +
+
+ + ); +}; + +export default Popup; diff --git a/apps/web/src/app/_components/header/header.tsx b/apps/web/src/app/_components/header/header.tsx index c756e3c1..2b1c264a 100644 --- a/apps/web/src/app/_components/header/header.tsx +++ b/apps/web/src/app/_components/header/header.tsx @@ -3,6 +3,7 @@ import { useState } from "react"; import Image from "next/image"; import Link from "next/link"; +import { useRouter, usePathname } from "next/navigation"; import { DropdownMenu, @@ -17,7 +18,7 @@ import { api } from "~/trpc/react"; import { handleSignOut } from "../auth/actions"; import CooperLogo from "../cooper-logo"; import MobileHeaderButton from "./mobile-header-button"; -import { Session } from "@cooper/auth"; +import type { Session } from "@cooper/auth"; import { UserRole } from "node_modules/@cooper/db/src/schema/misc"; interface HeaderProps { @@ -31,12 +32,24 @@ interface HeaderProps { */ export default function Header({ auth, loggedIn }: HeaderProps) { const [isOpen, setIsOpen] = useState(false); + const pathname = usePathname(); const session = api.auth.getSession.useQuery(); + const router = useRouter(); const utils = api.useUtils(); const isStudentOrDeveloper = session.data?.user.role === UserRole.STUDENT || session.data?.user.role === UserRole.DEVELOPER; + const handleLogoClick = (e: React.MouseEvent) => { + e.preventDefault(); + + if (pathname === "/review-form") { + window.dispatchEvent(new CustomEvent("review-form:leave-attempt")); + return; + } + router.push("/roles"); + }; + if (isOpen) { return (
@@ -44,8 +57,7 @@ export default function Header({ auth, loggedIn }: HeaderProps) { { - e.preventDefault(); - window.location.href = "/roles"; + handleLogoClick(e); }} >

Cooper

@@ -131,8 +143,7 @@ export default function Header({ auth, loggedIn }: HeaderProps) { { - e.preventDefault(); - window.location.href = "/roles"; + handleLogoClick(e); }} className={"flex items-center justify-center gap-3"} > diff --git a/apps/web/src/app/_components/reviews/draft-review-card.tsx b/apps/web/src/app/_components/reviews/draft-review-card.tsx new file mode 100644 index 00000000..12cfbfaf --- /dev/null +++ b/apps/web/src/app/_components/reviews/draft-review-card.tsx @@ -0,0 +1,113 @@ +"use client"; + +import type { ReviewType } from "@cooper/db/schema"; +import { cn } from "@cooper/ui"; +import { Card, CardContent } from "@cooper/ui/card"; +import { prettyLocationName } from "~/utils/locationHelpers"; +import { api } from "~/trpc/react"; +import { YellowStar, GrayStar } from "./review-card-stars"; +import { formatLastEditedDate } from "~/utils/dateHelpers"; + +interface DraftReviewCardProps { + className?: string; + reviewObj: ReviewType; +} + +export function DraftReviewCard({ + reviewObj, + className, +}: DraftReviewCardProps) { + const { data: role } = api.role.getById.useQuery( + { id: reviewObj.roleId ?? "" }, + { enabled: !!reviewObj.roleId }, + ); + + const { data: location } = api.location.getById.useQuery({ + id: reviewObj.locationId ?? "", + }); + + const { data: company } = api.company.getById.useQuery( + { id: reviewObj.companyId ?? "" }, + { enabled: !!reviewObj.companyId }, + ); + + const roleTitle = role?.title.trim(); + + return ( + +
+
+ +
+ {roleTitle ? ( + + {roleTitle} + + ) : ( + + Job title + + )} + + + Draft + +
+
+
+ {reviewObj.overallRating ? ( + + {reviewObj.overallRating.toFixed(1)} + + ) : ( + + {" "} + 0.0{" "} + + )} + {!reviewObj.overallRating ? ( + + ) : ( + + )} +
+
+
+
+
+ +
+ {company?.name ? ( + {company.name} + ) : ( + + Company name + + )} + + {location ? ( + + {prettyLocationName(location)} + + ) : ( + + Location + + )} +
+
+ + {formatLastEditedDate(reviewObj.updatedAt, reviewObj.createdAt)} + +
+
+
+
+
+ ); +} diff --git a/apps/web/src/app/_components/reviews/modal.tsx b/apps/web/src/app/_components/reviews/modal.tsx new file mode 100644 index 00000000..b1b5ac4e --- /dev/null +++ b/apps/web/src/app/_components/reviews/modal.tsx @@ -0,0 +1,19 @@ +import React, { ReactNode } from "react"; + +interface ModalContainerProps { + children: ReactNode; + title?: string; +} + +const ModalContainer: React.FC = ({ children, title }) => { + return ( +
+ {children} +
+ ); +}; + +export default ModalContainer; diff --git a/apps/web/src/app/_components/reviews/profile-review-card.tsx b/apps/web/src/app/_components/reviews/profile-review-card.tsx new file mode 100644 index 00000000..0b9f0047 --- /dev/null +++ b/apps/web/src/app/_components/reviews/profile-review-card.tsx @@ -0,0 +1,105 @@ +"use client"; + +import type { ReviewType } from "@cooper/db/schema"; +import { Card, CardContent } from "@cooper/ui/card"; +import { prettyLocationName } from "~/utils/locationHelpers"; +import { api } from "~/trpc/react"; +import { YellowStar } from "./review-card-stars"; +import { prettyWorkEnviornment } from "~/utils/stringHelpers"; +import { DeleteReviewDialog } from "./delete-review-dialogue"; +import { ReportButton } from "../shared/report-button"; +import { WorkEnvironmentType } from "@cooper/db/schema"; + +interface ReviewCardProps { + className?: string; + reviewObj: ReviewType; +} + +export function ReviewCard({ reviewObj }: ReviewCardProps) { + const { data: role } = api.role.getById.useQuery( + { id: reviewObj.roleId ?? "" }, + { enabled: !!reviewObj.roleId }, + ); + + // Get the current user's profile + const { data: currentProfile } = api.profile.getCurrentUser.useQuery(); + + // Check if the current user is the author of the review + const isAuthor = currentProfile?.id === reviewObj.profileId; + + const { data: location } = api.location.getById.useQuery({ + id: reviewObj.locationId ?? "", + }); + + const { data: company } = api.company.getById.useQuery( + { id: reviewObj.companyId ?? "" }, + { enabled: !!reviewObj.companyId }, + ); + + const roleTitle = role?.title.trim(); + + return ( + +
+
+ +
+ + {roleTitle} + +
+
+
+ + {reviewObj.overallRating?.toFixed(1) ?? "0.0"} + + +
+
+
+
+
+ +
+ + {company?.name} + + + + + {prettyLocationName(location)} + +
+
+
+
+ Job type{" "} + {reviewObj.jobType === "CO-OP" ? "Co-op" : reviewObj.jobType} +
+
+ Work model + {prettyWorkEnviornment( + reviewObj.workEnvironment as WorkEnvironmentType, + )} +
+
+ Pay $ + {reviewObj.hourlyPay}/hr +
+
+ +
+
+
{reviewObj.textReview}
+ {isAuthor && } +
+
+
+
+
+ ); +} diff --git a/apps/web/src/app/_components/reviews/review-card-stars.tsx b/apps/web/src/app/_components/reviews/review-card-stars.tsx index 0acee372..24cd5d12 100644 --- a/apps/web/src/app/_components/reviews/review-card-stars.tsx +++ b/apps/web/src/app/_components/reviews/review-card-stars.tsx @@ -1,11 +1,12 @@ -export function ReviewCardStars({ numStars }: { numStars: number }) { - const yellowStar = ( +export function YellowStar({ className }: { className?: string } = {}) { + return ( ); +} - const grayStar = ( +export function GrayStar({ className }: { className?: string } = {}) { + return ( ); +} + +export function ReviewCardStars({ numStars }: { numStars: number }) { + const yellowStar = ; + const grayStar = ; const fullStars = Math.floor(numStars); const fractionalStars = numStars % 1; diff --git a/apps/web/src/app/_components/reviews/review-card.tsx b/apps/web/src/app/_components/reviews/review-card.tsx index 1a76b375..5bce1215 100644 --- a/apps/web/src/app/_components/reviews/review-card.tsx +++ b/apps/web/src/app/_components/reviews/review-card.tsx @@ -41,7 +41,7 @@ export function ReviewCard({ reviewObj, className }: ReviewCardProps) {
- {reviewObj.overallRating.toFixed(1)} + {reviewObj.overallRating?.toFixed(1) ?? "N/A"}
- {reviewObj.workTerm.charAt(0).toUpperCase() + - reviewObj.workTerm.slice(1).toLowerCase()}{" "} + {reviewObj.workTerm + ? reviewObj.workTerm.charAt(0).toUpperCase() + + reviewObj.workTerm.slice(1).toLowerCase() + : "N/A"}{" "} {reviewObj.workYear}
diff --git a/apps/web/src/app/_components/reviews/role-info.tsx b/apps/web/src/app/_components/reviews/role-info.tsx index b30ab758..ddbceccd 100644 --- a/apps/web/src/app/_components/reviews/role-info.tsx +++ b/apps/web/src/app/_components/reviews/role-info.tsx @@ -3,7 +3,6 @@ import { useEffect, useMemo, useState } from "react"; import Image from "next/image"; import Link from "next/link"; -import type { ReviewType, RoleType } from "@cooper/db/schema"; import { cn } from "@cooper/ui"; import { CardContent, CardHeader, CardTitle } from "@cooper/ui/card"; import Logo from "@cooper/ui/logo"; @@ -21,13 +20,15 @@ import CollapsableInfoCard from "./collapsable-info"; import InfoCard from "./info-card"; import { ReviewCard } from "./review-card"; import RoundBarGraph from "./round-bar-graph"; -import { Button } from "node_modules/@cooper/ui/src/button"; +import type { ReviewType, RoleType } from "@cooper/db/schema"; +import { Status } from "@cooper/db/schema"; import { - DropdownMenu, DropdownMenuContent, - DropdownMenuLabel, DropdownMenuTrigger, -} from "node_modules/@cooper/ui/src/dropdown-menu"; + DropdownMenuLabel, + DropdownMenu, +} from "@cooper/ui/dropdown-menu"; +import { Button } from "@cooper/ui/button"; import { ChevronDown } from "lucide-react"; import { ReportButton } from "../shared/report-button"; @@ -167,7 +168,7 @@ export function RoleInfo({ className, roleObj, onBack }: RoleCardProps) { const avgs = api.review.list .useQuery({}) - .data?.map((review) => review.overallRating); + .data?.map((review) => review.overallRating ?? 0); const cooperAvg: number = Math.round( ((avgs ?? []).reduce((accumulator, currentValue) => { @@ -195,13 +196,21 @@ export function RoleInfo({ className, roleObj, onBack }: RoleCardProps) { { enabled: !!profileId }, ); + const publishedUserReviewCount = useMemo( + () => + (usersReviews.data ?? []).filter( + (review) => review.status === Status.PUBLISHED, + ).length, + [usersReviews.data], + ); + // Filter reviews based on selected rating and search term const filteredReviews = reviews.data?.filter((review) => { // Filter by rating (empty = all; otherwise range min–max inclusive) const ratingMatch = ratingFilter.length === 0 || (() => { - const r = Math.round(review.overallRating); + const r = Math.round(review.overallRating ?? 0); const min = Math.min(...ratingFilter.map(Number)); const max = Math.max(...ratingFilter.map(Number)); return r >= min && r <= max; @@ -225,9 +234,13 @@ export function RoleInfo({ className, roleObj, onBack }: RoleCardProps) { const list = [...filteredReviews]; switch (selectedFilter) { case "highest rating": - return list.sort((a, b) => b.overallRating - a.overallRating); + return list.sort( + (a, b) => (b.overallRating ?? 0) - (a.overallRating ?? 0), + ); case "lowest rating": - return list.sort((a, b) => a.overallRating - b.overallRating); + return list.sort( + (a, b) => (a.overallRating ?? 0) - (b.overallRating ?? 0), + ); case "most recent": default: { const termOrder: Record = { @@ -236,9 +249,12 @@ export function RoleInfo({ className, roleObj, onBack }: RoleCardProps) { FALL: 3, }; return list.sort((a, b) => { - const yearDiff = b.workYear - a.workYear; + const yearDiff = (b.workYear ?? 0) - (a.workYear ?? 0); if (yearDiff !== 0) return yearDiff; - return (termOrder[b.workTerm] ?? 0) - (termOrder[a.workTerm] ?? 0); + return ( + (termOrder[b.workTerm ?? 0] ?? 0) - + (termOrder[a.workTerm ?? 0] ?? 0) + ); }); } } @@ -318,7 +334,7 @@ export function RoleInfo({ className, roleObj, onBack }: RoleCardProps) {
-
+
{reviews.isSuccess && reviews.data.length > 0 && @@ -507,7 +523,7 @@ export function RoleInfo({ className, roleObj, onBack }: RoleCardProps) { {reviews.isSuccess && reviews.data.length === 0 && (

No reviews yet

- {usersReviews.isSuccess && usersReviews.data.length < 5 && ( + {usersReviews.isSuccess && publishedUserReviewCount < 5 && ( Add one! diff --git a/apps/web/src/utils/companyStatistics.ts b/apps/web/src/utils/companyStatistics.ts index 504cd613..00446841 100644 --- a/apps/web/src/utils/companyStatistics.ts +++ b/apps/web/src/utils/companyStatistics.ts @@ -9,7 +9,7 @@ function toCamelCase(word: string) { export function calculateWorkModels(reviews: ReviewType[] = []) { const totalReviews = reviews.length; const uniqueModels: string[] = [ - ...new Set(reviews.map((r) => r.workEnvironment)), + ...new Set(reviews.map((r) => r.workEnvironment ?? "")), ]; return uniqueModels.map((model) => { @@ -25,7 +25,9 @@ export function calculateWorkModels(reviews: ReviewType[] = []) { export function calculateJobTypes(reviews: ReviewType[] = []) { const totalReviews = reviews.length; - const uniqueTypes: string[] = [...new Set(reviews.map((r) => r.jobType))]; + const uniqueTypes: string[] = [ + ...new Set(reviews.map((r) => r.jobType ?? "")), + ]; return uniqueTypes.map((model) => { const count = reviews.filter((r) => r.jobType === model).length; diff --git a/apps/web/src/utils/dateHelpers.ts b/apps/web/src/utils/dateHelpers.ts index 519fbd38..fc97bfec 100644 --- a/apps/web/src/utils/dateHelpers.ts +++ b/apps/web/src/utils/dateHelpers.ts @@ -30,5 +30,37 @@ export function formatDate(date?: Date) { const day = String(date.getDate()).padStart(2, "0"); // Ensure two digits for the day // Return the formatted date string - return `${day} ${month} ${year}`; + return `${month} ${day}, ${year}`; } + +export const formatLastEditedDate = ( + updatedAt?: Date | null, + createdAt?: Date, +): string => { + const date = updatedAt ?? createdAt; + + if (!date) { + return ""; + } + + const now = new Date(); + const msPerDay = 1000 * 60 * 60 * 24; + const msPerHour = 1000 * 60 * 60; + const msPerMin = 1000 * 60; + const timeDifference = now.getTime() - date.getTime(); + const daysDifference = Math.floor(timeDifference / msPerDay); + const hoursDifference = Math.floor(timeDifference / msPerHour); + const minutesDifference = Math.floor(timeDifference / msPerMin); + + if (minutesDifference <= 59) { + return `Last edited ${minutesDifference} minutes ago`; + } + if (hoursDifference <= 23) { + return `Last edited ${hoursDifference} hrs ago`; + } + if (daysDifference <= 6) { + return `Last edited ${daysDifference} days ago`; + } else { + return `Last edited ${formatDate(date)}`; + } +}; diff --git a/apps/web/src/utils/reviewCountByStars.ts b/apps/web/src/utils/reviewCountByStars.ts index 4f226b74..959631be 100644 --- a/apps/web/src/utils/reviewCountByStars.ts +++ b/apps/web/src/utils/reviewCountByStars.ts @@ -5,7 +5,7 @@ export function calculateRatings(reviews: ReviewType[] = []) { return [5, 4, 3, 2, 1].map((star) => { const count = reviews.filter( - (r) => r.overallRating.toFixed(0) === star.toString(), + (r) => (r.overallRating ?? 0).toFixed(0) === star.toString(), ).length; const percentage = totalReviews > 0 ? Math.round((count / totalReviews) * 100) : 0; diff --git a/apps/web/src/utils/reviewsAggregationHelpers.ts b/apps/web/src/utils/reviewsAggregationHelpers.ts index 59bfb390..a3390418 100644 --- a/apps/web/src/utils/reviewsAggregationHelpers.ts +++ b/apps/web/src/utils/reviewsAggregationHelpers.ts @@ -22,7 +22,7 @@ export function mostCommonWorkEnviornment( export function averageStarRating(reviews: ReviewType[]): number { const totalStars = reviews.reduce((accum, curr) => { - return accum + curr.overallRating; + return accum + (curr.overallRating ?? 0); }, 0); return totalStars / reviews.length; } diff --git a/packages/api/src/router/admin.ts b/packages/api/src/router/admin.ts index 4da75f57..69753332 100644 --- a/packages/api/src/router/admin.ts +++ b/packages/api/src/router/admin.ts @@ -113,8 +113,8 @@ async function getExpandedSearchEntityIds(db: CooperDb, search: string) { } for (const rv of textMatchedReviews) { reviewIds.add(rv.id); - companyIds.add(rv.companyId); - roleIds.add(rv.roleId); + companyIds.add(rv.companyId ?? ""); + roleIds.add(rv.roleId ?? ""); } if (companyIds.size === 0 && roleIds.size === 0 && reviewIds.size === 0) { diff --git a/packages/api/src/router/company.ts b/packages/api/src/router/company.ts index e89dce2c..b8d01162 100644 --- a/packages/api/src/router/company.ts +++ b/packages/api/src/router/company.ts @@ -13,6 +13,7 @@ import { Industry, Review, Role, + Status, } from "@cooper/db/schema"; import { @@ -76,18 +77,18 @@ export const companyRouter = { : sql`HAVING COUNT(${Review.id}) > 0`; const companiesWithRatings = await ctx.db.execute(sql` - SELECT - ${Company}.*, + SELECT + ${Company}.*, COALESCE(AVG(${Review.overallRating}::float), 0) AS avg_rating FROM ${Company} LEFT JOIN ${Review} ON NULLIF(${Review.companyId}, '')::uuid = ${Company.id} + AND ${Review.status} = ${Status.PUBLISHED} ${whereClause} GROUP BY ${Company.id} ${havingClause} ORDER BY avg_rating DESC `); - const companies = companiesWithRatings.rows.map((role) => ({ ...(role as CompanyType), })); @@ -123,6 +124,7 @@ export const companyRouter = { INNER JOIN ${Role} ON ${Role.companyId}::uuid = ${Company.id} INNER JOIN ${Review} ON ${Review.roleId}::uuid = ${Role.id} WHERE ${Review.hidden} = false AND ${Review.roleId} != '' AND ${Review.roleId} IS NOT NULL + AND ${Review.status} = ${Status.PUBLISHED} `); const companyIdsWithReviews = new Set( @@ -246,7 +248,6 @@ export const companyRouter = { industry: input.industry, website: input.website ?? `${input.companyName}.com`, }; - // Generate unique slug for company const companyBaseSlug = createSlug(input.companyName); const existingCompanies = await ctx.db.query.Company.findMany({ @@ -310,17 +311,24 @@ export const companyRouter = { .input(z.object({ companyId: z.string() })) .query(async ({ ctx, input }) => { const reviews = await ctx.db.query.Review.findMany({ - where: eq(Review.companyId, input.companyId), + where: and( + eq(Review.companyId, input.companyId), + eq(Review.status, Status.PUBLISHED), + ), }); const calcAvg = (field: keyof ReviewType) => { return totalReviews > 0 - ? reviews.reduce((sum, review) => sum + Number(review[field]), 0) / + ? reviews + .filter((review) => review.status === Status.PUBLISHED) + .reduce((sum, review) => sum + Number(review[field]), 0) / totalReviews : 0; }; - const totalReviews = reviews.length; + const totalReviews = reviews.filter( + (review) => review.status === Status.PUBLISHED, + ).length; const averageOverallRating = calcAvg("overallRating"); const averageHourlyPay = calcAvg("hourlyPay"); diff --git a/packages/api/src/router/profile.ts b/packages/api/src/router/profile.ts index 6216f94e..33b2933c 100644 --- a/packages/api/src/router/profile.ts +++ b/packages/api/src/router/profile.ts @@ -1,6 +1,7 @@ import type { TRPCRouterRecord } from "@trpc/server"; import { z } from "zod"; +import type { Session } from "@cooper/auth"; import { and, desc, eq, sql } from "@cooper/db"; import { Company, @@ -11,8 +12,6 @@ import { } from "@cooper/db/schema"; import { UpdateProfileNameMajorSchema } from "../../../db/src/schema/profiles"; -import type { Session } from "@cooper/auth"; - import { CreateProfileToCompanySchema, ProfilesToCompanies, diff --git a/packages/api/src/router/review.ts b/packages/api/src/router/review.ts index 4883db9e..a9789a65 100644 --- a/packages/api/src/router/review.ts +++ b/packages/api/src/router/review.ts @@ -5,14 +5,14 @@ import Fuse from "fuse.js"; import { z } from "zod"; import type { ReviewType } from "@cooper/db/schema"; -import { and, desc, eq, inArray } from "@cooper/db"; +import { and, desc, eq, inArray, isNull } from "@cooper/db"; import { CompaniesToLocations, Company, CreateReviewSchema, Review, + Status, } from "@cooper/db/schema"; - import { protectedProcedure, publicProcedure, @@ -36,7 +36,7 @@ export const reviewRouter = { const { options } = input; const conditions = [ - eq(Review.hidden, false), + eq(Review.status, Status.PUBLISHED), options?.cycle && eq(Review.workTerm, options.cycle), options?.term && eq(Review.workEnvironment, options.term), ].filter(Boolean); @@ -63,7 +63,10 @@ export const reviewRouter = { .input(z.object({ id: z.string() })) .query(({ ctx, input }) => { return ctx.db.query.Review.findMany({ - where: and(eq(Review.hidden, false), eq(Review.roleId, input.id)), + where: and( + eq(Review.roleId, input.id), + eq(Review.status, Status.PUBLISHED), + ), }); }), @@ -71,7 +74,10 @@ export const reviewRouter = { .input(z.object({ id: z.string() })) .query(({ ctx, input }) => { return ctx.db.query.Review.findMany({ - where: and(eq(Review.hidden, false), eq(Review.companyId, input.id)), + where: and( + eq(Review.companyId, input.id), + eq(Review.status, Status.PUBLISHED), + ), }); }), @@ -96,7 +102,7 @@ export const reviewRouter = { // Initialize bad words filter const filter = new Filter(); - if (filter.isProfane(input.textReview)) { + if (filter.isProfane(input.textReview ?? "")) { throw new TRPCError({ code: "PRECONDITION_FAILED", message: "Review text cannot contain profane words", @@ -111,7 +117,7 @@ export const reviewRouter = { // Create a clean version of the input with filtered strings const cleanInput = { ...input, - textReview: filter.clean(input.textReview), + textReview: filter.clean(input.textReview ?? ""), // Keep non-string fields as they are profileId: input.profileId, interviewReview: filter.clean(input.interviewReview ?? ""), @@ -121,7 +127,10 @@ export const reviewRouter = { const reviews = await ctx.db.query.Review.findMany({ where: eq(Review.profileId, cleanInput.profileId), }); - if (reviews.length >= 5) { + const publishedReviews = reviews.filter( + (review) => review.status === Status.PUBLISHED, + ); + if (publishedReviews.length >= 5) { throw new TRPCError({ code: "PRECONDITION_FAILED", message: "You can only leave 5 reviews", @@ -141,20 +150,18 @@ export const reviewRouter = { // Check if a CompaniesToLocations object already exists with the given companyId and locationId if (input.locationId && input.companyId) { - const companyId = input.companyId; - const locationId = input.locationId; const existingRelation = await ctx.db.query.CompaniesToLocations.findFirst({ where: and( - eq(CompaniesToLocations.companyId, companyId), - eq(CompaniesToLocations.locationId, locationId), + eq(CompaniesToLocations.companyId, input.companyId ?? ""), + eq(CompaniesToLocations.locationId, input.locationId ?? ""), ), }); if (!existingRelation) { await ctx.db.insert(CompaniesToLocations).values({ - locationId, - companyId, + locationId: input.locationId ?? "", + companyId: input.companyId ?? "", }); } } @@ -162,6 +169,96 @@ export const reviewRouter = { return ctx.db.insert(Review).values(cleanInput); }), + saveDraft: protectedProcedure + .input(CreateReviewSchema) + .mutation(async ({ ctx, input }) => { + if (!input.profileId) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "You must be logged in to save a draft", + }); + } + + const draftInput = { + ...input, + status: Status.DRAFT, + }; + + const existingReview = await ctx.db.query.Review.findFirst({ + where: and( + input.companyId == null + ? isNull(Review.companyId) + : eq(Review.companyId, input.companyId), + input.locationId == null + ? isNull(Review.locationId) + : eq(Review.locationId, input.locationId), + eq(Review.profileId, input.profileId), + input.workTerm == null + ? isNull(Review.workTerm) + : eq(Review.workTerm, input.workTerm), + input.workYear == null + ? isNull(Review.workYear) + : eq(Review.workYear, input.workYear), + eq(Review.status, Status.DRAFT), + input.roleId == null + ? isNull(Review.roleId) + : eq(Review.roleId, input.roleId), + input.overallRating == null + ? isNull(Review.overallRating) + : eq(Review.overallRating, input.overallRating), + input.hourlyPay == null + ? isNull(Review.hourlyPay) + : eq(Review.hourlyPay, input.hourlyPay), + input.interviewDifficulty == null + ? isNull(Review.interviewDifficulty) + : eq(Review.interviewDifficulty, input.interviewDifficulty), + input.cultureRating == null + ? isNull(Review.cultureRating) + : eq(Review.cultureRating, input.cultureRating), + input.supervisorRating == null + ? isNull(Review.supervisorRating) + : eq(Review.supervisorRating, input.supervisorRating), + input.interviewRating == null + ? isNull(Review.interviewRating) + : eq(Review.interviewRating, input.interviewRating), + input.federalHolidays == null + ? isNull(Review.federalHolidays) + : eq(Review.federalHolidays, input.federalHolidays), + input.drugTest == null + ? isNull(Review.drugTest) + : eq(Review.drugTest, input.drugTest), + input.freeLunch == null + ? isNull(Review.freeLunch) + : eq(Review.freeLunch, input.freeLunch), + input.freeMerch == null + ? isNull(Review.freeMerch) + : eq(Review.freeMerch, input.freeMerch), + input.travelBenefits == null + ? isNull(Review.travelBenefits) + : eq(Review.travelBenefits, input.travelBenefits), + input.overtimeNormal == null + ? isNull(Review.overtimeNormal) + : eq(Review.overtimeNormal, input.overtimeNormal), + input.pto == null ? isNull(Review.pto) : eq(Review.pto, input.pto), + input.textReview == null + ? isNull(Review.textReview) + : eq(Review.textReview, input.textReview), + input.interviewReview == null + ? isNull(Review.interviewReview) + : eq(Review.interviewReview, input.interviewReview), + ), + }); + + if (existingReview) { + throw new TRPCError({ + code: "CONFLICT", + message: "This draft is a duplicate of an existing draft.", + }); + } + + return ctx.db.insert(Review).values(draftInput); + }), + delete: protectedProcedure.input(z.string()).mutation(({ ctx, input }) => { return ctx.db.delete(Review).where(eq(Review.id, input)); }), @@ -183,7 +280,10 @@ export const reviewRouter = { const companyIds = companies.map((company) => company.id); const reviews = await ctx.db.query.Review.findMany({ - where: inArray(Review.companyId, companyIds), + where: and( + inArray(Review.companyId, companyIds), + eq(Review.status, Status.PUBLISHED), + ), }); const calcAvg = (field: keyof ReviewType) => { diff --git a/packages/api/src/router/role.ts b/packages/api/src/router/role.ts index 5d6cd5b7..1ae1ee70 100644 --- a/packages/api/src/router/role.ts +++ b/packages/api/src/router/role.ts @@ -5,7 +5,13 @@ import { z } from "zod"; import type { ReviewType, RoleType } from "@cooper/db/schema"; import { and, asc, desc, eq, sql } from "@cooper/db"; -import { Company, CreateRoleSchema, Review, Role } from "@cooper/db/schema"; +import { + Company, + CreateRoleSchema, + Review, + Role, + Status, +} from "@cooper/db/schema"; import { protectedProcedure, @@ -155,7 +161,10 @@ export const roleRouter = { const rolesWithReviews = await ctx.db.execute(sql` SELECT DISTINCT ${Review.roleId}::uuid as role_id FROM ${Review} - WHERE ${Review.hidden} = false AND ${Review.roleId} != '' AND ${Review.roleId} IS NOT NULL + WHERE ${Review.roleId} != '' + AND ${Review.roleId} IS NOT NULL + AND ${Review.status} = ${Status.PUBLISHED} + AND ${Review.hidden} = false `); const roleIds = rolesWithReviews.rows.map((row) => String(row.role_id)); @@ -229,11 +238,19 @@ export const roleRouter = { .input(z.object({ roleId: z.string() })) .query(async ({ ctx, input }) => { let reviews = await ctx.db.query.Review.findMany({ - where: and(eq(Review.hidden, false), eq(Review.roleId, input.roleId)), + where: and( + eq(Review.hidden, false), + eq(Review.roleId, input.roleId), + eq(Review.status, Status.PUBLISHED), + ), orderBy: ordering.default, }); reviews = await ctx.db.query.Review.findMany({ - where: and(eq(Review.hidden, false), eq(Review.roleId, input.roleId)), + where: and( + eq(Review.hidden, false), + eq(Review.roleId, input.roleId), + eq(Review.status, Status.PUBLISHED), + ), }); const calcAvg = (field: keyof ReviewType) => { diff --git a/packages/api/src/router/roleAndCompany.ts b/packages/api/src/router/roleAndCompany.ts index 65ff76bf..09d71b74 100644 --- a/packages/api/src/router/roleAndCompany.ts +++ b/packages/api/src/router/roleAndCompany.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import type { CompanyType, RoleType } from "@cooper/db/schema"; +import { Status } from "@cooper/db/schema"; import { asc, desc, sql } from "@cooper/db"; import { Company, Review, Role } from "@cooper/db/schema"; @@ -52,7 +53,10 @@ export const roleAndCompanyRouter = { FROM ${Role} INNER JOIN ${Company} ON NULLIF(${Role.companyId}, '')::uuid = ${Company.id} INNER JOIN ${Review} ON ${Review.roleId}::uuid = ${Role.id} - WHERE ${Company.hidden} = false AND ${Role.hidden} = false AND ${Review.hidden} = false + WHERE ${Review.status} = ${Status.PUBLISHED} + AND ${Company.hidden} = false + AND ${Role.hidden} = false + AND ${Review.hidden} = false GROUP BY ${Role.id} ORDER BY avg_rating DESC `); @@ -67,6 +71,7 @@ export const roleAndCompanyRouter = { INNER JOIN ${Role} ON ${Review.roleId}::uuid = ${Role.id} INNER JOIN ${Company} ON NULLIF(${Role.companyId}, '')::uuid = ${Company.id} WHERE ${Company.hidden} = false AND ${Role.hidden} = false AND ${Review.hidden} = false AND ${Review.roleId} != '' AND ${Review.roleId} IS NOT NULL + AND ${Review.status} = ${Status.PUBLISHED} `); const roleIds = rolesWithReviews.rows.map((row) => String(row.role_id)); @@ -91,7 +96,10 @@ export const roleAndCompanyRouter = { FROM ${Company} LEFT JOIN ${Role} ON NULLIF(${Role.companyId}, '')::uuid = ${Company.id} INNER JOIN ${Review} ON ${Review.roleId}::uuid = ${Role.id} - WHERE ${Company.hidden} = false AND ${Review.hidden} = false AND ${Role.hidden} = false + WHERE ${Review.status} = ${Status.PUBLISHED} + AND ${Company.hidden} = false + AND ${Review.hidden} = false + AND ${Role.hidden} = false GROUP BY ${Company.id} ORDER BY avg_rating DESC `); @@ -110,7 +118,12 @@ export const roleAndCompanyRouter = { FROM ${Company} INNER JOIN ${Role} ON ${Role.companyId}::uuid = ${Company.id} INNER JOIN ${Review} ON ${Review.roleId}::uuid = ${Role.id} - WHERE ${Company.hidden} = false AND ${Role.hidden} = false AND ${Review.hidden} = false AND ${Review.roleId} != '' AND ${Review.roleId} IS NOT NULL + WHERE ${Company.hidden} = false + AND ${Role.hidden} = false + AND ${Review.hidden} = false + AND ${Review.roleId} != '' + AND ${Review.roleId} IS NOT NULL + AND ${Review.status} = ${Status.PUBLISHED} `); const companyIdsWithReviews = new Set( @@ -184,14 +197,18 @@ export const roleAndCompanyRouter = { if (jobTypeFilterActive && roles.length > 0) { const roleIds = roles.map((r) => r.id); const reviewsWithJobType = await ctx.db.query.Review.findMany({ - where: (review, { inArray }) => inArray(review.roleId, roleIds), + where: (review, { inArray, and, eq }) => + and( + inArray(review.roleId, roleIds), + eq(review.status, Status.PUBLISHED), + ), columns: { companyId: true, jobType: true }, }); for (const r of reviewsWithJobType) { const cid = r.companyId; - const arr = companyJobTypesMap.get(cid) ?? []; - if (!arr.includes(r.jobType)) arr.push(r.jobType); - companyJobTypesMap.set(cid, arr); + const arr = companyJobTypesMap.get(cid ?? "") ?? []; + if (!arr.includes(r.jobType ?? "")) arr.push(r.jobType ?? ""); + companyJobTypesMap.set(cid ?? "", arr); } } @@ -222,6 +239,7 @@ export const roleAndCompanyRouter = { roleIds.map((id) => sql`${id}`), sql`,`, )}) + AND ${Review.status} = ${Status.PUBLISHED} GROUP BY ${Role.id} `); @@ -238,6 +256,7 @@ export const roleAndCompanyRouter = { roleIds.map((id) => sql`${id}`), sql`,`, )}) + AND ${Review.status} = ${Status.PUBLISHED} GROUP BY ${Role.id} `); @@ -255,6 +274,7 @@ export const roleAndCompanyRouter = { roleIds.map((id) => sql`${id}`), sql`,`, )}) + AND ${Review.status} = ${Status.PUBLISHED} GROUP BY ${Role.id} `); @@ -279,6 +299,7 @@ export const roleAndCompanyRouter = { roleIds.map((id) => sql`${id}`), sql`,`, )}) + AND ${Review.status} = ${Status.PUBLISHED} GROUP BY ${Role.id} `); @@ -299,6 +320,7 @@ export const roleAndCompanyRouter = { roleIds.map((id) => sql`${id}`), sql`,`, )}) + AND ${Review.status} = ${Status.PUBLISHED} GROUP BY ${Role.id} `); @@ -323,6 +345,7 @@ export const roleAndCompanyRouter = { companyIds.map((id) => sql`${id}`), sql`,`, )}) + AND ${Review.status} = ${Status.PUBLISHED} GROUP BY ${Company.id} `); @@ -340,6 +363,7 @@ export const roleAndCompanyRouter = { companyIds.map((id) => sql`${id}`), sql`,`, )}) + AND ${Review.status} = ${Status.PUBLISHED} GROUP BY ${Company.id} `); @@ -358,6 +382,7 @@ export const roleAndCompanyRouter = { companyIds.map((id) => sql`${id}`), sql`,`, )}) + AND ${Review.status} = ${Status.PUBLISHED} GROUP BY ${Company.id} `); @@ -383,6 +408,7 @@ export const roleAndCompanyRouter = { companyIds.map((id) => sql`${id}`), sql`,`, )}) + AND ${Review.status} = ${Status.PUBLISHED} GROUP BY ${Company.id} `); @@ -404,6 +430,7 @@ export const roleAndCompanyRouter = { companyIds.map((id) => sql`${id}`), sql`,`, )}) + AND ${Review.status} = ${Status.PUBLISHED} GROUP BY ${Company.id} `); @@ -610,7 +637,10 @@ export const roleAndCompanyRouter = { FROM ${Role} INNER JOIN ${Company} ON NULLIF(${Role.companyId}, '')::uuid = ${Company.id} INNER JOIN ${Review} ON ${Review.roleId}::uuid = ${Role.id} - WHERE ${Company.hidden} = false AND ${Role.hidden} = false AND ${Review.hidden} = false + WHERE ${Review.status} = ${Status.PUBLISHED} + AND ${Company.hidden} = false + AND ${Role.hidden} = false + AND ${Review.hidden} = false GROUP BY ${Role.id} ORDER BY avg_rating DESC `); @@ -624,7 +654,12 @@ export const roleAndCompanyRouter = { FROM ${Review} INNER JOIN ${Role} ON ${Review.roleId}::uuid = ${Role.id} INNER JOIN ${Company} ON NULLIF(${Role.companyId}, '')::uuid = ${Company.id} - WHERE ${Company.hidden} = false AND ${Role.hidden} = false AND ${Review.hidden} = false AND ${Review.roleId} != '' AND ${Review.roleId} IS NOT NULL + WHERE ${Company.hidden} = false + AND ${Role.hidden} = false + AND ${Review.hidden} = false + AND ${Review.roleId} != '' + AND ${Review.roleId} IS NOT NULL + AND ${Review.status} = ${Status.PUBLISHED} `); const roleIds = rolesWithReviews.rows.map((row) => String(row.role_id)); @@ -649,7 +684,10 @@ export const roleAndCompanyRouter = { FROM ${Company} LEFT JOIN ${Role} ON NULLIF(${Role.companyId}, '')::uuid = ${Company.id} INNER JOIN ${Review} ON ${Review.roleId}::uuid = ${Role.id} - WHERE ${Company.hidden} = false AND ${Review.hidden} = false AND ${Role.hidden} = false + WHERE ${Review.status} = ${Status.PUBLISHED} + AND ${Company.hidden} = false + AND ${Review.hidden} = false + AND ${Role.hidden} = false GROUP BY ${Company.id} ORDER BY avg_rating DESC `); @@ -668,7 +706,12 @@ export const roleAndCompanyRouter = { FROM ${Company} INNER JOIN ${Role} ON ${Role.companyId}::uuid = ${Company.id} INNER JOIN ${Review} ON ${Review.roleId}::uuid = ${Role.id} - WHERE ${Company.hidden} = false AND ${Role.hidden} = false AND ${Review.hidden} = false AND ${Review.roleId} != '' AND ${Review.roleId} IS NOT NULL + WHERE ${Company.hidden} = false + AND ${Role.hidden} = false + AND ${Review.hidden} = false + AND ${Review.roleId} != '' + AND ${Review.roleId} IS NOT NULL + AND ${Review.status} = ${Status.PUBLISHED} `); const companyIdsWithReviews = new Set( diff --git a/packages/api/tests/review.test.ts b/packages/api/tests/review.test.ts index 1e56b8ae..cedd6081 100644 --- a/packages/api/tests/review.test.ts +++ b/packages/api/tests/review.test.ts @@ -56,7 +56,7 @@ describe("Review Router", async () => { expect(db.query.Review.findMany).toHaveBeenCalledWith({ orderBy: expect.anything(), - where: and(eq(Review.hidden, false)), + where: and(eq(Review.status, Status.PUBLISHED)), }); }); @@ -69,7 +69,10 @@ describe("Review Router", async () => { expect(db.query.Review.findMany).toHaveBeenCalledWith({ orderBy: expect.anything(), - where: and(eq(Review.hidden, false), eq(Review.workTerm, "SPRING")), + where: and( + eq(Review.status, Status.PUBLISHED), + eq(Review.workTerm, "SPRING"), + ), }); }); @@ -83,7 +86,7 @@ describe("Review Router", async () => { expect(db.query.Review.findMany).toHaveBeenCalledWith({ orderBy: expect.anything(), where: and( - eq(Review.hidden, false), + eq(Review.status, Status.PUBLISHED), eq(Review.workEnvironment, "REMOTE"), ), }); @@ -100,7 +103,7 @@ describe("Review Router", async () => { expect(db.query.Review.findMany).toHaveBeenCalledWith({ orderBy: expect.anything(), where: and( - eq(Review.hidden, false), + eq(Review.status, Status.PUBLISHED), eq(Review.workTerm, "SPRING"), eq(Review.workEnvironment, "REMOTE"), ), @@ -201,7 +204,7 @@ describe("Review Router", async () => { otherBenefits: "Good", roleId: "1", profileId: "1", - status: Status.DRAFT, + status: Status.PUBLISHED, }, { id: "2", @@ -234,7 +237,7 @@ describe("Review Router", async () => { otherBenefits: "Good", roleId: "1", profileId: "1", - status: Status.DRAFT, + status: Status.PUBLISHED, }, ]); @@ -253,7 +256,10 @@ describe("Review Router", async () => { const companyIds = companies.map((company) => company.id); expect(db.query.Review.findMany).toHaveBeenCalledWith({ - where: inArray(Review.companyId, companyIds), + where: and( + inArray(Review.companyId, companyIds), + eq(Review.status, Status.PUBLISHED), + ), }); expect(result).toEqual({ diff --git a/packages/db/drizzle/0018_square_supernaut.sql b/packages/db/drizzle/0018_square_supernaut.sql new file mode 100644 index 00000000..837afef7 --- /dev/null +++ b/packages/db/drizzle/0018_square_supernaut.sql @@ -0,0 +1,14 @@ +ALTER TABLE "review" ALTER COLUMN "reviewHeadline" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "review" ALTER COLUMN "textReview" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "review" ALTER COLUMN "jobType" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "review" ALTER COLUMN "workEnvironment" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "review" ALTER COLUMN "drugTest" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "review" ALTER COLUMN "overtimeNormal" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "review" ALTER COLUMN "pto" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "review" ALTER COLUMN "federalHolidays" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "review" ALTER COLUMN "freeLunch" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "review" ALTER COLUMN "travelBenefits" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "review" ALTER COLUMN "freeMerch" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "review" ALTER COLUMN "snackBar" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "review" ALTER COLUMN "roleId" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "review" ALTER COLUMN "companyId" DROP NOT NULL; \ No newline at end of file diff --git a/packages/db/drizzle/0019_solid_tigra.sql b/packages/db/drizzle/0019_solid_tigra.sql new file mode 100644 index 00000000..13291be0 --- /dev/null +++ b/packages/db/drizzle/0019_solid_tigra.sql @@ -0,0 +1,8 @@ + +ALTER TABLE "review" ALTER COLUMN "workTerm" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "review" ALTER COLUMN "workYear" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "review" ALTER COLUMN "overallRating" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "review" ALTER COLUMN "cultureRating" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "review" ALTER COLUMN "supervisorRating" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "review" ALTER COLUMN "interviewRating" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "review" ALTER COLUMN "interviewDifficulty" DROP NOT NULL; \ No newline at end of file diff --git a/packages/db/drizzle/meta/0018_snapshot.json b/packages/db/drizzle/meta/0018_snapshot.json new file mode 100644 index 00000000..4a8621f9 --- /dev/null +++ b/packages/db/drizzle/meta/0018_snapshot.json @@ -0,0 +1,1187 @@ +{ + "id": "50eefb3a-3a8e-4927-8c9c-82c8b6d631ed", + "prevId": "6bce540c-416b-4f53-939d-a40c81c56e9c", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_state": { + "name": "session_state", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "name": "account_provider_providerAccountId_pk", + "columns": ["provider", "providerAccountId"] + } + }, + "uniqueConstraints": {} + }, + "public.company": { + "name": "company", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "hidden": { + "name": "hidden", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "industry": { + "name": "industry", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "website": { + "name": "website", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "company_slug_unique": { + "name": "company_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + } + }, + "public.companies_to_locations": { + "name": "companies_to_locations", + "schema": "", + "columns": { + "companyId": { + "name": "companyId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "locationId": { + "name": "locationId", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "companies_to_locations_companyId_company_id_fk": { + "name": "companies_to_locations_companyId_company_id_fk", + "tableFrom": "companies_to_locations", + "tableTo": "company", + "columnsFrom": ["companyId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "companies_to_locations_locationId_location_id_fk": { + "name": "companies_to_locations_locationId_location_id_fk", + "tableFrom": "companies_to_locations", + "tableTo": "location", + "columnsFrom": ["locationId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "companies_to_locations_companyId_locationId_pk": { + "name": "companies_to_locations_companyId_locationId_pk", + "columns": ["companyId", "locationId"] + } + }, + "uniqueConstraints": {} + }, + "public.company_request": { + "name": "company_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "industry": { + "name": "industry", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "website": { + "name": "website", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "locationId": { + "name": "locationId", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "status": { + "name": "status", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'PENDING'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.flagged": { + "name": "flagged", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "entityType": { + "name": "entityType", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "entityId": { + "name": "entityId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "isActive": { + "name": "isActive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "deactivatedAt": { + "name": "deactivatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deactivatedByAdminId": { + "name": "deactivatedByAdminId", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "flagged_adminId_user_id_fk": { + "name": "flagged_adminId_user_id_fk", + "tableFrom": "flagged", + "tableTo": "user", + "columnsFrom": ["adminId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "flagged_deactivatedByAdminId_user_id_fk": { + "name": "flagged_deactivatedByAdminId_user_id_fk", + "tableFrom": "flagged", + "tableTo": "user", + "columnsFrom": ["deactivatedByAdminId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.hidden": { + "name": "hidden", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "entityType": { + "name": "entityType", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "entityId": { + "name": "entityId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "isActive": { + "name": "isActive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "deactivatedAt": { + "name": "deactivatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deactivatedByAdminId": { + "name": "deactivatedByAdminId", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "hidden_adminId_user_id_fk": { + "name": "hidden_adminId_user_id_fk", + "tableFrom": "hidden", + "tableTo": "user", + "columnsFrom": ["adminId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hidden_deactivatedByAdminId_user_id_fk": { + "name": "hidden_deactivatedByAdminId_user_id_fk", + "tableFrom": "hidden", + "tableTo": "user", + "columnsFrom": ["deactivatedByAdminId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.location": { + "name": "location", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "city": { + "name": "city", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "country": { + "name": "country", + "type": "varchar", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "firstName": { + "name": "firstName", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "lastName": { + "name": "lastName", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "major": { + "name": "major", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "minor": { + "name": "minor", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "graduationYear": { + "name": "graduationYear", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "graduationMonth": { + "name": "graduationMonth", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "userId": { + "name": "userId", + "type": "varchar", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_userId_unique": { + "name": "profile_userId_unique", + "nullsNotDistinct": false, + "columns": ["userId"] + } + } + }, + "public.profiles_to_companies": { + "name": "profiles_to_companies", + "schema": "", + "columns": { + "profileId": { + "name": "profileId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "companyId": { + "name": "companyId", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "profiles_to_companies_profileId_profile_id_fk": { + "name": "profiles_to_companies_profileId_profile_id_fk", + "tableFrom": "profiles_to_companies", + "tableTo": "profile", + "columnsFrom": ["profileId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "profiles_to_companies_companyId_company_id_fk": { + "name": "profiles_to_companies_companyId_company_id_fk", + "tableFrom": "profiles_to_companies", + "tableTo": "company", + "columnsFrom": ["companyId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "profiles_to_companies_profileId_companyId_pk": { + "name": "profiles_to_companies_profileId_companyId_pk", + "columns": ["profileId", "companyId"] + } + }, + "uniqueConstraints": {} + }, + "public.profiles_to_roles": { + "name": "profiles_to_roles", + "schema": "", + "columns": { + "profileId": { + "name": "profileId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "profiles_to_roles_profileId_profile_id_fk": { + "name": "profiles_to_roles_profileId_profile_id_fk", + "tableFrom": "profiles_to_roles", + "tableTo": "profile", + "columnsFrom": ["profileId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "profiles_to_roles_roleId_role_id_fk": { + "name": "profiles_to_roles_roleId_role_id_fk", + "tableFrom": "profiles_to_roles", + "tableTo": "role", + "columnsFrom": ["roleId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "profiles_to_roles_profileId_roleId_pk": { + "name": "profiles_to_roles_profileId_roleId_pk", + "columns": ["profileId", "roleId"] + } + }, + "uniqueConstraints": {} + }, + "public.profiles_to_reviews": { + "name": "profiles_to_reviews", + "schema": "", + "columns": { + "profileId": { + "name": "profileId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "reviewId": { + "name": "reviewId", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "profiles_to_reviews_profileId_profile_id_fk": { + "name": "profiles_to_reviews_profileId_profile_id_fk", + "tableFrom": "profiles_to_reviews", + "tableTo": "profile", + "columnsFrom": ["profileId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "profiles_to_reviews_reviewId_review_id_fk": { + "name": "profiles_to_reviews_reviewId_review_id_fk", + "tableFrom": "profiles_to_reviews", + "tableTo": "review", + "columnsFrom": ["reviewId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "profiles_to_reviews_profileId_reviewId_pk": { + "name": "profiles_to_reviews_profileId_reviewId_pk", + "columns": ["profileId", "reviewId"] + } + }, + "uniqueConstraints": {} + }, + "public.report": { + "name": "report", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "reportText": { + "name": "reportText", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "profileId": { + "name": "profileId", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "companyId": { + "name": "companyId", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "roleId": { + "name": "roleId", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "reviewId": { + "name": "reviewId", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.review": { + "name": "review", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "hidden": { + "name": "hidden", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "workTerm": { + "name": "workTerm", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "workYear": { + "name": "workYear", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "overallRating": { + "name": "overallRating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "cultureRating": { + "name": "cultureRating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "supervisorRating": { + "name": "supervisorRating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "interviewRating": { + "name": "interviewRating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "interviewDifficulty": { + "name": "interviewDifficulty", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "interviewReview": { + "name": "interviewReview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reviewHeadline": { + "name": "reviewHeadline", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "textReview": { + "name": "textReview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locationId": { + "name": "locationId", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "jobType": { + "name": "jobType", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Co-op'" + }, + "hourlyPay": { + "name": "hourlyPay", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "workEnvironment": { + "name": "workEnvironment", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "drugTest": { + "name": "drugTest", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "overtimeNormal": { + "name": "overtimeNormal", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "pto": { + "name": "pto", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "federalHolidays": { + "name": "federalHolidays", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "freeLunch": { + "name": "freeLunch", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "travelBenefits": { + "name": "travelBenefits", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "freeMerch": { + "name": "freeMerch", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "snackBar": { + "name": "snackBar", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "otherBenefits": { + "name": "otherBenefits", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "roleId": { + "name": "roleId", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "profileId": { + "name": "profileId", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "companyId": { + "name": "companyId", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'DRAFT'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.role_request": { + "name": "role_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "companyId": { + "name": "companyId", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "status": { + "name": "status", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'PENDING'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.role": { + "name": "role", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "hidden": { + "name": "hidden", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "companyId": { + "name": "companyId", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "createdBy": { + "name": "createdBy", + "type": "varchar", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "image": { + "name": "image", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "role": { + "name": "role", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'STUDENT'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/drizzle/meta/0019_snapshot.json b/packages/db/drizzle/meta/0019_snapshot.json new file mode 100644 index 00000000..eb3d36b9 --- /dev/null +++ b/packages/db/drizzle/meta/0019_snapshot.json @@ -0,0 +1,1187 @@ +{ + "id": "52387cf2-c4bd-4718-969c-5fabd286de9c", + "prevId": "50eefb3a-3a8e-4927-8c9c-82c8b6d631ed", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_state": { + "name": "session_state", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "name": "account_provider_providerAccountId_pk", + "columns": ["provider", "providerAccountId"] + } + }, + "uniqueConstraints": {} + }, + "public.company": { + "name": "company", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "hidden": { + "name": "hidden", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "industry": { + "name": "industry", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "website": { + "name": "website", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "company_slug_unique": { + "name": "company_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + } + }, + "public.companies_to_locations": { + "name": "companies_to_locations", + "schema": "", + "columns": { + "companyId": { + "name": "companyId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "locationId": { + "name": "locationId", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "companies_to_locations_companyId_company_id_fk": { + "name": "companies_to_locations_companyId_company_id_fk", + "tableFrom": "companies_to_locations", + "tableTo": "company", + "columnsFrom": ["companyId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "companies_to_locations_locationId_location_id_fk": { + "name": "companies_to_locations_locationId_location_id_fk", + "tableFrom": "companies_to_locations", + "tableTo": "location", + "columnsFrom": ["locationId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "companies_to_locations_companyId_locationId_pk": { + "name": "companies_to_locations_companyId_locationId_pk", + "columns": ["companyId", "locationId"] + } + }, + "uniqueConstraints": {} + }, + "public.company_request": { + "name": "company_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "industry": { + "name": "industry", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "website": { + "name": "website", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "locationId": { + "name": "locationId", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "status": { + "name": "status", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'PENDING'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.flagged": { + "name": "flagged", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "entityType": { + "name": "entityType", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "entityId": { + "name": "entityId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "isActive": { + "name": "isActive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "deactivatedAt": { + "name": "deactivatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deactivatedByAdminId": { + "name": "deactivatedByAdminId", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "flagged_adminId_user_id_fk": { + "name": "flagged_adminId_user_id_fk", + "tableFrom": "flagged", + "tableTo": "user", + "columnsFrom": ["adminId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "flagged_deactivatedByAdminId_user_id_fk": { + "name": "flagged_deactivatedByAdminId_user_id_fk", + "tableFrom": "flagged", + "tableTo": "user", + "columnsFrom": ["deactivatedByAdminId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.hidden": { + "name": "hidden", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "entityType": { + "name": "entityType", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "entityId": { + "name": "entityId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "isActive": { + "name": "isActive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "deactivatedAt": { + "name": "deactivatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deactivatedByAdminId": { + "name": "deactivatedByAdminId", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "hidden_adminId_user_id_fk": { + "name": "hidden_adminId_user_id_fk", + "tableFrom": "hidden", + "tableTo": "user", + "columnsFrom": ["adminId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hidden_deactivatedByAdminId_user_id_fk": { + "name": "hidden_deactivatedByAdminId_user_id_fk", + "tableFrom": "hidden", + "tableTo": "user", + "columnsFrom": ["deactivatedByAdminId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.location": { + "name": "location", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "city": { + "name": "city", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "country": { + "name": "country", + "type": "varchar", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "firstName": { + "name": "firstName", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "lastName": { + "name": "lastName", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "major": { + "name": "major", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "minor": { + "name": "minor", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "graduationYear": { + "name": "graduationYear", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "graduationMonth": { + "name": "graduationMonth", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "userId": { + "name": "userId", + "type": "varchar", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_userId_unique": { + "name": "profile_userId_unique", + "nullsNotDistinct": false, + "columns": ["userId"] + } + } + }, + "public.profiles_to_companies": { + "name": "profiles_to_companies", + "schema": "", + "columns": { + "profileId": { + "name": "profileId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "companyId": { + "name": "companyId", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "profiles_to_companies_profileId_profile_id_fk": { + "name": "profiles_to_companies_profileId_profile_id_fk", + "tableFrom": "profiles_to_companies", + "tableTo": "profile", + "columnsFrom": ["profileId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "profiles_to_companies_companyId_company_id_fk": { + "name": "profiles_to_companies_companyId_company_id_fk", + "tableFrom": "profiles_to_companies", + "tableTo": "company", + "columnsFrom": ["companyId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "profiles_to_companies_profileId_companyId_pk": { + "name": "profiles_to_companies_profileId_companyId_pk", + "columns": ["profileId", "companyId"] + } + }, + "uniqueConstraints": {} + }, + "public.profiles_to_roles": { + "name": "profiles_to_roles", + "schema": "", + "columns": { + "profileId": { + "name": "profileId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "profiles_to_roles_profileId_profile_id_fk": { + "name": "profiles_to_roles_profileId_profile_id_fk", + "tableFrom": "profiles_to_roles", + "tableTo": "profile", + "columnsFrom": ["profileId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "profiles_to_roles_roleId_role_id_fk": { + "name": "profiles_to_roles_roleId_role_id_fk", + "tableFrom": "profiles_to_roles", + "tableTo": "role", + "columnsFrom": ["roleId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "profiles_to_roles_profileId_roleId_pk": { + "name": "profiles_to_roles_profileId_roleId_pk", + "columns": ["profileId", "roleId"] + } + }, + "uniqueConstraints": {} + }, + "public.profiles_to_reviews": { + "name": "profiles_to_reviews", + "schema": "", + "columns": { + "profileId": { + "name": "profileId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "reviewId": { + "name": "reviewId", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "profiles_to_reviews_profileId_profile_id_fk": { + "name": "profiles_to_reviews_profileId_profile_id_fk", + "tableFrom": "profiles_to_reviews", + "tableTo": "profile", + "columnsFrom": ["profileId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "profiles_to_reviews_reviewId_review_id_fk": { + "name": "profiles_to_reviews_reviewId_review_id_fk", + "tableFrom": "profiles_to_reviews", + "tableTo": "review", + "columnsFrom": ["reviewId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "profiles_to_reviews_profileId_reviewId_pk": { + "name": "profiles_to_reviews_profileId_reviewId_pk", + "columns": ["profileId", "reviewId"] + } + }, + "uniqueConstraints": {} + }, + "public.report": { + "name": "report", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "reportText": { + "name": "reportText", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "profileId": { + "name": "profileId", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "companyId": { + "name": "companyId", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "roleId": { + "name": "roleId", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "reviewId": { + "name": "reviewId", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.review": { + "name": "review", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "hidden": { + "name": "hidden", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "workTerm": { + "name": "workTerm", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "workYear": { + "name": "workYear", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "overallRating": { + "name": "overallRating", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cultureRating": { + "name": "cultureRating", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "supervisorRating": { + "name": "supervisorRating", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "interviewRating": { + "name": "interviewRating", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "interviewDifficulty": { + "name": "interviewDifficulty", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "interviewReview": { + "name": "interviewReview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reviewHeadline": { + "name": "reviewHeadline", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "textReview": { + "name": "textReview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locationId": { + "name": "locationId", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "jobType": { + "name": "jobType", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Co-op'" + }, + "hourlyPay": { + "name": "hourlyPay", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "workEnvironment": { + "name": "workEnvironment", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "drugTest": { + "name": "drugTest", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "overtimeNormal": { + "name": "overtimeNormal", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "pto": { + "name": "pto", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "federalHolidays": { + "name": "federalHolidays", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "freeLunch": { + "name": "freeLunch", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "travelBenefits": { + "name": "travelBenefits", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "freeMerch": { + "name": "freeMerch", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "snackBar": { + "name": "snackBar", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "otherBenefits": { + "name": "otherBenefits", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "roleId": { + "name": "roleId", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "profileId": { + "name": "profileId", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "companyId": { + "name": "companyId", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'DRAFT'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.role_request": { + "name": "role_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "companyId": { + "name": "companyId", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "status": { + "name": "status", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'PENDING'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.role": { + "name": "role", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "hidden": { + "name": "hidden", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "companyId": { + "name": "companyId", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "createdBy": { + "name": "createdBy", + "type": "varchar", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "image": { + "name": "image", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "role": { + "name": "role", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'STUDENT'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index 7cb5ce88..1b390528 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -127,6 +127,20 @@ "when": 1774812140562, "tag": "0017_wakeful_red_shift", "breakpoints": true + }, + { + "idx": 18, + "version": "7", + "when": 1775094808913, + "tag": "0018_square_supernaut", + "breakpoints": true + }, + { + "idx": 19, + "version": "7", + "when": 1775168598794, + "tag": "0019_solid_tigra", + "breakpoints": true } ] } diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index c4bbcac9..6fe846ed 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -2,10 +2,10 @@ import type { CompanyType } from "./schema/companies"; import type { LocationType } from "./schema/locations"; import type { IndustryType, + JobTypeType, WorkEnvironmentType, WorkTermType, StatusType, - JobTypeType, ModerationEntityTypeType, ReportReasonType, UserRoleType, diff --git a/packages/db/src/schema/reviews.ts b/packages/db/src/schema/reviews.ts index 9114eaea..2518b1a4 100644 --- a/packages/db/src/schema/reviews.ts +++ b/packages/db/src/schema/reviews.ts @@ -23,37 +23,37 @@ import { Role } from "./roles"; export const Review = pgTable("review", { id: uuid("id").notNull().primaryKey().defaultRandom(), hidden: boolean("hidden").notNull().default(false), - workTerm: varchar("workTerm").notNull(), - workYear: integer("workYear").notNull(), - overallRating: integer("overallRating").notNull(), - cultureRating: integer("cultureRating").notNull(), - supervisorRating: integer("supervisorRating").notNull(), - interviewRating: integer("interviewRating").notNull(), - interviewDifficulty: integer("interviewDifficulty").notNull(), + workTerm: varchar("workTerm"), + workYear: integer("workYear"), + overallRating: integer("overallRating"), + cultureRating: integer("cultureRating"), + supervisorRating: integer("supervisorRating"), + interviewRating: integer("interviewRating"), + interviewDifficulty: integer("interviewDifficulty"), interviewReview: text("interviewReview"), - reviewHeadline: varchar("reviewHeadline").notNull(), - textReview: text("textReview").notNull(), + reviewHeadline: varchar("reviewHeadline"), + textReview: text("textReview"), locationId: varchar("locationId"), - jobType: varchar("jobType").notNull().default("Co-op"), + jobType: varchar("jobType").default("Co-op"), hourlyPay: decimal("hourlyPay"), - workEnvironment: varchar("workEnvironment").notNull(), - drugTest: boolean("drugTest").notNull(), - overtimeNormal: boolean("overtimeNormal").notNull(), - pto: boolean("pto").notNull(), - federalHolidays: boolean("federalHolidays").notNull(), - freeLunch: boolean("freeLunch").notNull(), - travelBenefits: boolean("travelBenefits").notNull(), - freeMerch: boolean("freeMerch").notNull(), - snackBar: boolean("snackBar").notNull().default(false), + workEnvironment: varchar("workEnvironment"), + drugTest: boolean("drugTest"), + overtimeNormal: boolean("overtimeNormal"), + pto: boolean("pto"), + federalHolidays: boolean("federalHolidays"), + freeLunch: boolean("freeLunch"), + travelBenefits: boolean("travelBenefits"), + freeMerch: boolean("freeMerch"), + snackBar: boolean("snackBar").default(false), otherBenefits: text("otherBenefits"), createdAt: timestamp("created_at").defaultNow().notNull(), updatedAt: timestamp("updatedAt", { mode: "date", withTimezone: true, }).$onUpdateFn(() => sql`now()`), - roleId: varchar("roleId").notNull(), + roleId: varchar("roleId"), profileId: varchar("profileId"), - companyId: varchar("companyId").notNull(), + companyId: varchar("companyId"), status: varchar("status").notNull().default(Status.DRAFT), }); @@ -81,29 +81,38 @@ export const ReviewRelations = relations(Review, ({ one, many }) => ({ })); export const CreateReviewSchema = createInsertSchema(Review, { - workTerm: z.nativeEnum(WorkTerm), - workYear: z.number(), - overallRating: z.number().min(1).max(5), - cultureRating: z.number().min(1).max(5), - supervisorRating: z.number().min(1).max(5), - interviewRating: z.number().min(1).max(5).optional().default(0), - interviewReview: z.string().optional(), - reviewHeadline: z.string().optional().default(""), - textReview: z.string(), - locationId: z.string().optional(), - jobType: z.nativeEnum(JobType), - hourlyPay: z.string().optional(), - workEnvironment: z.nativeEnum(WorkEnvironment), - drugTest: z.boolean(), - overtimeNormal: z.boolean(), - pto: z.boolean(), - federalHolidays: z.boolean(), - freeLunch: z.boolean(), - travelBenefits: z.boolean(), - snackBar: z.boolean(), - freeMerch: z.boolean(), + workTerm: z.nativeEnum(WorkTerm).nullish(), + workYear: z.number().nullish(), + overallRating: z.preprocess( + (value) => (value === 0 ? null : value), + z.number().min(1).max(5).nullish(), + ), + cultureRating: z.preprocess( + (value) => (value === 0 ? null : value), + z.number().min(1).max(5).nullish(), + ), + supervisorRating: z.preprocess( + (value) => (value === 0 ? null : value), + z.number().min(1).max(5).nullish(), + ), + interviewRating: z.number().min(1).max(5).optional().default(0).nullish(), + interviewReview: z.string().optional().nullish(), + reviewHeadline: z.string().optional().default("").nullish(), + textReview: z.string().nullish(), + locationId: z.string().optional().nullish(), + jobType: z.nativeEnum(JobType).nullish(), + hourlyPay: z.string().optional().nullish(), + workEnvironment: z.nativeEnum(WorkEnvironment).nullish(), + drugTest: z.boolean().nullish(), + overtimeNormal: z.boolean().nullish(), + pto: z.boolean().nullish(), + federalHolidays: z.boolean().nullish(), + freeLunch: z.boolean().nullish(), + travelBenefits: z.boolean().nullish(), + snackBar: z.boolean().nullish(), + freeMerch: z.boolean().nullish(), otherBenefits: z.string().nullish(), - roleId: z.string(), + roleId: z.string().nullish(), profileId: z.string(), status: z.nativeEnum(Status), }).omit({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4a1fe75b..44107d8d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -179,6 +179,9 @@ importers: dayjs: specifier: ^1.11.13 version: 1.11.13 + echarts: + specifier: ^6.0.0 + version: 6.0.0 fuse.js: specifier: ^7.0.0 version: 7.0.0 @@ -5706,6 +5709,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + echarts@6.0.0: + resolution: {integrity: sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -9735,6 +9741,9 @@ packages: tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + tslib@2.3.0: + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + tslib@2.6.3: resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} @@ -10415,6 +10424,9 @@ packages: zod@3.24.2: resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} + zrender@6.0.0: + resolution: {integrity: sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -16284,6 +16296,11 @@ snapshots: eastasianwidth@0.2.0: {} + echarts@6.0.0: + dependencies: + tslib: 2.3.0 + zrender: 6.0.0 + ee-first@1.1.1: {} electron-to-chromium@1.4.832: {} @@ -21274,6 +21291,8 @@ snapshots: tslib@1.14.1: {} + tslib@2.3.0: {} + tslib@2.6.3: {} tslib@2.8.1: {} @@ -22057,4 +22076,8 @@ snapshots: zod@3.24.2: {} + zrender@6.0.0: + dependencies: + tslib: 2.3.0 + zwitch@2.0.4: {}