UReview Monash

A small team of software developers helping the school build stuff

Edited @ 9 Dec 2022 | Written @ 9 Dec 2022

What is UReview Monash? 😵


UReview Monash is a student-run organization that helps Monash University Malaysia develop software (mainly websites), that whichever school requests. One of our most notable project done for the school is a Unit Review Feedback Website called UReview.


A Screenshot of UReview homepage, showing all unit cards and their ratings.

*Attached above is a screenshot of UReview Homepage, showing all unit cards and ratings



How did it all start? 🥳


I would like to describe UReview as a small startup consisting of only beginner developers. It all started back in 2019 where our founder Bhanuka Gamage and his team pitched the idea of having a unit-forum platform to the school (Monash University) for a hackathon. To their surprise, his team lost the competition, but gained the attention of a school lecturer who was willing to help them fight for school funding to get the project going.


At the end, UReview managed to secure a small amount of funding from the school to kickstart the project, and soon work to develop a MVP to secure future fundings started! Everything that I’m seeing here today on the platform was more or less developed by their team during the span of a few short months (this also means a lot of technical debt, which I will explain later).



Well? What’s my role then? 😽


💯
Since Year 3 Semester 1, I am now the Technical Lead and Lead Project Manager for UReview.

I joined back in the year 2021 (around October) during my Year 2 Semester 2 enrollment. There was a job opening on Monash SOIT Discord, and I just went for it. I was rather sick and tired of continuous learning from my courses, and wanted to find an outlet or a community to participate in to create stuff. So I joined UReview without hesitation. From there onwards, I was self-trained to be a Fullstack Developer and a Project Manager under the guidance of my good friend, my senior and someone I really admire, Jin Heng. Now it is my turn to take on his role and guide newcomers to navitating, learning and growing from UReview.



But what do I do technically? 🛠️


Good question! Let me explain a little about UReview’s Technology stack, from UI/UX → Frontend → Backend → DevOps. I will also be describing things I’ve learnt through self-learning and experimentation. Every conclusion below is through self-learning, and yes I’m very involved in each technical domain below.


Frontend Development

The original UReview site was built using Angular , and it was something that I had to learn when I first started. I love Angular for its complete “out of the box” features. Almost everything you need is built in. Routing? Protected Routes? State Management? Animations? Angular got you covered! No need to think about picking what package to use for a feature with 6 different implementations (ahem, React). However, this soon became a problem for UReview.


Although Angular is a complete and strongly opinionated framework, this does not prevent beginner developers (0 work experience) like us to give ourselves footguns due to its steep learning curve. Since it is opinionated, if you deviate from Google’s way of developing in Angular, or not using its features properly, you’re likely to incur heavy technical debt overtime, which became the downfall of our codebase.


Example 1: Failing to understand the power of NgModules

Below is a REAL code snippet from UReview’s Angular codebase on the only modules we have. Which is the default app.module.ts . We actually had several different small apps embedded into the website, but components from different apps are all chucked into one module. NgModules are difficult to understand, hence everyone had a “if it works it works” mentality. This made code a living hell for new developers to understand, or to understand the relationship between components. It was already like this when I joined and c’mon, no documentation?

JAVASCRIPT
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { SidebarModule } from 'ng-sidebar';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { SocialLoginModule, AuthServiceConfig, GoogleLoginProvider } from 'angularx-social-login';
import { GoogleChartsModule } from 'angular-google-charts';
import { TagCloudModule } from 'angular-tag-cloud-module';
import { AppComponent } from './app.component';
import { HeaderComponent } from './components/header/header.component';
import { AppRoutingModule } from './app-routing.module';
import { HomeComponent } from './components/home/home.component';
import { SidebarComponent } from './components/sidebar/sidebar.component';
import { UnitCardComponent } from './components/unit-card/unit-card.component';
import { LoginComponent } from './components/login/login.component';
import { UnitsComponent } from './components/units/units.component';
import { AddReviewComponent } from './components/add-review/add-review.component';
import { RatingComponent } from './components/rating/rating.component';
import { ProfileComponent } from './components/profile/profile.component';
import { ProfilePhotoComponent } from './components/profile-photo/profile-photo.component';
import { OfferingCardComponent } from './components/offering-card/offering-card.component';
import { FeedbackComponent } from './components/feedback/feedback.component';
import { HttpClientModule } from '@angular/common/http';
import { OfferingsComponent } from './components/offerings/offerings.component';
import { UnitSearchResultsComponent } from './components/unit-search-results/unit-search-results.component';
import { OfferingSearchResultsComponent } from './components/offering-search-results/offering-search-results.component';
import { OAUTH_CLIENT_ID } from './../environments/environment';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './services/authinterceptor.service';
import { AdminPageComponent } from './components/admin-page/admin-page.component';
import { HelpComponent } from './components/help/help.component';
import { TextInputValidatorDirective } from './directives/text-input-validator.directive';
import { ReviewPageComponent } from './components/review-page/review-page.component';
import { ReviewBoxSmallComponent } from './components/review-box-small/review-box-small.component';
import { ReviewCommentSmallComponent } from './components/review-comment-small/review-comment-small.component';
import { ReportPageComponent } from './components/report-page/report-page.component';
import { OfferingComponent } from './components/offering/offering.component';
import { NotificationComponent } from './components/notification/notification.component';
import { NotificationPageComponent } from './components/notification-page/notification-page.component';
import { SummaryComponent } from './components/summary/summary.component';
import { SummariesComponent } from './components/summaries/summaries.component';
import { SummaryCardComponent } from './components/summary-card/summary-card.component';
import { SummarySearchResultsComponent } from './components/summary-search-results/summary-search-results.component';
import { MaintenanceGuard } from './maintenance.guard';
import { MaintenanceComponent } from './components/maintenance/maintenance.component';
import { AboutusComponent } from './components/aboutus/aboutus.component';
import { ThreadPageComponent } from './components/thread-page/thread-page.component';
import {ThreadsBoxSmallComponent} from './components/threads-box-small/threads-box-small.component';
import { ThreadsCommentsPageComponent } from './components/threads-comments-page/threads-comments-page.component';
import { ThreadsCommentBoxComponent } from './components/threads-comment-box/threads-comment-box.component';
import { SurveyAdminPageComponent } from './components/survey-admin-page/survey-admin-page.component';
import { ThreadsMainPageComponent } from './components/threads-main-page/threads-main-page.component';
import { ThreadsCardsComponent } from './components/threads-cards/threads-cards.component';
import { CommonModule } from '@angular/common';
import { SurveyPageComponent } from './components/survey-page/survey-page.component';
const config = new AuthServiceConfig([
{
id: GoogleLoginProvider.PROVIDER_ID,
provider: new GoogleLoginProvider(OAUTH_CLIENT_ID)
}
]);
export function provideConfig() {
return config;
}
@NgModule({
declarations: [
AppComponent,
HeaderComponent,
HomeComponent,
SidebarComponent,
UnitCardComponent,
LoginComponent,
UnitsComponent,
AddReviewComponent,
RatingComponent,
ProfileComponent,
ProfilePhotoComponent,
OfferingCardComponent,
OfferingsComponent,
UnitSearchResultsComponent,
OfferingSearchResultsComponent,
FeedbackComponent,
AdminPageComponent,
HelpComponent,
TextInputValidatorDirective,
ReviewPageComponent,
ReviewBoxSmallComponent,
ReviewCommentSmallComponent,
ReportPageComponent,
OfferingComponent,
NotificationComponent,
NotificationPageComponent,
SummaryComponent,
SummariesComponent,
SummaryCardComponent,
SummarySearchResultsComponent,
SurveyPageComponent,
SurveyAdminPageComponent,
MaintenanceComponent,
AboutusComponent,
ThreadPageComponent,
ThreadsBoxSmallComponent,
ThreadsCommentsPageComponent,
ThreadsCommentBoxComponent,
ThreadsMainPageComponent,
ThreadsCardsComponent,
],
imports: [
CommonModule,
BrowserModule,
AppRoutingModule,
SocialLoginModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule,
NgbModule,
GoogleChartsModule,
TagCloudModule,
SidebarModule.forRoot()],
providers: [
{
provide: AuthServiceConfig,
useFactory: provideConfig,
},
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
},
MaintenanceGuard
],
bootstrap: [AppComponent]
})
export class AppModule { }

Needless to say, I struggled, and from there I understood what good code is NOT supposed to look like. Now, we are doing a soft-revamp on the site on Next.js instead! I would love to include all of my reasoning in the blog, but it is now becoming too lengthy to my liking. Drop me an email and I would love to have a discussion with you.



Backend Development

The UReview backend is built on Django REST Framework with DigitalOcean Postgres Database . But before this, we had to thoroughly understand the problem that we are trying to solve in order to create our Database Schema. Yes! Everything we learnt from the database unit (FIT2094) is actually useful! We had to come up with an ERD Diagram (that is quite extensive), in order to model relations properly. So far I don’t have any criticisms for the backend, except for the lack of documentation. I would love to share our ERD Diagram and all of the cool endpoints we built under a private call!


Example 1: Our answer model for a particular review for a particular question.

I will be showing a small part of our schema modelled in Django's ORM where, one Review has many Questions, and one Question has many Answers.

PYTHON
'''
Answer model.
'''
from django.db import models
from .choices.ratings import RatingField
class Answer(models.Model):
review = models.ForeignKey(to='api.Review', on_delete=models.CASCADE)
question = models.ForeignKey(to='api.Question', on_delete=models.CASCADE)
text = models.CharField(max_length=1000, blank=True, null=True)
rating = RatingField(blank=True, null=True)
created_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now=True)
class Meta:
app_label = 'api'
def __str__(self):
return f'{self.id} {self.question} Text: {self.text} Rating {self.rating}'


DevOps Development

We are currently using DigitalOcean as our hosting Platform-as-a-Service (PaaS) . We host build images on DO Droplets and DO Kubernetes Clusters for different apps. We also had to configure Kubernetes to execute Cron Jobs everyday at a specific time, such as sending out notification emails to users. This means that I also had to learn how to use Docker to manage our DO Container Registry , and learn how to configure DigitalOcean to take up less financial resources. I have successfully brought down costs by 60%, from around 200 USD a month to just 90 USD a month, simply by downscaling vertically after analyzing traffic and server loads.


Example 1: A sample YAML file that configures a Cron Job for sending daily notification emails.

Below is a very simple Cron Job that executes our seed_emails() function everyday at 9:00AM. I have hidden our Registry URL just in case, even if I’m well aware that any pushes to the registry will be denied without a generated Access Key.

YAML
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: email
spec:
schedule: "0 9 * * *"
failedJobsHistoryLimit: 5
successfulJobsHistoryLimit: 2
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
containers:
- name: ureview
imagePullPolicy: Always
image: <insert UReview Registry Domain Here>
command: ["python", "manage.py", "runscript", "seed_emails"]
ports:
- containerPort: 5000
restartPolicy: OnFailure


UReview Conclusion 📔


I have to thank UReview for kickstarting my developer journey. It’s where I learnt a lot about software development, and learn about all the amazing software architecture used in an actual production environment, even if it’s not the best! I hope to being this small team to new heights under my leadership and I am excited about what our team can achieve for the university!

Thanks for reading till the end 🥰🥰

Hopefully now you understand more about what I do for this particular project! If you have any questions, or see anything incorrect, please let me know by dropping me an email through the profile card on the About Me page!

If you realise 😆 that I am someone you would like to work with, do not hesitate to contact me 🧐 because I ❤️ everything I am doing now and want to share my joy 🥳 with you through collaboration.