Skip to content

Commit

Permalink
chore: merge 'origin/main' into init-cloud-functions
Browse files Browse the repository at this point in the history
  • Loading branch information
awpala committed Aug 25, 2024
2 parents 2f611a3 + 6c0e2c0 commit baea48f
Show file tree
Hide file tree
Showing 15 changed files with 1,089 additions and 42 deletions.
6 changes: 5 additions & 1 deletion example.env
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=
# firebase emulator suite vars
NEXT_PUBLIC_IS_EMULATOR_MODE= # <true | false>
NEXT_PUBLIC_FIRESTORE_EMULATOR_HOST= # <localhost:8080> -- ref: https://stackoverflow.com/a/66790889
TZ= # <US/Pacific | US/Mountain | US/Central | US/Eastern | etc.> -- Use same timezone as local to match NodeJS/Emulator to it; ref: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
TZ= # <US/Pacific | US/Mountain | US/Central | US/Eastern | etc.> -- Use same timezone as local to match NodeJS/Emulator to it; ref: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List

# supabase
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_SUPABASE_ANON_KEY=
4 changes: 3 additions & 1 deletion globals/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@ export interface CourseDataStatic {
export interface CourseDataDynamic {
courseId: TCourseId;
numReviews: number;
year?: number;
semesterId?: TSemesterId;
avgWorkload: TNullable<number>;
avgDifficulty: TNullable<number>;
avgOverall: TNullable<number>;
avgStaffSupport: TNullable<number>;
reviewsCountsByYearSem: TReviewsCountsByYearSemObject;
reviewsCountsByYearSem: TReviewsCountsByYearSemObject; // NOTE: placeholder only; remove once migrated from Firebase to Supabase
}

export interface Course extends CourseDataStatic, CourseDataDynamic {}
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@
"@emotion/react": "^11.11.1",
"@emotion/server": "^11.4.0",
"@emotion/styled": "^11.11.0",
"@fontsource/roboto": "^5.0.8",
"@fontsource/roboto": "^5.0.14",
"@mui/icons-material": "^5.11.11",
"@mui/material": "^5.14.5",
"@mui/x-data-grid": "^7.13.0",
"@supabase/supabase-js": "^2.45.2",
"@toast-ui/editor-plugin-code-syntax-highlight": "^3.1.0",
"@toast-ui/react-editor": "^3.2.2",
"@types/prismjs": "^1.26.4",
Expand Down Expand Up @@ -83,7 +84,7 @@
"eslint-config-next": "^14.2.3",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-storybook": "^0.6.11",
"husky": "^8.0.3",
"husky": "^9.1.5",
"jest": "^29.4.3",
"jest-environment-jsdom": "^29.3.1",
"jest-fetch-mock": "^3.0.3",
Expand Down
17 changes: 17 additions & 0 deletions services/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Services

A collection of services for the OMSHub application.

## Backend (Supabase)

CRUD operations for performing database interactions with Supabase (postgres-based).

Example usage:

```ts
import services from '@services/index';

const { getAllReviews } = services.backend;

const reviews = await getAllReviews();
```
204 changes: 204 additions & 0 deletions services/backend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import { createClient } from '@supabase/supabase-js';
import { TCourseId, Review, TSemesterId, TNullable, Course } from '@globals/types';
import { TDatabase } from '../supabase/types';
import { unpackReviewData, zipToCoursesData } from './utilities/supabaseData';
import { TCourseStats } from './utilities/supabaseTypes';

const supabase = createClient<TDatabase>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);

/* --- SUPABASE FUNCTIONS: READ OPERATIONS ---*/

/**
* Get rows from Supabase table of type `Review`, returned with most recent reviews first.
* @param limitCount Limit of rows returned. Default is defined as `DEFAULT_LIMIT` inside function body.
*/
const getAllReviews = async (limitCount?: number): Promise<Review[]> => {
const DEFAULT_LIMIT = 50; // prevent large read of DB if argument is omitted
limitCount = limitCount ?? DEFAULT_LIMIT;

try {
const { data, error } = await supabase
.rpc('getAllReviews', {
limit_count: limitCount,
});

if (error) throw error;

let reviews: Review[] = [];

if (data && data.length) {
reviews = unpackReviewData(data as Review[]);
}

return reviews;
} catch (error: any) {
console.error(error);
return [];
}
}

/**
* Get a single review from Supabase table of type `Review`.
* @param reviewId The review ID, of general form `courseId-year-semesterId-created`.
* @returns {Review}
*/
const getReviewByReviewId = async (reviewId: string): Promise<TNullable<Review>> => {
try {
const { data, error } = await supabase
.rpc('getReviewByReviewId', {
review_id: reviewId,
});

if (error) throw error;

const review: TNullable<Review> = data
? data[0] as Review
: null;

return review;
} catch (error: any) {
console.error(error);
return null;
}
}

/**
* Get rows from Supabase table of type `Review` for a course, returned with most recent reviews first.
* @param courseId The OMSCS course ID. If omitted, returns all courses' reviews.
* @returns {Review[]}
*/
const getReviewsByCourseId = async (courseId?: TCourseId): Promise<Review[]> => {
try {
const { data, error } = await supabase
.rpc('getReviewsByCourseId', {
course_id: courseId,
});

if (error) throw error;

let reviews: Review[] = [];

if (data && data.length) {
reviews = unpackReviewData(data as Review[]);
}

return reviews;
} catch (error: any) {
console.error(error);
return [];
}
}

/**
* Get rows from Supabase table of type `Review` for a user, returned with most recent reviews first.
* @param userId The Supabase Auth user ID.
* @returns {Review[]}
*/
const getReviewsByUserId = async (userId: string): Promise<Review[]> => {
try {
const { data, error } = await supabase
.rpc('getReviewsByUserId', {
user_id: userId,
});

if (error) throw error;

let reviews: Review[] = [];

if (data && data.length) {
reviews = unpackReviewData(data as Review[]);
}

return reviews;
} catch (error: any) {
console.error(error);
return [];
}
}

/**
* Get aggregate data from Supabase table of type `Course` for a course. Output includes zero-row data (i.e., `numReviews === 0`).
* @param courseId The OMSCS course ID. If omitted, returns all courses' stats (aggregated).
* @returns {Course[]}
*/
const getStatsByCourseId = async (courseId?: TCourseId): Promise<Course[]> => {
try {
const { data, error } = await supabase
.rpc('getStatsByCourseId', {
course_id: courseId,
});

if (error) throw error;

let courses: Course[] = [];

if (data) {
courses = zipToCoursesData(data as TCourseStats[]);
}

return courses;
} catch (error: any) {
console.error(error);
return [];
}
}

interface TArgsCourseStats {
courseId?: TCourseId;
year?: number;
semesterId?: TSemesterId;
}

/**
* Get aggregate data from Supabase table of type `Course` for a course. Output includes zero-row data (i.e., `numReviews === 0`). Can be queried independently for course and/or year and/or semester.
* @param courseId The OMSCS course ID. If omitted, returns all courses' stats (year-semesterId row-wise data).
* @param year The year of the course. If omitted, returns courseId and/or semesterId row-wise data.
* @param semesterId The semester of the course. If omitted, returns courseId and/or year row-wise data.
* @returns {Course[]}
*/
const getStatsByCourseYearSemester = async ({
courseId,
year,
semesterId,
}: TArgsCourseStats): Promise<Course[]> => {
try {
const { data, error } = await supabase
.rpc('getStatsByCourseYearSemester', {
course_id: courseId,
year_: year,
semester_id: semesterId,
});

if (error) throw error;

let courses: Course[] = [];

if (data) {
courses = zipToCoursesData(data as TCourseStats[]);
}

return courses;
} catch (error: any) {
console.error(error);
return [];
}
}

/* --- EXPORTS --- */

// TODO: Add remaining operations (CREATE, UPDATE, DELETE)

const backend = {
// READ OPERATIONS
getAllReviews,
getReviewByReviewId,
getReviewsByCourseId,
getReviewsByUserId,
getStatsByCourseId,
getStatsByCourseYearSemester,
};

export default backend;
7 changes: 7 additions & 0 deletions services/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import backend from './backend';

const services = {
backend,
};

export default services;
Loading

0 comments on commit baea48f

Please sign in to comment.