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

Generic data model and Supabase data service #125

Closed
wants to merge 2 commits 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
  • Removed unused components and imports in Homepage

Note: 首頁 GUI 可暫時忽略

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new home page route with a visually appealing layout.
    • Added a User model to manage user data with properties like username, email, and bio.
    • Implemented a service for seamless interaction with the Supabase database, allowing fetching of single and multiple records.
  • Bug Fixes

    • Resolved issues with the previous routing structure to ensure proper navigation.
  • Documentation

    • Updated routing manifest to reflect new routes and services.

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
- Removed VStack imports and unused template components
Copy link
Contributor

coderabbitai bot commented Nov 23, 2024

Walkthrough

The changes introduce a new abstract class GenericModel and its concrete subclass User, which includes various user-related properties and a method for parsing JSON data into a User instance. Additionally, a new routing structure is established for the home page, integrating it into the existing routing framework. A new service class, SupabaseService, is also added to facilitate database interactions, providing methods to fetch single and multiple records from a Supabase database.

Changes

File Change Summary
src/models/GenericModel.ts Added an abstract class GenericModel with static properties TABLE_NAME, DATABASE_MAP, and an abstract method parseJson.
src/models/User.ts Introduced a User class extending GenericModel, with static properties TABLE_NAME, DATABASE_MAP, multiple user attributes, and a method parseJson for creating a User instance from JSON data.
src/routeTree.gen.ts Added a new route HomeIndexRoute for /home/, integrated into various routing interfaces, and updated the route manifest.
src/routes/home/index.tsx Defined a new home page route using createFileRoute, including a layout component styled with Tailwind CSS and containing headings and an avatar image.
src/services/SupabaseService.ts Created a SupabaseService class with static methods fetchSingle and fetchMultiple for database operations, utilizing TypeScript generics for type safety.

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: Return User instance
Loading
sequenceDiagram
    participant User
    participant SupabaseService
    participant Database

    User->>SupabaseService: fetchMultiple(User)
    SupabaseService->>Database: Query for all records
    Database-->>SupabaseService: Return records
    SupabaseService-->>User: Return array of User instances
Loading

Poem

🐇 In the meadow where rabbits play,
New models hop in, brightening the day.
With routes to explore and data to find,
A service for Supabase, oh so kind!
Let's celebrate changes, both big and small,
In our code's garden, we cherish them all! 🌼


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: 8

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

1-2: LGTM! Consider documenting the contract.

The abstract class declaration is appropriate for defining a generic data model contract. Consider adding JSDoc comments to document:

  • The purpose of this base class
  • The contract that implementing classes must fulfill
  • Example usage
+/**
+ * Abstract base class for database models.
+ * Provides a contract for database mapping and JSON parsing.
+ * 
+ * @example
+ * class User extends GenericModel {
+ *   static TABLE_NAME = 'users';
+ *   static DATABASE_MAP = { id: 'id', name: 'full_name' };
+ *   
+ *   static parseJson(json: Record<string, unknown>): User {
+ *     // Implementation
+ *   }
+ * }
+ */
 export default abstract class GenericModel {

1-13: Consider additional data layer capabilities.

As this class serves as the foundation for your data layer, consider adding:

  1. Validation methods to ensure data integrity
  2. Type-safe transformation utilities for handling database types
  3. Error handling patterns for database operations
  4. Methods for handling relationships between models

Example additions:

abstract class GenericModel {
  // ... existing code ...
  
  abstract validate(): ValidationResult;
  
  protected static transformDatabaseValue<T>(
    value: unknown,
    field: keyof DatabaseMap
  ): T;
  
  protected static handleDatabaseError(
    error: unknown,
    operation: DatabaseOperation
  ): void;
}

Would you like me to provide a detailed implementation of these suggestions?

🧰 Tools
🪛 Biome (1.9.4)

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

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

(lint/complexity/noStaticOnlyClass)


[error] 11-11: This code is unreachable

... because this statement will throw an exception beforehand

(lint/correctness/noUnreachable)


[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: Unexpected any. Specify a different type.

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

src/services/SupabaseService.ts (2)

1-5: Consider refactoring to standalone functions instead of static methods.

The class currently only contains static methods. Consider refactoring these into standalone functions for better maintainability and to align with functional programming principles.

-export default class SupabaseService {
-    public static async fetchSingle...
-    public static async fetchMultiple...
-}
+export async function fetchSingle...
+export async function fetchMultiple...

1-84: Consider adding comprehensive error handling and logging.

The service would benefit from:

  1. A custom error class for Supabase-related errors
  2. Logging of database operations
  3. Retry mechanism for transient failures

Would you like me to provide an implementation for these improvements?

🧰 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)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

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

📒 Files selected for processing (5)
  • src/models/GenericModel.ts (1 hunks)
  • src/models/User.ts (1 hunks)
  • src/routeTree.gen.ts (13 hunks)
  • src/routes/home/index.tsx (1 hunks)
  • src/services/SupabaseService.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/routes/home/index.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
src/models/GenericModel.ts

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

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

(lint/complexity/noStaticOnlyClass)


[error] 11-11: This code is unreachable

... because this statement will throw an exception beforehand

(lint/correctness/noUnreachable)


[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: 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/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 (3)
src/models/User.ts (1)

1-3: Verify GenericModel abstract class implementation

The User class extends GenericModel, but we need to ensure all abstract members are properly implemented.

✅ Verification successful

Implementation of abstract members is correct

The User class correctly implements all required abstract members from GenericModel:

  • Static TABLE_NAME is defined as "members"
  • Static DATABASE_MAP is implemented with the required field mappings
  • Static parseJson method is overridden with proper implementation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for GenericModel implementation to verify abstract members
ast-grep --pattern 'abstract class GenericModel {
  $$$
}'

Length of output: 741


Script:

#!/bin/bash
# Search for User class implementation to verify abstract members
ast-grep --pattern 'class User extends GenericModel {
  $$$
}'

Length of output: 1890

src/services/SupabaseService.ts (1)

1-2: Verify GenericModel integration.

Let's ensure the service properly integrates with all required GenericModel features.

✅ Verification successful

Integration with GenericModel is properly implemented

The SupabaseService correctly integrates with GenericModel by:

  • Using proper type constraints (<T extends GenericModel>)
  • Utilizing all required GenericModel features:
    • TABLE_NAME for table identification
    • DATABASE_MAP for field mapping
    • parseJson for object instantiation

The service implementation provides a type-safe and flexible way to perform database operations with any model that extends GenericModel.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for GenericModel implementations to verify compatibility
ast-grep --pattern 'class $_ extends GenericModel {
  static TABLE_NAME = $_;
  static DATABASE_MAP = $_;
}'

Length of output: 110


Script:

#!/bin/bash
# Let's first check the GenericModel implementation
cat src/models/GenericModel.ts

# Then search for any class extending GenericModel
ast-grep --pattern 'class $_ extends GenericModel'

# Also check how SupabaseService uses GenericModel
cat src/services/SupabaseService.ts

Length of output: 2676

src/routeTree.gen.ts (1)

Line range hint 1-344: LGTM! Note: This is an auto-generated file.

The changes consistently integrate the new /home/ route across all necessary interfaces and objects. The route configuration follows the established patterns used by other routes in the application.

Let's verify the route configuration consistency:

✅ Verification successful

Route configuration is properly integrated and source files exist

The verification confirms:

  • Both route files exist: src/routes/home/index.tsx and src/routes/home/infoCard.tsx
  • The HomeIndex route is consistently defined across all required sections:
    • Import declaration
    • Route configuration with correct path /home/
    • Type definitions in interfaces
    • Route tree configuration
  • The route follows the same pattern as other routes (e.g., MapIndex, SalesIndex)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that the home route is consistently defined and the source file exists

# Check if the home route source file exists
if ! [ -f "src/routes/home/index.tsx" ]; then
  echo "Error: Home route source file not found"
  exit 1
fi

# Verify route definition consistency
rg -U --multiline '/home/' src/routeTree.gen.ts | grep -E '(path:|filePath:)'

Length of output: 189


Script:

#!/bin/bash
# Let's verify the complete route configuration for home routes

# Check both route files
echo "Checking home route files:"
ls -l src/routes/home/index.tsx src/routes/home/infoCard.tsx 2>/dev/null

echo -e "\nChecking all home route references:"
rg -A 2 "HomeIndex" src/routeTree.gen.ts

echo -e "\nVerifying route manifest entries:"
rg -A 2 '"/home' src/routeTree.gen.ts | grep -E '(filePath:|path:)'

Length of output: 1487

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.

⚠️ Potential issue

Improve type safety of DATABASE_MAP.

The Object type is too broad and unsafe. Consider using a more specific type that properly represents the database field mappings.

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

Also, consider making these properties abstract to enforce implementation:

-    static TABLE_NAME: string;
+    abstract static TABLE_NAME: string;
-    static DATABASE_MAP: Record<string, string>;
+    abstract static DATABASE_MAP: Record<string, 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
// Abstract property for table name
static TABLE_NAME: string;
static DATABASE_MAP: Object;
// Abstract property for table name
abstract static TABLE_NAME: string;
abstract static DATABASE_MAP: Record<string, string>;
🧰 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 +8 to +12
// Abstract method for parsing JSON
static parseJson(json: any): GenericModel {
throw new Error("parseJson method not implemented.");
return json
}
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

Improve type safety and implementation of parseJson.

Several issues need to be addressed:

  1. Using any type defeats TypeScript's type safety
  2. The return statement after throw is unreachable
  3. The method should use generics to ensure type-safe implementations
-    static parseJson(json: any): GenericModel {
-        throw new Error("parseJson method not implemented.");
-        return json
-    }
+    abstract static parseJson<T extends GenericModel>(
+        json: Record<string, unknown>
+    ): T;

This change:

  • Makes the method abstract to enforce implementation
  • Uses generics to ensure implementing classes return their specific type
  • Uses Record<string, unknown> for better type safety of JSON input
📝 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
// Abstract method for parsing JSON
static parseJson(json: any): GenericModel {
throw new Error("parseJson method not implemented.");
return json
}
// Abstract method for parsing JSON
abstract static parseJson<T extends GenericModel>(
json: Record<string, unknown>
): T;
🧰 Tools
🪛 Biome (1.9.4)

[error] 11-11: This code is unreachable

... because this statement will throw an exception beforehand

(lint/correctness/noUnreachable)

🪛 eslint (1.23.1)

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

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

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

  1. The method uses any type which reduces type safety
  2. Using this in static context could be confusing
  3. Missing input validation for required fields

Consider applying this improvement:

-    static parseJson(json: any) : User {
+    static parseJson(json: Record<string, unknown>) : User {
         const user = new User()
 
-        for (const [userProperty, jsonField] of Object.entries(this.DATABASE_MAP))
+        for (const [userProperty, jsonField] of Object.entries(User.DATABASE_MAP)) {
             if (json[jsonField] !== undefined)
-                (user as any)[userProperty] = json[jsonField];
+                (user as Record<string, unknown>)[userProperty] = json[jsonField];
+        }
+
+        // Validate required fields
+        if (!user.id || !user.email) {
+            throw new Error('Missing required fields: id and email are mandatory');
+        }
 
         return user
     }
📝 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()
for (const [userProperty, jsonField] of Object.entries(User.DATABASE_MAP)) {
if (json[jsonField] !== undefined)
(user as Record<string, unknown>)[userProperty] = json[jsonField];
}
// Validate required fields
if (!user.id || !user.email) {
throw new Error('Missing required fields: id and email are mandatory');
}
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 +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

Use lowercase primitive types

Replace uppercase String with lowercase string for better TypeScript conventions.

Apply this diff to fix the type declarations:

-    public id:                  String  = ""
-    public username:            String  = ""
-    public email:               String  = ""
-    public phone:               String  = ""
-    public avatar:              String  = ""
-    public profileBackground:   String  = ""
+    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  = ""
+    public identity:           string  = ""
+    public department:         string  = ""
+    public grade:              string  = ""
+    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 = ""
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 = ""
🧰 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 +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 complete database mappings

  1. Replace the generic Object type with a proper type definition for better type safety.
  2. Several instance properties are missing from DATABASE_MAP.

Apply this diff to improve type safety and add missing mappings:

-    static DATABASE_MAP: Object = {
+    static DATABASE_MAP: Record<string, string> = {
         id:         "uuid",
         username:   "username",
         email:      "fk_email",
         identity:   "fk_identity",
         avatar:     "avatar",
+        phone:      "phone",
+        profileBackground: "profile_background",
+        joinedAt:   "joined_at",
+        department: "department",
+        grade:      "grade",
+        bio:        "bio"
     }

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 +23 to +27
public static async fetchSingle<T extends GenericModel>(
modelClass: new() => T,
targetID: String,
criteria?: Partial<Record<string, any>>,
): Promise<T | null> {
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

Fix TypeScript type declarations.

The method signature has some type-related issues:

  • Use lowercase string instead of String
  • Consider adding proper typing for criteria instead of using Record<string, any>
 public static async fetchSingle<T extends GenericModel>(
     modelClass: new() => T,
-    targetID: String,
-    criteria?: Partial<Record<string, any>>,
+    targetID: string,
+    criteria?: Partial<Record<keyof T, unknown>>,
 ): Promise<T | null> {
📝 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 fetchSingle<T extends GenericModel>(
modelClass: new() => T,
targetID: String,
criteria?: Partial<Record<string, any>>,
): Promise<T | null> {
public static async fetchSingle<T extends GenericModel>(
modelClass: new() => T,
targetID: string,
criteria?: Partial<Record<keyof T, unknown>>,
): Promise<T | null> {
🧰 Tools
🪛 Biome (1.9.4)

[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)

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.

⚠️ Potential issue

Add pagination and improve error handling.

The current implementation has several potential issues:

  • Fetching all records without pagination could lead to performance issues
  • Error handling is missing
  • The variable name 'user' is misleading as it could be any model type
 public static async fetchMultiple<T extends GenericModel>(
     modelClass: new() => T,
+    limit: number = 100,
+    offset: number = 0
 ): Promise<Array<T>> {
     const model = (modelClass as any)
     const tableName = model.TABLE_NAME

-    const data = await supabase.from(tableName)
-                    .select('*')
+    const { data, error } = await supabase.from(tableName)
+                    .select('*')
+                    .range(offset, offset + limit - 1)

+    if (error) {
+        throw new Error(`Failed to fetch ${tableName}: ${error.message}`);
+    }
                     
     const records = data.data

     const results: Array<T> = []

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

     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,
limit: number = 100,
offset: number = 0
): Promise<Array<T>> {
const model = (modelClass as any)
const tableName = model.TABLE_NAME
const { data, error } = await supabase.from(tableName)
.select('*')
.range(offset, offset + limit - 1)
if (error) {
throw new Error(`Failed to fetch ${tableName}: ${error.message}`);
}
const records = data.data
const results: Array<T> = []
records?.forEach(record => {
const item = model.parseJson(record)
results.push(item)
})
return results
}
🧰 Tools
🪛 eslint (1.23.1)

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

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

Comment on lines +31 to +39
var query = supabase.from(tableName)
.select('*')
.eq("uuid", targetID)

for (const key in criteria)
if (model.DATABASE_MAP[key])
query = query.eq(model.DATABASE_MAP[key], criteria[key])

const data = await query;
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 query construction and error handling.

Several improvements can be made to the query construction:

  • Use const instead of var
  • Add error handling for the Supabase query
  • Consider validating UUID format before query
-        var query = supabase.from(tableName)
+        const query = supabase.from(tableName)
                         .select('*')
                         .eq("uuid", targetID)
         
         for (const key in criteria)
             if (model.DATABASE_MAP[key])
                 query = query.eq(model.DATABASE_MAP[key], criteria[key])
 
-        const data = await query;
+        const { data, error } = await query;
+        if (error) {
+            throw new Error(`Failed to fetch ${tableName}: ${error.message}`);
+        }

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

🧰 Tools
🪛 eslint (1.23.1)

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

(no-var)

@1989ONCE 1989ONCE added the structure refactor, db model design label Dec 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
structure refactor, db model design
Development

Successfully merging this pull request may close these issues.

2 participants