Skip to content

Commit

Permalink
Merge pull request #89 from XionWCFM/cursor
Browse files Browse the repository at this point in the history
๏ฟฝfix: ์ปดํŒŒ์ผ ์—๋Ÿฌ๋กœ ์ธํ•œ ๋นŒ๋“œ ์‹คํŒจ
  • Loading branch information
XionWCFM authored Dec 14, 2024
2 parents 6e2fd4f + deaafdd commit 0ac9be4
Show file tree
Hide file tree
Showing 44 changed files with 2,381 additions and 295 deletions.
19 changes: 18 additions & 1 deletion .github/workflows/ci-build.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CI
name: CI Build

on:
push:
Expand Down Expand Up @@ -47,6 +47,23 @@ jobs:
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Inject environment variables
run: |
echo "NEXT_PUBLIC_CLOUD_FRONT_URL=${{ secrets.NEXT_PUBLIC_CLOUD_FRONT_URL }}" >> apps/blog/.env
echo "NEXT_PUBLIC_SUPABASE_URL=${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}" >> apps/blog/.env
echo "NEXT_PUBLIC_SUPABASE_ANON_KEY=${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}" >> apps/blog/.env
echo "NEXT_PUBLIC_POSTHOG_KEY=${{ secrets.NEXT_PUBLIC_POSTHOG_KEY }}" >> apps/blog/.env
echo "NEXT_PUBLIC_POSTHOG_HOST=${{ secrets.NEXT_PUBLIC_POSTHOG_HOST }}" >> apps/blog/.env
echo "NEXT_PUBLIC_GTM_ID=${{ secrets.NEXT_PUBLIC_GTM_ID }}" >> apps/blog/.env
echo "NEXT_PUBLIC_GA_ID=${{ secrets.NEXT_PUBLIC_GA_ID }}" >> apps/blog/.env
echo "NEXT_PUBLIC_GSC_ID=${{ secrets.NEXT_PUBLIC_GSC_ID }}" >> apps/blog/.env
echo "NEXT_PUBLIC_SUPABASE_PROJECT_ID=${{ secrets.NEXT_PUBLIC_SUPABASE_PROJECT_ID }}" >> apps/blog/.env
echo "NEXT_SUPABASE_SERVICE_ROLE=${{ secrets.NEXT_SUPABASE_SERVICE_ROLE }}" >> apps/blog/.env
echo "JWT_SECRET=${{ secrets.JWT_SECRET }}" >> apps/blog/.env
echo "NEXT_PUBLIC_BASE_URL=${{ secrets.NEXT_PUBLIC_BASE_URL }}" >> apps/blog/.env
echo "GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }}" >> apps/blog/.env
echo "NEXT_PUBLIC_GOOGLE_CLIENT_ID=${{ secrets.NEXT_PUBLIC_GOOGLE_CLIENT_ID }}" >> apps/blog/.env
- name: Install dependencies
if: steps.cache.outputs.cache-hit == false
run: pnpm install --frozen-lockfile
Expand Down
49 changes: 0 additions & 49 deletions .github/workflows/commit-labeler.yml

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/labeler.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Pull Request Labeler
name: Labeler

on: pull_request_target

Expand Down
45 changes: 20 additions & 25 deletions .github/workflows/pr-size-labeler.yml
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
name: Pr Size Labeler
name: PR Size Labeler

on: [pull_request]
on:
pull_request:
types: [opened, synchronize]

jobs:
labeler:
size-label:
permissions:
pull-requests: write
contents: read
issues: write
pull-requests: write
runs-on: ubuntu-latest
name: Label the PR size
steps:
- uses: codelytv/pr-size-labeler@v1
- name: Assign PR Size Label
uses: pascalgn/[email protected]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
xs_label: 'size/xs'
xs_max_size: '10'
s_label: 'size/s'
s_max_size: '100'
m_label: 'size/m'
m_max_size: '500'
l_label: 'size/l'
l_max_size: '1000'
xl_label: 'size/xl'
fail_if_xl: 'false'
message_if_xl: >
This PR exceeds the recommended size of 1000 lines.
Please make sure you are NOT addressing multiple issues with one PR.
Note this PR might be rejected due to its size.
github_api_url: "https://api.github.com"
files_to_ignore: |
sizes: |
{
"0": "size/XS",
"10": "size/S",
"30": "size/M",
"100": "size/L",
"500": "size/XL",
"1000": "size/XXL"
}
ignore: |
pnpm-lock.yaml
package-lock.json
yarn.lock
75 changes: 0 additions & 75 deletions .github/workflows/pull-request-title-convention.yml

This file was deleted.

5 changes: 5 additions & 0 deletions apps/blog/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# Million Lint
.million

certificates
26 changes: 26 additions & 0 deletions apps/blog/app/(admin)/write/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";

import { MdxEditor } from "@repo/mdx/editor";
import { Debounce } from "@xionwcfm/react";
import { Flex, Stack } from "@xionwcfm/xds";
import dynamic from "next/dynamic";
import { useState } from "react";

const MdxViewer = dynamic(() => import("@repo/mdx").then((mod) => mod.MdxViewer), { ssr: false });

export default function Page() {
const [markdown, setMarkdown] = useState("");

return (
<Flex className="p-32" w={"screen"} h={"screen"} gap={"16"}>
<Stack className="w-full">
<MdxEditor content={markdown} onChange={(value) => setMarkdown(value)} />
</Stack>
<Stack className="w-full border rounded-sm px-16">
<Debounce value={markdown} delay={2500}>
{({ debounced }) => <MdxViewer source={debounced} />}
</Debounce>
</Stack>
</Flex>
);
}
46 changes: 36 additions & 10 deletions apps/blog/app/(blog)/posts/[...slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { MdxRemote } from "@repo/mdx";
import { Box, Spacing, Stack } from "@xionwcfm/xds";
import { Chip } from "@xionwcfm/xds/chip";
import type { Metadata } from "next";
import { redirect } from "next/navigation";
import { getAllPosts, getPost } from "~/entities/post/model/post.service";
import PostDetailPage from "~/page/PostDetailPage";
import { PostDetailAuthorAndDate } from "~/entities/post/ui/post/PostDetailAuthorAndDate";
import { PostDetailAuthorWithChar } from "~/entities/post/ui/post/PostDetailAuthorWithChar";
import { PostDetailTitle } from "~/entities/post/ui/post/PostDetailTitle";
import { BASE_SITE_URL } from "~/shared/constants";
import { Border } from "~/shared/ui/common/Border";
import { createMetadata } from "~/shared/utils/external/create-meta-data";

type PostProps = {
Expand All @@ -11,6 +17,35 @@ type PostProps = {
}>;
};

export default async function Post({ params }: PostProps) {
const slug = (await params).slug;
const post = await getPost(slug);
if (!post) {
return redirect("/");
}
return (
<Stack px={{ initial: "16", md: "0" }}>
<Box my="16">
<PostDetailTitle>{post.title}</PostDetailTitle>
</Box>
<Stack direction={"row"}>
<Chip>{post.categories}</Chip>
</Stack>
<Box my="16">
<PostDetailAuthorAndDate date={post.releaseDate} />
</Box>
<Border />
<Spacing h={"16"} />
<MdxRemote source={post.content} />
<Box my="40">
<PostDetailAuthorWithChar />
</Box>
<Border />
<Spacing h={"40"} />
</Stack>
);
}

export const generateMetadata = async ({ params }: PostProps): Promise<Metadata> => {
const slug = (await params).slug;
const post = await getPost(slug);
Expand All @@ -30,12 +65,3 @@ export async function generateStaticParams() {
}

export const dynamic = "force-static";

export default async function Post({ params }: PostProps) {
const slug = (await params).slug;
const post = await getPost(slug);
if (!post) {
return redirect("/");
}
return <PostDetailPage post={post} />;
}
78 changes: 78 additions & 0 deletions apps/blog/app/api/login/google/callback/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { type GoogleClaim, google } from "@repo/auth/google";
import { generateJwt } from "@repo/auth/jwt";
import { createServerSupabaseAdminClient } from "@repo/database/server";
import { env } from "@repo/env";
import { decodeIdToken } from "arctic";
import { cookies } from "next/headers";
import { type NextRequest, NextResponse } from "next/server";
import { z } from "zod";

export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url);
const { success: codeSuccess, data: code } = schema.safeParse(searchParams.get("code"));
const { success: stateSuccess, data: state } = schema.safeParse(searchParams.get("state"));

if (!(codeSuccess && stateSuccess)) {
return NextResponse.redirect(`${env.NEXT_PUBLIC_BASE_URL}/login?error=invalid_request`);
}

const storedState = req.cookies.get("google_oauth_state")?.value;
const codeVerifier = req.cookies.get("google_code_verifier")?.value;

if (state !== storedState || !codeVerifier) {
return NextResponse.redirect(`${env.NEXT_PUBLIC_BASE_URL}/login?error=invalid_state`);
}

try {
const tokens = await google.validateAuthorizationCode(code, codeVerifier);
const idToken = tokens.idToken();
const { sub: googleId, email, name, picture } = decodeIdToken(idToken) as GoogleClaim;

const cookie = await cookies();
const supabase = await createServerSupabaseAdminClient(cookie);

const { error } = await supabase.from("users").upsert(
[
{
google_id: googleId,
gmail: email,
name,
picture: picture ?? null,
},
],
{ onConflict: "google_id" },
);

if (error) {
return NextResponse.redirect(`${env.NEXT_PUBLIC_BASE_URL}/login?error=authentication_failed`);
}

const { data: userData, error: fetchError } = await supabase
.from("users")
.select("google_id, role, name, gmail")
.eq("google_id", googleId)
.single();

if (fetchError) {
return NextResponse.redirect(`${env.NEXT_PUBLIC_BASE_URL}/login?error=authentication_failed`);
}
const jwtToken = await generateJwt({
google_id: googleId,
mail: email,
name,
role: userData?.role ?? "viewer",
});

cookie.set("session_token", jwtToken, {
httpOnly: true,
secure: env.NODE_ENV === "production",
sameSite: "lax",
});

return NextResponse.redirect(`${env.NEXT_PUBLIC_BASE_URL}`);
} catch (_error) {
return NextResponse.redirect(`${env.NEXT_PUBLIC_BASE_URL}/login?error=authentication_failed`);
}
}

const schema = z.string().min(1);
Loading

0 comments on commit 0ac9be4

Please sign in to comment.