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: new application db schema #191

Closed
wants to merge 68 commits into from
Closed

feat: new application db schema #191

wants to merge 68 commits into from

Conversation

fvcci
Copy link
Contributor

@fvcci fvcci commented Oct 1, 2024

Overview

Created a new schema for the application database. Forms can now be dynamically created in our database with minimal effort on the development side. The new database schema is as follows:
image
In the future the DH10Application table will be deprecated, since the new table offers many needed benefits:

  • infinite question creation
  • infinite form creation
  • benefits come without requiring modification to the database or codebase, which would be required with the old database

Changes:

  • Database changes
    • deprecating User.typeform_response_id, User.hacker, and User.reviewer as they are unused.
    • Deprecating User.status, as FormSubmission.status should be used instead.
      • Advantages:
        • no need to mass update user statuses every year
    • New field User.answers
  • Affected pages:
    • dashboard
      • serves content matching the status recorded in a user's formSubmission field
      • deletes formSubmissions in the new application database on application redo
      • rsvp-ing
    • apply
      • submits applications to the new application database
    • grade
      • graders now modify the status in the user's formSubmission field
      • Application Viewing fetches answers AND questions from the application schema
    • welcome
    • checkin
      • status querying is now managed server side, speeding up load times
      • page not really used though
  • trpc API changes
    • removed dead code
      • applicationRouter.received
      • applicationRouter.status - removed also because User.status that it was querying from is deprecated
    • rerouted db queries from the old db to the new db
      • applicationRouter.getStatusCount()
      • applicationRouter.rsvp()
      • applicationRouter.submit()
      • applicationRouter.deleteApplication()
      • reviewerRouter.getApplications()
      • reviewerRouter.getApplication()
      • reviewerRouter.updateStatus -> reviewerRouter.updateApplicationShallow()
    • getPrevAutofill - replace TypeForm API querying with DH10Application querying
    • getApplicationShallow() - new command to query for shallow application info (like status)

todo in a later PR

  • Dynamically load form questions and answers on the front end without having to specify them manually in code.
  • deprecating DH10Application
  • answer restrictions checking in the new db.

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Introduced a dynamic form builder system with new tables for submissions, questions, and answers.
    • Enhanced application submission and review processes with updated queries and mutations.
    • Updated the application data retrieval in various components to improve user experience.
    • Implemented a check for user roles in the grading portal to restrict access.
    • New structured constants and types for managing application form questions and categories.
    • Added a new router for handling forms, categories, and questions with validation.
    • Introduced new configuration handling for application submissions.
    • Updated the Prisma schema to reflect new models and relationships for submissions.
  • Bug Fixes
    • Resolved issues with data retrieval and submission logic across various components.
  • Documentation
    • Updated the .gitignore file to include cockroach-data.
  • Chores
    • Significant updates to the package.json dependencies for improved performance and security.

fvcci added 30 commits August 25, 2024 10:54
- Will replace static DH10Application model with a more flexible and dynamic form-based architecture
- Introduced new models: FormSubmission, FormStructure, FormStructureQuestion, Answer, Question, and AnswerType for modular data handling
- Retained essential authentication models (Account, Session, User) and improved relationships and handling
- Deprecated `User.hacker`, `User.reviewer`, and `User.status` for better status management
…d created a User and FormSubmission relationship
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: 13

🧹 Outside diff range and nitpick comments (20)
prisma/migrations/20241002055837_dynamic_form_builder/migration.sql (4)

21-26: LGTM! Consider adding metadata fields.

The FormStructure table is correctly defined with id as the primary key. This minimal design allows for flexibility in form structures. However, consider adding metadata fields such as name, description, or createdAt to provide more context and improve manageability.

Example of additional fields:

CREATE TABLE "FormStructure" (
    "id" STRING NOT NULL,
    "name" STRING NOT NULL,
    "description" STRING,
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

    CONSTRAINT "FormStructure_pkey" PRIMARY KEY ("id")
);

28-35: LGTM! Consider making the statement field non-nullable.

The Answer table structure is well-designed with a composite primary key ensuring one answer per question per submitter. However, the statement field is currently nullable. Consider making it non-nullable to ensure all answers have a value, even if it's an empty string.

Suggested change:

CREATE TABLE "Answer" (
-    "statement" STRING,
+    "statement" STRING NOT NULL,
    "addressedQuestionId" STRING NOT NULL,
    "submitterId" STRING NOT NULL,

    CONSTRAINT "Answer_pkey" PRIMARY KEY ("addressedQuestionId","submitterId")
);

45-50: LGTM! Consider using a separate id as the primary key.

The FormQuestionCategory table structure is simple, using the category name as the primary key. While this works, consider using a separate id field as the primary key. This would allow for easier renaming of categories in the future without affecting foreign key relationships.

Suggested structure:

CREATE TABLE "FormQuestionCategory" (
    "id" STRING NOT NULL,
    "name" STRING NOT NULL,

    CONSTRAINT "FormQuestionCategory_pkey" PRIMARY KEY ("id")
);

CREATE UNIQUE INDEX "FormQuestionCategory_name_key" ON "FormQuestionCategory"("name");

61-68: LGTM! Consider RESTRICT on delete for questionId.

The foreign key constraints for FormStructureQuestion are generally well-defined. However, consider changing the ON DELETE action for questionId from CASCADE to RESTRICT. This would prevent accidental deletion of questions that are part of existing form structures.

Current constraints:

  1. formStructureId references FormStructure.id with CASCADE on delete and update.
  2. questionId references Question.id with CASCADE on delete and update.
  3. categoryId references FormQuestionCategory.name with CASCADE on delete and update.

Suggested change for the questionId foreign key:

ALTER TABLE "FormStructureQuestion" ADD CONSTRAINT "FormStructureQuestion_questionId_fkey" FOREIGN KEY ("questionId") REFERENCES "Question"("id") 
- ON DELETE CASCADE ON UPDATE CASCADE;
+ ON DELETE RESTRICT ON UPDATE CASCADE;
prisma/schema.prisma (4)

Line range hint 112-165: New DH10Application model as a transitional step

The new DH10Application model is introduced with a comment indicating its future deprecation once application data is fully migrated to FormSubmissions. This suggests a phased approach to updating the application system.

Recommendations:

  1. Document the migration plan from DH10Application to FormSubmissions, including timelines and steps.
  2. Ensure that any new application submissions use the new FormSubmission system to avoid adding more data to this transitional model.
  3. Create a task or issue to track the migration progress and eventual removal of this model.

Would you like assistance in creating a GitHub issue to track the migration from DH10Application to FormSubmissions?


182-201: New FormStructure and FormStructureQuestion models for dynamic forms

The introduction of FormStructure and FormStructureQuestion models provides a flexible framework for creating dynamic forms, aligning with the PR objective of enabling dynamic form creation with minimal development effort.

Key features:

  1. FormStructure acts as a container for form submissions and question structures.
  2. FormStructureQuestion links questions to forms and categories, with a display priority for ordering.
  3. The composite unique constraint on displayPriority and formStructureId ensures proper ordering of questions within a form.

Recommendations:

  1. Document the process of creating and managing dynamic forms using these new models.
  2. Consider creating helper functions or utilities to simplify the creation and modification of form structures.
  3. Ensure that the UI components are updated to render forms based on this new structure.

Would you like assistance in creating documentation for the new dynamic form structure system?


223-227: Addition of FormQuestionCategory model

The new FormQuestionCategory model enables the categorization of questions within form structures. This addition can significantly enhance the organization of forms and potentially improve the user experience.

Considerations:

  1. This model allows for flexible grouping of questions, which can be particularly useful for long or complex forms.
  2. Categories can be used to implement features like section-based form rendering or category-based form analytics.

Recommendations:

  1. Define a set of standard categories that can be used across different types of forms to maintain consistency.
  2. Update the UI components to utilize these categories for improved form layout and navigation.
  3. Consider implementing category-based validation or conditional rendering of form sections based on these categories.

Would you like assistance in defining a set of standard categories or implementing category-based form features?


Incomplete Deprecation of the Review Model

The Review model is still referenced in multiple parts of the codebase, indicating that its deprecation is not yet complete.

Recommended Actions:

  1. Update the Deprecation Comment:
    -/// @deprecated Use `User.status` instead
    +/// @deprecated Use `FormSubmission.status` instead
  2. Plan for Complete Removal:
    • Refactor all usages of the Review model to utilize the FormSubmission model.
    • Ensure that all dependencies and references are updated accordingly to prevent residual usage.
🔗 Analysis chain

Line range hint 66-75: Deprecation of the Review model

The entire Review model has been deprecated with the comment suggesting to use User.status instead. However, this comment seems outdated as User.status is also deprecated in favor of FormSubmission.status.

Consider the following actions:

  1. Update the deprecation comment to refer directly to FormSubmission.status.
  2. Plan for the complete removal of this model in a future update.
  3. Ensure all parts of the application that use the Review model are updated to use the new FormSubmission model.

Update the deprecation comment as follows:

-/// @deprecated Use `User.status` instead
+/// @deprecated Use `FormSubmission.status` instead

To verify the usage of the Review model in the codebase, run:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find references to the Review model in the codebase

rg -t ts -t tsx 'Review\s*\{|Review\s*\[' src/

Length of output: 598

src/server/router/reviewers.ts (3)

9-18: Consider preserving null values for the email field

The updated ApplicationForReview schema improves the structure of the application data. However, transforming null emails to empty strings may lead to ambiguity between an empty email and a missing one. It's generally better to keep null to represent the absence of data.

Consider modifying the email field to retain null values:

email: z
  .string()
  .nullable()
-  .transform((v) => (v === null ? "" : v)),

The replacement of dH10ApplicationId with formStructureId is a good change, suggesting a more flexible approach to identifying applications.


32-66: Excellent refactoring of getApplications query

The refactoring of the getApplications query significantly improves the robustness and flexibility of the application retrieval process. The use of DirectPrismaQuerier for fetching the current application form name is a good approach for efficient database querying.

The error handling for parsing applications is a valuable addition. Consider enhancing it further by including more specific error information:

 } catch (error) {
   throw new TRPCError({
     code: "INTERNAL_SERVER_ERROR",
-    message: "Failed to parse applications.",
+    message: `Failed to parse applications: ${error instanceof Error ? error.message : 'Unknown error'}`,
   });
 }

This change will provide more detailed error information, which can be helpful for debugging.


143-170: Consider simplifying the status icon logic

The update logic for formSubmission and the enhanced logging for status changes are good improvements. However, the nested ternary operators for icon assignment, while functional, can be hard to read and maintain.

Consider simplifying this logic using an object mapping:

const statusIcons = {
  [Status.ACCEPTED]: "✅",
  [Status.REJECTED]: "❌",
  [Status.WAITLISTED]: "🕰️",
  [Status.RSVP]: "🎟️",
};

icon: statusIcons[input.application.status] ?? "🤔",

This approach is more readable and easier to maintain, especially if more statuses are added in the future.

src/pages/checkin.tsx (2)

16-16: Remove unused import TRPCError

The TRPCError import appears to be unused in this file. It's best to remove unused imports to keep the code clean and prevent potential confusion.

Apply this change to remove the unused import:

-import { TRPCError } from "@trpc/server";

Line range hint 1-245: Overall improvements with a minor optimization suggestion

The changes in this file significantly enhance its quality:

  1. Improved type safety with InferGetServerSidePropsType.
  2. Better error handling in getServerSideProps.
  3. Optimized data fetching with DirectPrismaQuerier.
  4. Simplified component logic by leveraging server-side props.

These modifications align well with Next.js best practices and address previous issues.

As a minor optimization, consider memoizing the stateMap object to prevent unnecessary re-creation on each render:

const stateMap = useMemo(() => ({
  [Status.IN_REVIEW]: <PreCheckedIn />,
  [Status.ACCEPTED]: <PreCheckedIn />,
  [Status.WAITLISTED]: <PreCheckedIn />,
  [Status.REJECTED]: <PreCheckedIn />,
  [Status.RSVP]: <PreCheckedIn />,
  [Status.CHECKED_IN]: <PostCheckedIn />,
}), []);

This change would require adding useMemo to the imports from 'react'.

src/server/router/application.ts (7)

1-1: LGTM: New imports and interface enhance functionality.

The addition of DirectPrismaQuerier and trpcAssert imports, along with the AnswerForRouter interface, improves database querying, assertion handling, and answer structuring. These changes align well with the new schema focus on formSubmissions.

Consider adding a brief comment above the AnswerForRouter interface to explain its purpose and usage within the router.

Also applies to: 7-8, 85-89


109-122: LGTM: Improved getStatusCount procedure with better authorization and querying.

The updates to getStatusCount enhance security with trpcAssert for authorization checks and improve querying by using DirectPrismaQuerier. The change to group by formStructureId aligns with the new schema structure.

Consider extracting the authorization check into a separate helper function for reusability across other admin/reviewer-only procedures.


158-176: LGTM with a suggestion: rsvp procedure updated to use formSubmission.

The changes to the rsvp procedure align well with the new schema focusing on formSubmissions. However, there's a potential issue with the assertion:

trpcAssert(form.status === Status.ACCEPTED, "UNAUTHORIZED");

This assertion will throw an "UNAUTHORIZED" error if the status is not ACCEPTED, which is correct. However, it might be clearer to use a more specific error message:

trpcAssert(form.status === Status.ACCEPTED, "NOT_ACCEPTED");

Consider using a more specific error message for the status check to improve clarity.


203-367: LGTM with suggestions: submit procedure updated for new schema.

The submit procedure has been significantly updated to work with the new schema, using applicationSchema for input validation and storing data in a more granular format. These changes improve data integrity and flexibility.

However, there are a few points to consider:

  1. The procedure is quite long and complex. Consider refactoring it into smaller, more manageable functions for better readability and maintainability.

  2. The date handling for gradDate is correct, but you might want to consider using a utility function for date parsing to keep the main logic cleaner.

  3. Consider using a more descriptive name for the answers array, such as formattedAnswers, to better reflect its purpose.

Consider breaking down the submit procedure into smaller functions, each handling a specific part of the submission process (e.g., formatAnswers, upsertFormSubmission, upsertAnswers). This will improve readability and maintainability.


485-492: LGTM: Simplified getPrevAutofill procedure.

The getPrevAutofill procedure has been simplified to directly return the user's dh10application. This change reduces external API dependencies and aligns with the new schema structure.

Consider renaming this procedure to getDh10Application or similar to better reflect its current functionality, as it no longer specifically relates to autofill.


494-526: LGTM: Improved deleteApplication procedure with better error handling.

The deleteApplication procedure has been updated to work with the new schema, deleting the formSubmission instead of dh10Application. The addition of error handling and logging enhances the robustness of the procedure.

Consider adding a check to ensure the user has the necessary permissions to delete an application, especially if this procedure can be called for applications other than the current user's.


Line range hint 1-526: Overall LGTM: Significant improvements to application router.

This update to the application router brings substantial enhancements:

  1. Alignment with a new schema structure, focusing on formSubmission.
  2. Improved error handling and input validation.
  3. New procedures for better application management (getApplicationShallow).
  4. Simplified and more efficient data retrieval (getPrevAutofill).
  5. Enhanced security with trpcAssert for authorization checks.

These changes collectively improve the clarity, efficiency, and robustness of the application submission and retrieval processes. While there are some areas for potential further refinement (as noted in individual comments), the overall direction is very positive.

Consider creating a separate service layer for complex business logic, especially for procedures like submit. This would improve separation of concerns and make the router more focused on request handling and routing.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between e4264b0 and 8ef5e9e.

📒 Files selected for processing (8)
  • prisma/migrations/20241002055837_dynamic_form_builder/migration.sql (1 hunks)
  • prisma/schema.prisma (3 hunks)
  • prisma/seed.ts (1 hunks)
  • src/pages/checkin.tsx (5 hunks)
  • src/pages/dashboard.tsx (5 hunks)
  • src/server/db/directQueries.ts (1 hunks)
  • src/server/router/application.ts (5 hunks)
  • src/server/router/reviewers.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/dashboard.tsx
🧰 Additional context used
📓 Learnings (1)
prisma/schema.prisma (1)
Learnt from: fvcci
PR: deltahacks/portal#191
File: prisma/schema.prisma:62-63
Timestamp: 2024-10-02T05:40:37.273Z
Learning: In our codebase, the relationships `applicationSubmissions` and `answers` in the `User` model are not one-to-many or one-to-one.
🔇 Additional comments (19)
src/server/db/directQueries.ts (2)

1-16: LGTM: Well-structured class with clear documentation.

The DirectPrismaQuerier class is well-documented, clearly stating its purpose and usage constraints. The constructor properly initializes the private prisma member, following good encapsulation practices.


1-60: Overall, good implementation with room for minor improvements.

The DirectPrismaQuerier class provides a clean interface for database queries and has been implemented thoughtfully. The suggestions provided for each method aim to enhance type safety, error handling, and consistency without changing the core functionality or breaking existing behavior.

Consider implementing these suggestions to further improve the robustness and maintainability of the code. Great job on creating a well-structured and documented class for handling common Prisma queries!

prisma/migrations/20241002055837_dynamic_form_builder/migration.sql (6)

11-19: LGTM! Table structure is well-designed.

The FormStructureQuestion table structure is well-designed. The composite primary key of formStructureId and questionId ensures unique question-form structure associations. The displayPriority field is a good addition for ordering questions within a form.


37-43: LGTM! Question table structure is appropriate.

The Question table structure is simple and appropriate for storing questions. Both id and statement fields are non-nullable, which ensures data integrity.


52-53: LGTM! Unique index ensures proper question ordering.

The unique index on displayPriority and formStructureId is a good addition. It ensures that each question has a unique display priority within a form structure, maintaining the correct order of questions in a form.


55-59: LGTM! Foreign key constraints are well-defined.

The foreign key constraints for FormSubmission are well-defined:

  1. formStructureId references FormStructure.id with CASCADE on delete and update, ensuring that submissions are deleted when a form structure is deleted.
  2. submitterId references User.id with RESTRICT on delete and CASCADE on update, preventing accidental deletion of users with submissions.

These constraints maintain data integrity and consistency.


1-74: Overall, well-structured migration for a dynamic form builder system.

This migration successfully implements a flexible schema for a dynamic form builder system. It creates tables for form structures, questions, categories, submissions, and answers, with appropriate relationships between them. The design allows for creating customizable forms with ordered questions and categorization.

Key strengths:

  1. Use of composite primary keys where appropriate.
  2. Flexible form structure design.
  3. Proper indexing for question ordering within forms.

Suggestions for improvement:

  1. Consider adding metadata fields to the FormStructure table.
  2. Make the statement field in the Answer table non-nullable.
  3. Use a separate id as the primary key for the FormQuestionCategory table.
  4. Reconsider some CASCADE on delete actions to prevent unexpected data loss.

Implementation of these suggestions would further enhance data integrity and system flexibility. Overall, this migration provides a solid foundation for the dynamic form builder feature.

To ensure this migration doesn't conflict with existing schema or data, run the following check:

#!/bin/bash
# Check for potential conflicts with existing schema
prisma migrate diff --from-empty --to-schema-datamodel prisma/schema.prisma --exit-code

This command will compare the new schema to an empty database, highlighting any potential issues or conflicts.


1-9: LGTM! Consider clarifying the "Status" enum.

The FormSubmission table structure looks good. The composite primary key and default values are appropriate. However, the status field uses an enum type "Status" which is not defined in this migration.

Could you clarify where the "Status" enum is defined? If it's not defined elsewhere, consider adding its definition to this migration.

✅ Verification successful

LGTM! The "Status" enum is defined in the initial migration.

The FormSubmission table correctly references the existing Status enum. No further action is needed.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for the "Status" enum definition
rg --type sql 'CREATE TYPE.*Status.*AS ENUM' prisma/

Length of output: 163


Script:

#!/bin/bash
# Search for the "Status" enum definition across the entire codebase
rg 'CREATE TYPE\s+"Status"\s+AS ENUM' --type sql

Length of output: 193

prisma/schema.prisma (3)

203-221: Introduction of Answer and Question models

The new Answer and Question models complete the form submission system by providing structures for storing individual answers and questions. This granular approach allows for flexible querying and analysis of form responses.

Key points:

  1. The Answer model links a user's response to a specific question and user.
  2. The Question model can be reused across multiple form structures, promoting reusability.

Recommendations:

  1. Develop a strategy for migrating existing application data to this new structure.
  2. Create efficient queries for retrieving and analyzing form responses, considering the potential for a large number of records.
  3. Implement appropriate indexing strategies to optimize query performance, especially for frequently accessed data.

To verify the usage of these new models in the codebase, run:

#!/bin/bash
# Description: Find references to Answer and Question models in the codebase

rg -t ts -t tsx 'Answer\s*\{|Answer\s*\[|Question\s*\{|Question\s*\[' src/

Line range hint 1-227: Summary of schema changes

This PR introduces a significant overhaul of the application's data model, transitioning from a static application system to a dynamic, flexible form-based system. Key changes include:

  1. Deprecation of old models and fields related to the previous application system.
  2. Introduction of new models (FormSubmission, FormStructure, FormStructureQuestion, Answer, Question, FormQuestionCategory) to support dynamic form creation and submission.
  3. Updates to the User model to integrate with the new form submission system.
  4. Addition of a transitional DH10Application model to facilitate data migration.

These changes align well with the PR objective of enabling dynamic form creation with minimal development effort. However, they also necessitate careful consideration of data migration, updates to business logic, and potential performance optimizations.

Recommendations:

  1. Develop a comprehensive migration plan for existing data.
  2. Update all affected parts of the application to use the new models.
  3. Review and optimize database queries, especially for form submission retrieval and analysis.
  4. Update documentation to reflect the new data model and form creation process.
  5. Consider the impact on existing APIs and update them accordingly.

To get an overview of the changes required in the codebase, run:

#!/bin/bash
# Description: Find files that likely need updates due to schema changes

rg -l 'typeform_response_id|hacker|reviewer|status|Review\s*\{|Review\s*\[|DH10Application' src/

This script will list files that reference old models or fields, helping identify areas that need updating.


169-180: Introduction of the FormSubmission model

The new FormSubmission model appears to be the cornerstone of the updated application system. It establishes relationships with both the FormStructure and User models, effectively replacing the functionality of deprecated fields in the User model and the Review model.

Key points:

  1. The model includes a status field, which likely replaces the deprecated status fields in User and Review models.
  2. The composite primary key ([formStructureId, submitterId]) ensures unique submissions per user per form structure.

Recommendations:

  1. Ensure that all parts of the application that previously used the deprecated fields are updated to use this new model.
  2. Verify that the status transitions (e.g., from IN_REVIEW to ACCEPTED) are properly handled in the business logic.

To verify the integration of FormSubmission in the codebase, run:

✅ Verification successful

FormSubmission Model Integration Verified

The FormSubmission model is properly integrated in the following files:

  • src/server/router/application.ts
  • src/server/router/sponsors.ts

All references to the new model are appropriately handled.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find references to FormSubmission in the codebase

rg -t ts -t tsx 'FormSubmission' src/

Length of output: 598


Script:

#!/bin/bash
# Description: Find all references to FormSubmission in the TypeScript codebase

rg -t ts -t tsx 'FormSubmission' src/

Length of output: 65


Script:

#!/bin/bash
# Description: Find all references to FormSubmission in TypeScript and TSX files

rg 'FormSubmission' 'src/**/*.ts' 'src/**/*.tsx'

Length of output: 153


Script:

#!/bin/bash
# Description: Find all references to FormSubmission in TypeScript and TSX files

rg 'FormSubmission' --glob '*.{ts,tsx}'

Length of output: 1377

src/server/router/reviewers.ts (4)

5-6: LGTM: New imports for enhanced functionality

The addition of assert, trpcAssert, and DirectPrismaQuerier imports aligns well with the changes made in the file, introducing new utility functions and a direct database querying mechanism.


23-26: LGTM: Clear and concise FormItem interface

The addition of the FormItem interface provides a clear structure for question-answer pairs. This will enhance type safety and improve code readability when working with form data.


125-141: LGTM: Improved application update process

The renaming of updateStatus to updateApplicationShallow and the changes to its input structure are good improvements:

  1. The new name better reflects the function's purpose.
  2. Using submitterId aligns with the new schema.
  3. The inclusion of an application object with a status field provides flexibility for future extensions.
  4. The use of DirectPrismaQuerier is consistent with other parts of the file and likely improves efficiency.

These changes enhance the clarity and maintainability of the code.


Line range hint 1-170: Overall excellent improvements to the application review process

The changes in this file significantly enhance the application review process:

  1. The new schema structure with nested submitter object improves data organization.
  2. The use of DirectPrismaQuerier likely improves query efficiency.
  3. Error handling has been added, increasing robustness.
  4. The renaming and restructuring of methods improve clarity and align with the new schema.
  5. Logging enhancements provide better tracking of status changes.

These improvements collectively result in a more maintainable, efficient, and clear codebase for handling application reviews. The minor suggestions provided in the review comments, if implemented, will further refine the code quality.

src/pages/checkin.tsx (2)

Line range hint 186-208: Improved type safety and simplified rendering logic

The changes to the Checkin component are well-implemented:

  • The use of InferGetServerSidePropsType enhances type safety.
  • Simplifying the rendering logic by directly using the status prop from server-side props improves code readability and potentially performance.

These modifications align with best practices for Next.js applications.


240-245: Improved server-side data fetching and error handling

Great job on improving the getServerSideProps function:

  1. The use of DirectPrismaQuerier for fetching the user's application status is a good optimization.
  2. Returning the application status as a prop aligns well with the Checkin component's new structure.
  3. The change to return { notFound: true } when no application is found properly addresses the previous issue with error handling in getServerSideProps.

These changes enhance the overall robustness and efficiency of the server-side logic.

prisma/seed.ts (1)

133-148: Well-structured type definitions and categories

The type definitions and FORM_QUESTION_CATEGORIES constant are well-structured and make good use of TypeScript features. The approach ensures type safety and consistency between the constants and their derived types.

This design will help catch potential errors at compile-time and improve overall code reliability.

src/server/router/application.ts (1)

185-201: LGTM: New getApplicationShallow procedure added.

The getApplicationShallow procedure is a well-structured addition that aligns with the new schema. It efficiently retrieves a user's application using DirectPrismaQuerier and properly handles cases where no submitter ID is provided.

src/server/db/directQueries.ts Outdated Show resolved Hide resolved
src/server/db/directQueries.ts Outdated Show resolved Hide resolved
src/server/router/reviewers.ts Outdated Show resolved Hide resolved
prisma/seed.ts Outdated Show resolved Hide resolved
prisma/seed.ts Outdated Show resolved Hide resolved
prisma/seed.ts Outdated Show resolved Hide resolved
prisma/seed.ts Outdated Show resolved Hide resolved
prisma/seed.ts Outdated Show resolved Hide resolved
prisma/seed.ts Outdated Show resolved Hide resolved
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: 2

🧹 Outside diff range and nitpick comments (14)
src/server/db/directQueries.ts (2)

18-35: Consider adding an explicit return type.

The getDeltaHacksApplicationFormName method is well-implemented with proper error handling using assertions. To further improve type safety and code readability, consider adding an explicit return type:

async getDeltaHacksApplicationFormName(): Promise<string> {
  // ... existing implementation ...
}

This change would make the method's contract clearer to callers.


37-53: Consider adding an explicit return type.

The hasKilledApplications method is well-implemented with proper error handling. To enhance type safety and code clarity, consider adding an explicit return type:

async hasKilledApplications(): Promise<boolean> {
  // ... existing implementation ...
}

This change would make the method's contract clearer to callers.

package.json (1)

Line range hint 5-11: Consider reviewing and updating scripts

Given the significant updates to dependencies, especially Prisma, it might be worth reviewing the scripts section to ensure they're still optimal and up-to-date. For example, you might want to consider:

  1. Adding a script for Prisma migrations if not already handled elsewhere.
  2. Updating the dev script to use prisma db push instead of prisma generate if you're using Prisma in prototype mode.
  3. Adding a postinstall script to run prisma generate automatically after package installation.

Example:

"scripts": {
  "postinstall": "prisma generate",
  "dev": "pnpm i && prisma db push && next dev",
  // ... other scripts
}

These are just suggestions; please adapt them to your project's specific needs.

prisma/seed/data.ts (3)

1-127: LGTM! Consider adding question properties for enhanced flexibility.

The QUESTIONS constant is well-structured and uses the as const assertion for type safety. This approach ensures that all question IDs are valid at compile-time, which is excellent.

Consider enhancing the question objects with additional properties such as:

  • required: boolean to indicate if a question is mandatory
  • type: string to specify the expected answer format (e.g., 'text', 'date', 'select')

This would provide more flexibility and clarity in form rendering and validation.

Example:

{
  id: "first_name",
  statement: "First Name",
  required: true,
  type: "text"
},

151-219: Well-structured form definition. Consider future scalability.

The FORM_STRUCTURES constant effectively defines the DeltaHacks X Application Form using the previously defined interfaces. The organization of questions into categories is logical and comprehensive, covering all aspects of the application process.

While the current implementation is solid, consider the following suggestion for future scalability:

Instead of hardcoding the form structure, consider creating a function that generates the form structure based on configuration objects. This approach would make it easier to create and maintain multiple form structures in the future.

Example:

function createFormStructure(id: string, categoryConfigs: Array<{categoryId: FormQuestionCategoryId, questionIds: QuestionId[]}>): FormStructure {
  return {
    id,
    categories: categoryConfigs.map(config => ({
      categoryId: config.categoryId,
      questions: config.questionIds
    }))
  };
}

export const FORM_STRUCTURES: FormStructure[] = [
  createFormStructure("DeltaHacks X Application Form", [
    { categoryId: "Personal Information", questionIds: ["first_name", "last_name", /* ... */] },
    // ... other categories
  ])
];

This approach would make it easier to maintain and update form structures in the future.


221-225: Consistent form configuration. Minor suggestion for improvement.

The DELTAHACKS_APPLICATION_FORM_CONFIG constant provides a centralized configuration for the DeltaHacks application form, which is good for maintainability and consistency across the application.

For improved consistency, consider aligning the id and name values with the id used in the FORM_STRUCTURES constant:

export const DELTAHACKS_APPLICATION_FORM_CONFIG = {
  id: "DeltaHacks X Application Form",
  name: "DeltaHacks X Application Form",
  value: "DeltaHacks X Application Form",
};

This change would ensure that the form is consistently identified across different parts of the application.

src/server/router/reviewers.ts (5)

5-26: LGTM! Consider simplifying email transformation

The new imports, updated ApplicationForReview schema, and FormItem interface are well-structured and align with the changes in the file. The nested submitter object in ApplicationForReview is a good improvement for data organization.

Consider simplifying the email transformation:

email: z
  .string()
  .nullable()
-  .transform((v) => (v === null ? "" : v)),
+  .transform((v) => v ?? ""),

This change uses the nullish coalescing operator, which is more concise and achieves the same result.


32-66: LGTM! Consider enhancing error logging

The updates to the getApplications procedure are well-implemented:

  • The authorization check using trpcAssert is more concise.
  • Using DirectPrismaQuerier to get the application form name is a good approach.
  • The query is more specific by using formStructureId.
  • Error handling for parsing applications is a valuable addition.

Consider enhancing the error logging to include more details:

 } catch (error) {
   throw new TRPCError({
     code: "INTERNAL_SERVER_ERROR",
-    message: "Failed to parse applications.",
+    message: "Failed to parse applications.",
+    cause: error,
   });
 }

This change will provide more context for debugging if an error occurs.


71-123: LGTM! Consider adding type assertion for better type safety

The updates to the getApplication procedure are well-implemented:

  • Using submitterId instead of dH10ApplicationId aligns with the new schema.
  • The complex query structure efficiently retrieves all necessary data in a single query.
  • Building a Map of FormItems is a good approach for organizing the data.

Consider adding a type assertion for better type safety when accessing questionStructure.question.answers[0]:

 answer: questionStructure.question.answers[0]?.statement ?? null,
+answer: (questionStructure.question.answers[0] as { statement: string | null } | undefined)?.statement ?? null,

This change ensures that TypeScript understands the structure of the answers array elements.


125-171: LGTM! Consider simplifying status icon logic

The updates to the updateApplicationShallow procedure (formerly updateStatus) are well-implemented:

  • The renaming better reflects the function's purpose.
  • Using submitterId and formStructureId for updating aligns with the new schema.
  • The use of DirectPrismaQuerier is consistent with other procedures.
  • The LogSnag tracking update is comprehensive.

Consider simplifying the status icon logic using an object lookup:

-icon:
-  input.application.status === Status.ACCEPTED
-    ? "✅"
-    : input.application.status === Status.REJECTED
-    ? "❌"
-    : input.application.status === Status.WAITLISTED
-    ? "🕰️"
-    : input.application.status === Status.RSVP
-    ? "🎟️"
-    : "🤔",
+icon: {
+  [Status.ACCEPTED]: "✅",
+  [Status.REJECTED]: "❌",
+  [Status.WAITLISTED]: "🕰️",
+  [Status.RSVP]: "🎟️"
+}[input.application.status] ?? "🤔",

This change makes the code more readable and easier to maintain.


Line range hint 172-214: Consider updating authorization check for consistency

The submit procedure remains largely unchanged. For consistency with other procedures in this file, consider updating the authorization check to use trpcAssert.

Update the authorization check:

-if (
-  !(
-    ctx.session.user.role.includes(Role.ADMIN) ||
-    ctx.session.user.role.includes(Role.REVIEWER)
-  )
-) {
-  throw new TRPCError({ code: "UNAUTHORIZED" });
-}
+trpcAssert(
+  ctx.session.user.role.includes(Role.ADMIN) ||
+    ctx.session.user.role.includes(Role.REVIEWER),
+  "UNAUTHORIZED"
+);

This change will make the authorization check consistent with other procedures in the file.

prisma/seed/seed.ts (3)

90-95: Avoid logging sensitive error details in production

Logging detailed error messages and stack traces can expose sensitive information in production environments.

Consider adjusting the logging to be less verbose in production:


88-102: Handle errors gracefully without abrupt termination

Using process.exit(1) can abruptly terminate the process, potentially interrupting asynchronous operations. It's better to allow the process to exit naturally after completing all pending tasks.

Consider removing process.exit(1) and allowing the script to exit gracefully:


31-36: Type annotation for formStructureQuestion may be unnecessary

The explicit type annotation for formStructureQuestion can be omitted as TypeScript can infer the type based on the assigned object.

You can simplify the code by removing the redundant type annotation:

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 8ef5e9e and d1642aa.

📒 Files selected for processing (6)
  • package.json (1 hunks)
  • prisma/seed/data.ts (1 hunks)
  • prisma/seed/seed.ts (1 hunks)
  • src/components/Applicant.tsx (2 hunks)
  • src/server/db/directQueries.ts (1 hunks)
  • src/server/router/reviewers.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/Applicant.tsx
🧰 Additional context used
📓 Learnings (1)
prisma/seed/data.ts (1)
Learnt from: fvcci
PR: deltahacks/portal#191
File: prisma/seed.ts:150-201
Timestamp: 2024-10-02T17:06:51.970Z
Learning: In this codebase, the `FormStructureQuestion[]` TypeScript type is used to ensure all question IDs and category IDs are valid at compile time, making additional runtime validation unnecessary.
🔇 Additional comments (10)
src/server/db/directQueries.ts (1)

1-16: Well-structured class with clear purpose and good practices.

The DirectPrismaQuerier class is well-designed:

  • It encapsulates the PrismaClient as a private member.
  • The constructor allows for dependency injection of the PrismaClient.
  • The comprehensive comment clearly explains the purpose and usage of the class.

This structure promotes good separation of concerns and maintainability.

package.json (4)

13-15: LGTM: Prisma seed configuration added

The addition of the Prisma seed configuration is a good practice. It allows for easy database initialization with test data using TypeScript.


17-41: Dependency updates look good, but consider React version

The dependency updates are generally good for security and feature improvements. However, note that React and React DOM have been updated to version 18.3.1, which is a pre-release version. Consider if using a pre-release version in production is appropriate for your project.

-    "react": "^18.2.0",
-    "react-dom": "^18.2.0",
+    "react": "^18.3.1",
+    "react-dom": "^18.3.1",

Please ensure that your application is thoroughly tested with this new React version before deploying to production.

Also applies to: 44-44, 47-47, 51-52, 54-56, 58-60, 62-64, 67-68, 70-71


78-81: DevDependency updates approved, but verify Prisma compatibility

The devDependency updates are good for maintaining the latest development tools and features. However, the Prisma update is significant:

-    "prisma": "^5.6",
+    "prisma": "^5.19.1",

Please review the Prisma changelog for any breaking changes between these versions and ensure your database schema and queries are compatible with the new version.

Also applies to: 83-85, 87-88


27-27: ⚠️ Potential issue

Align Prisma client and CLI versions

There's still a minor version mismatch between @prisma/client (^5.20.0) and the prisma devDependency (^5.19.1). While this might not cause immediate issues, it's generally recommended to keep these versions in sync to avoid potential compatibility problems.

Consider updating both packages to the same version, preferably the latest stable release. You can do this by running:

npm install @prisma/[email protected] [email protected] --save-exact

This will ensure both packages are at version 5.20.0 exactly, preventing any potential discrepancies.

Also applies to: 87-87

prisma/seed/data.ts (2)

129-139: Well-structured type aliases and categories.

The type aliases QuestionId and FormQuestionCategoryId derived from their respective constants ensure type safety and consistency throughout the codebase. The FORM_QUESTION_CATEGORIES constant is well-organized and covers all aspects of the application form.

This approach aligns well with TypeScript best practices, making the code more maintainable and less prone to errors.


141-149: Well-defined interfaces for form structure.

The FormStructureCategory and FormStructure interfaces provide a clear and type-safe structure for the application form. The use of previously defined type aliases (FormQuestionCategoryId and QuestionId) ensures consistency and type safety throughout the form definition.

This structure allows for flexible form composition while maintaining strict typing, which is excellent for maintainability and preventing errors.

src/server/router/reviewers.ts (1)

Line range hint 1-214: Overall, excellent improvements to the application review process

The changes in this file significantly enhance the application review process:

  • The new schema and data structures improve data organization.
  • Updated queries are more efficient and aligned with the new schema.
  • Error handling and authorization checks have been improved.
  • The use of DirectPrismaQuerier provides consistency across procedures.

These changes should result in better performance and maintainability of the code. Great work on this refactoring!

prisma/seed/seed.ts (2)

1-102: Overall, the seed script is well-structured and follows best practices

The script effectively utilizes asynchronous operations with Promise.all, handles data seeding efficiently, and maintains code readability.


73-77: Confirm that 'name' is a unique identifier for categories

Using name as the unique key in the upsert operation assumes all category names are unique. If duplicate names exist, it could lead to unexpected behavior.

To ensure category names are unique, you can run the following script:

✅ Verification successful

Confirmed: 'name' is a unique identifier for categories.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for duplicate category names in FORM_QUESTION_CATEGORIES

# Expected Result: No duplicate names should be found
grep -oP '"name":\s*"\K[^"]+' prisma/seed/data.ts | sort | uniq -d

Length of output: 68


Script:

#!/bin/bash
# Description: Improved check for duplicate category names in FORM_QUESTION_CATEGORIES with error handling

# Extract all category names, sort them, and identify duplicates
duplicates=$(grep -oP '"name":\s*"\K[^"]+' prisma/seed/data.ts | sort | uniq -d)

if [ -z "$duplicates" ]; then
  echo "No duplicate category names found."
else
  echo "Duplicate category names found:"
  echo "$duplicates"
fi

Length of output: 180

src/server/db/directQueries.ts Outdated Show resolved Hide resolved
prisma/seed/seed.ts Outdated Show resolved Hide resolved
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 (22)
prisma/seed/data.ts (4)

1-7: LGTM! Consider enhancing type safety for questions.

The FormStructure interface is well-defined and provides a clear structure for forms. It allows for flexible form creation with multiple categories and questions.

To enhance type safety, consider defining an enum or union type for common question types, which could be used instead of string[] for the questions array. This would provide better type checking and autocompletion for specific question types. For example:

type QuestionType = 'text' | 'number' | 'date' | 'multipleChoice' | 'checkbox';

interface Question {
  text: string;
  type: QuestionType;
}

export interface FormStructure {
  id: string;
  categories: {
    name: string;
    questions: Question[];
  }[];
}

9-77: LGTM! Consider data privacy for sensitive questions.

The FORM_STRUCTURES constant is well-structured and follows the FormStructure interface correctly. It provides a comprehensive set of questions for the DeltaHacks application.

Please ensure that appropriate data privacy measures are in place for handling sensitive information such as gender, race, and emergency contact details. Consider the following:

  1. Implement strict access controls for this data in the database.
  2. Encrypt sensitive data at rest and in transit.
  3. Provide clear privacy policies to users about how their data will be used and stored.
  4. Ensure compliance with relevant data protection regulations (e.g., GDPR, CCPA) based on your user base.

79-83: LGTM! Consider simplifying the naming convention.

The DELTAHACKS_APPLICATION_FORM_CONFIG constant provides a clear configuration for the DeltaHacks application form. The value field correctly matches the id in the FORM_STRUCTURES array, ensuring consistency.

Consider simplifying the id and name fields to avoid repetition:

export const DELTAHACKS_APPLICATION_FORM_CONFIG = {
  id: "Application",
  name: "Application",
  value: "DeltaHacks X Application Form",
};

This change would make the configuration more concise while still maintaining its clarity and purpose.


1-83: Great foundation for dynamic form creation and management!

This file provides a well-structured and extensible foundation for creating and managing application forms. The FormStructure interface, FORM_STRUCTURES constant, and DELTAHACKS_APPLICATION_FORM_CONFIG work together to create a flexible and type-safe system for defining forms.

For future development:

  1. Consider adding more form structures to the FORM_STRUCTURES array as needed for different types of applications or surveys.
  2. Implement a validation layer that uses these structures to ensure data integrity when processing form submissions.
  3. Create utility functions that can generate frontend form components based on these structures, promoting consistency between the backend definitions and frontend rendering.
prisma/migrations/20241003064705_dynamic_form_builder/migration.sql (1)

56-72: LGTM: Appropriate foreign key constraints

The foreign key constraints are well-defined and maintain referential integrity between the tables. The use of ON DELETE RESTRICT and ON UPDATE CASCADE is generally a good practice.

Consider: Evaluate delete behaviors

While ON DELETE RESTRICT is a safe default, consider if different delete behaviors might be more appropriate for some relationships:

  1. For FormSubmission_formStructureId_fkey and QuestionCategory_formStructureId_fkey, you might want ON DELETE CASCADE if deleting a FormStructure should remove all associated submissions and categories.
  2. For Answer_addressedQuestionId_fkey, ON DELETE CASCADE might be appropriate if deleting a Question should remove all associated answers.

Evaluate these based on your application's requirements and data lifecycle management needs.

prisma/seed/seed.ts (1)

38-41: Consider relaxing the assertion condition.

The current assertion might be too strict if there are existing categories in the database. Consider modifying it to ensure that at least the new categories have been added:

 assert(
-  categories.length >= formStructure.categories.length,
+  categories.length >= formStructure.categories.length,
   "All categories should've been added before"
 );

This change allows for the possibility of existing categories while still ensuring that all new categories have been added successfully.

prisma/schema.prisma (7)

49-53: Approve changes to User model with a note on data management shift

The changes to the User model, including the deprecation of several fields and the addition of new relationships, indicate a positive shift towards a more flexible form submission system. This approach should allow for easier management of diverse application types in the future.

However, there's a minor inconsistency in the deprecation comments:

The comment for the status field suggests using FormSubmission.status, while other comments suggest using FormSubmission without specifying a field. Consider updating the comment for consistency:

-  status               Status     @default(IN_REVIEW) /// @deprecated Use `FormSubmission.status` instead
+  status               Status     @default(IN_REVIEW) /// @deprecated Use `FormSubmission` instead

Also applies to: 61-63


Line range hint 66-76: Update deprecation comment for Review model

The deprecation of the Review model aligns with the shift towards the new FormSubmission system. However, the current deprecation comment may lead to confusion.

Update the deprecation comment to point directly to the new FormSubmission model:

-/// @deprecated Use `User.status` instead
+/// @deprecated Use `FormSubmission` instead

This change will provide clearer guidance and prevent potential confusion caused by referencing another deprecated field.


Line range hint 112-157: Acknowledge transition approach and suggest detailed migration plan

The addition of the DH10Application model with a note about future deprecation shows good foresight in managing the transition to the new FormSubmission system.

To improve the migration process:

  1. Consider adding a TODO comment with a target date or milestone for the migration.
  2. Create a migration guide or plan that outlines how each field in DH10Application will map to the new FormSubmission structure.
  3. Implement a migration script to automate the data transfer process.

This will help ensure a smooth transition and prevent the temporary solution from becoming permanent.


169-180: Approve FormSubmission model structure with a suggestion

The new FormSubmission model is well-structured and aligns with the changes made to the User model. The use of a composite primary key allows for multiple submissions per user across different form structures, which provides good flexibility.

To enhance data integrity:

Consider adding a unique constraint on the submissionTime for each user to prevent duplicate submissions:

model FormSubmission {
  submissionTime DateTime @default(now())
  status         Status   @default(IN_REVIEW)

  formStructure   FormStructure @relation(fields: [formStructureId], references: [id])
  formStructureId String

  submitter   User   @relation(fields: [submitterId], references: [id])
  submitterId String

  @@id([formStructureId, submitterId])
+ @@unique([submitterId, submissionTime])
}

This ensures that each submission for a user has a unique timestamp, preventing potential issues with duplicate submissions.


182-213: Approve form structure models with a suggestion for consistency

The new FormStructure, QuestionCategory, and Question models create a well-organized, hierarchical structure for managing forms and questions. The use of position fields with unique constraints ensures proper ordering within each level.

For consistency and clarity:

Consider renaming the formPosition field in the QuestionCategory model to categoryPosition to match the naming convention used in the Question model:

model QuestionCategory {
  id           String @id @default(cuid())
  name         String
- formPosition Int
+ categoryPosition Int

  formStructure   FormStructure @relation(fields: [formStructureId], references: [id])
  formStructureId String

  questions Question[]

  @@unique([name, formStructureId])
- @@unique([formPosition, formStructureId])
+ @@unique([categoryPosition, formStructureId])
}

This change would make the naming more consistent across the models and clarify that the position is relative to other categories within the same form structure.


215-225: Approve Answer model with suggestion for submission association

The new Answer model effectively captures user responses to specific questions. The composite primary key ensures data integrity by allowing only one answer per question per user.

To improve data management and querying capabilities:

Consider adding a link to the FormSubmission model. This would allow you to associate answers with specific submissions, which can be useful for tracking changes over time if a user submits multiple applications. Here's a suggested modification:

model Answer {
  statement String?

  addressedQuestion   Question @relation(fields: [addressedQuestionId], references: [id])
  addressedQuestionId String

  submitter   User   @relation(fields: [submitterId], references: [id])
  submitterId String

+ formSubmission   FormSubmission @relation(fields: [formStructureId, submitterId], references: [formStructureId, submitterId])
+ formStructureId  String

- @@id([addressedQuestionId, submitterId])
+ @@id([addressedQuestionId, submitterId, formStructureId])
}

model FormSubmission {
  // ... existing fields ...
+ answers Answer[]
}

This change would allow you to easily retrieve all answers for a specific submission and track changes in user responses across multiple submissions.


Line range hint 1-225: Summary of schema changes and their impact

The changes to the Prisma schema represent a significant improvement in the application's data model:

  1. The introduction of FormSubmission, FormStructure, QuestionCategory, Question, and Answer models creates a flexible system for managing diverse types of forms and submissions.
  2. Deprecation of old fields and models (like Review) with clear notices helps manage the transition to the new system.
  3. The new structure allows for dynamic form creation and submission, which should make the application more adaptable to changing requirements.

These changes will likely have a substantial impact on the application:

  • Data migration: Ensure a comprehensive plan is in place to migrate existing data to the new structure.
  • API updates: The application's API will need to be updated to work with the new data model.
  • Frontend changes: Forms and data display components will need to be refactored to work with the new, more flexible structure.
  • Performance considerations: With the more complex relationships, pay attention to query optimization, especially when retrieving full form submissions with all related data.

Overall, these changes provide a solid foundation for a more flexible and maintainable application. Great work on improving the data model!

src/server/router/reviewers.ts (2)

35-72: LGTM: Improved getApplications procedure with robust error handling

The updates to the getApplications procedure are well-implemented:

  1. The authorization check is more concise.
  2. Using DirectPrismaQuerier for retrieving the form name is a good practice.
  3. The query now correctly uses formStructureId for filtering applications.
  4. The added error handling for parsing applications is a valuable safeguard.

One minor suggestion:

Consider adding a log or metric for parsing failures to help track the frequency of such errors:

catch (error) {
  console.error("Failed to parse applications:", error);
  // Consider adding a metric here if you have a metrics system
  throw new TRPCError({
    code: "INTERNAL_SERVER_ERROR",
    message: "Failed to parse applications.",
  });
}

Line range hint 210-255: LGTM: Submit procedure unchanged

The submit procedure remains largely unchanged, which is fine if it's still functioning as intended. It retains the necessary authorization checks and logic for submitting reviews.

However, for consistency with the rest of the file:

Consider updating the authorization check to use trpcAssert like in other procedures:

trpcAssert(
  ctx.session.user.role.includes(Role.ADMIN) ||
    ctx.session.user.role.includes(Role.REVIEWER),
  "UNAUTHORIZED"
);

This would make the error handling consistent across all procedures in this file.

src/components/Applicant.tsx (1)

136-141: Approve conditional rendering, suggest consistent data access

The conditional rendering based on the email domain is implemented correctly. However, consider applying the refactored data access method suggested earlier to maintain consistency and improve readability.

Example using the suggested helper function:

{submitter.email.endsWith("mcmaster.ca") && (
  <FormCheckbox
    label={getQuestionAnswer(5, 0).question}
    checked={getQuestionAnswer(5, 0).answer === "true"}
    readOnly
  />
)}
src/server/router/application.ts (4)

109-126: Consider improving error handling when formName is not found.

The changes to use DirectPrismaQuerier and group by formStructureId are good improvements. However, when formName is not found, the function returns an empty array without logging or throwing an error. Consider adding more robust error handling in this case.

 if (!formName) {
+  console.error("DeltaHacks application form name not found");
   return [];
 }

This will help in debugging if the form name is unexpectedly missing.


162-191: Improve error message specificity in rsvp procedure.

The changes to use formSubmission instead of user for RSVP status are good. However, the error message for the first assertion could be more specific:

 trpcAssert(
   formName,
   "NOT_FOUND",
-  "No application found for the User to RSVP"
+  "No active application form found for RSVP"
 );

This provides more clarity about what exactly was not found.


Line range hint 457-489: Improve user-friendliness of error messages in checkIn procedure.

The changes to the checkIn procedure improve the robustness of the check-in process. Consider making the error messages more user-friendly:

 if (user?.qrcode !== null) {
   throw new TRPCError({
     code: "FORBIDDEN",
-    message: "You are already checked in.",
+    message: "You have already checked in. If you believe this is an error, please contact support.",
   });
 }

 if (qrCount != 0) {
   throw new TRPCError({
     code: "FORBIDDEN",
-    message: "This QR code is already in use",
+    message: "This QR code is already in use. Please request a new QR code from the registration desk.",
   });
 }

These messages provide more context and guidance to the user.


Line range hint 490-545: Improve error handling in getUser procedure.

The changes to the getUser procedure align with the new requirements. However, consider improving the error handling when typeform_response_id is null or undefined:

 if (
   user?.typeform_response_id === null ||
   user?.typeform_response_id === undefined
 ) {
-  throw new TRPCError({ code: "NOT_FOUND" });
+  throw new TRPCError({ 
+    code: "NOT_FOUND",
+    message: "User's Typeform response not found. The user may not have completed the application form."
+  });
 }

This provides more context about why the data couldn't be found, which can be helpful for debugging and user communication.

src/pages/apply.tsx (1)

Line range hint 767-786: Improved application status handling

The addition of the killed prop and its associated conditional rendering is a good way to control the visibility of the application form. However, consider using a more descriptive variable name like isApplicationClosed for better clarity.

Apply this change:

-const Apply: NextPage<
-  InferGetServerSidePropsType<typeof getServerSideProps>
-> = ({ email, killed }) => {
+const Apply: NextPage<
+  InferGetServerSidePropsType<typeof getServerSideProps>
+> = ({ email, isApplicationClosed }) => {
   // ...

-  {killed && (
+  {isApplicationClosed && (
     // ...
   )}

Remember to update the variable name in other parts of the code as well.

src/server/router/fileBuilder.ts (1)

1-100: Enhance input validation

To ensure data integrity, consider adding more detailed validations to your Zod schemas, such as minimum length for strings, positive numbers for positions, and UUID formats for IDs.

Example enhancements:

 z.object({
   id: z.string(),
+  // If IDs are UUIDs
+  id: z.string().uuid(),
 })

 z.object({
   name: z.string(),
+  name: z.string().min(1),
   formPosition: z.number(),
+  formPosition: z.number().nonnegative(),
   formStructureId: z.string(),
+  formStructureId: z.string().uuid(),
 })
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between d1642aa and b8b3440.

📒 Files selected for processing (10)
  • prisma/migrations/20241003064705_dynamic_form_builder/migration.sql (1 hunks)
  • prisma/schema.prisma (3 hunks)
  • prisma/seed/data.ts (1 hunks)
  • prisma/seed/seed.ts (1 hunks)
  • src/components/Applicant.tsx (2 hunks)
  • src/pages/apply.tsx (4 hunks)
  • src/server/db/directQueries.ts (1 hunks)
  • src/server/router/application.ts (5 hunks)
  • src/server/router/fileBuilder.ts (1 hunks)
  • src/server/router/reviewers.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/server/db/directQueries.ts
🧰 Additional context used
📓 Learnings (3)
prisma/schema.prisma (1)
Learnt from: fvcci
PR: deltahacks/portal#191
File: prisma/schema.prisma:62-63
Timestamp: 2024-10-02T05:40:37.273Z
Learning: In our codebase, the relationships `applicationSubmissions` and `answers` in the `User` model are not one-to-many or one-to-one.
prisma/seed/data.ts (1)
Learnt from: fvcci
PR: deltahacks/portal#191
File: prisma/seed.ts:150-201
Timestamp: 2024-10-02T17:06:51.970Z
Learning: In this codebase, the `FormStructureQuestion[]` TypeScript type is used to ensure all question IDs and category IDs are valid at compile time, making additional runtime validation unnecessary.
src/components/Applicant.tsx (1)
Learnt from: fvcci
PR: deltahacks/portal#191
File: src/components/Applicant.tsx:105-108
Timestamp: 2024-10-02T05:34:43.794Z
Learning: In `ApplicationContent`, `applicationForReview` and the data fetched via `trpc.reviewer.getApplication.useQuery` represent different data and are not redundant.
🔇 Additional comments (22)
prisma/migrations/20241003064705_dynamic_form_builder/migration.sql (2)

47-54: LGTM: Well-designed unique indexes

The unique indexes created for QuestionCategory and Question tables are well-designed:

  1. QuestionCategory_name_formStructureId_key: Ensures unique category names within a form.
  2. QuestionCategory_formPosition_formStructureId_key: Guarantees unique positions for categories within a form.
  3. Question_categoryPosition_categoryId_key: Ensures unique positions for questions within a category.

These indexes will help maintain data integrity and support efficient querying.


1-45: LGTM: Well-structured table definitions

The table definitions for FormSubmission, FormStructure, QuestionCategory, Question, and Answer are well-structured and appear to cover all necessary fields for a dynamic form builder application. The use of composite primary keys where appropriate is a good design choice.

Verify: Nullable 'statement' field in Answer table

The 'statement' field in the Answer table (line 40) is defined as nullable. Is this intentional? If so, consider adding a comment explaining why answers can be empty. If not, consider making it NOT NULL.

prisma/seed/seed.ts (5)

1-11: LGTM: Imports and PrismaClient initialization look good.

The necessary dependencies are imported, and the PrismaClient is initialized with appropriate logging configuration. This setup will be helpful for debugging database operations during the seeding process.


13-72: LGTM: createFormStructure function is well-implemented.

The function is well-structured, uses transactions for data consistency, and includes appropriate error handling. The use of createMany with skipDuplicates: true is a good approach for handling potential duplicate entries.


74-93: LGTM: main function is well-implemented.

The main function includes several good practices:

  1. Checking for production environment to prevent accidental seeding.
  2. Using upsert for the application form config, which handles both creation and update scenarios.
  3. Utilizing Promise.all for parallel processing of form structures, which can improve performance.

These implementations contribute to a robust and efficient seeding process.


95-109: LGTM: Error handling and database disconnection are properly implemented.

The error handling is comprehensive, logging both the error and stack trace. The finally block ensures that the database connection is always closed, regardless of whether the seeding process succeeds or fails. This implementation addresses the concern raised in the past review comment about potential unclosed database connections.


1-109: Overall, excellent implementation of the database seeding mechanism.

This file demonstrates a well-structured and robust implementation of a database seeding mechanism using Prisma. Key strengths include:

  1. Proper error handling and logging throughout the file.
  2. Efficient use of Prisma's features like createMany and upsert.
  3. Safety measures such as production environment checks.
  4. Ensuring database disconnection in all scenarios.

The code follows good practices and is well-organized. With the minor suggestion about relaxing the assertion condition, this implementation provides a solid foundation for seeding the application's database.

src/server/router/reviewers.ts (3)

5-29: LGTM: Improved type definitions and imports

The changes to the imports and type definitions look good. The updated ApplicationForReview schema with a nested submitter object improves data organization. The new Application interface also aligns well with the updated structure.


Line range hint 1-255: Great job on overall code structure and consistency

The changes in this file demonstrate significant improvements:

  1. Better organization of procedures and types.
  2. Consistent use of new utilities like DirectPrismaQuerier and trpcAssert.
  3. Enhanced error handling in most procedures.
  4. Improved type definitions that better reflect the data structure.

These changes contribute to a more maintainable and robust codebase. Great work on refactoring this file!


Line range hint 1-255: Summary: Significant improvements to the application review process

This pull request introduces substantial enhancements to the reviewers.ts file:

  1. Updated data structures (ApplicationForReview, Application) that better represent the application schema.
  2. Improved querying methods using DirectPrismaQuerier for more efficient database interactions.
  3. Enhanced error handling and input validation across all procedures.
  4. Better logging with LogSnag for improved observability.
  5. Consistent use of authorization checks using trpcAssert.

These changes should result in a more robust, maintainable, and efficient application review process. The code is now better structured and more consistent, which will make future modifications easier.

Great job on this refactoring effort!

src/components/Applicant.tsx (3)

Line range hint 1-354: Overall feedback: Functional implementation with room for improvement

The changes in this file successfully implement the new application data structure, aligning with the PR objectives. The code is functional but could benefit from refactoring to improve readability, maintainability, and scalability.

Key points:

  1. Consider implementing error and loading state handling for data fetching.
  2. Refactor repetitive data access patterns for improved readability.
  3. Implement a more scalable approach for rendering form fields, possibly using a declarative structure.
  4. Verify the consistency of the UpdateStatusDropdown prop change across the codebase.

These improvements will make the code more robust and easier to maintain as the application evolves. Despite these suggestions, the current implementation is a solid foundation for the new application schema.


126-132: 🛠️ Refactor suggestion

Apply consistent data access pattern

The same pattern of accessing application data continues here. Apply the refactoring suggestion from the previous comment to these inputs as well for consistency and improved readability.


345-345: Approve prop update and suggest verification

The change from 'id' to 'submitterId' in the UpdateStatusDropdown component prop is correct and aligns with the new data structure. This ensures that the correct identifier is passed for updating the application status.

To ensure this change is consistent throughout the codebase, run the following verification script:

This script will help identify any instances where the UpdateStatusDropdown component might still be using the old 'id' prop, ensuring consistency across the application.

✅ Verification successful

Prop update confirmed and verified

All instances of UpdateStatusDropdown now correctly use the submitterId prop, ensuring consistency and proper functionality across the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify that all instances of UpdateStatusDropdown use submitterId

echo "Checking UpdateStatusDropdown usage:"
rg --type typescript "UpdateStatusDropdown" -A 3 -B 3

echo "\nChecking for any remaining uses of 'id' prop:"
rg --type typescript "UpdateStatusDropdown.*id=" -A 1 -B 1

Length of output: 378


Script:

#!/bin/bash
# Verify that all instances of UpdateStatusDropdown use submitterId

echo "Checking UpdateStatusDropdown usage:"
rg --type tsx "UpdateStatusDropdown" -A 3 -B 3

echo "\nChecking for any remaining uses of 'id' prop:"
rg --type tsx "UpdateStatusDropdown.*id=" -A 1 -B 1

Length of output: 350


Script:

#!/bin/bash
# Verify that all instances of UpdateStatusDropdown use submitterId

echo "Checking UpdateStatusDropdown usage with submitterId:"
rg "UpdateStatusDropdown" --glob "*.tsx" -A 3 -B 3 | grep "submitterId"

echo "\nChecking for any remaining uses of 'id' prop in UpdateStatusDropdown:"
rg "UpdateStatusDropdown.*id=" --glob "*.tsx" -A 1 -B 1

Length of output: 669

src/server/router/application.ts (3)

1-8: LGTM: New imports and type definitions.

The new imports and type definitions are appropriate for the changes made in the file. The AnswerForRouter interface is well-defined and used later in the submit procedure.

Also applies to: 85-88


572-580: Clarify the intention of getPrevAutofill changes.

The getPrevAutofill procedure has been simplified to directly return user?.dh10application. However, given the other changes in this file that move away from the dh10application structure towards formSubmission, it's not clear if this is intentional.

Is this procedure meant to be updated to use the new formSubmission structure? If so, it might need to be refactored similar to other procedures in this file.

If the current implementation is correct, consider adding a comment explaining why this procedure still uses the old structure.


Line range hint 1-622: Overall improvements with some areas for attention.

The changes in this file represent a significant improvement in the application submission and retrieval processes. The new schema focusing on formSubmissions provides more flexibility and easier maintenance. Key improvements include:

  1. Better use of Prisma for database operations
  2. Improved error handling in most procedures
  3. More structured approach to storing and retrieving application data

However, there are a few areas that need attention:

  1. Consistency in using input parameters vs. session data in some procedures
  2. Potential need for refactoring in the submit procedure
  3. Clarification needed on the getPrevAutofill procedure

Addressing these points will further enhance the robustness and maintainability of this module.

src/pages/apply.tsx (4)

40-40: Improved modularity with DirectPrismaQuerier

The addition of DirectPrismaQuerier is a positive change that likely enhances the separation of concerns and modularity in database operations.


797-798: Improved database query structure

The use of DirectPrismaQuerier enhances the organization of database operations, promoting better separation of concerns.


Line range hint 1-813: Overall improvements to application form handling

The changes in this file significantly enhance the application form's functionality and structure. Key improvements include:

  1. Introduction of DirectPrismaQuerier for better database query organization.
  2. Updated application submission endpoint for potentially more flexible handling.
  3. Improved control over application availability with the killed (suggested rename to isApplicationClosed) prop.
  4. Added safeguard against uninitialized application forms.

These changes contribute to a more robust and maintainable application process. Consider implementing the suggested minor improvements in naming and error handling to further enhance the code quality.


181-181: Verify the updated application submission endpoint

The mutation has been changed from submitDh10 to submit, which might indicate a more generic approach to application submission. Please ensure that the new endpoint is correctly implemented on the server-side and handles the submission properly.

src/server/router/fileBuilder.ts (2)

43-57: ⚠️ Potential issue

Ensure correct updating of category and handle potential errors

In the updateCategory mutation, if the name and formStructureId are part of the composite unique key used in the where clause, updating them in the data field may cause issues, as you cannot modify primary key fields in an update operation.

Consider removing name and formStructureId from the data field if they are part of the composite key, or adjust the where clause and data accordingly.

 .mutation(async ({ ctx, input }) => {
   await ctx.prisma.questionCategory.update({
     where: {
       name_formStructureId: {
         name: input.name,
         formStructureId: input.formStructureId,
       },
     },
     data: {
-      formStructureId: input.formStructureId,
-      name: input.name,
       formPosition: input.formPosition,
     },
   });
 }),

Alternatively, if you need to update the name or formStructureId, you might need to implement a different logic, such as deleting and recreating the category with the new key values.

Please ensure that updating the composite key fields is handled correctly according to your Prisma schema.


85-99: ⚠️ Potential issue

Ensure correct updating of question and handle potential errors

In the updateQuestion mutation, similar to the updateCategory mutation, if categoryPosition and categoryId are part of the composite unique key in the where clause, updating them in the data field may lead to errors.

Consider removing categoryPosition and categoryId from the data field if they are part of the composite key.

 .mutation(async ({ ctx, input }) => {
   await ctx.prisma.question.update({
     where: {
       categoryPosition_categoryId: {
         categoryPosition: input.categoryPosition,
         categoryId: input.categoryId,
       },
     },
     data: {
       statement: input.statement,
-      categoryPosition: input.categoryPosition,
-      categoryId: input.categoryId,
     },
   });
 }),

Ensure that updates to composite key fields are handled appropriately according to your Prisma schema.

src/server/router/reviewers.ts Outdated Show resolved Hide resolved
src/server/router/reviewers.ts Outdated Show resolved Hide resolved
src/components/Applicant.tsx Outdated Show resolved Hide resolved
src/components/Applicant.tsx Outdated Show resolved Hide resolved
src/components/Applicant.tsx Outdated Show resolved Hide resolved
Comment on lines 800 to 804
// The formName can be uninitialized on the initial migration.
// After migration, formName should be immediately initialized.
const formName = await querier.getDeltaHacksApplicationFormName();
if (!formName) {
return { redirect: { destination: "welcome", permanent: false } };
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

Enhance error handling for uninitialized form

The check for formName is a good addition. However, consider logging this error and providing a more informative message to the user.

Apply this change:

 if (!formName) {
+  console.error('DeltaHacks application form name is not initialized');
-  return { redirect: { destination: "welcome", permanent: false } };
+  return { 
+    redirect: { 
+      destination: "/error?message=Application%20form%20not%20available", 
+      permanent: false 
+    } 
+  };
 }

This change assumes you have an error page that can display custom messages. If not, you may need to create one.

📝 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
// The formName can be uninitialized on the initial migration.
// After migration, formName should be immediately initialized.
const formName = await querier.getDeltaHacksApplicationFormName();
if (!formName) {
return { redirect: { destination: "welcome", permanent: false } };
// The formName can be uninitialized on the initial migration.
// After migration, formName should be immediately initialized.
const formName = await querier.getDeltaHacksApplicationFormName();
if (!formName) {
console.error('DeltaHacks application form name is not initialized');
return {
redirect: {
destination: "/error?message=Application%20form%20not%20available",
permanent: false
}
};
}

Comment on lines 11 to 15
.mutation(async ({ ctx, input }) => {
await ctx.prisma.formStructure.create({
data: { id: input.id },
});
}),
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

Return the created form for confirmation

Currently, the createForm mutation does not return any value to confirm the creation of the form. It's a good practice to return the created object or at least a confirmation message to the client.

You can modify the mutation to return the newly created formStructure:

 .mutation(async ({ ctx, input }) => {
-  await ctx.prisma.formStructure.create({
+  const newForm = await ctx.prisma.formStructure.create({
     data: { id: input.id },
   });
+  return newForm;
 }),
📝 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
.mutation(async ({ ctx, input }) => {
await ctx.prisma.formStructure.create({
data: { id: input.id },
});
}),
.mutation(async ({ ctx, input }) => {
const newForm = await ctx.prisma.formStructure.create({
data: { id: input.id },
});
return newForm;
}),

Comment on lines 67 to 75
.mutation(async ({ ctx, input }) => {
await ctx.prisma.question.create({
data: {
statement: input.statement,
categoryPosition: input.categoryPosition,
categoryId: input.categoryId,
},
});
}),
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

Return the created question for confirmation

The createQuestion mutation does not return the created question. Returning it can help the client confirm the creation and use the question data immediately.

Modify the mutation to return the newly created question:

 .mutation(async ({ ctx, input }) => {
-  await ctx.prisma.question.create({
+  const newQuestion = await ctx.prisma.question.create({
     data: {
       statement: input.statement,
       categoryPosition: input.categoryPosition,
       categoryId: input.categoryId,
     },
   });
+  return newQuestion;
 }),
📝 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
.mutation(async ({ ctx, input }) => {
await ctx.prisma.question.create({
data: {
statement: input.statement,
categoryPosition: input.categoryPosition,
categoryId: input.categoryId,
},
});
}),
.mutation(async ({ ctx, input }) => {
const newQuestion = await ctx.prisma.question.create({
data: {
statement: input.statement,
categoryPosition: input.categoryPosition,
categoryId: input.categoryId,
},
});
return newQuestion;
}),

Comment on lines 25 to 33
.mutation(async ({ ctx, input }) => {
await ctx.prisma.questionCategory.create({
data: {
formStructureId: input.formStructureId,
name: input.name,
formPosition: input.formPosition,
},
});
}),
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

Return the created category for confirmation

The createCategory mutation does not return the created category, which might be useful for the client to confirm the operation and utilize the new category immediately.

Modify the mutation to return the newly created questionCategory:

 .mutation(async ({ ctx, input }) => {
-  await ctx.prisma.questionCategory.create({
+  const newCategory = await ctx.prisma.questionCategory.create({
     data: {
       formStructureId: input.formStructureId,
       name: input.name,
       formPosition: input.formPosition,
     },
   });
+  return newCategory;
 }),
📝 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
.mutation(async ({ ctx, input }) => {
await ctx.prisma.questionCategory.create({
data: {
formStructureId: input.formStructureId,
name: input.name,
formPosition: input.formPosition,
},
});
}),
.mutation(async ({ ctx, input }) => {
const newCategory = await ctx.prisma.questionCategory.create({
data: {
formStructureId: input.formStructureId,
name: input.name,
formPosition: input.formPosition,
},
});
return newCategory;
}),

Comment on lines 5 to 100
createForm: protectedProcedure
.input(
z.object({
id: z.string(),
})
)
.mutation(async ({ ctx, input }) => {
await ctx.prisma.formStructure.create({
data: { id: input.id },
});
}),

createCategory: protectedProcedure
.input(
z.object({
name: z.string(),
formPosition: z.number(),
formStructureId: z.string(),
})
)
.mutation(async ({ ctx, input }) => {
await ctx.prisma.questionCategory.create({
data: {
formStructureId: input.formStructureId,
name: input.name,
formPosition: input.formPosition,
},
});
}),

updateCategory: protectedProcedure
.input(
z.object({
name: z.string(),
formPosition: z.number(),
formStructureId: z.string(),
})
)
.mutation(async ({ ctx, input }) => {
await ctx.prisma.questionCategory.update({
where: {
name_formStructureId: {
name: input.name,
formStructureId: input.formStructureId,
},
},
data: {
formStructureId: input.formStructureId,
name: input.name,
formPosition: input.formPosition,
},
});
}),

createQuestion: protectedProcedure
.input(
z.object({
statement: z.string(),
categoryPosition: z.number(),
categoryId: z.string(),
})
)
.mutation(async ({ ctx, input }) => {
await ctx.prisma.question.create({
data: {
statement: input.statement,
categoryPosition: input.categoryPosition,
categoryId: input.categoryId,
},
});
}),

updateQuestion: protectedProcedure
.input(
z.object({
statement: z.string(),
categoryPosition: z.number(),
categoryId: z.string(),
})
)
.mutation(async ({ ctx, input }) => {
await ctx.prisma.question.update({
where: {
categoryPosition_categoryId: {
categoryPosition: input.categoryPosition,
categoryId: input.categoryId,
},
},
data: {
statement: input.statement,
categoryPosition: input.categoryPosition,
categoryId: input.categoryId,
},
});
}),
});
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 error handling for database operations

All the mutations currently await Prisma operations without handling potential errors. It's important to implement error handling to catch and handle exceptions gracefully, providing meaningful feedback to the client.

Consider wrapping the database operations in try-catch blocks and returning appropriate error messages:

 .mutation(async ({ ctx, input }) => {
+  try {
     // Database operation
+  } catch (error) {
+    // Handle error
+    throw new Error('An error occurred while processing your request.');
+  }
 }),

Alternatively, you can use TRPC's built-in error handling mechanisms.

Committable suggestion was skipped due to low confidence.

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

🧹 Outside diff range and nitpick comments (21)
src/server/db/configQueries.ts (2)

4-21: LGTM with a minor suggestion for improved error handling.

The function getDeltaHacksApplicationFormName is well-structured and follows good practices. It correctly uses async/await, has appropriate error handling through assertions, and is focused on a single responsibility.

Consider adding a try-catch block to handle potential database errors gracefully:

 export const getDeltaHacksApplicationFormName = async (
   prisma: PrismaClient
 ) => {
+  try {
     const deltaHacksApplicationFormConfig = await prisma.config.findUnique({
       where: {
         id: "DeltaHacksApplication",
       },
     });
     if (!deltaHacksApplicationFormConfig) {
       return null;
     }

     assert(
       deltaHacksApplicationFormConfig.value.length > 0,
       "Invalid DeltaHacksApplicationConfig value"
     );
     return deltaHacksApplicationFormConfig.value;
+  } catch (error) {
+    console.error("Error fetching DeltaHacksApplication config:", error);
+    return null;
+  }
 };

This change would ensure that any unexpected database errors are logged and handled gracefully.


1-39: Overall good implementation with room for improvement in error handling and consistency.

This file provides essential functionality for managing application configurations. The functions are well-structured and use Prisma effectively. To further enhance the code:

  1. Implement consistent error handling across both functions.
  2. Consider creating a reusable utility function for config retrieval to reduce code duplication.
  3. Add JSDoc comments to improve function documentation.

Example of a reusable utility function:

async function getConfig(prisma: PrismaClient, configName: string): Promise<string | null> {
  try {
    const config = await prisma.config.findFirst({
      where: { name: configName },
      select: { value: true },
    });
    return config?.value ?? null;
  } catch (error) {
    console.error(`Error fetching ${configName} config:`, error);
    return null;
  }
}

This approach would simplify both functions and ensure consistent error handling.

src/pages/welcome.tsx (2)

88-91: LGTM: Robust form name retrieval and error handling.

The addition of getDeltaHacksApplicationFormName and the subsequent null check improves the robustness of the application. Returning a 404 response when the form doesn't exist is appropriate.

Consider adding a log statement when the form name is not found to aid in debugging:

if (!formName) {
  console.error("DeltaHacks application form name not found");
  return { notFound: true };
}

93-97: LGTM: Updated application retrieval logic.

The new query using formSubmission.findFirst is more specific and aligns well with the new schema. It correctly uses the retrieved form name and the user's ID.

Consider adding a select clause to only fetch the necessary fields, which can improve query performance:

const application = await prisma.formSubmission.findFirst({
  where: {
    formStructureId: formName,
    submitterId: session?.user.id,
  },
  select: {
    id: true,
    // Add other necessary fields
  }
});
prisma/schema.prisma (6)

62-63: New relationships added to User model

The addition of applicationSubmissions and answers fields establishes the necessary relationships for the new form submission structure. This change aligns well with the PR objectives.

Consider adding inline comments or documentation to clarify the nature of these relationships, especially since they are not typical one-to-many or one-to-one relationships as mentioned in the past review comments.


112-113: Plan for DH10Application model deprecation

The comment indicates that the DH10Application model will be deprecated after data migration to FormSubmissions. To ensure a smooth transition:

  1. Create a detailed migration plan with timelines.
  2. Implement tracking mechanisms to monitor the progress of data migration.
  3. Set up alerts or reminders to revisit this model's deprecation once migration is complete.

Would you like assistance in creating a migration plan or setting up tracking mechanisms?


169-180: Approve FormSubmission model with index suggestion

The FormSubmission model effectively captures the core of the new form submission structure, aligning well with the PR objectives. The composite primary key ensures uniqueness of submissions per user and form structure.

Consider adding an index on the status field if you anticipate frequent queries filtering or sorting by status:

@@index([status])

This can improve query performance, especially as the number of submissions grows.


189-201: Approve QuestionCategory model with documentation suggestion

The QuestionCategory model is well-structured with appropriate unique constraints ensuring proper organization within each form structure. This aligns well with the dynamic form creation objective.

Consider adding a comment to explain the purpose and behavior of the formPosition field:

model QuestionCategory {
  // ... other fields ...
  formPosition Int    // Determines the order of categories within the form
  // ... rest of the model ...
}

This will help other developers understand the significance of this field in organizing the form structure.


203-226: Approve Question and Answer models with suggestion

The Question and Answer models effectively complete the form submission structure, allowing for flexible question types and organized responses. The constraints and relationships are well-defined.

Consider adding a reference to the FormSubmission in the Answer model to directly link answers to specific submissions. This could be useful for querying all answers for a particular submission:

model Answer {
  statement String?

  addressedQuestion   Question @relation(fields: [addressedQuestionId], references: [id])
  addressedQuestionId String

  submitter   User   @relation(fields: [submitterId], references: [id])
  submitterId String

+ formSubmission   FormSubmission @relation(fields: [formStructureId, submitterId], references: [formStructureId, submitterId])
+ formStructureId  String

  @@id([addressedQuestionId, submitterId])
+ @@index([formStructureId, submitterId])
}

This change would allow for more efficient querying of all answers related to a specific form submission.


Line range hint 1-226: Overall assessment of schema changes

The changes introduced in this schema represent a significant shift towards a more flexible and dynamic form management system, aligning well with the PR objectives. Key points:

  1. The new models (FormSubmission, FormStructure, QuestionCategory, Question, and Answer) provide a robust structure for dynamic form creation and submission.
  2. Deprecation of old fields in the User model and the Review model indicates a clear move towards the new system.
  3. The DH10Application model serves as a transitional step, with plans for future deprecation.

These changes will greatly enhance the application's ability to handle diverse form types with minimal development effort. However, ensure that:

  1. A comprehensive migration strategy is in place for transitioning data from old to new structures.
  2. All parts of the application using deprecated fields are updated to use the new models.
  3. Adequate documentation is provided, especially for complex relationships and field purposes.
  4. Performance considerations (like suggested indexes) are addressed as the system scales.

Great work on redesigning the schema to support more flexible form management!

src/server/router/reviewers.ts (3)

32-71: LGTM: Improved getApplications procedure with minor suggestion

The updates to the getApplications procedure are well-implemented:

  1. The use of trpcAssert for authorization checks enhances error handling.
  2. The query now correctly uses formStructureId, aligning with the new schema.
  3. Error handling for parsing failures adds robustness.

However, consider simplifying the parsing logic:

return ApplicationForReview.array().parse(application);

If ApplicationForReview.array().parse() is type-safe, the try-catch block might be unnecessary. This could make the code more concise without sacrificing safety.


73-153: LGTM: Comprehensive improvements to getApplication procedure

The updates to the getApplication procedure are well-implemented:

  1. Using submitterId aligns with the new schema.
  2. The complex query structure efficiently fetches all necessary data in a single database call.
  3. Sorting logic ensures consistent ordering of categories and questions.

Consider extracting the sorting logic into a separate function for better readability:

function sortQuestionCategories(categories: any[]): void {
  categories.sort((a, b) => a.formPosition - b.formPosition);
  categories.forEach(category => {
    category.questions.sort((a, b) => a.categoryPosition - b.categoryPosition);
  });
}

// Usage
sortQuestionCategories(applicationSubmission.formStructure.questionCategories);

This refactoring would make the main function more concise and the sorting logic more reusable.


155-207: LGTM: Well-implemented updateApplicationShallow procedure

The updates to the updateApplicationShallow procedure (formerly updateStatus) are well-implemented:

  1. The new name better reflects the function's purpose.
  2. Using submitterId and formStructureId for unique identification aligns with the new schema.
  3. LogSnag tracking provides better visibility into status changes.

Consider refactoring the status icon logic for better readability:

const statusIcons = {
  [Status.ACCEPTED]: "✅",
  [Status.REJECTED]: "❌",
  [Status.WAITLISTED]: "🕰️",
  [Status.RSVP]: "🎟️",
};

const getStatusIcon = (status: Status) => statusIcons[status] || "🤔";

// Usage
icon: getStatusIcon(input.application.status),

This approach would make the code more maintainable and easier to extend in the future.

src/server/router/application.ts (6)

109-122: LGTM with a minor suggestion: Improved authorization and form name handling.

The changes enhance error handling with trpcAssert and add flexibility by dynamically fetching the form name. The early return for missing form name is a good practice.

Consider adding a more specific error message when the form name is not found:

 if (!formName) {
+  throw new TRPCError({
+    code: "NOT_FOUND",
+    message: "DeltaHacks application form name not configured",
+  });
   return [];
 }

This would provide more context for debugging if the issue occurs.


163-191: LGTM with a suggestion: RSVP procedure updated to use formSubmission.

The changes align well with the new schema focusing on formSubmissions. The use of trpcAssert improves error handling and robustness. However, there's a potential issue with the assertion:

trpcAssert(form, "NOT_FOUND");
trpcAssert(form.status === Status.ACCEPTED, "UNAUTHORIZED");

This assertion will throw an "UNAUTHORIZED" error if the status is not ACCEPTED, but it will throw a "NOT_FOUND" error if the form is null. Consider separating these checks for clarity:

trpcAssert(form, "NOT_FOUND", "No application found for the User to RSVP");
trpcAssert(form.status === Status.ACCEPTED, "UNAUTHORIZED", "User's application is not in ACCEPTED status");

This change would provide more specific error messages based on the actual condition that failed.


195-220: LGTM: New getApplicationShallow procedure added.

The new getApplicationShallow procedure is a good addition that aligns with the new schema changes. It includes proper null checks and allows fetching applications for different users, which can be useful for admin functions.

Consider adding a permission check to ensure only authorized users (e.g., admins) can fetch applications for other users:

if (input.submitterId !== ctx.session.user.id) {
  trpcAssert(
    ctx.session.user.role.includes(Role.ADMIN),
    "UNAUTHORIZED",
    "Only admins can fetch applications for other users"
  );
}

This would prevent potential unauthorized access to other users' applications.


222-462: LGTM with suggestions: submit procedure updated for new schema.

The submit procedure has been significantly updated to work with the new schema, using applicationSchema for input validation and storing data in a more granular format. These changes improve data integrity and flexibility.

However, there are a few points to consider:

  1. The procedure is quite long and complex. Consider refactoring it into smaller, more manageable functions for better readability and maintainability.

  2. There's a potential issue with the date handling for gradDate:

if (!Number.isNaN(possible.getTime())) {
  gradDate = possible;
}

Consider adding a null check before calling getTime():

if (possible && !Number.isNaN(possible.getTime())) {
  gradDate = possible;
}
  1. Consider using a more descriptive name for the answers array, such as formattedAnswers, to better reflect its purpose.

  2. The use of Promise.all for inserting answers is good for performance, but consider adding error handling:

try {
  await Promise.all(
    answers.map(async (answerPartial) => {
      // ... existing code ...
    })
  );
} catch (error) {
  console.error("Error inserting answers:", error);
  throw new TRPCError({
    code: "INTERNAL_SERVER_ERROR",
    message: "Failed to save application answers",
  });
}

This would provide better error handling and logging for potential issues during answer insertion.


589-626: LGTM with a suggestion: deleteApplication procedure updated for new schema.

The deleteApplication procedure has been updated to work with the new schema, deleting a formSubmission instead of a dH10Application. The addition of error handling and logging improves the robustness of the procedure.

Consider adding a check to ensure the user has the necessary permissions to delete the application:

trpcAssert(
  ctx.session.user.id === input.userId || ctx.session.user.role.includes(Role.ADMIN),
  "UNAUTHORIZED",
  "You don't have permission to delete this application"
);

This would prevent unauthorized deletion of applications.


Line range hint 1-626: Overall LGTM: Significant improvements to application router.

The changes in this file align well with the new database schema and introduce several improvements:

  1. Enhanced error handling with trpcAssert throughout the file.
  2. Improved input validation using applicationSchema.
  3. New procedures like getApplicationShallow that add functionality.
  4. Updated procedures that work with the new formSubmission-based schema.

While the changes are generally positive, consider the following for future improvements:

  1. Refactor long procedures (like submit) into smaller, more manageable functions.
  2. Add more specific error messages to improve debugging and user experience.
  3. Implement consistent permission checks across procedures that handle sensitive operations.

These changes significantly enhance the robustness and flexibility of the application router.

src/pages/apply.tsx (2)

797-808: LGTM: Improved configuration handling and error checking

The changes in the getServerSideProps function are well-implemented:

  1. Handling the case where the form name might be uninitialized during initial migration is a good edge case consideration.
  2. Using Config functions improves modularity and separates concerns.
  3. Passing the 'killed' status to the component is consistent with the PR objectives.

Consider adding more detailed error logging when the form name is not initialized:

 const formName = await Config.getDeltaHacksApplicationFormName(prisma);
 if (!formName) {
+  console.error('DeltaHacks application form name is not initialized');
   return { redirect: { destination: "welcome", permanent: false } };
 }

This will help with debugging in case of initialization issues.


Line range hint 1-811: LGTM: Overall improvements in configuration handling and modularity

The changes in this file are well-implemented and consistent with the PR objectives. The improvements in configuration handling and the generalization of the submission process enhance the modularity and maintainability of the code.

To further improve code organization, consider extracting the ApplyForm component into a separate file. This would reduce the size of the current file and improve readability. You can create a new file src/components/ApplyForm.tsx and import it in this file:

+import ApplyForm from '../components/ApplyForm';
 
 const Apply: NextPage<
   InferGetServerSidePropsType<typeof getServerSideProps>
 > = ({ email, killed }) => {
   // ... existing code ...
 };

This change would make the file structure more modular and easier to maintain.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between b8b3440 and 90adc6d.

📒 Files selected for processing (12)
  • prisma/migrations/20241006184524_dynamic_form_builder/migration.sql (1 hunks)
  • prisma/schema.prisma (3 hunks)
  • prisma/seed/data.ts (1 hunks)
  • prisma/seed/seed.ts (1 hunks)
  • src/pages/apply.tsx (4 hunks)
  • src/pages/checkin.tsx (5 hunks)
  • src/pages/dashboard.tsx (5 hunks)
  • src/pages/welcome.tsx (2 hunks)
  • src/server/db/configQueries.ts (1 hunks)
  • src/server/router/application.ts (5 hunks)
  • src/server/router/formBuilder.ts (1 hunks)
  • src/server/router/reviewers.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • prisma/seed/seed.ts
🧰 Additional context used
📓 Learnings (3)
prisma/schema.prisma (1)
Learnt from: fvcci
PR: deltahacks/portal#191
File: prisma/schema.prisma:62-63
Timestamp: 2024-10-02T05:40:37.273Z
Learning: In our codebase, the relationships `applicationSubmissions` and `answers` in the `User` model are not one-to-many or one-to-one.
prisma/seed/data.ts (1)
Learnt from: fvcci
PR: deltahacks/portal#191
File: prisma/seed.ts:150-201
Timestamp: 2024-10-02T17:06:51.970Z
Learning: In this codebase, the `FormStructureQuestion[]` TypeScript type is used to ensure all question IDs and category IDs are valid at compile time, making additional runtime validation unnecessary.
src/pages/dashboard.tsx (1)
Learnt from: fvcci
PR: deltahacks/portal#191
File: src/pages/dashboard.tsx:476-477
Timestamp: 2024-10-02T05:48:11.375Z
Learning: In `getServerSideProps`, if `session.user` is already checked for null before using `session.user.id`, there's no need to add an additional null check for `session.user.id`.
🔇 Additional comments (32)
prisma/migrations/20241006184524_dynamic_form_builder/migration.sql (1)

1-6: ⚠️ Potential issue

Caution: Adding a required column without a default value

The warning message indicates that adding a required column (name) without a default value is only possible if the FormStructure table is empty. This could lead to migration failures if the table already contains data.

To verify if the FormStructure table is empty, you can run the following query before applying the migration:

Replace your_database_name with the actual database name used in your project.

src/server/db/configQueries.ts (1)

1-2: LGTM: Imports are appropriate and concise.

The import statements are relevant to the file's functionality, importing necessary dependencies without any superfluous imports.

prisma/seed/data.ts (2)

1-1: LGTM: Import statement is correct.

The import of FormStructure from the relative path is properly structured and follows best practices.


3-71: LGTM: Well-structured form definition with compile-time validation.

The FORM_STRUCTURES constant is well-organized and follows a logical hierarchy for the DeltaHacks X Application Form. The use of the FormStructure type ensures that all question IDs and category IDs are valid at compile-time, eliminating the need for additional runtime validation.

src/pages/welcome.tsx (3)

8-8: LGTM: New import for config queries.

The addition of this import statement is consistent with the changes in the getServerSideProps function, where Config.getDeltaHacksApplicationFormName is used.


100-104: LGTM: Simplified redirection logic.

The conditional logic for redirecting to the dashboard has been appropriately simplified. It now correctly checks for the existence of an application and redirects accordingly. This change is consistent with the new schema and the updates made earlier in the function.


Line range hint 1-104: Overall assessment: Well-implemented changes aligned with PR objectives.

The modifications to src/pages/welcome.tsx are consistent with the PR objectives of creating a new schema for the application database. The changes improve code robustness, maintainability, and align well with the new database structure. The simplified logic for checking and redirecting based on application status is particularly noteworthy.

To further improve the implementation:

  1. Consider adding error logging for better debugging.
  2. Optimize the database query by selecting only necessary fields.

These suggestions are minor, and the current implementation is already solid.

src/server/router/reviewers.ts (2)

5-29: LGTM: Improved type definitions and imports

The changes to the import statements and type definitions are well-implemented:

  1. The addition of trpcAssert improves error handling.
  2. The ApplicationForReview type now includes a nested submitter object, which enhances type safety.
  3. The new Application interface provides a clear structure for application data.

These changes contribute to better code organization and type checking.


Line range hint 208-247: Verify alignment of submit procedure with new schema

The submit procedure remains largely unchanged. While this may be intentional, it's worth verifying that it still aligns with the new application schema and structure introduced in other parts of the file.

Please review this procedure to ensure:

  1. It uses the correct identifiers (e.g., submitterId instead of hackerId if applicable).
  2. The data structure it creates or modifies is consistent with the new schema.
  3. Any authorization checks are consistent with those in other procedures.
src/server/router/application.ts (3)

1-8: LGTM: Import statements updated appropriately.

The new imports for Config and trpcAssert align well with the changes in the file, suggesting improved error handling and configuration management.


85-89: LGTM: New interface AnswerForRouter added.

The AnswerForRouter interface provides a clear structure for handling answers in the router. This addition enhances code readability and type safety.


580-588: LGTM: getPrevAutofill procedure simplified.

The getPrevAutofill procedure has been updated to directly return the user's dh10application, which aligns with the new schema. The use of select in the Prisma query optimizes the database call by only fetching the required field.

src/pages/apply.tsx (1)

181-181: LGTM: Updated mutation hook to use generalized endpoint

The change from trpc.application.submitDh10.useMutation to trpc.application.submit.useMutation is a good improvement. It generalizes the submission endpoint, which aligns with the PR objective of creating a new schema for the application database.

To ensure this change is consistent across the codebase, run the following script:

This script will help identify any remaining instances of the old mutation that might need to be updated.

✅ Verification successful

Mutation hook successfully updated across the codebase

No remaining instances of trpc.application.submitDh10.useMutation were found. The mutation hook update has been successfully verified.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify that all occurrences of the old mutation have been updated
rg "trpc\.application\.submitDh10\.useMutation" --type typescript

Length of output: 102


Script:

#!/bin/bash
# Verify that all occurrences of the old mutation have been updated
rg "trpc\.application\.submitDh10\.useMutation" --type ts --type tsx

Length of output: 98


Script:

#!/bin/bash
# Verify that all occurrences of the old mutation have been updated
rg "trpc\.application\.submitDh10\.useMutation" -g '*.ts' -g '*.tsx'

Length of output: 70

src/server/router/formBuilder.ts (2)

6-16: FormStructure schema is well-defined

The Zod schema for FormStructure accurately defines the structure of the form, ensuring that the input data will be properly validated before processing.


81-87: ⚠️ Potential issue

Correct the exported router name from 'fileBuilder' to 'formBuilder'

The exported router is currently named fileBuilder, which seems inconsistent with the file name formBuilder.ts and the functionality provided. Consider renaming it to formBuilder for consistency and clarity.

Run the following script to verify any usages of fileBuilder in the codebase:

✅ Verification successful

Renaming fileBuilder to formBuilder is Safe and Recommended

The exported router fileBuilder is only used within src/server/router/formBuilder.ts. Renaming it to formBuilder will enhance consistency and clarity without affecting other parts of the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for occurrences of 'fileBuilder' in TypeScript files.

# Test: Find all references to 'fileBuilder'
rg --type ts 'fileBuilder'

Length of output: 95

src/pages/checkin.tsx (9)

1-5: Imports are correctly updated

The necessary types are properly imported from "next", including InferGetServerSidePropsType, which is used for typing server-side props.


16-16: Importing configuration queries

The import statement correctly brings in Config from "../server/db/configQueries", which is used later to fetch configuration data.


185-187: Component signature updated to accept server-side props

The Checkin component now properly accepts status as a prop, utilizing InferGetServerSidePropsType for type safety and clarity.


Line range hint 188-194: Verify handling of Status.REJECTED in stateMap

Currently, Status.REJECTED is mapped to the <PreCheckedIn /> component. Please verify if this is the intended behavior. Rejected users might require a different component or message indicating their application status.

Would you like assistance in creating a component to handle the rejected status?


207-207: Rendering based on application status

The code correctly uses stateMap to render the appropriate component based on the user's application status.


239-243: Fetching form name and handling absence appropriately

The code accurately retrieves the application form name using Config.getDeltaHacksApplicationFormName(prisma) and handles the case where it is not found by returning { notFound: true }.


244-250: Fetching the user's application submission

The query to prisma.formSubmission.findFirst correctly searches for the user's application based on formStructureId and submitterId.


251-253: Handling missing application gracefully

Appropriate handling by returning { notFound: true } when the user's application does not exist.


255-255: Passing application status as a prop

The application status is correctly passed to the component via props, ensuring that the Checkin component receives the necessary data.

src/pages/dashboard.tsx (8)

18-19: Import Config and assert modules

The addition of Config and assert imports enhances functionality and maintains code clarity.


60-64: Proper error handling in mutation onSuccess callback

Wrapping the cache invalidation with a try-catch block ensures that any errors are caught and logged, preventing unhandled promise rejections.


228-233: Async handling and user assertion in onClick handler

The onClick handler is correctly marked as async, and using assert to ensure that session.user.id exists before proceeding is a good practice.


430-435: Fetching application data with appropriate submitter ID

Using session?.user?.id ?? null ensures that the query receives a valid submitterId, maintaining robustness in case the session is undefined.


436-447: Efficient rendering based on application status

The Content component utilizes a state map to render components based on the application status, enhancing code readability and maintainability.


476-479: Retrieving form name and handling 'notFound' scenario

Fetching formName using Config.getDeltaHacksApplicationFormName and returning { notFound: true } when absent ensures proper error handling and user redirection.


481-486: Querying user's application submission accurately

The Prisma query retrieves the user's application submission based on formStructureId and submitterId, ensuring the correct data is fetched.


492-494: Passing 'killed' prop when application exists

Returning { props: { killed } } when an application is found ensures that the necessary data is available to the Dashboard component.

Comment on lines 7 to 8
-- AlterTable
ALTER TABLE "FormStructure" ADD COLUMN "name" STRING NOT 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

Consider adding a DEFAULT value or making the column nullable

To ensure backward compatibility and prevent potential migration failures, consider one of the following options:

  1. Add a DEFAULT value to the new column:
ALTER TABLE "FormStructure" ADD COLUMN "name" STRING NOT NULL DEFAULT 'Untitled Form';
  1. Make the column nullable:
ALTER TABLE "FormStructure" ADD COLUMN "name" STRING;

Choose the option that best fits your application's requirements and data integrity needs.

Comment on lines 23 to 39
export const hasKilledApplications = async (prisma: PrismaClient) => {
const killedConfig = await prisma.config.findFirst({
where: { name: "killApplications" },
select: { value: true },
});

// Default behaviour if config isn't present
if (!killedConfig) {
return true;
}
try {
return JSON.parse(killedConfig.value) !== false;
} catch (error) {
console.error("Invalid killApplications configuration:", error);
throw error;
}
};
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

⚠️ Potential issue

Consider improving function naming, default behavior, and error handling.

The hasKilledApplications function is well-structured but has room for improvement:

  1. Function naming: The current name implies a boolean return, but the function actually checks a configuration. Consider renaming to getApplicationKillStatus or similar.

  2. Default behavior: Returning true by default might be counterintuitive. Consider making the default behavior explicit in the function name or documentation.

  3. Error handling: The current implementation throws an error on invalid JSON, which might disrupt the application flow. Consider handling this gracefully.

Here's a suggested refactor:

export const getApplicationKillStatus = async (prisma: PrismaClient): Promise<boolean> => {
  try {
    const killedConfig = await prisma.config.findFirst({
      where: { name: "killApplications" },
      select: { value: true },
    });

    if (!killedConfig) {
      console.warn("killApplications config not found, defaulting to false");
      return false;
    }

    const parsedValue = JSON.parse(killedConfig.value);
    return typeof parsedValue === 'boolean' ? parsedValue : false;
  } catch (error) {
    console.error("Error processing killApplications configuration:", error);
    return false; // Default to false in case of any error
  }
};

This refactor improves naming, handles errors gracefully, and provides a safer default behavior.

Comment on lines +73 to +77
export const DELTAHACKS_APPLICATION_FORM_CONFIG = {
id: "CurrentDeltaHacksApplication",
name: "deltaHacksApplicationFormId",
value: "fewadghtregfdasf", // unique id of form
};
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

Consider generating the form ID dynamically.

The DELTAHACKS_APPLICATION_FORM_CONFIG constant is well-structured, but the value property contains a hardcoded unique identifier. This approach might lead to maintainability issues in the future.

Consider generating this ID dynamically or storing it in a configuration file that can be easily updated without modifying the source code. This would improve flexibility and make it easier to manage different environments (e.g., development, staging, production).

Example of a more flexible approach:

import { v4 as uuidv4 } from 'uuid';

export const DELTAHACKS_APPLICATION_FORM_CONFIG = {
  id: "CurrentDeltaHacksApplication",
  name: "deltaHacksApplicationFormId",
  value: process.env.DELTAHACKS_FORM_ID || uuidv4(), // Use environment variable or generate UUID
};

This approach allows you to set the ID via an environment variable or generate a new one if not provided.

}

/// @deprecated Use `User.status` instead
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

Update deprecation comment for Review model

The current deprecation comment for the Review model suggests using User.status, which is itself deprecated. This creates an inconsistency in the deprecation chain.

Update the deprecation comment to reflect the new structure:

-/// @deprecated Use `User.status` instead
+/// @deprecated Use `FormSubmission.status` instead
📝 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
/// @deprecated Use `User.status` instead
/// @deprecated Use `FormSubmission.status` instead

Comment on lines 182 to 187
model FormStructure {
id String @id @default(cuid())
name String
submissions FormSubmission[]
questionCategories QuestionCategory[]
}
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

Consider adding a unique constraint to FormStructure name

The FormStructure model effectively represents the structure of forms. To prevent duplicate form names and ensure easier querying:

Consider adding a unique constraint to the name field:

model FormStructure {
  id                 String             @id @default(cuid())
- name               String
+ name               String             @unique
  submissions        FormSubmission[]
  questionCategories QuestionCategory[]
}

This will ensure that each form structure has a unique name across the system.

📝 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
model FormStructure {
id String @id @default(cuid())
name String
submissions FormSubmission[]
questionCategories QuestionCategory[]
}
model FormStructure {
id String @id @default(cuid())
name String @unique
submissions FormSubmission[]
questionCategories QuestionCategory[]
}

Comment on lines +49 to +53
typeform_response_id String? /// @deprecated Use FormSubmission instead
role Role[] @default([HACKER])
hacker Review[] @relation("hacker")
reviewer Review[] @relation("reviewer")
status Status @default(IN_REVIEW)
hacker Review[] @relation("hacker") /// @deprecated Use `FormSubmission.status` instead
reviewer Review[] @relation("reviewer") /// @deprecated Use `FormSubmission.status` instead
status Status @default(IN_REVIEW) /// @deprecated Use `FormSubmission.status` instead
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Deprecated fields are still referenced in the codebase

The following files still reference deprecated fields:

  • src/data/applicationSelectData.ts
  • src/components/UpdateStatusDropdown.tsx
  • src/server/router/reviewers.ts
  • src/server/router/sponsors.ts
  • src/server/router/index.ts
  • src/schemas/application.ts
  • src/pages/checkin.tsx
  • src/pages/dashboard.tsx
  • src/pages/apply.tsx
  • src/pages/scanner.tsx
  • src/components/Applicant.tsx
  • src/pages/grade.tsx
  • src/components/ApplicationsTable.tsx

Please update all references to these deprecated fields to use the new FormSubmission model.

🔗 Analysis chain

Ensure proper migration strategy for deprecated fields

The deprecation of typeform_response_id, hacker, reviewer, and status fields in favor of the new FormSubmission model is a significant change. While this aligns with the PR objectives, it's crucial to:

  1. Implement a clear migration strategy for existing data.
  2. Update all references to these deprecated fields throughout the codebase.
  3. Consider adding a migration script to move data from the old fields to the new FormSubmission model.

To ensure all references are updated, run the following script:


Adjusting the search command to include .ts and .tsx files

The previous script encountered an error due to the unrecognized file type tsx. To ensure the search includes both TypeScript and TSX files, please run the following command:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Find references to deprecated fields in the codebase
rg -t ts -t tsx 'typeform_response_id|hacker|reviewer|User\.status' src/

Length of output: 102


Script:

#!/bin/bash
# Find references to deprecated fields in the codebase
rg 'typeform_response_id|hacker|reviewer|User\.status' src/ -g "*.ts" -g "*.tsx"

Length of output: 3690

@@ -37,6 +37,7 @@ import {
workshopType,
} from "../data/applicationSelectData";
import { useEffect } from "react";
import * as Config from "../server/db/configQueries";
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

Consider using specific imports instead of importing everything

While moving configuration queries to a separate module is a good practice, using import * can potentially lead to naming conflicts and make it harder to track which specific functions are being used. Consider importing only the necessary functions explicitly.

Replace the current import with specific imports:

-import * as Config from "../server/db/configQueries";
+import { getDeltaHacksApplicationFormName, hasKilledApplications } from "../server/db/configQueries";

This change will make it clearer which functions are being used from the Config module and prevent potential naming conflicts.

Committable suggestion was skipped due to low confidence.

Comment on lines 45 to 48
assert(
categories.length >= formStructure.categories.length,
"All categories should've been added before"
);
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

Consider asserting exact match in category count

Currently, the assertion checks if the number of categories retrieved from the database is greater than or equal to the number of categories in the form structure. To ensure data integrity, consider asserting that the counts are exactly equal. This confirms that all intended categories were added and no unexpected extra categories exist.

Apply this diff to adjust the assertion:

-  assert(
-    categories.length >= formStructure.categories.length,
-    "All categories should've been added before"
-  );
+  assert(
+    categories.length === formStructure.categories.length,
+    "All categories should've been added before"
+  );
📝 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
assert(
categories.length >= formStructure.categories.length,
"All categories should've been added before"
);
assert(
categories.length === formStructure.categories.length,
"All categories should've been added before"
);

Comment on lines 54 to 78
await Promise.all(
formStructure.categories.map(async ({ name: categoryName, questions }) => {
const categoryId = categoryNameToIdMap.get(categoryName);
assert(categoryId);

try {
await prisma.question.createMany({
data: questions.map((question, i) => {
return {
statement: question,
categoryPosition: i,
categoryId,
};
}),
skipDuplicates: true,
});
} catch (e) {
console.error(
`Error occured while adding the following: categoryName: ${categoryName}, categoryId: ${categoryId}, questions:`,
questions
);
throw e;
}
})
);
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

Consider wrapping database operations in a transaction

To ensure atomicity and maintain data integrity, it's advisable to wrap the database operations within a transaction. If an error occurs during any of the operations (creating the form, categories, or questions), a transaction will roll back all changes, preventing partial data from being saved to the database.

Apply the following refactor:

 export const createForm = async (
   prisma: PrismaClient,
   formStructure: FormStructure
 ) => {
+  await prisma.$transaction(async (prisma) => {
     const form = await prisma.formStructure.create({
       data: { name: formStructure.name },
     });

     await prisma.questionCategory.createMany({
       data: formStructure.categories.map(({ name }, i) => ({
         formStructureId: form.id,
         name,
         formPosition: i,
       })),
       skipDuplicates: true,
     });

     const categories = await prisma.questionCategory.findMany({
       where: {
         formStructureId: form.id,
       },
       select: {
         id: true,
         name: true,
       },
     });

     assert(
-      categories.length === formStructure.categories.length,
+      categories.length === formStructure.categories.length,
       "All categories should've been added before"
     );

     const categoryNameToIdMap = new Map(
       categories.map(({ id, name }) => [name, id])
     );

     await Promise.all(
       formStructure.categories.map(async ({ name: categoryName, questions }) => {
         const categoryId = categoryNameToIdMap.get(categoryName);
         assert(categoryId);

         try {
           await prisma.question.createMany({
             data: questions.map((question, i) => {
               return {
                 statement: question,
                 categoryPosition: i,
                 categoryId,
               };
             }),
             skipDuplicates: true,
           });
         } catch (e) {
           console.error(
             `Error occured while adding the following: categoryName: ${categoryName}, categoryId: ${categoryId}, questions:`,
             questions
           );
           throw e;
         }
       })
     );
+  });
 };

Committable suggestion was skipped due to low confidence.

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 (13)
prisma/schema.prisma (5)

49-53: Approve changes to User model with migration strategy suggestion

The deprecation of fields and addition of applicationSubmissions align well with the new schema design. This change effectively shifts the application data management to the new FormSubmission model.

Consider implementing a data migration strategy to move existing data from the deprecated fields to the new FormSubmission model. This will ensure a smooth transition and maintain data integrity during the schema update.

Also applies to: 61-62


Line range hint 111-165: Acknowledge transitional DH10Application model and suggest deprecation timeline

The introduction of the DH10Application model as a transitional step in the migration process is a good approach. It allows for a gradual transition of data to the new FormSubmission structure.

To ensure a smooth transition and prevent this model from becoming a permanent fixture:

  1. Establish a clear timeline for migrating data from DH10Application to FormSubmission.
  2. Create tasks for updating all code that references DH10Application to use FormSubmission instead.
  3. Set a deadline for the complete deprecation and removal of the DH10Application model.

This approach will help maintain focus on fully adopting the new schema design.


168-181: Approve FormSubmission model with performance optimization suggestion

The FormSubmission model is well-structured and forms the core of the new application submission system. The relationships and constraints are appropriately defined.

Consider adding an index on the submitterId field to optimize queries that filter or sort by the submitter:

model FormSubmission {
  // ... existing fields ...

  submitter   User     @relation(fields: [submitterId], references: [id])
  submitterId String
+ @@index([submitterId])

  // ... rest of the model ...
}

This index can improve performance for queries that involve looking up submissions for a specific user.


191-221: Approve FormItem, Question, and Answer models with optimization suggestion

The structure and relationships of the FormItem, Question, and Answer models are well-defined and allow for flexible form creation. The constraints and relations maintain data integrity effectively.

Consider adding an index on the formSubmissionId in the Answer model to optimize queries that retrieve all answers for a specific submission:

model Answer {
  // ... existing fields ...

  formSubmission   FormSubmission @relation(fields: [formSubmissionId], references: [id])
  formSubmissionId String

  @@id([addressedQuestionId, formSubmissionId])
+ @@index([formSubmissionId])
}

This index can improve performance when fetching all answers for a given form submission.


Line range hint 1-221: Overall approval of schema changes with migration advice

The new schema design effectively addresses the PR objectives by introducing a flexible system for dynamic form creation and submission handling. The introduction of FormSubmission, FormStructure, FormItem, Question, and Answer models provides a robust foundation for managing various types of applications.

To ensure a smooth transition to the new schema:

  1. Develop a comprehensive data migration plan to move existing data from deprecated fields and models to the new structure.
  2. Update all related application code to use the new models and relationships.
  3. Create a timeline for phasing out deprecated models and fields, including the transitional DH10Application model.
  4. Consider adding appropriate indexes on frequently queried fields to optimize performance.
  5. Ensure that all team members are familiar with the new schema structure and its implications on the application logic.

These changes represent a significant improvement in the database design, allowing for more flexible and maintainable form management in the future.

src/server/router/reviewers.ts (4)

35-71: LGTM: Enhanced getApplications procedure with improved error handling

The changes to the getApplications procedure are well-implemented:

  1. The use of trpcAssert for authorization checks is more concise and consistent.
  2. The query now correctly uses formStructureId, aligning with the new schema.
  3. The addition of error handling for parsing failures improves robustness.

One minor suggestion for improvement:

Consider adding more specific error information to aid in debugging:

 throw new TRPCError({
   code: "INTERNAL_SERVER_ERROR",
-  message: "Failed to parse applications.",
+  message: `Failed to parse applications: ${error instanceof Error ? error.message : 'Unknown error'}`,
 });

This change would provide more context about the parsing failure, making it easier to diagnose issues in production.


75-146: LGTM: Comprehensive improvements to getApplication procedure

The updates to the getApplication procedure are well-implemented:

  1. The use of submitterId aligns with the new schema design.
  2. The complex query structure efficiently fetches all necessary data in a single database call.
  3. The sorting logic ensures consistent ordering of questions.

One suggestion for improvement:

Consider extracting the complex query structure into a separate function for better readability:

async function getApplicationSubmission(ctx, formStructureId, submitterId) {
  return ctx.prisma.formSubmission.findUniqueOrThrow({
    where: {
      formStructureId_submitterId: {
        formStructureId,
        submitterId,
      },
    },
    include: {
      // ... (rest of the include structure)
    },
  });
}

// Usage in the procedure
const applicationSubmission = await getApplicationSubmission(
  ctx,
  deltaHacksApplicationFormName,
  input.submitterId
);

This refactoring would make the main procedure more concise and easier to understand.


148-199: LGTM: Well-implemented updateApplicationShallow procedure

The updates to the updateApplicationShallow procedure (formerly updateStatus) are well-implemented:

  1. The new name better reflects the function's purpose.
  2. The use of submitterId and formStructureId aligns with the new schema.
  3. The authorization check using trpcAssert is consistent with other parts of the code.
  4. Enhanced logging with LogSnag provides better visibility into status changes.

One suggestion for improvement:

Consider refactoring the status icon logic for better readability and maintainability:

const statusIcons = {
  [Status.ACCEPTED]: "✅",
  [Status.REJECTED]: "❌",
  [Status.WAITLISTED]: "🕰️",
  [Status.RSVP]: "🎟️",
};

const getStatusIcon = (status: Status) => statusIcons[status] || "🤔";

// Usage
icon: getStatusIcon(input.application.status),

This refactoring would make the main function more concise and the icon logic more maintainable.


Line range hint 200-247: Consider updating authorization check in submit procedure

While the submit procedure remains functional, its authorization check is inconsistent with the style used in other updated procedures. Consider refactoring it to use trpcAssert for consistency:

- if (
-   !(
-     ctx.session.user.role.includes(Role.ADMIN) ||
-     ctx.session.user.role.includes(Role.REVIEWER)
-   )
- ) {
-   throw new TRPCError({ code: "UNAUTHORIZED" });
- }
+ trpcAssert(
+   ctx.session.user.role.includes(Role.ADMIN) ||
+   ctx.session.user.role.includes(Role.REVIEWER),
+   "UNAUTHORIZED"
+ );

This change would improve consistency across the codebase and leverage the trpcAssert function for error handling.

src/server/router/application.ts (4)

163-186: Consider separating assertions for clarity

The changes to the rsvp procedure align well with the new schema. However, there's a potential issue with the assertion on line 175:

trpcAssert(form.status === Status.ACCEPTED, "UNAUTHORIZED");

This assertion will throw an "UNAUTHORIZED" error if the status is not ACCEPTED, but it will also throw if the form is null (which should never happen due to the previous assertion). Consider separating these checks for clarity:

trpcAssert(form, "NOT_FOUND");
trpcAssert(form.status === Status.ACCEPTED, "UNAUTHORIZED");

This change would provide more specific error messages based on the actual condition that failed.


195-220: Consider improving error handling in getApplicationShallow

The new getApplicationShallow procedure is a good addition and aligns well with the new schema. However, consider improving the error handling when formName is not found. Instead of silently returning null, it might be better to throw a specific error:

if (!formName) {
  throw new TRPCError({
    code: "NOT_FOUND",
    message: "Application form not configured",
  });
}

This would provide more explicit feedback about why the application couldn't be retrieved.


Line range hint 415-453: LGTM with a suggestion: Consider using a transaction for QR code assignment

The changes to the checkIn procedure improve the robustness of the check-in process by ensuring that users don't check in multiple times and that QR codes are unique. This is a good improvement.

However, there's a potential race condition between checking if the QR code is in use and assigning it to the user. To mitigate this, consider using a database transaction to ensure atomicity of these operations.

Here's a suggestion on how to implement this:

await ctx.prisma.$transaction(async (prisma) => {
  const qrCount = await prisma.user.count({
    where: { qrcode: input },
  });

  if (qrCount !== 0) {
    throw new TRPCError({
      code: "FORBIDDEN",
      message: "This QR code is already in use",
    });
  }

  await prisma.user.update({
    where: { id: ctx.session.user.id },
    data: {
      qrcode: input,
      status: Status.CHECKED_IN,
    },
  });
});

This ensures that no other operation can interfere between checking the QR code's availability and assigning it to the user.


Line range hint 1-578: Overall assessment: Good progress, with room for improvement

The changes in this file represent a significant shift towards a new database schema, centering around the formSubmission structure. This is a positive direction that should improve the flexibility and maintainability of the application system.

Key improvements:

  1. Consistent use of Config.getDeltaHacksApplicationFormName for form identification.
  2. Better structure for application data in the submit procedure.
  3. Enhanced error checking in several procedures.

Main areas for improvement:

  1. Refactor the submit procedure to improve maintainability.
  2. Ensure consistent error handling across all procedures.
  3. Address potential race conditions in the checkIn procedure.
  4. Update getPrevAutofill to use the new schema.
  5. Verify and possibly adjust the user ID handling in deleteApplication.

Consider these improvements to further enhance the robustness and maintainability of the application router.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 90adc6d and 74817dd.

📒 Files selected for processing (9)
  • prisma/migrations/20241008204241_dynamic_form_builder/migration.sql (1 hunks)
  • prisma/schema.prisma (3 hunks)
  • prisma/seed/data.ts (1 hunks)
  • src/components/Applicant.tsx (2 hunks)
  • src/pages/welcome.tsx (1 hunks)
  • src/server/db/configQueries.ts (1 hunks)
  • src/server/router/application.ts (5 hunks)
  • src/server/router/formBuilder.ts (1 hunks)
  • src/server/router/reviewers.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • prisma/seed/data.ts
  • src/server/db/configQueries.ts
🧰 Additional context used
📓 Learnings (1)
src/components/Applicant.tsx (4)
Learnt from: fvcci
PR: deltahacks/portal#191
File: src/components/Applicant.tsx:105-108
Timestamp: 2024-10-08T21:54:39.143Z
Learning: In `ApplicationContent`, `applicationForReview` and the data fetched via `trpc.reviewer.getApplication.useQuery` represent different data and are not redundant.
Learnt from: fvcci
PR: deltahacks/portal#191
File: src/components/Applicant.tsx:105-108
Timestamp: 2024-10-02T05:34:43.794Z
Learning: In `ApplicationContent`, `applicationForReview` and the data fetched via `trpc.reviewer.getApplication.useQuery` represent different data and are not redundant.
Learnt from: fvcci
PR: deltahacks/portal#191
File: src/components/Applicant.tsx:145-180
Timestamp: 2024-10-08T21:54:39.143Z
Learning: In the `ApplicationContent` component within `src/components/Applicant.tsx`, not all fields in the education section are `FormInput` components; some fields use different component types, so refactoring should account for this diversity.
Learnt from: fvcci
PR: deltahacks/portal#191
File: src/components/Applicant.tsx:145-180
Timestamp: 2024-10-06T16:35:06.464Z
Learning: In the `ApplicationContent` component within `src/components/Applicant.tsx`, not all fields in the education section are `FormInput` components; some fields use different component types, so refactoring should account for this diversity.
🔇 Additional comments (18)
src/server/router/formBuilder.ts (1)

1-17: LGTM: Imports and schema definition are well-structured

The imports cover all necessary dependencies, and the FormStructure schema is clearly defined using Zod. The use of z.enum for itemType ensures that only valid types ("category" or "question") are accepted, which is a good practice for type safety.

prisma/migrations/20241008204241_dynamic_form_builder/migration.sql (9)

1-10: FormSubmission table structure looks good.

The table includes all necessary fields for tracking form submissions. The use of TIMESTAMP(3) for submissionTime and the default value for status are appropriate.


12-18: FormStructure table is well-defined.

The simple structure with id and name is sufficient for defining form structures.


20-28: FormItem table structure is appropriate.

The use of INT4 for formPosition is a good choice for ordering items within a form.


30-36: Question table uses formItemId as primary key.

This implies a one-to-one relationship between Question and FormItem. Ensure this is the intended design, as it means each form item can have at most one question.


47-48: Unique index on FormSubmission is appropriate.

This index ensures that a user can only have one submission per form structure. This is likely the intended behavior, but it's worth verifying with the product requirements.

Please confirm that users should be limited to one submission per form structure.


50-51: Unique index on FormItem maintains order without duplicates.

This index ensures that each item has a unique position within its form structure, which is appropriate for maintaining order.


1-69: Overall, the migration script creates a solid foundation for a dynamic form builder system.

The schema design is well-structured, with appropriate constraints and indexes in place. It allows for flexible form structures and submissions while maintaining data integrity.

Key points to verify:

  1. Confirm that the one-to-one relationship between FormItem and Question is intentional.
  2. Verify if it's acceptable for the Answer.statement to be nullable.
  3. Confirm that users should be limited to one submission per form structure.
  4. Verify the existence of the User table in previous migrations.

These verifications will ensure that the database schema aligns perfectly with the intended application behavior.


53-69: Foreign key constraints maintain referential integrity.

The foreign key constraints are well-defined and maintain referential integrity between the tables. The use of ON DELETE RESTRICT and ON UPDATE CASCADE is a good practice, preventing accidental deletions and ensuring data consistency.

Note: The constraint on FormSubmission.submitterId references a User table. This implies that the User table exists in a previous migration, which is an acceptable assumption but worth verifying.

Verify the existence of the User table:

#!/bin/bash
# Check for the existence of the User table in previous migrations
rg --type sql 'CREATE TABLE.*User'

38-45: Answer table uses a composite primary key.

The composite primary key (addressedQuestionId, formSubmissionId) is appropriate for this many-to-many relationship. However, the 'statement' field is nullable.

Verify if it's intentional to allow null values for the answer statement:

src/pages/welcome.tsx (1)

92-103: LGTM: Improved application check and redirect logic

The changes to the application check and redirect logic are well-implemented:

  1. Using prisma.formSubmission.findFirst aligns with the new schema mentioned in the PR objectives.
  2. The query correctly uses the form name and user ID to find the submission.
  3. The simplified redirect logic is cleaner and easier to understand.

These changes should improve the efficiency and maintainability of the code.

prisma/schema.prisma (1)

65-65: ⚠️ Potential issue

Update deprecation comment for Review model

The current deprecation comment for the Review model suggests using User.status, which is itself deprecated. This creates an inconsistency in the deprecation chain.

Update the deprecation comment to reflect the new structure:

-/// @deprecated Use `User.status` instead
+/// @deprecated Use `FormSubmission.status` instead

Likely invalid or redundant comment.

src/server/router/reviewers.ts (2)

5-29: LGTM: Improved imports and schema definitions

The changes in this section enhance the structure and type safety of the application:

  1. The addition of trpcAssert import suggests improved error handling.
  2. The ApplicationForReview schema now has a nested submitter object, providing a more organized data structure.
  3. The replacement of dH10ApplicationId with formStructureId aligns with the new schema design.
  4. The new FormItemAndAnswer interface adds clarity to the data structure used in the application.

These changes contribute to better code organization and type safety.


Line range hint 1-247: Overall: Well-implemented changes with minor suggestions for improvement

The changes in this file significantly enhance the application's structure, type safety, and error handling:

  1. The new schema design is consistently applied across most procedures.
  2. Authorization checks and error handling have been improved using trpcAssert.
  3. Complex queries have been implemented to efficiently retrieve necessary data.

Minor suggestions for further improvement:

  1. Consider refactoring complex query structures into separate functions for better readability.
  2. Update the submit procedure to use the new authorization check style for consistency.
  3. Refactor the status icon logic in updateApplicationShallow for better maintainability.

These changes have greatly improved the codebase, and addressing the minor suggestions would further enhance its quality and consistency.

src/components/Applicant.tsx (2)

343-343: LGTM: Consistent use of submitterId

The change from id to submitterId in the UpdateStatusDropdown component prop is consistent with the new data structure used in ApplicationContent. This update ensures that the correct identifier is used throughout the application.


105-108: 🛠️ Refactor suggestion

Consider adding error and loading state handling

The data fetching logic has been updated to use the new schema, which is good. However, there's no explicit handling of loading or error states. Consider adding checks for these states to improve the user experience and prevent potential runtime errors.

Here's a suggestion to handle these states:

const { data: application, isLoading, error } = trpc.reviewer.getApplication.useQuery({
  submitterId: submitter.id,
});

if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!application) return <div>No application data found</div>;
⛔ Skipped due to learnings
Learnt from: fvcci
PR: deltahacks/portal#191
File: src/components/Applicant.tsx:105-108
Timestamp: 2024-10-08T21:54:39.143Z
Learning: In `ApplicationContent`, `applicationForReview` and the data fetched via `trpc.reviewer.getApplication.useQuery` represent different data and are not redundant.
Learnt from: fvcci
PR: deltahacks/portal#191
File: src/components/Applicant.tsx:105-108
Timestamp: 2024-10-02T05:34:43.794Z
Learning: In `ApplicationContent`, `applicationForReview` and the data fetched via `trpc.reviewer.getApplication.useQuery` represent different data and are not redundant.
src/server/router/application.ts (2)

109-122: LGTM: Updated getStatusCount procedure

The changes to the getStatusCount procedure align well with the new schema. The use of Config.getDeltaHacksApplicationFormName adds flexibility, and grouping by formStructureId is consistent with the new data structure.

Also applies to: 124-127


540-578: ⚠️ Potential issue

Verify the use of ctx.session.user.id in deleteApplication

The changes to deleteApplication procedure align well with the new schema. However, there's a potential issue:

await ctx.prisma.formSubmission.delete({
  where: {
    formStructureId_submitterId: {
      formStructureId: deltaHacksApplicationFormName,
      submitterId: ctx.session.user.id,
    },
  },
});

The procedure takes userId as input, but uses ctx.session.user.id when deleting the formSubmission. This might not be the intended behavior if the goal is to allow deleting applications for different users (e.g., by an admin).

If the intention is to allow admins to delete other users' applications, consider using input.userId instead:

submitterId: input.userId,

If not, you may want to add a check to ensure that the logged-in user can only delete their own application:

trpcAssert(input.userId === ctx.session.user.id, "UNAUTHORIZED");

To verify this behavior, you can run the following script:

This will help confirm how input.userId is being used in the deleteApplication procedure.

Comment on lines +19 to +52
export const createForm = async (
prisma: PrismaClient,
formStructure: FormStructure
) => {
const form = await prisma.formStructure.create({
data: { name: formStructure.name },
});

const formItems = await prisma.formItem.createManyAndReturn({
data: formStructure.formItems.map(({ statement }, i) => ({
formStructureId: form.id,
statement,
formPosition: i,
})),
});

await prisma.question.createMany({
data: formStructure.formItems
.map(({ itemType }, formPosition) => ({ itemType, formPosition }))
.filter(({ itemType }) => itemType == "question")
.map(({ formPosition }) => {
// The above list operations were to obtain this formItem
// to create questions.
const formItem = formItems.find(
(item) => item.formPosition == formPosition
);

trpcAssert(formItem, "NOT_FOUND");
return {
formItemId: formItem?.id,
};
}),
});
};
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

Consider enhancing error handling and using transactions

While the createForm function logic is correct, there are a few areas for improvement:

  1. Error handling could be more robust. Consider adding try-catch blocks to handle potential errors during database operations.

  2. The function is not wrapped in a transaction. This could lead to partial data being saved if an error occurs during the process.

  3. The assertion on line 46 uses trpcAssert, which might not be the most appropriate for this context. Consider using a standard assertion or throwing a custom error.

Here's a suggested refactor to address these issues:

export const createForm = async (
  prisma: PrismaClient,
  formStructure: FormStructure
) => {
  return prisma.$transaction(async (tx) => {
    try {
      const form = await tx.formStructure.create({
        data: { name: formStructure.name },
      });

      const formItems = await tx.formItem.createMany({
        data: formStructure.formItems.map(({ statement }, i) => ({
          formStructureId: form.id,
          statement,
          formPosition: i,
        })),
      });

      const createdFormItems = await tx.formItem.findMany({
        where: { formStructureId: form.id },
      });

      await tx.question.createMany({
        data: formStructure.formItems
          .map(({ itemType }, formPosition) => ({ itemType, formPosition }))
          .filter(({ itemType }) => itemType == "question")
          .map(({ formPosition }) => {
            const formItem = createdFormItems.find(
              (item) => item.formPosition == formPosition
            );

            if (!formItem) {
              throw new Error(`Form item not found for position ${formPosition}`);
            }

            return {
              formItemId: formItem.id,
            };
          }),
      });

      return form;
    } catch (error) {
      console.error("Error creating form:", error);
      throw error;
    }
  });
};

This refactored version wraps the entire operation in a transaction, improves error handling, and replaces the trpcAssert with a standard error throw.

Comment on lines +54 to +60
export const fileBuilder = router({
createForm: protectedProcedure
.input(FormStructure)
.mutation(async ({ ctx, input: formStructure }) => {
await createForm(ctx.prisma, formStructure);
}),
});
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

Consider returning created form data

The fileBuilder router is well-structured, using protectedProcedure for authorization and validating input against the FormStructure schema. However, the mutation doesn't return any data, which could be useful for the client.

Consider modifying the mutation to return the created form data:

export const fileBuilder = router({
  createForm: protectedProcedure
    .input(FormStructure)
    .mutation(async ({ ctx, input: formStructure }) => {
      const createdForm = await createForm(ctx.prisma, formStructure);
      return createdForm;
    }),
});

This change would allow the client to receive the ID and other details of the newly created form, which could be useful for subsequent operations or UI updates.

const userEntry = await prisma.user.findFirst({
where: { id: session.user.id },
include: { dh10application: true },
const formName = await Config.getDeltaHacksApplicationFormName(prisma);
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 missing import for Config object

The Config object is used but not imported in this file. To prevent runtime errors, please add the following import at the top of the file:

import { Config } from "../server/db/configQueries";

Comment on lines +88 to +90
if (!formName) {
return { notFound: true };
}
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

Consider improving error handling for missing form name

Currently, if the form name is not found, a 404 error is returned. This might not provide the best user experience. Consider handling this case more gracefully, such as redirecting to an error page with a more informative message or logging the error for administrators.

Here's a suggested improvement:

if (!formName) {
  console.error("DeltaHacks application form name not found");
  return {
    redirect: {
      destination: "/error",
      permanent: false,
    },
  };
}

This assumes you have an /error page. If not, you might want to create one to handle such cases.

Comment on lines +183 to +189
model FormStructure {
id String @id @default(cuid())
name String

submissions FormSubmission[]
formItems FormItem[]
}
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

Consider adding a unique constraint to FormStructure name

The FormStructure model effectively represents the structure of forms. To prevent duplicate form names and ensure easier querying:

Consider adding a unique constraint to the name field:

model FormStructure {
  id   String @id @default(cuid())
- name String
+ name String @unique

  submissions FormSubmission[]
  formItems   FormItem[]
}

This will ensure that each form structure has a unique name across the system.

📝 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
model FormStructure {
id String @id @default(cuid())
name String
submissions FormSubmission[]
formItems FormItem[]
}
model FormStructure {
id String @id @default(cuid())
name String @unique
submissions FormSubmission[]
formItems FormItem[]
}

Comment on lines +112 to +302
text={data?.hackerKind}
label={application?.at(23)?.formItem.statement ?? ""}
text={application?.at(23)?.answer}
/>
<FormInput
id="workshopChoices"
label="What workshops are you interested in?"
text={data?.workshopChoices.join(", ")}
label={application?.at(24)?.formItem.statement ?? ""}
text={application?.at(24)?.answer}
/>
<FormInput
id="discoverdFrom"
label="How did you hear about DeltaHacks?"
text={data?.discoverdFrom.join(", ")}
label={application?.at(25)?.formItem.statement ?? ""}
text={application?.at(25)?.answer}
/>
<FormInput
id="gender"
label={application?.at(26)?.formItem.statement ?? ""}
text={application?.at(26)?.answer}
/>
<FormInput
id="race"
label={application?.at(27)?.formItem.statement ?? ""}
text={application?.at(27)?.answer}
/>
<FormInput id="gender" label="Gender" text={data?.gender} />
<FormInput id="race" label="Race" text={data?.race} />
<FormCheckbox
id="alreadyHaveTeam"
label="Do you already have a team?"
checked={data?.alreadyHaveTeam}
label={application?.at(28)?.formItem.statement ?? ""}
checked={application?.at(28)?.answer === "true"}
readOnly
/>
<FormCheckbox
id="considerCoffee"
label="Would you like to be considered for a coffee chat with a sponser?"
checked={data?.considerCoffee}
label={application?.at(29)?.formItem.statement ?? ""}
checked={application?.at(29)?.answer === "true"}
readOnly
/>
<FormDivider label="Emergency Contact" />
<FormDivider label={application?.at(30)?.formItem.statement ?? ""} />
<div className="flex flex-col md:flex-row md:items-end md:gap-4">
<FormInput
id="emergencyContactName"
label="Name of Emergency Contact"
label={application?.at(31)?.formItem.statement ?? ""}
text={application?.at(31)?.answer}
placeholder="James Doe"
text={data?.emergencyContactName}
/>
<FormInput
id="emergencyContactRelation"
label="Relation to Emergency Contact"
label={application?.at(32)?.formItem.statement ?? ""}
text={application?.at(32)?.answer}
placeholder="Parent / Guardian / Friend / Spouse"
text={data?.emergencyContactRelation}
/>
</div>
<FormInput
id="emergencyContactPhone"
label="Emergency Contact Phone Number"
label={application?.at(33)?.formItem.statement ?? ""}
text={application?.at(33)?.answer}
placeholder="000-000-0000"
text={data?.emergencyContactPhone}
/>
<FormDivider label="MLH Consent" />
<FormDivider label={application?.at(34)?.formItem.statement ?? ""} />
<FormCheckbox
id="agreeToMLHCodeOfConduct"
label="Agree to MLH Code of Conduct"
link="https://static.mlh.io/docs/mlh-code-of-conduct.pdf"
checked={data?.agreeToMLHCodeOfConduct}
label={application?.at(35)?.formItem.statement ?? ""}
checked={application?.at(35)?.answer === "true"}
readOnly
/>
<FormCheckbox
id="agreeToMLHPrivacyPolicy"
label="Agree to MLH Privacy Policy"
link="https://mlh.io/privacy"
checked={data?.agreeToMLHPrivacyPolicy}
label={application?.at(36)?.formItem.statement ?? ""}
checked={application?.at(36)?.answer === "true"}
readOnly
/>
<FormCheckbox
id="agreeToMLHCommunications"
label="Agree to MLH Communications"
checked={data?.agreeToMLHCommunications}
label={application?.at(37)?.formItem.statement ?? ""}
checked={application?.at(37)?.answer === "true"}
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

Consider creating a helper function for form field data access

The current implementation repeatedly uses application?.at(index)?.formItem.statement for labels and application?.at(index)?.answer for values. This pattern, while consistent, can make the code harder to read and maintain. Consider creating a helper function to simplify this access:

const getFormField = (index: number) => ({
  label: application?.at(index)?.formItem.statement ?? "",
  value: application?.at(index)?.answer ?? "",
});

// Usage example:
const { label, value } = getFormField(1);
<FormInput label={label} text={value} placeholder="John" />

This approach would make the code more concise and easier to maintain, reducing the risk of errors when accessing form field data.

Comment on lines +222 to +414
statement: input.previousHackathonsCount.toString(),
addressedQuestionId: await getQuestionId(13),
},
{
statement: input.longAnswerChange,
addressedQuestionId: await getQuestionId(15),
},
{
statement: input.longAnswerExperience,
addressedQuestionId: await getQuestionId(16),
},
{
statement: input.longAnswerTech,
addressedQuestionId: await getQuestionId(17),
},
{
statement: input.longAnswerMagic,
addressedQuestionId: await getQuestionId(18),
},
{
statement: input.socialText ?? null,
addressedQuestionId: await getQuestionId(20),
},
{
statement: input.interests?.toString() ?? null,
addressedQuestionId: await getQuestionId(21),
},
{
statement: input.tshirtSize,
addressedQuestionId: await getQuestionId(22),
},
{
statement: input.hackerKind,
addressedQuestionId: await getQuestionId(23),
},
{
statement: JSON.stringify(input.workshopChoices),
addressedQuestionId: await getQuestionId(24),
},
{
statement: JSON.stringify(input.discoverdFrom.toString()),
addressedQuestionId: await getQuestionId(25),
},
{
statement: input.gender,
addressedQuestionId: await getQuestionId(26),
},
{
statement: input.race,
addressedQuestionId: await getQuestionId(27),
},
{
statement: input.alreadyHaveTeam.toString(),
addressedQuestionId: await getQuestionId(28),
},
{
statement: input.considerCoffee.toString(),
addressedQuestionId: await getQuestionId(29),
},
{
statement: input.emergencyContactName,
addressedQuestionId: await getQuestionId(31),
},
{
statement: input.emergencyContactRelation,
addressedQuestionId: await getQuestionId(32),
},
{
statement: input.emergencyContactPhone,
addressedQuestionId: await getQuestionId(33),
},
{
statement: input.agreeToMLHCodeOfConduct.toString(),
addressedQuestionId: await getQuestionId(35),
},
{
statement: input.agreeToMLHPrivacyPolicy.toString(),
addressedQuestionId: await getQuestionId(36),
},
{
statement: input.agreeToMLHCommunications.toString(),
addressedQuestionId: await getQuestionId(37),
},
];

console.log("formsubmission: ", formSubmission.id);
await ctx.prisma.answer.createMany({
data: answers.map((answerPartial) => ({
...answerPartial,
formSubmissionId: formSubmission.id,
})),
});

await ctx.logsnag.track({
channel: "applications",
event: "Application Submitted",
user_id: `${ctx.session.user.name} - ${ctx.session.user.email}`,
description: "A user has submitted an application.",
icon: "📝",
});
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

Consider refactoring the submit procedure for better maintainability

The changes to the submit procedure align well with the new schema and provide better structure for application data. The use of applicationSchema for input validation is a good practice. However, the procedure has become quite long and complex, which might make it hard to maintain.

Consider refactoring this procedure into smaller, more focused functions. For example:

  1. A function to handle formSubmission creation
  2. A function to process and format answers
  3. A function to handle the bulk insert of answers

This would improve readability and make the code easier to maintain and test.

Additionally, there are a few points to consider:

  1. The getQuestionId function is making database queries in a loop, which could be inefficient. Consider fetching all question IDs at once and then mapping them to positions.

  2. The date handling for gradDate could be simplified:

const gradDate = input.studyExpectedGraduation
  ? new Date(input.studyExpectedGraduation)
  : null;
  1. Consider using a more descriptive name for the answers array, such as formattedAnswers, to better reflect its purpose.

Comment on lines +531 to +539
currentLevel: responsePreprocessing.get("3SPBWlps2PBj")?.text ?? "N/A",
hackathonCount:
responsePreprocessing.get("MyObNZSNMZOZ")?.text ?? "N/A",
longAnswer1: responsePreprocessing.get("rCIqmnIUzvAV")?.text ?? "N/A",
longAnswer2: responsePreprocessing.get("h084NVJ0kEsO")?.text ?? "N/A",
longAnswer3: responsePreprocessing.get("wq7KawPVuW4I")?.text ?? "N/A",
socialLinks: responsePreprocessing.get("CE5WnCcBNEtj")?.text ?? "N/A",
resume:
responsePreprocessing
.get("z8wTMK3lMO00")
?.file_url?.replace(
"https://api.typeform.com/forms",
"/api/resumes"
) ?? "N/A",
extra: responsePreprocessing.get("GUpky3mnQ3q5")?.text ?? "N/A",
tshirtSize: responsePreprocessing.get("Q9xv6pezGeSc")?.text ?? "N/A",
hackerType: responsePreprocessing.get("k9BrMbznssVX")?.text ?? "N/A",
hasTeam: responsePreprocessing.get("3h36sGge5G4X")?.boolean ?? false,
workShop: responsePreprocessing.get("Q3MisVaz3Ukw")?.text ?? "N/A",
gender: responsePreprocessing.get("b3sr6g16jGjj")?.text ?? "N/A",
considerSponserChat:
responsePreprocessing.get("LzF2H4Fjfwvq")?.boolean ?? false,
howDidYouHear: responsePreprocessing.get("OoutsXd4RFcR")?.text ?? "N/A",
background: responsePreprocessing.get("kGs2PWAnqBI3")?.text ?? "N/A",
emergencyContactInfo: {
firstName: responsePreprocessing.get("o5rMp5fj0BMa")?.text ?? "N/A",
lastName: responsePreprocessing.get("irlsiZFKVJKD")?.text ?? "N/A",
phoneNumber:
responsePreprocessing.get("ceNTt9oUhO6Q")?.phone_number ?? "N/A",
email: responsePreprocessing.get("onIT7bTImlRj")?.email ?? "N/A",
},
mlhAgreement:
responsePreprocessing.get("F3vbQhObxXFa")?.boolean ?? false,
mlhCoc: responsePreprocessing.get("f3ELfiV5gVSs")?.boolean ?? false,
};
})[0];

if (converted === undefined) {
return {};
}

const pt = applicationSchema.partial();

type AutofillType = z.infer<typeof pt>;

const autofill: AutofillType = {};

if (converted.firstName !== "N/A") {
autofill["firstName"] = converted.firstName;
}
if (converted.lastName !== "N/A") {
autofill["lastName"] = converted.lastName;
}
if (converted.birthday !== undefined) {
autofill["birthday"] = new Date(
converted.birthday.toISOString().slice(0, 10)
);
}

if (converted.major !== "N/A") {
autofill["studyMajor"] = converted.major;
}
if (converted.school !== "N/A") {
autofill["studyLocation"] = converted.school;
}
if (converted.willBeEnrolled !== false) {
autofill["studyEnrolledPostSecondary"] = converted.willBeEnrolled;
}
if (converted.graduationYear !== undefined) {
autofill["studyExpectedGraduation"] = new Date(
converted.graduationYear.toISOString().slice(0, 10)
);
}
if (converted.degree !== "N/A") {
autofill["studyDegree"] = converted.degree;
}

if (converted.hackathonCount !== "N/A") {
autofill["previousHackathonsCount"] = parseInt(converted.hackathonCount);
}
// emergencyContact

if (converted.emergencyContactInfo.firstName !== "N/A") {
autofill["emergencyContactName"] =
converted.emergencyContactInfo.firstName;
}

if (converted.emergencyContactInfo.lastName !== "N/A") {
autofill["emergencyContactName"] =
autofill["emergencyContactName"] +
" " +
converted.emergencyContactInfo.lastName;
}

if (converted.emergencyContactInfo.phoneNumber !== "N/A") {
autofill["emergencyContactPhone"] =
converted.emergencyContactInfo.phoneNumber;
}
return user?.dh10application ?? {};
}),
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

Update getPrevAutofill to use new schema

The simplification of the getPrevAutofill procedure is good, but it seems to be returning dh10application, which might be deprecated based on earlier changes in the file. Consider updating this to use the new formSubmission schema:

const user = await ctx.prisma.user.findUnique({
  where: { id: ctx.session.user.id },
  select: {
    formSubmissions: {
      where: {
        formStructureId: await Config.getDeltaHacksApplicationFormName(ctx.prisma)
      },
      take: 1
    }
  },
});

return user?.formSubmissions[0] ?? {};

This change would align the getPrevAutofill procedure with the new schema and provide consistent data across the application.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

refactor: replace dh10application with a dynamic form builder schema
3 participants