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

Feature: Generic data model and Supabase data service #124

Closed
wants to merge 1 commit into from

Conversation

yeahlowflicker
Copy link
Collaborator

@yeahlowflicker yeahlowflicker commented Nov 23, 2024

通用資料模型 & 資料庫連接服務

透過建立通用模型,供各功能自行延伸,並利用 SupabaseService,使 CRUD 可集中處理。目前引入了 fetchSingle() 與 fetchMultiple() 功能,服務會同時自動轉換資料庫欄位,並以給定的 type 輸出結果。

A generic data template model has been implemented. This model can be extended to other classes, such as User. It includes the TABLE_NAME and DATABASE_MAPPING properties for a more efficient database access.

The SupabaseService has been implemented to support general data fetching. It currently supports the fetching of a single object and an array of objects. There is no need to implement the data fetching logic repeatedly for each model.

  • Added SupabaseService
  • Added GenericModel
  • Added User model
  • Added data fetching tests during Homepage initialization

Note: 首頁 GUI 可暫時忽略

Summary by CodeRabbit

Release Notes

  • New Features
    • Introduced a new abstract class GenericModel for enhanced data handling.
    • Added a User class with various user attributes and JSON parsing capabilities.
    • Implemented a home page route with basic structure and styling.
    • Launched SupabaseService for streamlined database interactions, including methods for fetching single and multiple records.

These updates enhance data management and user interface capabilities within the application.

A generic data template model has been implemented. This model can be
extended to other classes, such as User. It includes the TABLE_NAME
and DATABASE_MAPPING properties for a more efficient database access.

The SupabaseService has been implemented to support general data
fetching. It currently supports the fetching of a single object and
an array of objects. There is no need to implement the data fetching
logic repeatedly for each model.

- Added SupabaseService
- Added GenericModel
- Added User model
- Added data fetching tests during Homepage initialization
Copy link
Contributor

coderabbitai bot commented Nov 23, 2024

Walkthrough

The changes introduce a new abstract class GenericModel that serves as a base for database models, defining properties and methods for handling database interactions. A specific implementation, User, extends this class, providing user-specific properties and a method for JSON parsing. Additionally, a new service class, SupabaseService, is created to facilitate interactions with a Supabase database, featuring methods for fetching single and multiple records. Finally, a new route component for the home page is added, which lays out a basic structure for future enhancements.

Changes

File Change Summary
src/models/GenericModel.ts Added abstract class GenericModel with static properties TABLE_NAME, DATABASE_MAP, and abstract method parseJson.
src/models/User.ts Introduced User class extending GenericModel, with static properties TABLE_NAME and DATABASE_MAP, public properties for user attributes, and a parseJson method.
src/routes/home/index.tsx Created home page route component, defining a structure with a header and avatar, utilizing Tailwind CSS for styling.
src/services/SupabaseService.ts Added SupabaseService class with static methods fetchSingle and fetchMultiple for database interaction using Supabase.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant SupabaseService
    participant Database

    User->>SupabaseService: fetchSingle(User, targetID)
    SupabaseService->>Database: Query for targetID
    Database-->>SupabaseService: Return record
    SupabaseService->>User: parseJson(record)
    User-->>SupabaseService: Return User instance
Loading

🐰 "In the code where changes bloom,
A GenericModel finds its room.
With User class, a tale unfolds,
In Supabase, the data molds.
A home route waits, with style so bright,
New paths ahead, all set for flight!" 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 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 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
Contributor

@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: 14

🧹 Outside diff range and nitpick comments (7)
src/models/GenericModel.ts (2)

1-2: LGTM! Consider documenting the contract.

The abstract class declaration is well-structured for its purpose as a base model. However, since this is a foundational piece, consider adding JSDoc comments to document:

  • The purpose and responsibility of this class
  • Expected usage patterns for extending classes
  • Requirements for implementing the abstract members

Example documentation:

/**
 * Abstract base class for database models providing a consistent interface
 * for table operations and JSON parsing.
 * 
 * @abstract
 * @example
 * class User extends GenericModel {
 *   static TABLE_NAME = 'users';
 *   static DATABASE_MAP = { ... };
 *   
 *   static parseJson(json: unknown): User {
 *     // Implementation
 *   }
 * }
 */

1-12: Consider enhancing the base model with common database operations.

Since this is a foundational piece for database models, consider adding:

  1. Common CRUD operation methods
  2. Validation utilities
  3. Type-safe query building helpers
  4. Error handling utilities

This would provide a more complete and consistent interface for all model implementations.

Example additions:

abstract class GenericModel {
    // ... existing code ...
    
    static async findById(id: string): Promise<ThisType> {
        // Implementation
    }
    
    static async findMany(criteria: QueryCriteria): Promise<ThisType[]> {
        // Implementation
    }
    
    abstract validate(): ValidationResult;
    
    protected static buildQuery(): QueryBuilder {
        // Implementation
    }
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 2-12: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


[error] 6-6: Don't use 'Object' as a type.

Prefer explicitly define the object shape. This type means "any non-nullable value", which is slightly better than 'unknown', but it's still a broad type.

(lint/complexity/noBannedTypes)

🪛 eslint (1.23.1)

[error] 6-6: Don't use Object as a type. The Object type actually means "any non-nullish value", so it is marginally better than unknown.

  • If you want a type meaning "any object", you probably want object instead.
  • If you want a type meaning "any value", you probably want unknown instead.
  • If you really want a type meaning "any non-nullish value", you probably want NonNullable<unknown> instead.

(@typescript-eslint/ban-types)


[error] 9-9: 'json' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 9-9: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

src/services/SupabaseService.ts (3)

39-47: Enhance error handling and response typing

The error handling could be more robust:

-    const data = await query;
+    const { data, error } = await query;
+    
+    if (error) {
+      throw new Error(`Failed to fetch ${modelClass.name}: ${error.message}`);
+    }
 
     if (!data.data || data.data.length == 0) {
         return null
     }
     const record = data.data[0]
 
-    return model.parseJson(record)
+    return modelClass.prototype.constructor.parseJson(record)

1-84: Add unit tests for the service

The service lacks unit tests which are crucial for maintaining reliability.

Would you like me to help create a comprehensive test suite for this service? The tests would cover:

  • Single record fetching with various criteria
  • Multiple record fetching with pagination and filters
  • Error handling scenarios
  • Edge cases
🧰 Tools
🪛 Biome (1.9.4)

[error] 5-84: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


[error] 25-25: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)

🪛 eslint (1.23.1)

[error] 25-25: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 26-26: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 28-28: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 31-33: Unexpected var, use let or const instead.

(no-var)


[error] 67-67: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


1-84: Consider implementing a proper error handling strategy

The current implementation would benefit from a more robust error handling strategy:

  1. Define custom error types for different failure scenarios
  2. Implement proper error propagation
  3. Add logging for debugging purposes
// Example error types
export class SupabaseServiceError extends Error {
  constructor(message: string, public cause?: Error) {
    super(message);
    this.name = 'SupabaseServiceError';
  }
}

export class RecordNotFoundError extends SupabaseServiceError {
  constructor(modelName: string, id: string) {
    super(`${modelName} with id ${id} not found`);
    this.name = 'RecordNotFoundError';
  }
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 5-84: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


[error] 25-25: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)

🪛 eslint (1.23.1)

[error] 25-25: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 26-26: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 28-28: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 31-33: Unexpected var, use let or const instead.

(no-var)


[error] 67-67: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

src/routes/home/index.tsx (2)

74-87: Clean up or implement commented UI components.

There are multiple commented-out UI components that appear to be part of the intended implementation. Either remove them if they're not needed or implement them properly.


41-89: Add proper TypeScript types and accessibility attributes.

The component could benefit from:

  1. Type definitions for the route parameters and component props
  2. ARIA labels and roles for better accessibility
  3. Semantic HTML structure

Consider wrapping the profile section in a more semantic structure:

-    <section className='h-[200px] mb-[40px] bg-yellow-50 relative border-b-[5px] border-gray-600'>
+    <section 
+      aria-label="User Profile"
+      className='h-[200px] mb-[40px] bg-yellow-50 relative border-b-[5px] border-gray-600'
+    >
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 7134cae and d3b748f.

📒 Files selected for processing (4)
  • src/models/GenericModel.ts (1 hunks)
  • src/models/User.ts (1 hunks)
  • src/routes/home/index.tsx (1 hunks)
  • src/services/SupabaseService.ts (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/models/GenericModel.ts

[error] 2-12: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


[error] 6-6: Don't use 'Object' as a type.

Prefer explicitly define the object shape. This type means "any non-nullable value", which is slightly better than 'unknown', but it's still a broad type.

(lint/complexity/noBannedTypes)

src/models/User.ts

[error] 30-30: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 6-6: Don't use 'Object' as a type.

Prefer explicitly define the object shape. This type means "any non-nullable value", which is slightly better than 'unknown', but it's still a broad type.

(lint/complexity/noBannedTypes)


[error] 14-14: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)


[error] 15-15: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)


[error] 16-16: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)


[error] 17-17: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)


[error] 18-18: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)


[error] 19-19: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)


[error] 21-21: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)


[error] 22-22: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)


[error] 23-23: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)


[error] 24-24: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)

src/services/SupabaseService.ts

[error] 5-84: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


[error] 25-25: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)

🪛 eslint (1.23.1)
src/models/GenericModel.ts

[error] 6-6: Don't use Object as a type. The Object type actually means "any non-nullish value", so it is marginally better than unknown.

  • If you want a type meaning "any object", you probably want object instead.
  • If you want a type meaning "any value", you probably want unknown instead.
  • If you really want a type meaning "any non-nullish value", you probably want NonNullable<unknown> instead.

(@typescript-eslint/ban-types)


[error] 9-9: 'json' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 9-9: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

src/models/User.ts

[error] 6-6: Don't use Object as a type. The Object type actually means "any non-nullish value", so it is marginally better than unknown.

  • If you want a type meaning "any object", you probably want object instead.
  • If you want a type meaning "any value", you probably want unknown instead.
  • If you really want a type meaning "any non-nullish value", you probably want NonNullable<unknown> instead.

(@typescript-eslint/ban-types)


[error] 14-14: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 15-15: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 16-16: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 17-17: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 18-18: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 19-19: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 21-21: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 22-22: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 23-23: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 24-24: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 27-27: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 32-32: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

src/routes/home/index.tsx

[error] 4-4: 'homeItems' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

src/services/SupabaseService.ts

[error] 25-25: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 26-26: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 28-28: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 31-33: Unexpected var, use let or const instead.

(no-var)


[error] 67-67: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

🔇 Additional comments (1)
src/models/User.ts (1)

1-3: LGTM! Clean class structure and inheritance.

The class properly extends GenericModel and follows good inheritance practices.

Comment on lines +8 to +11
// Abstract method for parsing JSON
static parseJson(json: any): GenericModel {
throw new Error("parseJson method not implemented.");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve type safety and error handling of parseJson method.

Several improvements can be made to enhance type safety and developer experience:

  1. Use unknown instead of any for better type safety
  2. Make the return type match the implementing class using generics
  3. Improve the error message to be more descriptive
-    static parseJson(json: any): GenericModel {
-        throw new Error("parseJson method not implemented.");
+    abstract static parseJson(json: unknown): ThisType {
+        throw new Error(
+            `${this.name}.parseJson() must be implemented to parse database records.`
+        );
+    }

Consider also adding runtime type checking:

protected static validateJson(json: unknown): void {
    if (!json || typeof json !== 'object') {
        throw new Error(`Invalid JSON data provided to ${this.name}.parseJson()`);
    }
}
🧰 Tools
🪛 eslint (1.23.1)

[error] 9-9: 'json' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 9-9: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

Comment on lines +3 to +6
// Abstract property for table name
static TABLE_NAME: string;

static DATABASE_MAP: Object;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve type safety of DATABASE_MAP.

The Object type is too broad and provides little type safety. Consider:

  1. Defining a specific interface for the mapping structure
  2. Using a generic type parameter for flexibility
-    static DATABASE_MAP: Object;
+    /** Mapping of model properties to database columns */
+    static DATABASE_MAP: Record<string, string>;
+    // Or for more flexibility:
+    static DATABASE_MAP: {
+        [K in keyof ThisType]: string;
+    };

Also, consider making these properties abstract to enforce implementation:

-    static TABLE_NAME: string;
+    abstract static TABLE_NAME: string;
-    static DATABASE_MAP: Object;
+    abstract static DATABASE_MAP: Record<string, string>;

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

🧰 Tools
🪛 Biome (1.9.4)

[error] 6-6: Don't use 'Object' as a type.

Prefer explicitly define the object shape. This type means "any non-nullable value", which is slightly better than 'unknown', but it's still a broad type.

(lint/complexity/noBannedTypes)

🪛 eslint (1.23.1)

[error] 6-6: Don't use Object as a type. The Object type actually means "any non-nullish value", so it is marginally better than unknown.

  • If you want a type meaning "any object", you probably want object instead.
  • If you want a type meaning "any value", you probably want unknown instead.
  • If you really want a type meaning "any non-nullish value", you probably want NonNullable<unknown> instead.

(@typescript-eslint/ban-types)

Comment on lines +5 to +12
static TABLE_NAME = "members"
static DATABASE_MAP: Object = {
id: "uuid",
username: "username",
email: "fk_email",
identity: "fk_identity",
avatar: "avatar",
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve type safety and documentation of database mappings.

  1. Replace the broad Object type with a specific interface:
-    static DATABASE_MAP: Object = {
+    static DATABASE_MAP: Record<keyof User, string> = {
         id:         "uuid",
         username:   "username",
         email:      "fk_email",
         identity:   "fk_identity",
         avatar:     "avatar",
     }
  1. Document foreign key relationships:
+    /** Database field mappings
+     * @property fk_email - Foreign key reference to user's email
+     * @property fk_identity - Foreign key reference to user's identity
+     */
     static DATABASE_MAP: Record<keyof User, string> = {

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

🧰 Tools
🪛 Biome (1.9.4)

[error] 6-6: Don't use 'Object' as a type.

Prefer explicitly define the object shape. This type means "any non-nullable value", which is slightly better than 'unknown', but it's still a broad type.

(lint/complexity/noBannedTypes)

🪛 eslint (1.23.1)

[error] 6-6: Don't use Object as a type. The Object type actually means "any non-nullish value", so it is marginally better than unknown.

  • If you want a type meaning "any object", you probably want object instead.
  • If you want a type meaning "any value", you probably want unknown instead.
  • If you really want a type meaning "any non-nullish value", you probably want NonNullable<unknown> instead.

(@typescript-eslint/ban-types)

Comment on lines +14 to +24
public id: String = ""
public username: String = ""
public email: String = ""
public phone: String = ""
public avatar: String = ""
public profileBackground: String = ""
public joinedAt: Date = new Date()
public identity: String = ""
public department: String = ""
public grade: String = ""
public bio: String = ""
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve type definitions and add property documentation.

  1. Use correct TypeScript primitive types:
-    public id:                  String  = ""
-    public username:            String  = ""
-    public email:               String  = ""
-    public phone:               String  = ""
-    public avatar:              String  = ""
-    public profileBackground:   String  = ""
-    public joinedAt:            Date    = new Date()
-    public identity:            String  = ""
-    public department:          String  = ""
-    public grade:               String  = ""
-    public bio:                 String  = ""
+    /** Unique identifier for the user */
+    public id: string = ""
+    /** User's display name */
+    public username: string = ""
+    /** User's email address */
+    public email: string = ""
+    /** User's phone number */
+    public phone: string = ""
+    /** URL to user's avatar image */
+    public avatar: string = ""
+    /** URL to user's profile background image */
+    public profileBackground: string = ""
+    /** Timestamp when user joined */
+    public joinedAt: Date = new Date()
+    /** User's role or identity type */
+    public identity: string = ""
+    /** User's academic department */
+    public department: string = ""
+    /** User's academic grade/year */
+    public grade: string = ""
+    /** User's biography or description */
+    public bio: string = ""
📝 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
public id: String = ""
public username: String = ""
public email: String = ""
public phone: String = ""
public avatar: String = ""
public profileBackground: String = ""
public joinedAt: Date = new Date()
public identity: String = ""
public department: String = ""
public grade: String = ""
public bio: String = ""
/** Unique identifier for the user */
public id: string = ""
/** User's display name */
public username: string = ""
/** User's email address */
public email: string = ""
/** User's phone number */
public phone: string = ""
/** URL to user's avatar image */
public avatar: string = ""
/** URL to user's profile background image */
public profileBackground: string = ""
/** Timestamp when user joined */
public joinedAt: Date = new Date()
/** User's role or identity type */
public identity: string = ""
/** User's academic department */
public department: string = ""
/** User's academic grade/year */
public grade: string = ""
/** User's biography or description */
public bio: string = ""
🧰 Tools
🪛 Biome (1.9.4)

[error] 14-14: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)


[error] 15-15: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)


[error] 16-16: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)


[error] 17-17: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)


[error] 18-18: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)


[error] 19-19: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)


[error] 21-21: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)


[error] 22-22: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)


[error] 23-23: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)


[error] 24-24: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)

🪛 eslint (1.23.1)

[error] 14-14: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 15-15: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 16-16: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 17-17: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 18-18: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 19-19: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 21-21: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 22-22: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 23-23: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)


[error] 24-24: Don't use String as a type. Use string instead

(@typescript-eslint/ban-types)

Comment on lines +27 to +35
static parseJson(json: any) : User {
const user = new User()

for (const [userProperty, jsonField] of Object.entries(this.DATABASE_MAP))
if (json[jsonField] !== undefined)
(user as any)[userProperty] = json[jsonField];

return user
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve type safety and error handling in parseJson method.

The current implementation has several issues that should be addressed:

-    static parseJson(json: any) : User {
-        const user = new User()
-
-        for (const [userProperty, jsonField] of Object.entries(this.DATABASE_MAP))
-            if (json[jsonField] !== undefined)
-                (user as any)[userProperty] = json[jsonField];
-
-        return user
-    }
+    static parseJson(json: Record<string, unknown>): User {
+        const user = new User();
+        
+        try {
+            for (const [userProperty, jsonField] of Object.entries(User.DATABASE_MAP)) {
+                if (json[jsonField] !== undefined) {
+                    // Type-safe assignment with validation
+                    const value = json[jsonField];
+                    switch(userProperty) {
+                        case 'joinedAt':
+                            user[userProperty] = new Date(value as string);
+                            break;
+                        default:
+                            if (typeof value === 'string') {
+                                user[userProperty as keyof User] = value;
+                            }
+                    }
+                }
+            }
+        } catch (error) {
+            throw new Error(`Failed to parse user data: ${error.message}`);
+        }
+        
+        return user;
+    }

Changes made:

  1. Added proper type safety with Record<string, unknown>
  2. Replaced this with explicit class name
  3. Added error handling
  4. Added type validation for properties
  5. Special handling for Date type conversion
📝 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
static parseJson(json: any) : User {
const user = new User()
for (const [userProperty, jsonField] of Object.entries(this.DATABASE_MAP))
if (json[jsonField] !== undefined)
(user as any)[userProperty] = json[jsonField];
return user
}
static parseJson(json: Record<string, unknown>): User {
const user = new User();
try {
for (const [userProperty, jsonField] of Object.entries(User.DATABASE_MAP)) {
if (json[jsonField] !== undefined) {
// Type-safe assignment with validation
const value = json[jsonField];
switch(userProperty) {
case 'joinedAt':
user[userProperty] = new Date(value as string);
break;
default:
if (typeof value === 'string') {
user[userProperty as keyof User] = value;
}
}
}
}
} catch (error) {
throw new Error(`Failed to parse user data: ${error.message}`);
}
return user;
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 30-30: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

🪛 eslint (1.23.1)

[error] 27-27: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 32-32: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

Comment on lines +64 to +83
public static async fetchMultiple<T extends GenericModel>(
modelClass: new() => T,
): Promise<Array<T>> {
const model = (modelClass as any)
const tableName = model.TABLE_NAME

const data = await supabase.from(tableName)
.select('*')

const records = data.data

const results: Array<T> = []

records?.forEach(record => {
const user = model.parseJson(record)
results.push(user)
})

return results
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add pagination and filtering support to fetchMultiple

The current implementation has several limitations:

  1. No pagination support could lead to performance issues with large datasets
  2. No filtering capabilities limits usefulness
  3. Similar type safety issues as fetchSingle
 public static async fetchMultiple<T extends GenericModel>(
     modelClass: new() => T,
+    options?: {
+      page?: number;
+      pageSize?: number;
+      filters?: Partial<Record<keyof T, unknown>>;
+    }
 ): Promise<Array<T>> {
-    const model = (modelClass as any)
-    const tableName = model.TABLE_NAME
+    const tableName = modelClass.prototype.constructor.TABLE_NAME
+    if (!tableName) {
+      throw new Error(`Table name not defined for model ${modelClass.name}`)
+    }
 
-    const data = await supabase.from(tableName)
-                    .select('*')
+    let query = supabase.from(tableName).select('*')
+    
+    // Apply filters
+    if (options?.filters) {
+      for (const [key, value] of Object.entries(options.filters)) {
+        if (modelClass.prototype.constructor.DATABASE_MAP?.[key]) {
+          query = query.eq(
+            modelClass.prototype.constructor.DATABASE_MAP[key],
+            value
+          )
+        }
+      }
+    }
+    
+    // Apply pagination
+    if (options?.page && options?.pageSize) {
+      const start = (options.page - 1) * options.pageSize
+      query = query.range(start, start + options.pageSize - 1)
+    }
+    
+    const { data, error } = await query
+    
+    if (error) {
+      throw new Error(`Failed to fetch ${modelClass.name} list: ${error.message}`)
+    }
                     
-    const records = data.data
+    if (!data || data.length === 0) {
+      return []
+    }
 
     const results: Array<T> = []
 
-    records?.forEach(record => {
-        const user = model.parseJson(record)
-        results.push(user)
+    data.forEach(record => {
+        results.push(modelClass.prototype.constructor.parseJson(record))
     })
 
     return results
📝 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
public static async fetchMultiple<T extends GenericModel>(
modelClass: new() => T,
): Promise<Array<T>> {
const model = (modelClass as any)
const tableName = model.TABLE_NAME
const data = await supabase.from(tableName)
.select('*')
const records = data.data
const results: Array<T> = []
records?.forEach(record => {
const user = model.parseJson(record)
results.push(user)
})
return results
}
public static async fetchMultiple<T extends GenericModel>(
modelClass: new() => T,
options?: {
page?: number;
pageSize?: number;
filters?: Partial<Record<keyof T, unknown>>;
}
): Promise<Array<T>> {
const tableName = modelClass.prototype.constructor.TABLE_NAME
if (!tableName) {
throw new Error(`Table name not defined for model ${modelClass.name}`)
}
let query = supabase.from(tableName).select('*')
// Apply filters
if (options?.filters) {
for (const [key, value] of Object.entries(options.filters)) {
if (modelClass.prototype.constructor.DATABASE_MAP?.[key]) {
query = query.eq(
modelClass.prototype.constructor.DATABASE_MAP[key],
value
)
}
}
}
// Apply pagination
if (options?.page && options?.pageSize) {
const start = (options.page - 1) * options.pageSize
query = query.range(start, start + options.pageSize - 1)
}
const { data, error } = await query
if (error) {
throw new Error(`Failed to fetch ${modelClass.name} list: ${error.message}`)
}
if (!data || data.length === 0) {
return []
}
const results: Array<T> = []
data.forEach(record => {
results.push(modelClass.prototype.constructor.parseJson(record))
})
return results
}
🧰 Tools
🪛 eslint (1.23.1)

[error] 67-67: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

Comment on lines +18 to +40
// User.fetchSingle().then(user => {
// console.log(user)
// })

// const supabaseService = new SupabaseService()
// SupabaseService.fetchSingle(User, "c7e80cb0-7d3d-411f-9983-5e3addf62980", { email: "ncuapp@test.com" }).then(
// response => {
// console.log(response)
// }
// )

// SupabaseService.fetchMultiple(User).then(
// response => {
// console.log(response)
// }
// )

// async function fetch() {
// const { data } = await supabase.from('members').select('*')
// console.log(data)
// }
// fetch()

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Clean up commented data fetching implementations.

There are multiple commented sections showing different approaches to data fetching. This could lead to confusion about the intended implementation. Additionally, there are hardcoded values (UUID, email) that shouldn't be committed.

Consider:

  1. Choosing and implementing one data fetching approach
  2. Moving test data to a dedicated test file
  3. Using environment variables for sensitive values

Comment on lines +4 to +16
const homeItems = [
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
]
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove or utilize the unused homeItems array.

The homeItems array contains placeholder data and is currently unused since the mapping code is commented out. Either remove this array or implement its intended functionality.

-const homeItems = [
-  { title: "test", description: "Hello world." },
-  { title: "test", description: "Hello world." },
-  { title: "test", description: "Hello world." },
-  { title: "test", description: "Hello world." },
-  { title: "test", description: "Hello world." },
-  { title: "test", description: "Hello world." },
-  { title: "test", description: "Hello world." },
-  { title: "test", description: "Hello world." },
-  { title: "test", description: "Hello world." },
-  { title: "test", description: "Hello world." },
-  { title: "test", description: "Hello world." },
-]
📝 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 homeItems = [
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
{ title: "test", description: "Hello world." },
]
🧰 Tools
🪛 eslint (1.23.1)

[error] 4-4: 'homeItems' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

Comment on lines +57 to +61
<div className="avatar absolute right-[15px] bottom-[-25px]">
<div className="ring-gray-300 w-[125px] rounded-full ring">
<img src="https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp" />
</div>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace stock photo URL with a proper avatar handling system.

The current implementation uses a hardcoded stock photo URL. Consider:

  1. Implementing proper avatar handling with fallbacks
  2. Adding alt text for accessibility
  3. Handling loading and error states
       <div className="avatar absolute right-[15px] bottom-[-25px]">
         <div className="ring-gray-300 w-[125px] rounded-full ring">
-          <img src="https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp" />
+          <img 
+            src={user?.avatarUrl ?? defaultAvatarUrl} 
+            alt={`${user?.name}'s avatar`}
+            onError={(e) => e.currentTarget.src = defaultAvatarUrl}
+          />
         </div>
       </div>

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

Comment on lines +47 to +48
<h1 className='font-bold text-4xl text-black'>這裡最多七個字</h1>
<h2 className='font-bold text-black'>中文系 二年級</h2>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Externalize text content for internationalization.

The hardcoded Chinese text should be moved to a localization system to support multiple languages.

-        <h1 className='font-bold text-4xl text-black'>這裡最多七個字</h1>
-        <h2 className='font-bold text-black'>中文系 二年級</h2>
+        <h1 className='font-bold text-4xl text-black'>{t('home.title')}</h1>
+        <h2 className='font-bold text-black'>{t('home.subtitle')}</h2>

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

1 participant