Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: db migration #97

Merged
merged 4 commits into from
Jan 29, 2025
Merged

feat: db migration #97

merged 4 commits into from
Jan 29, 2025

Conversation

XionWCFM
Copy link
Owner

@XionWCFM XionWCFM commented Jan 29, 2025

🥷 제목

구현한 내용

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a new comments system for blog posts
    • Enhanced post retrieval with more flexible data fetching
  • Improvements

    • Updated post metadata handling
    • Refined URL generation for blog posts
    • Improved date and category management
  • Dependency Updates

    • Updated Next.js to version 15.1.6
    • Updated Framer Motion to version 12.0.6
    • Added Shiki syntax highlighting package
  • Database Changes

    • Introduced a new comments table
    • Modified posts table structure
    • Updated category and slug management

Copy link

vercel bot commented Jan 29, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
xionwcfmrepo ⬜️ Ignored (Inspect) Jan 29, 2025 1:28pm

Copy link

coderabbitai bot commented Jan 29, 2025

Walkthrough

This pull request introduces significant changes to the blog application's architecture, focusing on post retrieval, rendering, and database schema. The modifications shift from file-based post management to a Supabase-driven approach, updating how posts are fetched, displayed, and metadata is generated. Key changes include introducing new API functions for post retrieval, modifying component props, and updating the database schema to support more flexible post categorization.

Changes

File Change Summary
apps/blog/app/(blog)/posts/[...slug]/page.tsx Updated post retrieval and rendering logic, using getPostBySlug and adjusting metadata generation
apps/blog/app/page.tsx Modified post fetching and filtering logic, enhanced PostCard rendering
apps/blog/app/sitemap.ts Updated sitemap generation to work with new post retrieval method
apps/blog/package.json Dependency version updates and additions
apps/blog/src/entities/post/api/* New API functions for post retrieval (getAllPosts, getPostBySlug)
apps/blog/src/entities/post/ui/PostCard.tsx Refactored props and rendering logic
packages/database/src/typesDb.ts Added comment table and modified posts table schema

Sequence Diagram

sequenceDiagram
    participant Client
    participant PostAPI
    participant Supabase
    
    Client->>PostAPI: Request posts
    PostAPI->>Supabase: Query posts
    Supabase-->>PostAPI: Return post data
    PostAPI-->>Client: Filtered and sorted posts
    
    Client->>PostAPI: Request specific post
    PostAPI->>Supabase: Query post by slug
    Supabase-->>PostAPI: Return post details
    PostAPI-->>Client: Post metadata and content
Loading

Possibly related PRs

Suggested Labels

@apps/blog, packages, size/size/XL

Poem

🐰 Hop, hop, through the code's new terrain,
Posts now dance with Supabase's refrain
Slugs and categories take their flight
Database schema shining bright
A rabbit's journey of digital delight! 🌟

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (6)
apps/blog/src/entities/post/api/getAllPosts.ts (1)

9-11: Enhance error handling with context

The current error handling loses context about what operation failed.

  if (error) {
-   throw error;
+   throw new Error(`Failed to fetch posts: ${error.message}`);
  }
apps/blog/app/sitemap.ts (1)

11-14: Use actual lastModified dates

The sitemap uses the current date for all entries instead of actual post modification dates.

  const postUrls = posts.map((post) => ({
    url: `${BASE_SITE_URL}${ROUTES.postDetail([post.category, post.slug])}`,
-   lastModified: new Date(),
+   lastModified: new Date(post.updated_at || post.created_at),
  }));
packages/database/src/server.ts (2)

8-8: Document the implications of missing cookieStore

The function now accepts an optional cookieStore, but the implications of it being undefined are not documented.

+ /**
+  * Creates a Supabase client for server-side operations.
+  * @param cookieStore - Optional cookie store. If not provided, cookie-based operations will be no-ops.
+  * @param admin - Whether to create an admin client with elevated privileges.
+  * @returns A configured Supabase client instance
+  */
export const createServerSupabaseClient = async (cookieStore?: Awaited<ReturnType<typeof cookies>>, admin = false) => {

Line range hint 18-25: Improve error handling in cookie operations

The current implementation silently catches errors in cookie operations. Consider logging these errors for debugging purposes.

  set(name: string, value: string, options: CookieOptions) {
    try {
      cookieStore?.set({ name, value, ...options });
    } catch (error) {
-     // The `set` method was called from a Server Component.
-     // This can be ignored if you have middleware refreshing
-     // user sessions.
+     console.debug(
+       'Cookie set operation failed (expected in Server Components):',
+       error instanceof Error ? error.message : 'Unknown error'
+     );
    }
  },
  remove(name: string, options: CookieOptions) {
    try {
      cookieStore?.set({ name, value: "", ...options });
    } catch (error) {
-     // The `delete` method was called from a Server Component.
-     // This can be ignored if you have middleware refreshing
-     // user sessions.
+     console.debug(
+       'Cookie remove operation failed (expected in Server Components):',
+       error instanceof Error ? error.message : 'Unknown error'
+     );
    }
  },

Also applies to: 27-34

apps/blog/app/page.tsx (1)

36-44: Consistent date formatting across the application.

The date formatting uses a specific format (yyyy.MM.dd. HH:mm). Consider extracting this to a constant or utility function for consistency.

+const POST_DATE_FORMAT = "yyyy.MM.dd. HH:mm";
+const formatPostDate = (date: string) => format(parseISO(date), POST_DATE_FORMAT);

 <PostCard
   key={post.title}
   title={post.title}
   category={post.category}
   description={post.description}
   href={ROUTES.postDetail([post.category, post.slug])}
   authorNickname={AUTHOR_NICKNAME}
-  date={format(parseISO(post.release_date), "yyyy.MM.dd. HH:mm")}
+  date={formatPostDate(post.release_date)}
 />
apps/blog/app/(blog)/posts/[...slug]/page.tsx (1)

71-71: URL structure change requires redirect strategy.

The URL structure has changed to include categories. Consider:

  1. Adding redirects for old URL format
  2. Updating sitemap generation
  3. Implementing URL migration strategy

Also applies to: 79-79

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36f75cd and a20f847.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • apps/blog/app/(blog)/posts/[...slug]/page.tsx (3 hunks)
  • apps/blog/app/page.tsx (2 hunks)
  • apps/blog/app/sitemap.ts (1 hunks)
  • apps/blog/package.json (2 hunks)
  • apps/blog/posts/frontend/single-flight.mdx (1 hunks)
  • apps/blog/src/entities/post/api/getAllPosts.ts (1 hunks)
  • apps/blog/src/entities/post/api/getPostBySlug.ts (1 hunks)
  • apps/blog/src/entities/post/model/post-service.test.ts (0 hunks)
  • apps/blog/src/entities/post/model/post.model.ts (0 hunks)
  • apps/blog/src/entities/post/model/post.service.ts (0 hunks)
  • apps/blog/src/entities/post/ui/post/PostCard.tsx (2 hunks)
  • apps/blog/src/widgets/PostRecommend.tsx (3 hunks)
  • packages/database/src/server.ts (2 hunks)
  • packages/database/src/typesDb.ts (2 hunks)
💤 Files with no reviewable changes (3)
  • apps/blog/src/entities/post/model/post-service.test.ts
  • apps/blog/src/entities/post/model/post.service.ts
  • apps/blog/src/entities/post/model/post.model.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/blog/posts/frontend/single-flight.mdx
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build and Test
🔇 Additional comments (8)
apps/blog/src/widgets/PostRecommend.tsx (2)

22-22: Improved type safety with generic constraints.

Good improvements:

  1. URL construction now uses slug instead of filePath, aligning with database-driven approach
  2. Generic type constraint ensures type safety while maintaining flexibility

Also applies to: 36-38


4-5: Cookie handling implementation needs verification.

The addition of cookie-based post retrieval suggests potential user-specific content filtering. Verify if this is intentional and if proper security measures are in place.

Also applies to: 10-11

✅ Verification successful

Cookie implementation verified as secure and appropriate

The cookie usage is part of Supabase's authentication system for maintaining server-side sessions. It follows security best practices and doesn't involve any sensitive content filtering.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for cookie usage patterns and security measures
rg -A 3 "cookies\(\)" .
ast-grep --pattern 'getAllPosts($cookieStore)'

Length of output: 953


Script:

#!/bin/bash
# Check getAllPosts implementation and related security measures
ast-grep --pattern 'export $_ getAllPosts($_) {
  $$$
}'

# Check for any security-related utilities used with cookies
rg -A 5 "createServerSupabaseClient|createServerSupabaseAdminClient" .

Length of output: 3783

apps/blog/src/entities/post/ui/post/PostCard.tsx (2)

5-10: Props restructuring improves component reusability.

The shift from a single post object to individual props:

  1. Provides better type safety
  2. Makes the component more reusable
  3. Aligns with database schema

14-16: Verify prop usage consistency across the application.

The component now uses granular props instead of a post object. Ensure all component instances are updated accordingly.

Also applies to: 24-24, 34-34, 37-37, 40-40

✅ Verification successful

Props usage is consistent across the application

The PostCard component's granular props pattern is consistently implemented. The only usage in apps/blog/app/page.tsx correctly provides all required props individually, with no instances of passing post objects directly.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Find all PostCard usages and verify prop pattern
ast-grep --pattern 'PostCard $props'

Length of output: 38


Script:

#!/bin/bash
# Find PostCard usages with various patterns
echo "=== Direct PostCard references ==="
rg -l "PostCard" --type tsx --type jsx

echo -e "\n=== PostCard prop patterns ==="
rg "<PostCard.*?>" -U --type tsx --type jsx

echo -e "\n=== Looking for potential post object spread ==="
rg "<PostCard.*?\{\.\.\..*?\}" -U --type tsx --type jsx

Length of output: 495


Script:

#!/bin/bash
echo "=== Finding PostCard component definition and usages ==="
cd apps/blog && find . -type f -exec grep -l "PostCard" {} \;

echo -e "\n=== Checking PostCard usage patterns ==="
cd apps/blog && find . -type f -exec grep -A 2 "<PostCard" {} \;

echo -e "\n=== Looking for spread operator usage ==="
cd apps/blog && find . -type f -exec grep -A 2 "<PostCard.*{....*}" {} \;

Length of output: 692


Script:

#!/bin/bash
echo "=== Checking PostCard usage in page.tsx ==="
cat apps/blog/app/page.tsx

echo -e "\n=== Checking PostCard component definition ==="
cat apps/blog/src/entities/post/ui/post/PostCard.tsx

Length of output: 3997

apps/blog/app/page.tsx (1)

13-16: Review post filtering logic for edge cases.

The post filtering logic includes:

  1. Authority check (viewer)
  2. Date comparison
    Consider handling edge cases:
  • Invalid dates
  • Missing authority values
✅ Verification successful

Post filtering implementation is properly typed and safe

The current implementation is correct as:

  • Database schema enforces non-null constraints for both authority and release_date
  • authority is restricted to a valid enum type ("admin" | "editor" | "viewer")
  • release_date is guaranteed to be a valid ISO string
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for null/undefined handling in post filtering
ast-grep --pattern 'post.authority'
ast-grep --pattern 'parseISO($date)'

Length of output: 203


Script:

#!/bin/bash
# Find type definitions and implementations
ast-grep --pattern 'type Post'
ast-grep --pattern 'interface Post'
fd getAllPosts --exec cat {}
rg "release_date.*null|authority.*null" -A 2

Length of output: 566


Script:

#!/bin/bash
# Look for type definitions and schema
fd -e ts -e tsx -e sql | xargs rg -l "Post|posts" --ignore-case
rg "create table.*posts" -A 10
rg "from.*posts.*where" -A 2

Length of output: 891


Script:

#!/bin/bash
# Check database types and post filtering implementations
cat packages/database/src/typesDb.ts
rg "post\.authority.*===.*viewer" -A 2
rg "parseISO.*release_date" -A 2

Length of output: 9534

apps/blog/app/(blog)/posts/[...slug]/page.tsx (1)

25-27: Verify slug handling for all possible scenarios.

The new slug handling:

  1. Uses last() with fallback
  2. Retrieves post by slug
    Consider handling:
  • Invalid slug formats
  • Non-existent slugs
✅ Verification successful

Slug handling implementation is robust and secure

The implementation properly handles all scenarios:

  • Valid slugs are statically generated with [category, slug] structure
  • Invalid formats fall back to empty string via last()
  • Non-existent slugs trigger proper error handling with redirect
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check slug handling patterns
rg -A 3 "getPostBySlug" .
ast-grep --pattern 'last($slug)'

Length of output: 1634


Script:

#!/bin/bash
# Check static params and last() implementation
rg -A 5 "generateStaticParams" ./apps/blog/app/(blog)/posts/
ast-grep --pattern 'function last'
rg -l "from.*es-toolkit"

Length of output: 243


Script:

#!/bin/bash
# Search with properly escaped paths and broader patterns
rg -A 5 "generateStaticParams" "apps/blog/app"
rg "import.*last.*from" .
rg "export.*function.*last" .

Length of output: 626

packages/database/src/typesDb.ts (1)

21-58: Consider adding indices for performance optimization.

The comment table schema looks well-structured with appropriate fields and relationships. However, consider adding indices for frequently queried fields to optimize performance.

Recommended indices:

  • post_id (already covered by foreign key)
  • created_at (for chronological sorting)
  • (post_id, created_at) (for fetching latest comments per post)

Also verify:

  1. Do we need a composite index on (post_id, is_deleted) for efficient filtering of non-deleted comments?
  2. Should we add a constraint to prevent empty content?
apps/blog/package.json (1)

44-45: Verify compatibility of updated dependencies.

The changes look reasonable, but please verify:

  1. next update from 15.1.3 to 15.1.6: Check changelog for breaking changes
  2. New shiki dependency: Ensure it's the recommended version for syntax highlighting

Also applies to: 73-73, 81-81

✅ Verification successful

Dependencies update verified and safe to proceed

The Next.js update (15.1.3 to 15.1.6) contains only bug fixes with no breaking changes. Shiki is properly integrated for syntax highlighting in the MDX components.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for Next.js version compatibility
curl -s https://api.github.com/repos/vercel/next.js/releases | jq -r '.[] | select(.tag_name | contains("v15.1")) | {tag_name, body}'

# Verify if shiki is used in the codebase
rg -A 5 "shiki|highlight"

Length of output: 10202


export const getAllPosts = async (cookieStore?: Awaited<ReturnType<typeof cookies>>) => {
const supabase = await createServerSupabaseClient(cookieStore);
const { data, error } = await supabase.from("posts").select("*");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding pagination and column selection

The current implementation fetches all posts and all columns at once, which could lead to performance issues as the dataset grows.

Consider:

  1. Adding pagination parameters
  2. Explicitly selecting only needed columns
- const { data, error } = await supabase.from("posts").select("*");
+ const { data, error } = await supabase
+   .from("posts")
+   .select('id, title, slug, category, created_at, updated_at')
+   .order('created_at', { ascending: false })
+   .range(0, 9);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { data, error } = await supabase.from("posts").select("*");
const { data, error } = await supabase
.from("posts")
.select('id, title, slug, category, created_at, updated_at')
.order('created_at', { ascending: false })
.range(0, 9);

Comment on lines +5 to +14
export const getPostBySlug = async (slug: string, cookieStore?: Awaited<ReturnType<typeof cookies>>) => {
const supabase = await createServerSupabaseClient(cookieStore);
const { data, error } = await supabase.from("posts").select("*").eq("slug", slug).single();

if (error) {
throw error;
}

return data;
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add type safety and input validation

The function lacks type safety for the returned data and input validation for the slug parameter.

+ import { type Post } from './types';
+ 
+ const isValidSlug = (slug: string) => {
+   return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug);
+ };
+
- export const getPostBySlug = async (slug: string, cookieStore?: Awaited<ReturnType<typeof cookies>>) => {
+ export const getPostBySlug = async (
+   slug: string,
+   cookieStore?: Awaited<ReturnType<typeof cookies>>
+ ): Promise<Post> => {
+   if (!isValidSlug(slug)) {
+     throw new Error('Invalid slug format');
+   }
+
    const supabase = await createServerSupabaseClient(cookieStore);
-   const { data, error } = await supabase.from("posts").select("*").eq("slug", slug).single();
+   const { data, error } = await supabase
+     .from("posts")
+     .select('id, title, slug, category, content, created_at, updated_at')
+     .eq("slug", slug)
+     .single();

    if (error) {
-     throw error;
+     throw new Error(`Failed to fetch post with slug ${slug}: ${error.message}`);
    }

    return data;
  };

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +8 to 13
const cookieStore = await cookies();
const posts = await getAllPosts(cookieStore);

const postUrls = posts.map((post) => ({
url: `${BASE_SITE_URL}${ROUTES.postDetail(post.filePath)}`,
url: `${BASE_SITE_URL}${ROUTES.postDetail([post.category, post.slug])}`,
lastModified: new Date(),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling and caching strategy

The sitemap generation lacks error handling and could benefit from caching to improve performance.

+ import { cache } from 'react';
+
+ const CACHE_REVALIDATE_SECONDS = 3600; // 1 hour
+
+ const getCachedPosts = cache(async (cookieStore: Awaited<ReturnType<typeof cookies>>) => {
+   try {
+     return await getAllPosts(cookieStore);
+   } catch (error) {
+     console.error('Failed to generate sitemap:', error);
+     return [];
+   }
+ });

  export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
    const cookieStore = await cookies();
-   const posts = await getAllPosts(cookieStore);
+   const posts = await getCachedPosts(cookieStore);

Committable suggestion skipped: line range outside the PR's diff.

@@ -83,34 +121,37 @@ export type Database = {
posts: {
Row: {
authority: Database["public"]["Enums"]["role"];
can_view: boolean;
category: string;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Resolve schema inconsistency between direct category and post_categories table.

There appears to be a conflict in the database design:

  1. Posts table now has a direct category field
  2. Existing post_categories table suggests a many-to-many relationship

This could lead to data inconsistencies. Consider either:

  • Removing the post_categories table if multiple categories are no longer needed
  • Removing the direct category field if multiple categories should be maintained

Also applies to: 136-136, 148-148


💡 Codebase verification

Make the slug field required to prevent URL generation issues

The slug field is used extensively in URL generation, routing, and sitemap creation. Making it optional could lead to broken links. The category field usage is appropriate, and removing can_view appears safe as it's not referenced in the codebase.

🔗 Analysis chain

Verify the impact of schema changes on existing functionality.

Several concerns regarding the schema changes:

  1. Removing can_view might break existing access control. How is visibility now managed?
  2. category as an unconstrained string could lead to inconsistencies. Consider using an enum or reference table.
  3. slug should probably be required and unique for URL generation.

Let's verify the impact:

Also applies to: 130-130, 136-136, 142-142, 148-148, 154-154

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for existing usage of can_view
rg -A 5 "can_view"

# Check if slugs are used in routing
rg -A 5 "params.*slug|slug.*params"

# Check category usage patterns
rg -A 5 "post.*category|category.*post"

Length of output: 3115

@XionWCFM XionWCFM merged commit 70bcfcb into main Jan 29, 2025
7 checks passed
@XionWCFM XionWCFM deleted the supabase branch January 29, 2025 13:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant