Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
Expand Down Expand Up @@ -318,6 +319,7 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
Expand Down
16 changes: 9 additions & 7 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,23 @@ RUN npm run build


# Stage 2a: Run frontend tests
#FROM node:20-alpine as test
#RUN apk add --no-cache chromium
FROM node:20-alpine as test
RUN apk add --no-cache chromium
# Set environment variable for Puppeteer to use Chromium installed via Alpine's package manager
#ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
#WORKDIR /app
#COPY indiestream .
#RUN npm run ci:install
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
WORKDIR /app
COPY indiestream .
RUN npm run ci:install
# Run tests using headless chromium
# CMD ["npm", "run", "test", "--", "--configuration=ci"]
CMD ["npm", "run", "test", "--", "--configuration=ci"]


# Stage 2b: Serve app with nginx server
FROM nginx:alpine
# Copy the build output to replace the default nginx contents
COPY --from=build /app/dist/indiestream/browser /usr/share/nginx/html
# Copy nginx configurationf ile
COPY nginx.conf /etc/nginx/nginx.conf
# Need to expose port 80
EXPOSE 80
# Start nginx
Expand Down
50 changes: 1 addition & 49 deletions frontend/indiestream/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,49 +1 @@
<mat-toolbar color="primary" class="toolbar">
<div class="left-toolbar">
<button mat-icon-button aria-label="Menu icon" (click)="toggleMenu()">
<mat-icon>menu</mat-icon>
</button>
<h1>{{ title }}</h1>
</div>
<div class="right-toolbar">
<button mat-mini-fab [routerLink]="['account']">
<img src="{{authService.getPictureUrl()}}" alt="User Avatar" class="user-avatar">
</button>
</div>
</mat-toolbar>

<mat-sidenav-container autosize class="sidenav-container" >
<mat-sidenav [opened]="true" mode="side">
<mat-nav-list>
<a mat-list-item [routerLink]="['dashboard']">
<span class="text-with-icon">
<mat-icon>house</mat-icon>
<span *ngIf="uncollapsed">Dashboard</span>
</span>
</a>
<a mat-list-item [routerLink]="['upload']">
<span class="text-with-icon">
<mat-icon>cloud_upload</mat-icon>
<span *ngIf="uncollapsed">Upload Game</span>
</span>
</a>
<a mat-list-item [routerLink]="['account']">
<span class="text-with-icon">
<mat-icon>account_circle</mat-icon>
<span *ngIf="uncollapsed">Account</span>
</span>
</a>
<!-- has to be an a tag for the nav list to work, ugly-->
<a mat-list-item (click)="logout()">
<span class="text-with-icon">
<mat-icon>logout</mat-icon>
<span *ngIf="uncollapsed">Logout</span>
</span>
</a>
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content class="sidenav-content">
<router-outlet/>
</mat-sidenav-content>
</mat-sidenav-container>

<router-outlet/>
32 changes: 0 additions & 32 deletions frontend/indiestream/src/app/app.component.scss
Original file line number Diff line number Diff line change
@@ -1,33 +1 @@
.left-toolbar {
display: flex;
align-items: center;
}

.right-toolbar {
padding-right: 1rem;
display: flex;
align-items: center;
}

.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
}

.sidenav-container {
position: fixed;
height: 100%;
width: 100%;
}

.sidenav-content {
padding-left: 0.25rem;
padding-right: 0.25rem;
}

.user-avatar {
border-radius: 50%;
width: 44px;
height: 44px;
}
16 changes: 2 additions & 14 deletions frontend/indiestream/src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AppComponent } from './app.component';
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {OAuthService} from "angular-oauth2-oidc";

/*
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
Expand All @@ -15,17 +16,4 @@ describe('AppComponent', () => {
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});

it(`should have the 'indiegamestream' title`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('IndieGameStream');
});

it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('IndieGameStream');
});
});
});*/
50 changes: 4 additions & 46 deletions frontend/indiestream/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,16 @@
import {Component, ViewChild} from '@angular/core';
import {RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router';
import {CommonModule, NgOptimizedImage} from "@angular/common";
import { MatButtonModule } from '@angular/material/button';
import { HttpClientModule } from "@angular/common/http";
import { GameUploadComponent } from "./components/game-upload/game-upload.component";
import { GamesOverviewComponent } from "./components/games-overview/games-overview.component";
import { MatToolbar } from "@angular/material/toolbar";
import { MatIcon } from "@angular/material/icon";
import { MatSidenav, MatSidenavContainer, MatSidenavContent } from '@angular/material/sidenav';
import { MatListItem, MatNavList } from "@angular/material/list";
import { Location } from '@angular/common';
import {AuthService} from "./services/auth.service";
import {OAuthService} from "angular-oauth2-oidc";
import { Component } from '@angular/core';
import {RouterOutlet} from "@angular/router";

@Component({
selector: 'app-root',
standalone: true,
imports: [
RouterOutlet,
CommonModule,
MatButtonModule,
HttpClientModule,
GameUploadComponent,
GamesOverviewComponent,
MatToolbar,
MatIcon,
MatSidenav,
MatSidenavContainer,
MatNavList,
MatListItem,
MatSidenavContent,
MatSidenav,
MatSidenavContainer,
RouterLink,
RouterLinkActive,
NgOptimizedImage
RouterOutlet
],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
title = 'IndieGameStream';
status: string = '';
@ViewChild(MatSidenav)
sidenav!: MatSidenav;
uncollapsed = true;

constructor(public authService: AuthService, private oAuthService: OAuthService) { }

toggleMenu() {
this.uncollapsed = !this.uncollapsed;
}

logout() {
this.authService.logout();
}
}

26 changes: 10 additions & 16 deletions frontend/indiestream/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,23 @@ import {GamesOverviewComponent} from "./components/games-overview/games-overview
import {AccountComponent} from "./components/account/account.component";
import {LandingPageComponent} from "./components/landing-page/landing-page.component";
import {AuthGuard} from "./guards/auth.guard";
import {LayoutComponent} from "./components/layout/layout.component";

export const routes: Route[] = [
{
path: '',
component: LandingPageComponent,
pathMatch: 'full',
},
{
path: 'dashboard',
component: GamesOverviewComponent,
canActivate: [AuthGuard],
},
{
path: 'upload',
component: GameUploadComponent,
canActivate: [AuthGuard],
},
{
path: 'account',
component: AccountComponent,
path: '',
component: LayoutComponent,
canActivate: [AuthGuard],
},
{
path: '**',
redirectTo: '',
children: [
{ path: 'dashboard', component: GamesOverviewComponent },
{ path: 'upload', component: GameUploadComponent },
{ path: 'account', component: AccountComponent },
{ path: '**', redirectTo: 'dashboard', pathMatch: 'full' }
]
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,26 @@
<mat-form-field subscriptSizing="dynamic" class="input-title">
<mat-label>Title of your game</mat-label>
<input matInput type="text" formControlName="title">
<mat-error *ngIf="gameForm.get('title')?.hasError('required')">
A title is required
</mat-error>
<mat-error *ngIf="gameForm.get('title')?.hasError('maxlength')">
Max 20 characters allowed
</mat-error>
<mat-error *ngIf="gameForm.get('title')?.hasError('pattern')">
Title must be alphanumeric only
</mat-error>
</mat-form-field>
<mat-form-field subscriptSizing="dynamic">
<input type="file" class="input-file" (change)="onFileSelected($event)" #fileUpload>
<mat-label> Chose game</mat-label>
<input type="text" readonly matInput formControlName="filename">
<mat-error *ngIf="gameForm.get('filename')?.hasError('required')">
A game is required
</mat-error>
<mat-error *ngIf="gameForm.get('filename')?.hasError('invalidExtension')">
Allowed extensions are: {{this.allowedExtensions.join(', ')}}
</mat-error>
<button type="button" mat-icon-button matSuffix color="primary"
(click)="fileUpload.click()">
<mat-icon>attach_file</mat-icon>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { MatButtonModule } from '@angular/material/button';
import { MatProgressBar } from "@angular/material/progress-bar";
import { MatIcon } from "@angular/material/icon";
import { HttpClientModule, HttpEventType } from "@angular/common/http";
import { catchError, EMPTY, finalize, Subscription } from "rxjs";
import { catchError, EMPTY, finalize, tap, Subscription } from "rxjs";
import { NgIf } from "@angular/common";
import { GamesService } from "../../services/games.service";
import { MatFormField, MatHint, MatInput, MatLabel, MatSuffix } from "@angular/material/input";
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
import { MatError, MatFormField, MatHint, MatInput, MatLabel, MatSuffix } from "@angular/material/input";
import { AbstractControl, FormBuilder, ReactiveFormsModule, ValidationErrors, Validators } from "@angular/forms";
import { Router } from '@angular/router';

@Component({
selector: 'app-game-upload',
Expand All @@ -24,21 +25,30 @@ import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
MatSuffix,
ReactiveFormsModule,
HttpClientModule,
MatError,
],
templateUrl: './game-upload.component.html',
styleUrl: './game-upload.component.scss'
})
export class GameUploadComponent {
readonly allowedExtensions: string[] = ['.gba', '.gbc', '.nes', '.n64', '.v64', '.z64'];
readonly titleRegex: string = '^[a-zA-Z0-9-]+$';

uploadProgress: number = 0;
uploadSub: Subscription = new Subscription();
gameForm = this.fb.group({
title: ['', Validators.required],
filename: ['', Validators.required],
title: ['', {
validators: [Validators.required, Validators.maxLength(20), Validators.pattern(this.titleRegex)]
}],
filename: ['', {
validators: [Validators.required, this.fileExtensionValidator()]
}],
file: [new DataTransfer().files, Validators.required]
});

constructor(private gamesService: GamesService, private fb: FormBuilder) {}
constructor(private gamesService: GamesService, private fb: FormBuilder,
private router: Router
) {}

onFileSelected(event: any) {
const file:File = event.target.files[0];
Expand All @@ -60,8 +70,10 @@ export class GameUploadComponent {
);

this.uploadSub = upload$.subscribe(event => {
if (event.type == HttpEventType.UploadProgress && event.total !== undefined) {
if (event.type === HttpEventType.UploadProgress && event.total !== undefined) {
this.uploadProgress = Math.round(100 * (event.loaded / event.total));
} else if (event.type === HttpEventType.Response) {
this.router.navigate(['dashboard']);
}
})
}
Expand All @@ -73,4 +85,15 @@ export class GameUploadComponent {
this.uploadSub = new Subscription();
this.gameForm.reset();
}

fileExtensionValidator(): Validators {
return (control: AbstractControl): ValidationErrors | null => {
if (!control.value) {
return null; // Return null if there's no value (valid case)
}
const fileName: string = control.value;
const isValid = this.allowedExtensions.some(ext => fileName.toLowerCase().endsWith(ext.toLowerCase()));
return isValid ? null : { invalidExtension: { value: fileName } };
};
}
}
Loading