Skip to content

Commit

Permalink
Merge pull request #117 from theImmortalCoders/dev
Browse files Browse the repository at this point in the history
Deploy
  • Loading branch information
pablitoo1 authored Nov 12, 2024
2 parents 4f87e43 + 03e2117 commit 4387e6f
Show file tree
Hide file tree
Showing 70 changed files with 2,329 additions and 555 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ This project is available under the AGPL License. See [LICENSE](./LICENSE) for m

The application was created as part of the work of the Human-Computer Interaction Scientific Club "GEST" at the Rzeszów University of Technology.

## Compliance and distribution

If you modify this code and make it available to others, you must also make the modified source code available under the same AGPL-3.0 License. This applies especially if you deploy the modified software on a server or otherwise make it available over a network, in which case you must provide access to the modified source code to the users who interact with your version of the application.

Failure to comply with the terms of the AGPL license may result in legal consequences. It is important to follow the license terms when modifying, distributing, or deploying this software. Please ensure you understand and follow the obligations set forth in the [LICENSE](LICENSE) file.

## Description

Application with web minigames prepared to collect game data and connect AI models to game agents via websocket.
A web application with mini-games that collects game data and connects AI models to game agents via WebSocket. Users can interact with game data, view statistics, and manage game history through dashboard panel. The system supports different user roles and simplifies adding new game titles.

## Features

Expand Down
Binary file added public/images/games/pong.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ResetPasswordComponent } from './user-workflow/login/components/reset-p
import { DashboardPageComponent } from './dashboard/dashboard.page.component';
import { authGuard } from '@utils/helpers/auth.guard';
import { guestGuard } from '@utils/helpers/guest.guard';
import { GameListPageComponent } from './game-list/game-list.page.component';

export const routes: Routes = [
{
Expand All @@ -33,6 +34,11 @@ export const routes: Routes = [
component: GamePageComponent,
title: 'Game Page',
},
{
path: 'game-list',
component: GameListPageComponent,
title: 'Game List Page',
},
{
path: 'login',
canActivate: [guestGuard],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ describe('GameHandlingOptionsComponent', () => {

it('should open the edit game modal and fetch games', fakeAsync(() => {
const mockGames: IGameResponse[] = [
{ id: 1, name: 'Game 1' },
{ id: 2, name: 'Game 2' },
{ id: 1, name: 'Game 1', description: 'ee' },
{ id: 2, name: 'Game 2', description: 'bb' },
];
mockGameEndpointsService.getGames.and.returnValue(of(mockGames));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,24 @@ import {
class="flex flex-col space-y-4 w-full text-sm sm:text-base">
@if (modalVisibility === 'addNewGame') {
<div class="flex flex-col space-y-1">
<label for="newGameName" class="text-start"
>New game name</label
>
<label for="newGameName" class="text-start">Game name</label>
<input
id="newGameName"
type="text"
formControlName="newGameName"
placeholder="Type new game name"
class="custom-input" />
<label for="newGameDescription" class="text-start"
>Game description</label
>
<textarea
id="newGameDescription"
type="text"
formControlName="newGameDescription"
placeholder="Type new game description"
class="custom-input resize-none"
[rows]="5"
[maxlength]="280"></textarea>
</div>
} @else if (
(modalVisibility === 'editGame' ||
Expand Down Expand Up @@ -93,6 +102,17 @@ import {
formControlName="editedGameName"
placeholder="Type edited game name"
class="custom-input" />
<label for="editedGameDescription" class="text-start"
>Edited game description</label
>
<textarea
id="editedGameDescription"
type="text"
formControlName="editedGameDescription"
placeholder="Type edited game description"
class="custom-input resize-none"
[rows]="5"
[maxlength]="280"></textarea>
</div>
}
</form>
Expand Down Expand Up @@ -130,7 +150,9 @@ export class GameHandlingOptionsComponent implements OnDestroy {

public gameHandlingForm = this._formBuilder.group({
newGameName: ['', [Validators.required]],
newGameDescription: ['', [Validators.required]],
editedGameName: ['', [Validators.required]],
editedGameDescription: ['', [Validators.required]],
});

public gameList: IGameResponse[] | null = null;
Expand All @@ -147,6 +169,17 @@ export class GameHandlingOptionsComponent implements OnDestroy {
const target = event.target as HTMLSelectElement;
const selectedId = target?.value;
this.selectedGameId = parseInt(selectedId, 10);
const selectedGame = this.gameList?.find(
game => game.id === this.selectedGameId
);
const selectedGameName = selectedGame ? selectedGame.name : '';
const selectedGameDesctiption = selectedGame
? selectedGame.description
: '';
this.gameHandlingForm.controls.editedGameName.setValue(selectedGameName);
this.gameHandlingForm.controls.editedGameDescription.setValue(
selectedGameDesctiption
);
}

public getGameList(): void {
Expand Down Expand Up @@ -193,10 +226,16 @@ export class GameHandlingOptionsComponent implements OnDestroy {

public addNewGameFunction(): void {
this.errorMessage = null;
if (this.gameHandlingForm.value.newGameName) {
if (
this.gameHandlingForm.value.newGameName &&
this.gameHandlingForm.value.newGameDescription
) {
const formValues = this.gameHandlingForm.value;
if (formValues.newGameName) {
const gameData: IGameRequest = { name: formValues.newGameName };
if (formValues.newGameName && formValues.newGameDescription) {
const gameData: IGameRequest = {
name: formValues.newGameName,
description: formValues.newGameDescription,
};
this._addGameSubscription = this._gameEndpointsService
.addGame(gameData)
.subscribe({
Expand All @@ -220,17 +259,21 @@ export class GameHandlingOptionsComponent implements OnDestroy {
this.errorMessage = null;
if (
this.gameHandlingForm.value.editedGameName &&
this.gameHandlingForm.value.editedGameDescription &&
this.selectedGameId !== 0
) {
const formValues = this.gameHandlingForm.value;
if (formValues.editedGameName) {
const gameData: IGameRequest = { name: formValues.editedGameName };
if (formValues.editedGameName && formValues.editedGameDescription) {
const gameData: IGameRequest = {
name: formValues.editedGameName,
description: formValues.editedGameDescription,
};
this._editGameSubscription = this._gameEndpointsService
.updateGame(this.selectedGameId, gameData)
.subscribe({
next: () => {
this._notificationService.addNotification(
'Existing game name has been changed!',
'Existing game has been edited!',
3000
);
this.errorMessage = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ describe('RecordedGamesComponent', () => {
});

it('should get recorded games when available games are fetched', fakeAsync(() => {
const mockGames: IGameResponse[] = [{ id: 1, name: 'Game 1' }];
const mockGames: IGameResponse[] = [
{ id: 1, name: 'Game 1', description: 'dfgdfg' },
];
const mockRecordedGames: IRecordedGameResponse[] = [
{
id: 1,
Expand All @@ -63,6 +65,7 @@ describe('RecordedGamesComponent', () => {
endState: {},
outputSpec: '',
players: [],
sizeMb: 2,
},
];
mockGameEndpointsService.getGames.and.returnValue(of(mockGames));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { NotificationService } from 'app/shared/services/notification.service';
<span class="flex justify-center w-2/12">Game name</span>
<span class="flex justify-center w-3/12">Game start date</span>
<span class="flex justify-center w-3/12">Game end date</span>
<span class="flex justify-center w-1/12">Size</span>
<span class="flex justify-center w-1/12">Download</span>
<span class="flex justify-center w-1/12">Delete</span>
</div>
Expand All @@ -52,6 +53,9 @@ import { NotificationService } from 'app/shared/services/notification.service';
<span class="flex justify-center w-3/12">{{
recordedGame.ended | date: 'dd/MM/yyyy, HH:mm:ss'
}}</span>
<span class="flex justify-center w-1/12 text-nowrap"
>{{ recordedGame.sizeMb.toPrecision(2) }} MB</span
>
<button
class="flex group justify-center w-1/12"
(click)="downloadGameRecord(recordedGame.id)">
Expand Down
26 changes: 12 additions & 14 deletions src/app/dashboard/dashboard.page.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DashboardPageComponent } from './dashboard.page.component';
import { UserEndpointsService } from '@endpoints/user-endpoints.service';
import { StatsEndpointsService } from '@endpoints/stats-endpoints.service';
import { of, throwError } from 'rxjs';
import {
Expand All @@ -9,32 +8,31 @@ import {
} from 'app/shared/models/user.models';
import { TRole } from 'app/shared/models/role.enum';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { AuthEndpointsService } from '@endpoints/auth-endpoints.service';

describe('DashboardPageComponent', () => {
let component: DashboardPageComponent;
let fixture: ComponentFixture<DashboardPageComponent>;
let userEndpointsService: jasmine.SpyObj<UserEndpointsService>;
let statsEndpointsService: jasmine.SpyObj<StatsEndpointsService>;
let authEndpointsService: jasmine.SpyObj<AuthEndpointsService>;

beforeEach(async () => {
// Dodajemy `verifyJWTToken` z wartością domyślną jako Observable<boolean>
userEndpointsService = jasmine.createSpyObj('UserEndpointsService', [
'getMe',
'verifyJWTToken',
'logout',
]);
statsEndpointsService = jasmine.createSpyObj('StatsEndpointsService', [
'getUserStats',
]);
authEndpointsService = jasmine.createSpyObj('StatsEndpointsService', [
'verifyJWTToken',
'getMe',
'logout',
]);

// Ustawiamy verifyJWTToken, aby zwracał Observable true jako wartość domyślną
userEndpointsService.verifyJWTToken.and.returnValue(of(true));
authEndpointsService.verifyJWTToken.and.returnValue(of(true));

await TestBed.configureTestingModule({
imports: [DashboardPageComponent, HttpClientTestingModule],
providers: [
{ provide: UserEndpointsService, useValue: userEndpointsService },
{ provide: StatsEndpointsService, useValue: statsEndpointsService },
{ provide: AuthEndpointsService, useValue: authEndpointsService },
],
}).compileComponents();

Expand All @@ -60,7 +58,7 @@ describe('DashboardPageComponent', () => {
};

// Mock the getMe method to return a valid user response
userEndpointsService.getMe.and.returnValue(of(userResponse));
authEndpointsService.getMe.and.returnValue(of(userResponse));

const userStatsResponse: IUserStatsResponse = {
games: 15,
Expand All @@ -86,7 +84,7 @@ describe('DashboardPageComponent', () => {

it('should handle error from getMe', () => {
// Mock getMe to throw an error
userEndpointsService.getMe.and.returnValue(throwError('error'));
authEndpointsService.getMe.and.returnValue(throwError('error'));

component.ngOnInit();

Expand All @@ -105,7 +103,7 @@ describe('DashboardPageComponent', () => {
banned: false,
};

userEndpointsService.getMe.and.returnValue(of(userResponse));
authEndpointsService.getMe.and.returnValue(of(userResponse));
statsEndpointsService.getUserStats.and.returnValue(throwError('error'));

component.ngOnInit(); // This triggers the call to getUserStats
Expand Down
10 changes: 3 additions & 7 deletions src/app/dashboard/dashboard.page.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { CommonModule } from '@angular/common';
import {
AfterViewInit,
Component,
Expand All @@ -7,13 +6,12 @@ import {
OnInit,
} from '@angular/core';
import * as feather from 'feather-icons';
import { UserEndpointsService } from '@endpoints/user-endpoints.service';
import { AuthEndpointsService } from '@endpoints/auth-endpoints.service';
import {
IUserResponse,
IUserStatsResponse,
} from 'app/shared/models/user.models';
import { Subscription } from 'rxjs';
import { ProgressCircleBarComponent } from './components/sections/user-info/progress-circle-bar.component';
import { StatsEndpointsService } from '@endpoints/stats-endpoints.service';
import { UserInfoComponent } from './components/sections/user-info/user-info.component';
import { UserAccountSettingsComponent } from './components/sections/user-account-settings/user-account-settings.component';
Expand All @@ -27,8 +25,6 @@ import { TRole } from 'app/shared/models/role.enum';
selector: 'app-dashboard-page',
standalone: true,
imports: [
CommonModule,
ProgressCircleBarComponent,
UserInfoComponent,
UserAccountSettingsComponent,
GameHandlingOptionsComponent,
Expand Down Expand Up @@ -59,7 +55,7 @@ import { TRole } from 'app/shared/models/role.enum';
export class DashboardPageComponent
implements OnInit, OnDestroy, AfterViewInit
{
private _userEndpointsService = inject(UserEndpointsService);
private _authEndpointsService = inject(AuthEndpointsService);
private _statsEndpointsService = inject(StatsEndpointsService);

private _getMeSubscription = new Subscription();
Expand All @@ -72,7 +68,7 @@ export class DashboardPageComponent
public allowedRolesAdminTeacher: TRole[] = [TRole.Admin, TRole.Teacher];

public ngOnInit(): void {
this._getMeSubscription = this._userEndpointsService.getMe().subscribe({
this._getMeSubscription = this._authEndpointsService.getMe().subscribe({
next: (response: IUserResponse) => {
this.aboutMeUserInfo = response;
this.getUserStats(this.aboutMeUserInfo.id);
Expand Down
Loading

0 comments on commit 4387e6f

Please sign in to comment.