diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 2e9431a..1ff2e6e 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,6 +1,6 @@ import {Component} from '@angular/core'; import {RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router'; -import {ToastContainerComponent} from '../components/toast-container/toast-container/toast-container.component'; +import {ToastContainerComponent} from '../components/toast-container/toast-container.component'; import {NgbCollapse} from '@ng-bootstrap/ng-bootstrap'; import {routes} from './app.routes'; diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index e6930b8..9001451 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -1,5 +1,6 @@ import {Routes} from '@angular/router'; import {ShowsComponent} from '../pages/shows/shows.component'; +import {GenresComponent} from '../pages/genres/genres.component'; export const routes: Routes = [ { @@ -10,5 +11,9 @@ export const routes: Routes = [ { path: 'shows', component: ShowsComponent + }, + { + path: 'genres', + component: GenresComponent } ]; diff --git a/src/components/create-modal/create-edit-modal/create-edit-modal.component.css b/src/components/create-edit-modal/create-edit-modal.component.css similarity index 100% rename from src/components/create-modal/create-edit-modal/create-edit-modal.component.css rename to src/components/create-edit-modal/create-edit-modal.component.css diff --git a/src/components/create-edit-modal/create-edit-modal.component.html b/src/components/create-edit-modal/create-edit-modal.component.html new file mode 100644 index 0000000..145dbe0 --- /dev/null +++ b/src/components/create-edit-modal/create-edit-modal.component.html @@ -0,0 +1,62 @@ + +
+ + +
diff --git a/src/components/create-modal/create-edit-modal/create-edit-modal.component.spec.ts b/src/components/create-edit-modal/create-edit-modal.component.spec.ts similarity index 100% rename from src/components/create-modal/create-edit-modal/create-edit-modal.component.spec.ts rename to src/components/create-edit-modal/create-edit-modal.component.spec.ts diff --git a/src/components/create-modal/create-edit-modal/create-edit-modal.component.ts b/src/components/create-edit-modal/create-edit-modal.component.ts similarity index 65% rename from src/components/create-modal/create-edit-modal/create-edit-modal.component.ts rename to src/components/create-edit-modal/create-edit-modal.component.ts index caeac9d..6681b39 100644 --- a/src/components/create-modal/create-edit-modal/create-edit-modal.component.ts +++ b/src/components/create-edit-modal/create-edit-modal.component.ts @@ -1,12 +1,12 @@ import {Component, inject} from '@angular/core'; import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap'; -import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; -import {ShowsApiService} from '../../../services/shows/shows-api.service'; -import {ShowsApiCreation} from '../../../interfaces/shows-api-creation'; -import {ToastService} from '../../../services/toast/toast.service'; -import {Show} from '../../../interfaces/show'; +import {FormArray, FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; +import {ShowsApiService} from '../../services/shows/shows-api.service'; +import {ApiCreationResponse} from '../../interfaces/api-creation-response'; +import {ToastService} from '../../services/toast/toast.service'; +import {Show} from '../../interfaces/show'; import {formatDate} from '@angular/common'; -import {ShowsApiDeletionEdit} from '../../../interfaces/shows-api-deletion-edit'; +import {ApiDeletionEditResponse} from '../../interfaces/api-deletion-edit-response'; @Component({ selector: 'app-create-edit-modal', @@ -24,31 +24,59 @@ export class CreateEditModalComponent { protected editMode: boolean = false protected show?: Show + protected requiredImages: number constructor() { + this.requiredImages = 3 this.initForm() } private initForm() { let formattedDate: string - this.newShowForm = new FormGroup({ - title: new FormControl(this.show?.title, Validators.required), - seasons: new FormControl(this.show?.seasons, [Validators.required, Validators.min(1)]), - episodes: new FormControl(this.show?.episodes, [Validators.required, Validators.min(1)]), - description: new FormControl(this.show?.description, Validators.required) - }) if (this.show?.date !== undefined) { formattedDate = formatDate(this.show?.date, "YYYY-MM-dd", "en") } else { formattedDate = "" } - this.newShowForm.addControl("date", new FormControl(formattedDate, Validators.required)) + this.newShowForm = new FormGroup({ + title: new FormControl(this.show?.title, Validators.required), + seasons: new FormControl(this.show?.seasons, [Validators.required, Validators.min(1)]), + date: new FormControl(formattedDate, Validators.required), + episodes: new FormControl(this.show?.episodes, [Validators.required, Validators.min(1)]), + description: new FormControl(this.show?.description, Validators.required), + images: new FormArray([]) + }) + + if (this.show?.images !== undefined) { + this.show?.images.forEach((imageUrl: string) => { + this.addImageControl(imageUrl) + }) + } else { + let i: number = this.requiredImages + do { + this.addImageControl() + i-- + } while (i != 0) + } + } + + get images(): FormArray { + return this.newShowForm.get("images") as FormArray } protected dismiss() { this.activeModal.dismiss() } + protected addImageControl(value: string = "") { + const urlRegex = "(https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9]+\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]+\\.[^\\s]{2,})" + this.images.push(new FormControl(value, [Validators.required, Validators.pattern(urlRegex)])) + } + + protected removeImageControl(index: number) { + this.images.removeAt(index) + } + protected formSubmitted(form: FormGroup) { let show: Show = { title: form.get("title")?.value, @@ -56,14 +84,14 @@ export class CreateEditModalComponent { seasons: form.get("seasons")?.value, episodes: form.get("episodes")?.value, description: form.get("description")?.value, + images: form.get("images")?.value, //TODO: Allow user to specify genres genres: [] } - if (!this.editMode) { this.showsService.sendShow(show).subscribe({ - next: (response: ShowsApiCreation) => { + next: (response: ApiCreationResponse) => { show._id = response.newId }, error: err => { this.showToast(true, "created") @@ -78,7 +106,7 @@ export class CreateEditModalComponent { // I add it here show._id = this.show?._id this.showsService.updateShow(show).subscribe({ - next: (response: ShowsApiDeletionEdit) => { + next: (response: ApiDeletionEditResponse) => { // Do nothing }, error: err => { this.showToast(true, "edited") diff --git a/src/components/create-modal/create-edit-modal/create-edit-modal.component.html b/src/components/create-modal/create-edit-modal/create-edit-modal.component.html deleted file mode 100644 index 951fd1b..0000000 --- a/src/components/create-modal/create-edit-modal/create-edit-modal.component.html +++ /dev/null @@ -1,38 +0,0 @@ - -
- - -
diff --git a/src/components/toast-container/toast-container/toast-container.component.css b/src/components/toast-container/toast-container.component.css similarity index 100% rename from src/components/toast-container/toast-container/toast-container.component.css rename to src/components/toast-container/toast-container.component.css diff --git a/src/components/toast-container/toast-container/toast-container.component.html b/src/components/toast-container/toast-container.component.html similarity index 100% rename from src/components/toast-container/toast-container/toast-container.component.html rename to src/components/toast-container/toast-container.component.html diff --git a/src/components/toast-container/toast-container/toast-container.component.spec.ts b/src/components/toast-container/toast-container.component.spec.ts similarity index 100% rename from src/components/toast-container/toast-container/toast-container.component.spec.ts rename to src/components/toast-container/toast-container.component.spec.ts diff --git a/src/components/toast-container/toast-container/toast-container.component.ts b/src/components/toast-container/toast-container.component.ts similarity index 84% rename from src/components/toast-container/toast-container/toast-container.component.ts rename to src/components/toast-container/toast-container.component.ts index c1eac7a..6538dea 100644 --- a/src/components/toast-container/toast-container/toast-container.component.ts +++ b/src/components/toast-container/toast-container.component.ts @@ -1,5 +1,5 @@ import {Component, inject} from '@angular/core'; -import {ToastService} from '../../../services/toast/toast.service'; +import {ToastService} from '../../services/toast/toast.service'; import {NgbToast} from '@ng-bootstrap/ng-bootstrap'; @Component({ diff --git a/src/interfaces/shows-api-creation.ts b/src/interfaces/api-creation-response.ts similarity index 57% rename from src/interfaces/shows-api-creation.ts rename to src/interfaces/api-creation-response.ts index bf97065..3493889 100644 --- a/src/interfaces/shows-api-creation.ts +++ b/src/interfaces/api-creation-response.ts @@ -1,4 +1,4 @@ -export interface ShowsApiCreation { +export interface ApiCreationResponse { status: string message: string newId: string diff --git a/src/interfaces/api-deletion-edit-response.ts b/src/interfaces/api-deletion-edit-response.ts new file mode 100644 index 0000000..abc7095 --- /dev/null +++ b/src/interfaces/api-deletion-edit-response.ts @@ -0,0 +1,4 @@ +export interface ApiDeletionEditResponse { + status: string + message: string +} diff --git a/src/interfaces/api-id-response.ts b/src/interfaces/api-id-response.ts new file mode 100644 index 0000000..e667657 --- /dev/null +++ b/src/interfaces/api-id-response.ts @@ -0,0 +1,8 @@ +import {Show} from './show'; +import {Genre} from './genre'; + +export interface ApiIdResponse { + status: string + show?: Show + genre?: Genre +} diff --git a/src/interfaces/api-response.ts b/src/interfaces/api-response.ts new file mode 100644 index 0000000..dfede6f --- /dev/null +++ b/src/interfaces/api-response.ts @@ -0,0 +1,9 @@ +import {Show} from './show'; +import {Genre} from './genre'; + +export interface ApiResponse { + status: string + shows?: Show[] + genres?: Genre[] + totalShows: number +} diff --git a/src/interfaces/genre.ts b/src/interfaces/genre.ts new file mode 100644 index 0000000..c1e9d6e --- /dev/null +++ b/src/interfaces/genre.ts @@ -0,0 +1,6 @@ +export interface Genre { + // ID is assigned by the DB, so I don't want to have to specify it + _id?: string + name: string + showIDs: string[] +} diff --git a/src/interfaces/show.ts b/src/interfaces/show.ts index bdb1933..a81e707 100644 --- a/src/interfaces/show.ts +++ b/src/interfaces/show.ts @@ -7,4 +7,5 @@ export interface Show { episodes: number description: string genres: string[] + images: string[] } diff --git a/src/interfaces/shows-api-deletion-edit.ts b/src/interfaces/shows-api-deletion-edit.ts deleted file mode 100644 index a39c6ca..0000000 --- a/src/interfaces/shows-api-deletion-edit.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface ShowsApiDeletionEdit { - status: string - message: string -} diff --git a/src/interfaces/shows-api-id-response.ts b/src/interfaces/shows-api-id-response.ts deleted file mode 100644 index 85307d7..0000000 --- a/src/interfaces/shows-api-id-response.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {Show} from './show'; - -export interface ShowsApiIdResponse { - status: string - show: Show -} diff --git a/src/interfaces/shows-api-response.ts b/src/interfaces/shows-api-response.ts deleted file mode 100644 index f0bf994..0000000 --- a/src/interfaces/shows-api-response.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {Show} from './show'; - -export interface ShowsApiResponse { - status: string - shows: Show[] - totalShows: number -} diff --git a/src/pages/genres/genres.component.css b/src/pages/genres/genres.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/genres/genres.component.html b/src/pages/genres/genres.component.html new file mode 100644 index 0000000..924eeba --- /dev/null +++ b/src/pages/genres/genres.component.html @@ -0,0 +1 @@ +

genres works!

diff --git a/src/pages/genres/genres.component.spec.ts b/src/pages/genres/genres.component.spec.ts new file mode 100644 index 0000000..40327a5 --- /dev/null +++ b/src/pages/genres/genres.component.spec.ts @@ -0,0 +1,23 @@ +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {GenresComponent} from './genres.component'; + +describe('CategoriesComponent', () => { + let component: GenresComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [GenresComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(GenresComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/pages/genres/genres.component.ts b/src/pages/genres/genres.component.ts new file mode 100644 index 0000000..8ac0f86 --- /dev/null +++ b/src/pages/genres/genres.component.ts @@ -0,0 +1,11 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'app-genres', + imports: [], + templateUrl: './genres.component.html', + styleUrl: './genres.component.css' +}) +export class GenresComponent { + +} diff --git a/src/pages/shows/shows.component.html b/src/pages/shows/shows.component.html index 8f6cc94..6cefc1d 100644 --- a/src/pages/shows/shows.component.html +++ b/src/pages/shows/shows.component.html @@ -1,17 +1,19 @@
-
+
@for (show of shows; track show._id) { -
- -
-
{{ show.title }}
-

{{ show.date | date }}

- - +
+
+ +
+
{{ show.title }}
+

{{ show.date | date }}

+ + +
} diff --git a/src/pages/shows/shows.component.ts b/src/pages/shows/shows.component.ts index 6c1eefb..65cd046 100644 --- a/src/pages/shows/shows.component.ts +++ b/src/pages/shows/shows.component.ts @@ -1,11 +1,11 @@ import {Component, inject} from '@angular/core'; import {ShowsApiService} from '../../services/shows/shows-api.service'; import {Show} from '../../interfaces/show'; -import {ShowsApiResponse} from '../../interfaces/shows-api-response'; +import {ApiResponse} from '../../interfaces/api-response'; import {Toast} from '../../interfaces/toast'; import {ToastService} from '../../services/toast/toast.service'; import {NgbModal, NgbModalRef} from '@ng-bootstrap/ng-bootstrap'; -import {CreateEditModalComponent} from '../../components/create-modal/create-edit-modal/create-edit-modal.component'; +import {CreateEditModalComponent} from '../../components/create-edit-modal/create-edit-modal.component'; import {DatePipe} from '@angular/common'; import {DeleteModalComponent} from '../../components/delete-modal/delete-modal.component'; @@ -29,8 +29,8 @@ export class ShowsComponent { let loadToast: Toast = {body: "Loading shows..."}; this.toastService.show(loadToast); this.api.getShows().subscribe({ - next: (response: ShowsApiResponse) => { - this.shows = response.shows; + next: (response: ApiResponse) => { + this.shows = response.shows ?? []; }, error: (err: any) => { console.error("Error: ", err); }, complete: () => { diff --git a/src/services/genres/genres.service.spec.ts b/src/services/genres/genres.service.spec.ts new file mode 100644 index 0000000..8d71cf8 --- /dev/null +++ b/src/services/genres/genres.service.spec.ts @@ -0,0 +1,16 @@ +import {TestBed} from '@angular/core/testing'; + +import {GenresService} from './genres.service'; + +describe('GenresService', () => { + let service: GenresService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(GenresService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/services/genres/genres.service.ts b/src/services/genres/genres.service.ts new file mode 100644 index 0000000..c8e1c7f --- /dev/null +++ b/src/services/genres/genres.service.ts @@ -0,0 +1,40 @@ +import {inject, Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {Observable} from 'rxjs'; +import {ApiResponse} from '../../interfaces/api-response'; +import {ApiIdResponse} from '../../interfaces/api-id-response'; +import {Show} from '../../interfaces/show'; +import {ApiCreationResponse} from '../../interfaces/api-creation-response'; +import {ApiDeletionEditResponse} from '../../interfaces/api-deletion-edit-response'; + +@Injectable({ + providedIn: 'root' +}) +export class GenresService { + + private readonly url: string = "https://shows.toast003.xyz/api/"; + private genresEndpoint: string + private idEndpoint: string + private http: HttpClient = inject(HttpClient); + + constructor() { + this.genresEndpoint = this.url + "genres/" + this.idEndpoint = this.genresEndpoint + "id/" + } + + getGenres(): Observable { + return this.http.get(this.genresEndpoint) + } + + getGenre(id: string): Observable { + return this.http.get(this.idEndpoint + id) + } + + sendGenre(newShow: Show): Observable { + return this.http.post(this.genresEndpoint, newShow) + } + + updateGenre(show: Show): Observable { + return this.http.put(this.idEndpoint + show._id, show) + } +} diff --git a/src/services/shows/shows-api.service.ts b/src/services/shows/shows-api.service.ts index c8c91d9..36c46c3 100644 --- a/src/services/shows/shows-api.service.ts +++ b/src/services/shows/shows-api.service.ts @@ -1,18 +1,18 @@ import {inject, Injectable} from '@angular/core'; import {HttpClient} from '@angular/common/http'; import {Observable} from 'rxjs'; -import {ShowsApiResponse} from '../../interfaces/shows-api-response'; -import {ShowsApiCreation} from '../../interfaces/shows-api-creation'; -import {ShowsApiIdResponse} from '../../interfaces/shows-api-id-response'; -import {ShowsApiDeletionEdit} from '../../interfaces/shows-api-deletion-edit'; +import {ApiResponse} from '../../interfaces/api-response'; +import {ApiIdResponse} from '../../interfaces/api-id-response'; +import {ApiDeletionEditResponse} from '../../interfaces/api-deletion-edit-response'; import {Show} from '../../interfaces/show'; +import {ApiCreationResponse} from '../../interfaces/api-creation-response'; @Injectable({ providedIn: 'root' }) export class ShowsApiService { - private readonly url: string = "https://shows.everest.tailscale/api/"; + private readonly url: string = "https://shows.toast003.xyz/api/"; private showsEndpoint: string private idEndpoint: string private http: HttpClient = inject(HttpClient); @@ -22,23 +22,23 @@ export class ShowsApiService { this.idEndpoint = this.showsEndpoint + "id/" } - getShows(): Observable { - return this.http.get(this.showsEndpoint) + getShows(): Observable { + return this.http.get(this.showsEndpoint) } - getShow(id: string): Observable { - return this.http.get(this.idEndpoint + id) + getShow(id: string): Observable { + return this.http.get(this.idEndpoint + id) } - sendShow(newShow: Show): Observable { - return this.http.post(this.showsEndpoint, newShow) + sendShow(newShow: Show): Observable { + return this.http.post(this.showsEndpoint, newShow) } - deleteShw(id: string): Observable { - return this.http.delete(this.idEndpoint + id) + deleteShw(id: string): Observable { + return this.http.delete(this.idEndpoint + id) } - updateShow(show: Show): Observable { - return this.http.put(this.idEndpoint + show._id, show) + updateShow(show: Show): Observable { + return this.http.put(this.idEndpoint + show._id, show) } }