diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs index 930d47949..43bfc9438 100644 --- a/apps/docs/next.config.mjs +++ b/apps/docs/next.config.mjs @@ -1,3 +1,4 @@ +import process from 'node:process' import { composePlugins, withNx } from '@nx/next' import createMDX from 'fumadocs-mdx/config' import { fileGenerator, remarkDocGen, remarkInstall } from 'fumadocs-docgen' @@ -16,6 +17,14 @@ const nextConfig = { reactStrictMode: true, output: 'standalone', }, + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: process.env.ALLOWED_IMAGE_HOSTNAME || 'avatars.githubusercontent.com', + }, + ], + }, } const withMDX = createMDX({ diff --git a/apps/docs/src/content/docs/knowledge-base/cuHacking-2020-wiki/meta.json b/apps/docs/src/content/docs/knowledge-base/cuHacking-2020-wiki/meta.json index e5b91bd70..485f87c35 100644 --- a/apps/docs/src/content/docs/knowledge-base/cuHacking-2020-wiki/meta.json +++ b/apps/docs/src/content/docs/knowledge-base/cuHacking-2020-wiki/meta.json @@ -2,7 +2,6 @@ "title": "cuHacking 2020 wiki", "icon": "Bird", "pages": [ - "index", "development-timeline", "admin-console", "application-server", diff --git a/apps/docs/src/content/docs/libraries/db.mdx b/apps/docs/src/content/docs/libraries/db.mdx index e7c21ca5f..3712a2bc5 100644 --- a/apps/docs/src/content/docs/libraries/db.mdx +++ b/apps/docs/src/content/docs/libraries/db.mdx @@ -10,6 +10,111 @@ description: Database interactor with DrizzleORM. This library holds the schemas and commands for making and using the database using DrizzleORM and Drizzle-Kit. It puts together the schemas in a configuration, and provides one database object to interact with databases with type-safety. +### Mermaid Entity-Relationship Diagram + +Here is the Mermaid ERD for the portal database schema: + +```mermaid +erDiagram + User { + serial id "Primary key, unique" + varchar(32) firstName "First name of the user" + varchar(32) middleName "Middle name of the user, optional" + varchar(32) lastName "Last name of the user" + varchar(32) preferredName "Preferred name of the user, optional" + varchar(64) email "Email of the user, must be unique, verification required" + bool isEmailVerified "Whether the email is verified, default is 'no'" + varchar(128) avatarUrl "URL to the user's profile picture, optional" + text profileDescription "Profile description, optional" + date dateOfBirth "Date of birth of the user, verification required" + enum gender "Gender of the user, e.g., male, female, other, optional" + enum phoneNumberCountryCode "Country code of phone number, e.g., +1" + varchar(16) phoneNumber "Phone number of the user, must be unique, verification required" + bool isPhoneNumberVerified "Whether the phone number is verified, default is 'no'" + smallint numHackathonsAttended "Number of hackathons attended by the user" + text anyOtherComments "Any additional comments from the user, optional" + bool isDomestic "Whether the user is international or domestic, optional" + enum ethnicity "Ethnicity of the user, optional, black, white, asian, hispanic, middle-eastern," + int estimatedGradYear "Estimated graduation year of the user, optional" + bool agreeToMlhReqs "MLH requirements, default is 'No'" + int resumeId "References Resume.id, mandatory relationship" + int schoolId "References School.id, mandatory relationship" + int emergencyContactId "References EmergencyContact.id, mandatory relationship" + int teamId "References Team.id, mandatory relationship" + } + + EmergencyContact { + serial id "Primary key, unique" + varchar(32) name "Name of the emergency contact" + enum relationship "Relationship to the user, e.g., mother, father, sibling, friend, relative, other" + varchar(16) phoneNumber "Phone number of the contact, must be unique, verification required" + bool isPhoneNumberVerified "Whether the phone number is verified, default is 'no'" + } + + Team { + serial id "Primary key, unique" + varchar(64) name "Name of the team" + varchar(128) profileImageUrl "URL to the user's profile picture, optional" + varchar(256) projectLinkUrl "URL to the project, verification required" + int teamOwnerId "References User.id, one-to-one relationship" + bool hasSubmitted "Whether the team has submitted a project, default is 'No'" + varchar(128) teamProfileImageUrl "URL to the user's profile picture, optional" + } + + School { + serial id "Primary key, unique" + varchar(128) name "Name of the school, verification required" + enum levelOfStudy "Level of study, e.g., graduate school, high school, etc." + } + + Program { + serial id "Primary key, unique" + varchar(128) name "Name of the program" + int schoolId "References School.id" + enum programType "Type of program, e.g., bachelor, master, diploma, certificate, etc." + } + + Resume { + serial id "Primary key, unique" + varchar(128) fileLink "URL to the resume file, verification required" + bool hasPrivacyToggle "Privacy setting for the resume, default is false" + timestamp uploadedAt "Timestamp when the resume was uploaded, default is CURRENT_TIMESTAMP" + } + + SocialMedia { + serial id "Primary key, unique" + int userId "References User.id, optional relationship" + varchar(16) platformName "Name of the social media platform, e.g., LinkedIn, GitHub, etc." + varchar(128) profileUrl "URL to the social media profile, verification required" + } + + UserPreferences { + serial id "Primary key, unique" + int userId "References User.id, mandatory relationship" + enum preferredLanguage "Preferred language of the user, default is 'EN'" + enum eventPreferences "Event preferences, e.g., hardware, software, etc." + %% bool darkMode "Whether the user prefers dark mode, default is false" + bool privacyMode "Privacy mode, to be defined, default is 'No'" + bool isSubscribedToNewsletter "Whether the user is subscribed newsletter, default is 'No'" + enum shirtSize "Shirt size of the user" + enum pronouns "Preferred pronouns of the user, e.g., he/him, she/her, they/them, other" + enum[] dietRestrictions "Dietary restrictions, e.g., allergies, vegan, none, etc." + enum[] trackPreferences "Track preferences, e.g., hardware, software, etc., optional" + enum[] interests "Interests of the user, e.g., languages, etc., optional" + enum[] disabilities "Disabilities, if any" + enum[] applicableSkills "Applicable skills of the user" + } + + %% Relationships + User ||--|| Resume : "References" + User ||--|| School : "References" + User ||--o{ EmergencyContact : "References" + User }o--|| Team : "References" + Program ||--o{ School : "References" + SocialMedia }o--|| User : "References" + UserPreferences ||--|| User : "References" +``` + ### Key Features - One source of truth @@ -68,7 +173,7 @@ pnpm nx run db:studio ### Other Drizzle-Kit commands -All other `drizzle-kit` [https://orm.drizzle.team/kit-docs/commands](commands) are supported, such as `generate` and `migrate`. +All other `drizzle-kit` [commands](https://orm.drizzle.team/kit-docs/commands) are supported, such as `generate` and `migrate`. See the project graph to view them. Here is an example of running a migration: ```sh title="Terminal" pnpm nx run db:migrate @@ -121,4 +226,4 @@ Make sure that it is valid to make it optional. If this happens, a last resort solution would be to delete the docker container & its volume, and attempt a migration once again. -For more troubleshooting, see DrizzleORM's [https://orm.drizzle.team/kit-docs/faq](troubleshooting page) +For more troubleshooting, see DrizzleORM's [troubleshooting page](https://orm.drizzle.team/kit-docs/faq). diff --git a/libs/db/src/index.ts b/libs/db/src/index.ts index a92a06e33..fbf601fc9 100644 --- a/libs/db/src/index.ts +++ b/libs/db/src/index.ts @@ -10,5 +10,12 @@ export const db = drizzle(sql, { logger: envWebsiteDb.NODE_ENV === 'development', }) -export * from './schema/user' +export * from './schema/emergencyContact' +export * from './schema/program' +export * from './schema/resume' +export * from './schema/school' export * from './schema/session' +export * from './schema/socialMedia' +export * from './schema/team' +export * from './schema/user' +export * from './schema/userPreferences' diff --git a/libs/db/src/schema/emergencyContact.ts b/libs/db/src/schema/emergencyContact.ts new file mode 100644 index 000000000..9548dfff2 --- /dev/null +++ b/libs/db/src/schema/emergencyContact.ts @@ -0,0 +1,11 @@ +import { boolean, pgEnum, pgTable, serial, varchar } from 'drizzle-orm/pg-core' + +export const relationshipEnum = pgEnum('relationship', ['mother', 'father', 'sibling', 'friend', 'relative', 'other']) + +export const emergencyContact = pgTable('emergencyContact', { + id: serial('id').primaryKey(), + name: varchar('name', { length: 32 }), + relationship: relationshipEnum('relationship'), + phoneNumber: varchar('phoneNumber', { length: 16 }).unique(), + isPhoneNumberVerified: boolean('isPhoneNumberVerified').default(false), +}) diff --git a/libs/db/src/schema/index.ts b/libs/db/src/schema/index.ts index 9f282a48c..d960e5912 100644 --- a/libs/db/src/schema/index.ts +++ b/libs/db/src/schema/index.ts @@ -1,2 +1,9 @@ -export * from './user' +export * from './emergencyContact' +export * from './program' +export * from './resume' +export * from './school' export * from './session' +export * from './socialMedia' +export * from './team' +export * from './user' +export * from './userPreferences' diff --git a/libs/db/src/schema/program.ts b/libs/db/src/schema/program.ts new file mode 100644 index 000000000..e3c5739a2 --- /dev/null +++ b/libs/db/src/schema/program.ts @@ -0,0 +1,11 @@ +import { integer, pgEnum, pgTable, serial, varchar } from 'drizzle-orm/pg-core' +import { school } from './school' + +export const programTypeEnum = pgEnum('programType', ['bachelor', 'master', 'diploma', 'certificate']) + +export const program = pgTable('program', { + id: serial('id').primaryKey(), + name: varchar('name', { length: 128 }), + schoolId: integer('schoolId').references(() => school.id), + programType: programTypeEnum('programType'), +}) diff --git a/libs/db/src/schema/resume.ts b/libs/db/src/schema/resume.ts new file mode 100644 index 000000000..dee07fcae --- /dev/null +++ b/libs/db/src/schema/resume.ts @@ -0,0 +1,8 @@ +import { boolean, pgTable, serial, timestamp, varchar } from 'drizzle-orm/pg-core' + +export const resume = pgTable('resume', { + id: serial('id').primaryKey(), + fileLink: varchar('fileLink', { length: 128 }), + hasPrivacyToggle: boolean('hasPrivacyToggle').default(false), + uploadedAt: timestamp('uploadedAt').defaultNow(), +}) diff --git a/libs/db/src/schema/school.ts b/libs/db/src/schema/school.ts new file mode 100644 index 000000000..e32c9473b --- /dev/null +++ b/libs/db/src/schema/school.ts @@ -0,0 +1,9 @@ +import { pgEnum, pgTable, serial, varchar } from 'drizzle-orm/pg-core' + +export const levelOfStudyEnum = pgEnum('levelOfStudy', ['graduateSchool', 'highSchool']) + +export const school = pgTable('school', { + id: serial('id').primaryKey(), + name: varchar('name', { length: 128 }), + levelOfStudy: levelOfStudyEnum('levelOfStudy'), +}) diff --git a/libs/db/src/schema/session.ts b/libs/db/src/schema/session.ts index f96013b56..3467e9e3c 100644 --- a/libs/db/src/schema/session.ts +++ b/libs/db/src/schema/session.ts @@ -4,10 +4,10 @@ import { user } from './user' export const session = pgTable('session', { id: text('id').primaryKey(), - userId: text('user_id') + userId: text('userId') .notNull() .references(() => user.id), - expiresAt: timestamp('expires_at', { + expiresAt: timestamp('expiresAt', { withTimezone: true, mode: 'date', }).notNull(), diff --git a/libs/db/src/schema/socialMedia.ts b/libs/db/src/schema/socialMedia.ts new file mode 100644 index 000000000..e7ddc881d --- /dev/null +++ b/libs/db/src/schema/socialMedia.ts @@ -0,0 +1,11 @@ +import { integer, pgTable, serial, varchar } from 'drizzle-orm/pg-core' + +import { user } from './user' + +export const socialMedia = pgTable('socialMedia', { + id: serial('id').primaryKey(), + userId: integer('userId') + .references(() => user.id), + platformName: varchar('platformName', { length: 16 }), + profileUrl: varchar('profileUrl', { length: 128 }), +}) diff --git a/libs/db/src/schema/team.ts b/libs/db/src/schema/team.ts new file mode 100644 index 000000000..0e741b3cc --- /dev/null +++ b/libs/db/src/schema/team.ts @@ -0,0 +1,13 @@ +import { boolean, integer, pgTable, serial, varchar } from 'drizzle-orm/pg-core' + +export const team = pgTable('team', { + id: serial('id').primaryKey(), + name: varchar('name', { length: 64 }), + profileImageUrl: varchar('profileImageUrl', { length: 128 }), + projectLinkUrl: varchar('projectLinkUrl', { length: 256 }), + teamOwnerId: integer('teamOwnerId'), + hasSubmitted: boolean('hasSubmitted').default(false), + teamProfileImageUrl: varchar('teamProfileImageUrl', { + length: 128, + }), +}) diff --git a/libs/db/src/schema/user.ts b/libs/db/src/schema/user.ts index ff06dec8c..5b9d700c5 100644 --- a/libs/db/src/schema/user.ts +++ b/libs/db/src/schema/user.ts @@ -1,8 +1,36 @@ -import { pgTable, text } from 'drizzle-orm/pg-core' +import { boolean, date, integer, pgEnum, pgTable, serial, smallint, text, varchar } from 'drizzle-orm/pg-core' +import { resume } from './resume' +import { school } from './school' +import { emergencyContact } from './emergencyContact' +import { team } from './team' + +export const genderEnum = pgEnum('gender', ['male', 'female', 'other']) +export const phoneNumberCountryCodeEnum = pgEnum('phoneNumberCountryCode', ['+1']) +export const ethnicityEnum = pgEnum('ethnicity', ['black', 'white', 'asian', 'hispanic', 'middle-eastern']) export const user = pgTable('user', { - id: text('id').primaryKey(), - name: text('name'), - email: text('email').notNull(), - avatarUrl: text('avatar_url'), + id: serial('id').primaryKey(), + firstName: varchar('firstName', { length: 32 }).notNull(), + middleName: varchar('middleName', { length: 32 }), + lastName: varchar('lastName', { length: 32 }).notNull(), + preferredName: varchar('preferredName', { length: 32 }), + email: varchar('email', { length: 64 }).unique().notNull(), + isEmailVerified: boolean('isEmailVerified').default(false), + avatarUrl: varchar('avatarUrl', { length: 128 }), + profileDescription: text('profileDescription'), + dateOfBirth: date('dateOfBirth'), + gender: genderEnum('gender'), + phoneNumberCountryCode: phoneNumberCountryCodeEnum('phoneNumberCountryCode'), + phoneNumber: varchar('phoneNumber', { length: 16 }).unique(), + isPhoneNumberVerified: boolean('isPhoneNumberVerified').default(false), + numHackathonsAttended: smallint('numHackathonsAttended').default(0), + anyOtherComments: text('anyOtherComments'), + isDomestic: boolean('isDomestic'), + ethnicity: ethnicityEnum('ethnicity'), + estimatedGradYear: integer('estimatedGradYear'), + agreeToMlhReqs: boolean('agreeToMlhReqs').default(false), + resumeId: integer('resumeId').references(() => resume.id), + schoolId: integer('schoolId').references(() => school.id), + emergencyContactId: integer('emergencyContactId').references(() => emergencyContact.id), + teamId: integer('teamId').references(() => team.id), }) diff --git a/libs/db/src/schema/userPreferences.ts b/libs/db/src/schema/userPreferences.ts new file mode 100644 index 000000000..75fc9eff4 --- /dev/null +++ b/libs/db/src/schema/userPreferences.ts @@ -0,0 +1,28 @@ +import { boolean, integer, pgEnum, pgTable, serial } from 'drizzle-orm/pg-core' +import { user } from './user' + +export const preferredLanguageEnum = pgEnum('preferredLanguage', ['EN']) +export const eventPreferencesEnum = pgEnum('eventPreferences', ['hardware', 'software']) +export const shirtSizeEnum = pgEnum('shirtSize', ['XS', 'S', 'M', 'L', 'XL', 'XXL']) +export const pronounsEnum = pgEnum('pronouns', ['he/him', 'she/her', 'they/them', 'other']) +export const dietRestrictionsEnum = pgEnum('dietRestrictions', ['allergies', 'vegan', 'none']) +export const trackPreferencesEnum = pgEnum('trackPreferences', ['hardware', 'software']) +export const interestsEnum = pgEnum('interests', ['languages']) +export const disabilitiesEnum = pgEnum('disabilities', ['mobility', 'visual', 'hearing', 'cognitive', 'mental']) +export const applicableSkillsEnum = pgEnum('applicableSkills', ['JavaScript', 'TypeScript', 'Python', 'Java']) + +export const userPreferences = pgTable('userPreferences', { + id: serial('id').primaryKey(), + userId: integer('userId').references(() => user.id), + preferredLanguage: preferredLanguageEnum('preferredLanguage').default('EN'), + eventPreferences: eventPreferencesEnum('eventPreferences'), + privacyMode: boolean('privacyMode').default(false), + isSubscribedToNewsletter: boolean('isSubscribedToNewsletter').default(false), + shirtSize: shirtSizeEnum('shirtSize'), + pronouns: pronounsEnum('pronouns'), + dietRestrictions: dietRestrictionsEnum('dietRestrictions').array(), + trackPreferences: trackPreferencesEnum('trackPreferences').array(), + interests: interestsEnum('interests').array(), + disabilities: disabilitiesEnum('disabilities').array(), + applicableSkills: applicableSkillsEnum('applicableSkills').array(), +})