diff --git a/user-registration-app/set-env.ts b/user-registration-app/set-env.ts index 44974eb1..c6bfa166 100644 --- a/user-registration-app/set-env.ts +++ b/user-registration-app/set-env.ts @@ -1,4 +1,4 @@ -import { writeFile } from 'fs'; +import {writeFile} from 'fs'; // Only overwrite environment.ts if nodejs environment variables have been provided (i.e. only if Vercel is building) if ('PRODUCTION' in process.env) { @@ -11,6 +11,7 @@ if ('PRODUCTION' in process.env) { production: '${process.env.PRODUCTION}', api_url: '${process.env.API_URL}', api_v2_url: '${process.env.API_V2_URL}', + api_v3_url: '${process.env.API_V3_URL}', firebase: { apiKey: '${process.env.API_KEY}', authDomain: '${process.env.AUTH_DOMAIN}', diff --git a/user-registration-app/src/app/AppConstants.ts b/user-registration-app/src/app/AppConstants.ts index 75486371..93cc80a2 100644 --- a/user-registration-app/src/app/AppConstants.ts +++ b/user-registration-app/src/app/AppConstants.ts @@ -2,6 +2,7 @@ import { environment } from '../environments/environment'; export class AppConstants { public static API_BASE_URL: string = environment.api_url; public static API_BASE_URL_V2: string = environment.api_v2_url; + public static API_BASE_URL_V3: string = environment.api_v3_url; public static LOGIN_ENDPOINT = '/login'; public static REGISTER_ENDPOINT = '/register'; public static LIVE_ENDPOINT = '/live'; diff --git a/user-registration-app/src/app/app.module.ts b/user-registration-app/src/app/app.module.ts index ced23902..23e45d8c 100644 --- a/user-registration-app/src/app/app.module.ts +++ b/user-registration-app/src/app/app.module.ts @@ -38,6 +38,7 @@ import { TruncatePipe } from './services/pipes/truncate.pipe'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatIconModule } from '@angular/material/icon'; import { QRCodeModule } from 'angularx-qrcode'; +import {ActiveEventsPipe} from "./services/pipes/active-events.pipe"; @NgModule({ declarations: [ AppComponent, @@ -51,6 +52,7 @@ import { QRCodeModule } from 'angularx-qrcode'; RsvpComponent, LiveWorkshopComponent, TruncatePipe, + ActiveEventsPipe, ScheduleViewComponent, UserProfileViewComponent, UserRegistrationViewComponent, diff --git a/user-registration-app/src/app/models/api-error-response-v3.ts b/user-registration-app/src/app/models/api-error-response-v3.ts new file mode 100644 index 00000000..06838e33 --- /dev/null +++ b/user-registration-app/src/app/models/api-error-response-v3.ts @@ -0,0 +1,4 @@ +export interface ApiErrorResponseV3 { + statusCode: number; + message: string; +} diff --git a/user-registration-app/src/app/models/event-v3-model.ts b/user-registration-app/src/app/models/event-v3-model.ts new file mode 100644 index 00000000..6e46b1c5 --- /dev/null +++ b/user-registration-app/src/app/models/event-v3-model.ts @@ -0,0 +1,32 @@ +import {LocationModel} from './location-model'; + +export class EventV3Model { + public id: string; + public name: string; + public type: string; + public startTime: number; + public endTime: number; + public description: string; + public icon: string; + public location: LocationModel; + public wsPresenterNames: string; + public wsSkillLevel: string; + public wsRelevantSkills: string; + public wsUrls: string[]; + + static parseJSON(value: any) { + const event = new EventV3Model(); + if ('location' in value) { + const location = new LocationModel(); + event.location = Object.assign(location, value['location']); + } + return Object.assign(event, value); + } + + static parseFromJSONArray(array: any[]): EventV3Model[] { + return array.map((value) => { + const event = new EventV3Model(); + return Object.assign(event, value, { wsUrls: value.wsUrls }); + }); + } +} diff --git a/user-registration-app/src/app/models/extra-credit-class-v3-model.ts b/user-registration-app/src/app/models/extra-credit-class-v3-model.ts new file mode 100644 index 00000000..631dee17 --- /dev/null +++ b/user-registration-app/src/app/models/extra-credit-class-v3-model.ts @@ -0,0 +1,9 @@ +export class ExtraCreditClassV3Model { + public id: number; + public name: string; + + static parseJSON(value: any): ExtraCreditClassV3Model { + const model = new ExtraCreditClassV3Model(); + return Object.assign(model, value); + } +} diff --git a/user-registration-app/src/app/models/hackathon-static-model.ts b/user-registration-app/src/app/models/hackathon-static-model.ts new file mode 100644 index 00000000..9809492c --- /dev/null +++ b/user-registration-app/src/app/models/hackathon-static-model.ts @@ -0,0 +1,25 @@ +import {EventV3Model} from './event-v3-model'; +import {SponsorV3Model} from './sponsor-v3-model'; + +export class HackathonStaticModel { + public id: string; + public name: string; + public startTime: number; + public endTime: number; + public active: boolean; + public events: EventV3Model[]; + public sponsors: SponsorV3Model[]; + + static parseJSON(value: any): HackathonStaticModel { + const model = new HackathonStaticModel(); + model.id = value['id']; + model.name = value['name']; + model.startTime = value['startTime']; + model.endTime = value['endTime']; + model.active = value['active']; + model.events = EventV3Model.parseFromJSONArray(value['events']); + model.sponsors = SponsorV3Model.parseJSONArray(value['sponsors']); + + return model; + } +} diff --git a/user-registration-app/src/app/models/hackathon-v3-model.ts b/user-registration-app/src/app/models/hackathon-v3-model.ts new file mode 100644 index 00000000..e1f1ea34 --- /dev/null +++ b/user-registration-app/src/app/models/hackathon-v3-model.ts @@ -0,0 +1,12 @@ +export class HackathonV3Model { + public id: string; + public name: string; + public startTime: number; + public endTime: number; + public active: boolean; + + static parseJSON(value: any): HackathonV3Model { + const model = new HackathonV3Model(); + return Object.assign(model, value); + } +} diff --git a/user-registration-app/src/app/models/location-model.ts b/user-registration-app/src/app/models/location-model.ts new file mode 100644 index 00000000..4dcb20ad --- /dev/null +++ b/user-registration-app/src/app/models/location-model.ts @@ -0,0 +1,9 @@ +export class LocationModel { + public id: number; + public name: string; + + static parseJSON(value: any) { + const location = new LocationModel(); + return Object.assign(location, value); + } +} diff --git a/user-registration-app/src/app/models/sponsor-v3-model.ts b/user-registration-app/src/app/models/sponsor-v3-model.ts new file mode 100644 index 00000000..b29b2b38 --- /dev/null +++ b/user-registration-app/src/app/models/sponsor-v3-model.ts @@ -0,0 +1,24 @@ +import {SponsorModel} from './sponsor'; + +export class SponsorV3Model { + public id: number; + public name: string; + public darkLogo: string; + public lightLogo: string; + public level: string; + public order: number; + public link?: string; + public hackathonId: string; + + static parseJSON(value: any): SponsorV3Model { + const model = new SponsorV3Model(); + return Object.assign(model, value); + } + + static parseJSONArray(array: any[]): SponsorV3Model[] { + return array.map((s) => { + const model = new SponsorModel(); + return Object.assign(model, s); + }) + } +} diff --git a/user-registration-app/src/app/services/BaseHttpService/BaseHttpService.ts b/user-registration-app/src/app/services/BaseHttpService/BaseHttpService.ts index 6e466d1c..eab05e82 100644 --- a/user-registration-app/src/app/services/BaseHttpService/BaseHttpService.ts +++ b/user-registration-app/src/app/services/BaseHttpService/BaseHttpService.ts @@ -1,20 +1,20 @@ -import { catchError, mergeMap, retryWhen, shareReplay, switchMap, map, tap } from 'rxjs/operators'; -import { Observable, throwError, timer } from 'rxjs'; -import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; -import { AuthService } from '../AuthService/auth.service'; -import { CustomErrorHandlerService } from '../CustomErrorHandler/custom-error-handler.service'; -import { NgProgress } from 'ngx-progressbar'; -import { AppConstants } from '../../AppConstants'; +import {catchError, map, mergeMap, retryWhen, shareReplay, switchMap, tap} from 'rxjs/operators'; +import {Observable, throwError, timer} from 'rxjs'; +import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http'; +import {AuthService} from '../AuthService/auth.service'; +import {CustomErrorHandlerService} from '../CustomErrorHandler/custom-error-handler.service'; +import {NgProgress} from 'ngx-progressbar'; +import {AppConstants} from '../../AppConstants'; export class BaseHttpService { private readonly CACHE_SIZE = 3; protected memCache: Map>; private genericRetryStrategy = ({ - maxRetryAttempts = 3, - scalingDuration = 1000, - excludedStatusCodes = [], - }: { + maxRetryAttempts = 3, + scalingDuration = 1000, + excludedStatusCodes = [], + }: { maxRetryAttempts?: number; scalingDuration?: number; excludedStatusCodes?: number[]; @@ -51,6 +51,7 @@ export class BaseHttpService { ignoreCache?: boolean, useAuth = true, v2: boolean = false, + v3: boolean = false, uid: string = '', ): Observable { if (ignoreCache) { @@ -60,9 +61,14 @@ export class BaseHttpService { // Set the value in the memory cache let headers = new HttpHeaders(); let params = new HttpParams(); - const fullUrl = v2 - ? AppConstants.API_BASE_URL_V2.concat(API_ENDPOINT) - : AppConstants.API_BASE_URL.concat(API_ENDPOINT); + let fullUrl = AppConstants.API_BASE_URL.concat(API_ENDPOINT); + + if (v2) { + fullUrl = AppConstants.API_BASE_URL_V2.concat(API_ENDPOINT); + } else if (v3) { + fullUrl = AppConstants.API_BASE_URL_V3.concat(API_ENDPOINT); + } + if (ignoreCache) { params = params.set('ignoreCache', 'true'); } @@ -71,16 +77,17 @@ export class BaseHttpService { } let observable = useAuth ? // With authentication - this.authService.idToken.pipe( - switchMap((idToken: string) => { - headers = headers.set('idtoken', idToken); - return this.getInternal(fullUrl, headers, params); - }), - ) + this.authService.idToken.pipe( + switchMap((idToken: string) => { + headers = headers.set('idtoken', idToken); + return this.getInternal(fullUrl, headers, params); + }), + ) : // Without authentication - this.getInternal(fullUrl, headers, params); + this.getInternal(fullUrl, headers, params); observable = observable.pipe( - v2 ? map((apiResponse: any) => apiResponse.body.data) : tap(() => {}), + v2 ? map((apiResponse: any) => apiResponse.body.data) : tap(() => { + }), catchError((err) => { return v2 ? this.errorHandler.handleV2HttpError(err) @@ -98,25 +105,31 @@ export class BaseHttpService { params?: HttpParams, ): Observable { return this.http - .get(fullUrl, { headers, params }) + .get(fullUrl, {headers, params}) .pipe(shareReplay(this.CACHE_SIZE, 10 * 1000)) .pipe( - retryWhen(this.genericRetryStrategy({ excludedStatusCodes: [400, 401, 404, 409] })), + retryWhen(this.genericRetryStrategy({excludedStatusCodes: [400, 401, 404, 409]})), ) as Observable; } - protected post(API_ENDPOINT: string, formObject: FormData | any, v2: boolean = false) { + protected post(API_ENDPOINT: string, formObject: FormData | any, v2: boolean = false, v3: boolean = false) { this.memCache.set(API_ENDPOINT, null); return this.authService.idToken.pipe( switchMap((idToken: string) => { let headers = new HttpHeaders(); + let fullUrl = AppConstants.API_BASE_URL.concat(API_ENDPOINT); headers = headers.set('idtoken', idToken); + + if (v2) { + fullUrl = AppConstants.API_BASE_URL_V2.concat(API_ENDPOINT); + } else if (v3) { + fullUrl = AppConstants.API_BASE_URL_V3.concat(API_ENDPOINT); + } + return this.http.post( - v2 - ? AppConstants.API_BASE_URL_V2.concat(API_ENDPOINT) - : AppConstants.API_BASE_URL.concat(API_ENDPOINT), + fullUrl, formObject, - { headers, reportProgress: true }, + {headers, reportProgress: true}, ); }), catchError((err) => { diff --git a/user-registration-app/src/app/services/CustomErrorHandler/custom-error-handler.service.ts b/user-registration-app/src/app/services/CustomErrorHandler/custom-error-handler.service.ts index afbaa02e..048cb806 100644 --- a/user-registration-app/src/app/services/CustomErrorHandler/custom-error-handler.service.ts +++ b/user-registration-app/src/app/services/CustomErrorHandler/custom-error-handler.service.ts @@ -5,6 +5,7 @@ import { Error } from '../../models/interfaces'; import { ToastrService } from 'ngx-toastr'; import { Observable, throwError } from 'rxjs'; import { IApiResponse } from '../../models/api-response.v2'; +import {ApiErrorResponseV3} from '../../models/api-error-response-v3'; @Injectable() export class CustomErrorHandlerService { @@ -91,4 +92,9 @@ export class CustomErrorHandlerService { // this.toastrService.error(error.message); return throwError(error); } + + handleV3HttpError(err: { error: ApiErrorResponseV3 }) { + console.error(err); + return throwError(err); + } } diff --git a/user-registration-app/src/app/services/HttpService/HttpService.ts b/user-registration-app/src/app/services/HttpService/HttpService.ts index bca54ce9..75be4993 100644 --- a/user-registration-app/src/app/services/HttpService/HttpService.ts +++ b/user-registration-app/src/app/services/HttpService/HttpService.ts @@ -15,6 +15,7 @@ import { ProjectModel } from '../../models/project-model'; import { ExtraCreditClass } from '../../models/extra-credit-class'; import { UserExtraCreditApiResponse } from '../../models/user-extra-credit'; import { SponsorModel } from '../../models/sponsor'; +import {HackathonStaticModel} from '../../models/hackathon-static-model'; @Injectable() export class HttpService extends BaseHttpService { @@ -117,8 +118,22 @@ export class HttpService extends BaseHttpService { return this.get(API_ENDPOINT).pipe(map(Rsvp.parseJSON)); } + getActiveHackathon() { + return this + .get( + '/hackathons/active/static', + false, + false, + false, + true, + ) + .pipe( + map(HackathonStaticModel.parseJSON), + ); + } + getEvents() { - const API_ENDPOINT = 'live/events'; + const API_ENDPOINT = '/events'; return this.get(API_ENDPOINT, false, false, true).pipe(map(EventModel.parseFromJSONArray)); } @@ -137,7 +152,7 @@ export class HttpService extends BaseHttpService { getExtraCreditClassesForUser(uid: string) { const API_ENDPOINT = 'users/extra-credit/assignment?type=user'; - return this.get(API_ENDPOINT, true, true, true, uid).pipe( + return this.get(API_ENDPOINT, true, true, true, false, uid).pipe( map((classes: any[]) => classes.map((c) => UserExtraCreditApiResponse.parseJSON(c))), ); } diff --git a/user-registration-app/src/app/services/pipes/active-events.pipe.ts b/user-registration-app/src/app/services/pipes/active-events.pipe.ts new file mode 100644 index 00000000..cc556b4d --- /dev/null +++ b/user-registration-app/src/app/services/pipes/active-events.pipe.ts @@ -0,0 +1,11 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {EventV3Model} from '../../models/event-v3-model'; + +@Pipe({ + name: 'filterActive', +}) +export class ActiveEventsPipe implements PipeTransform { + transform(value: EventV3Model[]): EventV3Model[] { + return value.filter((e) => e.endTime >= Date.now()); + } +} diff --git a/user-registration-app/src/app/views/live-view/live-view.component.html b/user-registration-app/src/app/views/live-view/live-view.component.html index 2c184c7a..2dde0769 100644 --- a/user-registration-app/src/app/views/live-view/live-view.component.html +++ b/user-registration-app/src/app/views/live-view/live-view.component.html @@ -94,7 +94,7 @@ - + @@ -149,7 +153,7 @@ perfectly fine, working on an existing project is not.
  • - All projects must be created through Devpost + All projects must be created through Devpost by 12PM on Sunday (even if not completed!) and can be edited until 1:45PM Sunday.
  • All project code must be attached to the project's Devpost submission.
  • @@ -163,7 +167,7 @@ no claim over intellectual property produced at the event.
  • - All participants must agree to the + All participants must agree to the MLH Code of Conduct.
  • @@ -253,9 +257,9 @@ Travel Reimbursement Policy .

    - + - +

    @@ -270,8 +274,8 @@

    If your professor is offering extra credit for attending HackPSU, please sign up for extra credit for your class in your profile page here. - If your professor requires workshop attendance, please see a HackPSU organizer - outside the workshop so that we can record your attendance and confirm + If your professor requires workshop attendance, please see a HackPSU organizer + outside the workshop so that we can record your attendance and confirm that you participated at the event.

    @@ -323,7 +327,7 @@

    - You must have a Devpost submission created by Sunday 12pm (even if not completed) + You must have a Devpost submission created by Sunday 12pm (even if not completed) on our Devpost page. However, you can continue editing the submission until hacking ends on Sunday at 1:45pm.

    @@ -433,9 +437,9 @@

    - SUBMISSION: To submit please visit our Devpost page at - http://devpost.hackpsu.org. - Make sure to submit your project (even if not completed) on Devpost by 12PM Sunday! + SUBMISSION: To submit please visit our Devpost page at + http://devpost.hackpsu.org. + Make sure to submit your project (even if not completed) on Devpost by 12PM Sunday! You can edit it until 1:45PM Sunday. This is a hard deadline - submissions received after 12PM will not be considered for prizes.

    @@ -465,30 +469,30 @@ Check back soon for a list of workshops!

    - +
    - - +
    +
    +
    @@ -575,9 +579,9 @@
    - + diff --git a/user-registration-app/src/app/views/live-view/live-view.component.ts b/user-registration-app/src/app/views/live-view/live-view.component.ts index 2a8a61c7..fd909c84 100644 --- a/user-registration-app/src/app/views/live-view/live-view.component.ts +++ b/user-registration-app/src/app/views/live-view/live-view.component.ts @@ -1,10 +1,10 @@ -import { Component, NgZone, OnInit } from '@angular/core'; -import { animate, style, transition, trigger } from '@angular/animations'; -import { Subscription } from 'rxjs'; -import { CountdownService } from '../../services/CountdownService/countdown.service'; -import { HttpService } from '../../services/HttpService/HttpService'; -import { EventModel } from '../../models/event-model'; -import { SponsorModel } from '../../models/sponsor'; +import {Component, NgZone, OnInit} from '@angular/core'; +import {animate, style, transition, trigger} from '@angular/animations'; +import {Subscription} from 'rxjs'; +import {CountdownService} from '../../services/CountdownService/countdown.service'; +import {HttpService} from '../../services/HttpService/HttpService'; +import {EventV3Model} from '../../models/event-v3-model'; +import {SponsorV3Model} from '../../models/sponsor-v3-model'; declare let $: any; @@ -16,12 +16,12 @@ declare let $: any; animations: [ trigger('enterAnimation', [ transition(':enter', [ - style({ transform: 'scale(0)', opacity: 0 }), - animate('500ms', style({ transform: 'scale(1)', opacity: 1 })), + style({transform: 'scale(0)', opacity: 0}), + animate('500ms', style({transform: 'scale(1)', opacity: 1})), ]), transition(':leave', [ - style({ transform: 'scale(1)', opacity: 1 }), - animate('500ms', style({ transform: 'scale(0)', opacity: 0 })), + style({transform: 'scale(1)', opacity: 1}), + animate('500ms', style({transform: 'scale(0)', opacity: 0})), ]), ]), ], @@ -38,8 +38,11 @@ export class LiveViewComponent implements OnInit { minutes: number; seconds: number; - workshops: EventModel[]; - sponsors: SponsorModel[]; + activities: EventV3Model[]; + meals: EventV3Model[]; + workshops: EventV3Model[]; + activeWorkshops: EventV3Model[]; + sponsors: SponsorV3Model[]; bannerText: string; @@ -118,21 +121,22 @@ export class LiveViewComponent implements OnInit { private zone: NgZone, private countdownService: CountdownService, private httpService: HttpService, - ) {} + ) { + } ngOnInit() { - this.countdownService.startCountDown().subscribe(({ duration, bannerText }) => { + this.countdownService.startCountDown().subscribe(({duration, bannerText}) => { this.days = duration.days; this.hours = duration.hours; this.minutes = duration.minutes; this.seconds = duration.seconds; this.bannerText = bannerText; }); - this.httpService.getEvents().subscribe((eventsArr) => { - this.workshops = eventsArr.filter((event) => event.event_type === 'workshop'); - }); - this.httpService.getSponsors().subscribe((sponsorsArr) => { - this.sponsors = sponsorsArr.sort((a, b) => a.order - b.order); + this.httpService.getActiveHackathon().subscribe((hackathon) => { + this.activities = hackathon.events.filter((e) => e.type === 'activity'); + this.meals = hackathon.events.filter((e) => e.type === 'food'); + this.workshops = hackathon.events.filter((e) => e.type === 'workshop'); + this.sponsors = hackathon.sponsors; }); } } diff --git a/user-registration-app/src/app/views/live-workshop/live-workshop.component.ts b/user-registration-app/src/app/views/live-workshop/live-workshop.component.ts index d9e73238..17739bbc 100644 --- a/user-registration-app/src/app/views/live-workshop/live-workshop.component.ts +++ b/user-registration-app/src/app/views/live-workshop/live-workshop.component.ts @@ -14,7 +14,7 @@ export class LiveWorkshopComponent implements OnInit { @Input() title: string; @Input() description: string; @Input() location: string; - @Input() skills: string; + @Input() skills: string[]; @Input() downloads: string[]; @Input() presenters: string[]; @Input() collapseID: string; @@ -43,7 +43,7 @@ export class LiveWorkshopComponent implements OnInit { // Maybe not the best way to detect if it's rich text or not? isRichTextDescription(): boolean { - return this.description.charAt(0) === "<"; + return this.description.charAt(0) === '<'; } ngOnInit() { diff --git a/user-registration-app/src/app/views/schedule-view/schedule-view.component.html b/user-registration-app/src/app/views/schedule-view/schedule-view.component.html index 3e640498..00ee5291 100644 --- a/user-registration-app/src/app/views/schedule-view/schedule-view.component.html +++ b/user-registration-app/src/app/views/schedule-view/schedule-view.component.html @@ -3,9 +3,9 @@
    ACTIVITIES
    -
    + data-toggle="modal" [attr.data-target]="'#event_key-'+activity.id+'-modal'">
    @@ -17,10 +17,10 @@
    {{getStartTime(activity)}} - {{getEndTime(activity)}}
    -
    {{activity.location_name}}
    +
    {{activity.location.name}}
    -
    {{activity.event_title}}
    +
    {{activity.name}}
    @@ -35,9 +35,9 @@
    MEALS
    -
    + [attr.data-target]="'#event_key-'+activity.id+'-modal'">
    @@ -69,9 +69,9 @@
    WORKSHOPS
    -
    + data-toggle="modal" [attr.data-target]="'#event_key-'+activity.id+'-modal'">
    @@ -84,10 +84,10 @@
    {{getStartTime(activity)}} - {{getEndTime(activity)}}
    -
    {{activity.location_name}}
    +
    {{activity.location.name}}
    -
    {{activity.event_title}}
    +
    {{activity.name}}
    @@ -106,7 +106,7 @@