From 412a542e034a79253b20b6286c2520c9272c3aaa Mon Sep 17 00:00:00 2001 From: jgerstbe Date: Thu, 29 Oct 2020 23:52:33 +0100 Subject: [PATCH] [A] stroy.service @WIP, huddle.compo - story CRUD @WIP --- src/app/api/supabase/story.service.spec.ts | 16 ++ src/app/api/supabase/story.service.ts | 181 +++++++++++++++++++++ src/app/api/supabase/story.ts | 13 ++ src/app/app.module.ts | 2 + src/app/huddle/huddle.component.html | 2 +- src/app/huddle/huddle.component.ts | 28 +++- src/app/recorder/recorder.component.html | 16 +- src/app/recorder/recorder.component.ts | 21 ++- 8 files changed, 270 insertions(+), 9 deletions(-) create mode 100644 src/app/api/supabase/story.service.spec.ts create mode 100644 src/app/api/supabase/story.service.ts create mode 100644 src/app/api/supabase/story.ts diff --git a/src/app/api/supabase/story.service.spec.ts b/src/app/api/supabase/story.service.spec.ts new file mode 100644 index 0000000..628d70f --- /dev/null +++ b/src/app/api/supabase/story.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { StoryService } from './story.service'; + +describe('StoryService', () => { + let service: StoryService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(StoryService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/api/supabase/story.service.ts b/src/app/api/supabase/story.service.ts new file mode 100644 index 0000000..9ff5aa7 --- /dev/null +++ b/src/app/api/supabase/story.service.ts @@ -0,0 +1,181 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable, of, Subject } from 'rxjs'; +import { Story } from './story'; +import { SupaService } from './supa.service'; + +@Injectable({ + providedIn: 'root' +}) +export class StoryService { + // storyMap: Map = new Map(); + storyMap = {}; + stories: BehaviorSubject = new BehaviorSubject([]); + subscribedStandUpIds: number[] = []; + isListening: boolean = false; + + constructor( + private supa: SupaService, + ) { } + + /** + * Get Storys from local store. + * Requests data if store is emtpy. + * @param standup_id + * @returns Observable + */ + getStories(standup_id: number): Observable { + if (!this.subscribedStandUpIds.includes(standup_id)) { + this.subscribeToStories(standup_id); + console.log('getStories - REFRESH', standup_id) + const subject: Subject = new Subject(); + this.supa.client.from('story').select() + .filter('standup_id', 'eq', standup_id) + .then(data => { + this.updateStore(data.body); + subject.next(data.body); + subject.complete(); + }) + .catch(error => { + subject.error(error); + subject.complete(); + }); + return subject.asObservable(); + } else { + console.log('getStories - LOCAL', standup_id) + const stories = Object.values(this.storyMap).filter(e => e.standup_id === standup_id) + console.log('filteredStories', stories) + return of(stories); + } + } + + /** + * Listen to realtime events from story db. + */ + subscribeToStories(standup_id: number) { + // TODO check for this.subscribedStandUpIds.includes(standup_id) + if (!this.isListening) { + this.subscribedStandUpIds.push(standup_id); + this.supa.client.from('story').on('*', payload => { + console.log('subscribeToStories - REALTIME EVENT', payload) + if ((payload.eventType === 'INSERT') || (payload.eventType === 'UPDATE')) { + this.storyMap[payload.new.id] = payload.new; + } else { + delete this.storyMap[payload.old.id]; + } + this.next(); + }).subscribe(); + } + } + + /** + * Requests up to date data from API. + * Returns the local copy. + * @returns Observable + */ + // refreshStorys(): Observable { + // this.subscribeToStories(); + // this.supa.client.from('story').select() + // .then(stories => this.updateStore(stories.body)) + // .catch(error => console.log('Error: ', error)); + // return this.stories.asObservable(); + // } + + /** + * Update the local store with provided stories + * @param stories + */ + updateStore(stories: Story[]) { + stories.forEach(e => { + // this.storyMap.set(e.id, e); + this.storyMap[e.id] = e; + }); + this.next(); + } + + /** + * Emits local store. + */ + next = () => this.stories.next(Object.values(this.storyMap)); + + /** + * Retrieve story from local store. + * @param id + */ + getOne(id: number) { + if (this.storyMap[id]) { + return of(this.storyMap[id]); + } else { + const subject: Subject = new Subject(); + this.supa.client.from('story').select('id, name, description') + .filter('id', 'eq', id) + .then(data => { + this.updateStore([data.body[0]]); + subject.next(data.body[0]); + subject.complete(); + }) + .catch(error => { + subject.error(error); + subject.complete(); + }); + return subject.asObservable(); + } + } + + /** + * + * @param story + */ + updateOne(story: Story): Observable { + const subject: Subject = new Subject(); + this.supa.client.from('story').update(story) + .match({ id: story.id }) + .then(data => { + subject.next(data.body[0]); + subject.complete(); + }) + .catch(error => { + subject.error(error); + subject.complete(); + }); + return subject.asObservable(); + } + + + /** + * Removes one story from db. + * @param story + */ + deleteOne(story: Story): Observable { + const subject: Subject = new Subject(); + this.supa.client.from('story').delete() + .match({ id: story.id }) + .then(data => { + subject.next(story); + subject.complete(); + }) + .catch(error => { + subject.error(error); + subject.complete(); + }); + return subject.asObservable(); + } + + /** + * Creates a story on the db. + * @param story + */ + addOne(story: Story): Observable { + const subject: Subject = new Subject(); + this.supa.client.from('story').insert(story) + .then(data => { + subject.next(data.body[0]); + subject.complete(); + }) + .catch(error => { + subject.error(error); + subject.complete(); + }); + return subject.asObservable(); + } + +} diff --git a/src/app/api/supabase/story.ts b/src/app/api/supabase/story.ts new file mode 100644 index 0000000..d93208d --- /dev/null +++ b/src/app/api/supabase/story.ts @@ -0,0 +1,13 @@ +export class Story { + id: number; + standup_id: number; + user_id: string + base64: string; + created_at: string | Date; + + constructor(user_id: string, standup_id: number, base64: string) { + this.user_id = user_id; + this.standup_id = standup_id; + this.base64 = base64; + } +} \ No newline at end of file diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 9a62c19..3164f90 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -7,6 +7,7 @@ import { FormsModule } from '@angular/forms'; import { SupaService } from './api/supabase/supa.service'; import { StandupService } from './api/supabase/standup.service'; +import { StoryService } from './api/supabase/story.service'; import { AuthGuard } from './authguard.service'; import { AppComponent } from "./app.component"; @@ -40,6 +41,7 @@ import { DashboardComponent } from './dashboard/dashboard.component'; providers: [ SupaService, StandupService, + StoryService, AuthGuard, ], bootstrap: [ diff --git a/src/app/huddle/huddle.component.html b/src/app/huddle/huddle.component.html index c810131..f6f84cd 100644 --- a/src/app/huddle/huddle.component.html +++ b/src/app/huddle/huddle.component.html @@ -10,7 +10,7 @@
- +
diff --git a/src/app/huddle/huddle.component.ts b/src/app/huddle/huddle.component.ts index ab1f1d5..77a9d03 100644 --- a/src/app/huddle/huddle.component.ts +++ b/src/app/huddle/huddle.component.ts @@ -1,8 +1,11 @@ import { Component, OnInit, ViewChild } from "@angular/core"; import { ActivatedRoute } from '@angular/router'; import { NgbModal, NgbModalRef } from "@ng-bootstrap/ng-bootstrap"; +import { SupabaseAuthUser } from '@supabase/supabase-js'; import { StandUp } from '../api/supabase/standup'; import { StandupService } from '../api/supabase/standup.service'; +import { Story } from '../api/supabase/story'; +import { StoryService } from '../api/supabase/story.service'; import { SupaService } from '../api/supabase/supa.service'; @Component({ @@ -37,10 +40,13 @@ export class HuddleComponent implements OnInit { ]; selectedUser; standup: StandUp; + stories: Story[] = []; constructor( private modalService: NgbModal, private standupService: StandupService, + private storyService: StoryService, + private supaService: SupaService, private route: ActivatedRoute, ) {} @@ -52,6 +58,7 @@ export class HuddleComponent implements OnInit { data => { console.log(data); this.standup = data; + this.loadStories(data.id).subscribe(stories => this.stories = stories); }, error => { console.error(error); @@ -133,7 +140,26 @@ export class HuddleComponent implements OnInit { error => { console.error('Failed', error); } - ) + ); + } + + async uploadStory(base64: string) { + console.log('uploadStory', event); + const user: SupabaseAuthUser = await this.supaService.client.auth.user(); + // TODO ID muss in DB noch autoincremented werden. + const story = new Story(user.id, this.standup.id, base64); + this.storyService.addOne(story).subscribe( + data => { + console.log('Success', data); + }, + error => { + console.error('Failed', error); + } + ); + } + + loadStories(id: number) { + return this.storyService.getStories(id); } } diff --git a/src/app/recorder/recorder.component.html b/src/app/recorder/recorder.component.html index 5ceb59f..7177370 100644 --- a/src/app/recorder/recorder.component.html +++ b/src/app/recorder/recorder.component.html @@ -1,11 +1,21 @@ - + diff --git a/src/app/recorder/recorder.component.ts b/src/app/recorder/recorder.component.ts index d24c527..ae0a18c 100644 --- a/src/app/recorder/recorder.component.ts +++ b/src/app/recorder/recorder.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'; import { NgbModal, NgbModalRef } from "@ng-bootstrap/ng-bootstrap"; import * as rrtc from 'recordrtc'; @@ -10,6 +10,7 @@ import * as rrtc from 'recordrtc'; export class RecorderComponent implements OnInit { @ViewChild("content") content; @ViewChild("activeStory") video; + @Output('recordingEnded') recordingEnded: EventEmitter = new EventEmitter(); stream: MediaStream; recorder: rrtc.RecordRTCPromisesHandler; modal: NgbModalRef; @@ -46,12 +47,24 @@ export class RecorderComponent implements OnInit { } async stopRecording() { + const that = this; this.isRecording = false; await this.recorder.stopRecording(); let blob = await this.recorder.getBlob(); - // TODO upload instead - rrtc.invokeSaveAsDialog(blob, 'Recorded-Video.webm'); - this.modal.close(); + // read as b64 + const reader = new FileReader(); + reader.readAsDataURL(blob); + reader.onloadend = function() { + const base64data:string = String(reader.result); + console.log(base64data); + that.recordingEnded.emit(base64data); + that.modal.close(); + } + const handleError = (error) => { + console.error(error); + } + reader.onerror = handleError; + reader.onabort = handleError; } } \ No newline at end of file