diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..56e500a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,91 @@ +name: AllClass CI +run-name: AllClass CI β’ ${{ github.ref_name }} β’ by ${{ github.actor }} + +on: + push: + branches: + - main + - dev + - feat/** + pull_request: + branches: + - main + - dev + - feat/** + +jobs: + lint_build: + name: "π§ Lint & Build" + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: "========== π§ Lint & Build β’ START ==========" + run: echo "========== π§ Lint & Build β’ START ==========" + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: yarn + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Lint + run: yarn lint + + - name: Build + run: yarn build + env: + NEXT_TELEMETRY_DISABLED: 1 + NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }} + + - name: "========== π§ Lint & Build β’ END ==========" + if: always() + env: + JOB_STATUS: ${{ job.status }} + run: | + echo "========== π§ Lint & Build β’ END (status: ${JOB_STATUS}) ==========" + + e2e: + name: "π§ͺ AllClass E2E Test" + needs: lint_build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: "========== π§ͺ AllClass E2E β’ START ==========" + run: echo "========== π§ͺ AllClass E2E β’ START ==========" + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: yarn + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Cypress run + uses: cypress-io/github-action@v6 + with: + build: yarn build + start: yarn start + wait-on: 'http://localhost:3000' + browser: chrome + env: + CI: true + NEXT_TELEMETRY_DISABLED: 1 + NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }} + CYPRESS_TEST_USER_ID: ${{ secrets.CYPRESS_TEST_USER_ID }} + CYPRESS_TEST_USER_PW: ${{ secrets.CYPRESS_TEST_USER_PW }} + + - name: "========== π§ͺ AllClass E2E β’ END ==========" + if: always() + env: + JOB_STATUS: ${{ job.status }} + run: | + echo "========== π§ͺ AllClass E2E β’ END (status: ${JOB_STATUS}) ==========" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5ef6a52..10d5412 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,8 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* +# cypress local secrets +cypress.env.json # vercel .vercel diff --git a/app/[universityName]/[lectureId]/page.tsx b/app/[universityName]/[lectureId]/page.tsx index c1882a4..7324438 100644 --- a/app/[universityName]/[lectureId]/page.tsx +++ b/app/[universityName]/[lectureId]/page.tsx @@ -1,13 +1,11 @@ -import { ReviewCardListSkeleton } from '@/domains/review'; -import { LectureInfoSkeleton } from '@/domains/lecture'; -import { LectureInfoServer } from '@/domains/lecture/server/components/LectureInfoServer'; -import { ReviewListServer } from '@/domains/review/server/components/ReviewListServer'; +import { ReviewCardListSkeleton, ReviewListServer } from '@/domains/review'; +import { LectureInfoSkeleton, LectureInfoServer } from '@/domains/lecture'; import { Suspense } from 'react'; -import styles from '@/styles/global.module.css'; import { dehydrate, HydrationBoundary } from '@tanstack/react-query'; import { createQueryClient, prefetchLectureRating, prefetchAllSortOptions } from '@/utils'; import { universityNames } from '@/constants'; import { getLectureListStatic } from '@/lib'; +import styles from '@/styles/global.module.css'; export async function generateStaticParams() { const allParams = await Promise.allSettled( diff --git a/app/api/revalidate/route.ts b/app/api/revalidate/route.ts index f5e8ac0..4012046 100644 --- a/app/api/revalidate/route.ts +++ b/app/api/revalidate/route.ts @@ -3,7 +3,7 @@ import { NextRequest, NextResponse } from 'next/server'; export async function POST(request: NextRequest) { try { - const { universityName, action } = await request.json(); + const { universityName, action, lectureId, userNumber } = await request.json(); if (!universityName) { return NextResponse.json({ message: 'University name is required' }, { status: 400 }); @@ -14,6 +14,23 @@ export async function POST(request: NextRequest) { case 'lecture_removed': case 'lecture_modified': await revalidateTag(`lectures-${universityName}`); + if (lectureId) { + await revalidateTag(`lecture-info-${universityName}-${lectureId}`); + await revalidateTag(`reviews-${lectureId}`); + } + break; + + case 'review_added': + case 'review_updated': + case 'review_deleted': + if (lectureId) { + await revalidateTag(`reviews-${lectureId}`); + await revalidateTag(`lecture-info-${universityName}-${lectureId}`); + } + if (userNumber) { + await revalidateTag(`my-reviews-${userNumber}`); + await revalidateTag(`my-lectures-${userNumber}`); + } break; case 'university_added': @@ -29,6 +46,8 @@ export async function POST(request: NextRequest) { timestamp: new Date().toISOString(), action, universityName, + lectureId, + userNumber, }); } catch (error) { return NextResponse.json({ message: 'Internal server error' }, { status: 500 }); diff --git a/app/layout.tsx b/app/layout.tsx index 76efdfc..ab7d0ce 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -27,11 +27,9 @@ export default function RootLayout({ return (
-