Skip to content

Commit

Permalink
feat: added dbxFirebaseFunctionsModule
Browse files Browse the repository at this point in the history
- added LazyFirebaseFunctionsFactory and related components
  • Loading branch information
dereekb committed May 6, 2022
1 parent cab0411 commit 3d1bc69
Show file tree
Hide file tree
Showing 34 changed files with 508 additions and 35 deletions.
2 changes: 0 additions & 2 deletions apps/demo-api/src/app/function/auth/auth.function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ export const initUserOnCreate = onEventWithDemoNestContext<UserRecord>((withNest
functions.auth.user().onCreate(withNest(async (nest, data: UserRecord, context) => {
const uid = data.uid;

console.log('Init user: ', uid, context);

if (uid) {
await nest.profileActions.initProfileForUid(uid);
}
Expand Down
38 changes: 38 additions & 0 deletions apps/demo-firebase/src/lib/functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ProfileFunctionTypeMap } from './profile/profile.api';
import { FirebaseFunctionGetter, FirebaseFunctionsConfigMap, lazyFirebaseFunctionsFactory } from '@dereekb/firebase';
import { Functions } from 'firebase/functions';
import { guestbookFunctionMap, GuestbookFunctions, GuestbookFunctionTypeMap } from './guestbook';
import { profileFunctionMap, ProfileFunctions } from './profile';

/**
* FirebaseFunctionsMap type for Demo
*/
export type DemoFirebaseFunctionsMap = {
guestbookFunctions: GuestbookFunctionTypeMap;
profileFunctions: ProfileFunctionTypeMap;
}

/**
* LazyFirebaseFunctionsConfig for the DemoFirebaseFunctionsMap.
*
* The typings are enforced by the functions map.
*/
export const DEMO_FIREBASE_FUNCTIONS_CONFIG: FirebaseFunctionsConfigMap<DemoFirebaseFunctionsMap> = {
guestbookFunctions: [GuestbookFunctions, guestbookFunctionMap],
profileFunctions: [ProfileFunctions, profileFunctionMap]
};

/**
* The LazyFirebaseFunctions result type. It is an abstract class to allow for dependency injection.
*
* The typings are enforced by the functions map.
*/
export abstract class DemoFirebaseFunctionsGetter {
abstract readonly guestbookFunctions: FirebaseFunctionGetter<GuestbookFunctions>;
abstract readonly profileFunctions: FirebaseFunctionGetter<ProfileFunctions>;
}

export function makeDemoFirebaseFunctions(functions: Functions): DemoFirebaseFunctionsGetter {
const factory = lazyFirebaseFunctionsFactory<DemoFirebaseFunctionsMap>(DEMO_FIREBASE_FUNCTIONS_CONFIG);
return factory(functions);
}
53 changes: 53 additions & 0 deletions apps/demo-firebase/src/lib/guestbook/guestbook.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { GuestbookEntry } from './guestbook';
import { Expose } from "class-transformer";
import { FirebaseFunctionMap, firebaseFunctionMapFactory, FirebaseFunctionMapFunction, FirebaseFunctionTypeConfigMap } from "@dereekb/firebase";
import { IsNotEmpty, IsString, MaxLength } from "class-validator";
import { ModelKey } from '@dereekb/util';

export const GUESTBOOK_ENTRY_MESSAGE_MAX_LENGTH = 200;
export const GUESTBOOK_ENTRY_SIGNED_MAX_LENGTH = 40;

export abstract class GuestbookEntryParams {

@Expose()
@IsNotEmpty()
@IsString()
guestbook!: ModelKey;

}

export class UpdateGuestbookEntryParams extends GuestbookEntryParams {

@Expose()
@IsNotEmpty()
@IsString()
@MaxLength(GUESTBOOK_ENTRY_MESSAGE_MAX_LENGTH)
message!: string;

@Expose()
@IsNotEmpty()
@IsString()
@MaxLength(GUESTBOOK_ENTRY_SIGNED_MAX_LENGTH)
signed!: string;

}

export const guestbookEntryUpdateKey = 'guestbookEntryUpdateEntry';
export const guestbookEntryDeleteKey = 'guestbookEntryDeleteEntry';

export type GuestbookFunctionTypeMap = {
[guestbookEntryUpdateKey]: [UpdateGuestbookEntryParams, GuestbookEntry]
[guestbookEntryDeleteKey]: [GuestbookEntryParams, GuestbookEntry]
}

export const guestbookFunctionTypeConfigMap: FirebaseFunctionTypeConfigMap<GuestbookFunctionTypeMap> = {
[guestbookEntryUpdateKey]: null,
[guestbookEntryDeleteKey]: null
}

export abstract class GuestbookFunctions implements FirebaseFunctionMap<GuestbookFunctionTypeMap> {
[guestbookEntryUpdateKey]: FirebaseFunctionMapFunction<GuestbookFunctionTypeMap, 'guestbookEntryUpdateEntry'>;
[guestbookEntryDeleteKey]: FirebaseFunctionMapFunction<GuestbookFunctionTypeMap, 'guestbookEntryDeleteEntry'>;
}

export const guestbookFunctionMap = firebaseFunctionMapFactory(guestbookFunctionTypeConfigMap);
12 changes: 6 additions & 6 deletions apps/demo-firebase/src/lib/guestbook/guestbook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,6 @@ export function guestbookFirestoreCollection(firestoreContext: FirestoreContext)

// MARK: Guestbook Entry
export interface GuestbookEntry extends UserRelatedById {
/**
* Arbitrary word without spaces
*/
word: string;
/**
* Guestbook message.
*/
Expand All @@ -81,6 +77,10 @@ export interface GuestbookEntry extends UserRelatedById {
* Date the entry was originally created at.
*/
createdAt: Date;
/**
* Whether or not the entry has been published. This cannot be changed one published.
*/
published: boolean;
}

export interface GuestbookEntryRef extends DocumentReferenceRef<GuestbookEntry> { }
Expand All @@ -91,11 +91,11 @@ export const guestbookEntryCollectionPath = 'guestbookEntry';

export const guestbookEntryConverter = makeSnapshotConverterFunctions<GuestbookEntry>({
fields: {
word: firestoreString(),
message: firestoreString(),
signed: firestoreString(),
updatedAt: firestoreDate(),
createdAt: firestoreDate({ saveDefaultAsNow: true })
createdAt: firestoreDate({ saveDefaultAsNow: true }),
published: firestoreBoolean({ defaultBeforeSave: false })
}
});

Expand Down
1 change: 1 addition & 0 deletions apps/demo-firebase/src/lib/guestbook/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './guestbook';
export * from './guestbook.query';
export * from './guestbook.api';
1 change: 1 addition & 0 deletions apps/demo-firebase/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './guestbook';
export * from './profile';
export * from './collection';
export * from './functions';
8 changes: 4 additions & 4 deletions apps/demo-firebase/src/lib/profile/profile.api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Profile } from './profile';
import { Expose } from "class-transformer";
import { FirebaseFunctionMap, firebaseFunctionMapFactory, FirebaseFunctionTypeConfigMap } from "@dereekb/firebase";
import { FirebaseFunctionMap, firebaseFunctionMapFactory, FirebaseFunctionMapFunction, FirebaseFunctionTypeConfigMap } from "@dereekb/firebase";
import { IsNotEmpty, IsOptional, IsString, MaxLength } from "class-validator";

export class SetProfileUsernameParams {
Expand Down Expand Up @@ -44,9 +44,9 @@ export const profileFunctionTypeConfigMap: FirebaseFunctionTypeConfigMap<Profile
/**
* Declared as an abstract class so we can inject it into our Angular app using this token.
*/
// ignore to prevent having to re-implement all function map types. We only want to use this as an Angular token without importing InjectionToken.
// @ts-ignore
export abstract class ProfileFunctions implements FirebaseFunctionMap<ProfileFunctionTypeMap> { }
export abstract class ProfileFunctions implements FirebaseFunctionMap<ProfileFunctionTypeMap> {
[profileSetUsernameKey]: FirebaseFunctionMapFunction<ProfileFunctionTypeMap, 'profileSetUsername'>;
}

/**
* Used to generate our ProfileFunctionMap for a Functions instance.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<dbx-dialog-content>
<p>This is a dialog.</p>
<button mat-raised-button (click)="close()">Closed</button>
</dbx-dialog-content>
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Component } from '@angular/core';
import { AbstractDialogDirective } from '@dereekb/dbx-web';
import { MatDialog } from '@angular/material/dialog';
import { HandleActionFunction } from '@dereekb/dbx-core';
import { DemoGuestbookEntryFormValue } from '../../../../shared/modules/guestbook/component/guestbook.entry.form.component';
import { GuestbookEntryDocumentStore } from './../../../../shared/modules/guestbook/store/guestbook.entry.document.store';
import { of } from 'rxjs';

export interface DemoGuestbookEntryPopupComponentConfig {
guestbookEntryDocumentStore: GuestbookEntryDocumentStore;
}

@Component({
template: `
<dbx-dialog-content>
<p class="dbx-note">Enter your message for the guest book.</p>
<div dbxAction [dbxActionHandler]="handleFormAction">
<demo-guestbook-entry-form dbxActionForm></demo-guestbook-entry-form>
<p></p>
<dbx-button [raised]="true" [text]="(exists$ | async) ? 'Update Entry' : 'Create Entry'" dbxActionButton></dbx-button>
</div>
</dbx-dialog-content>
`
})
export class DemoGuestbookEntryPopupComponent extends AbstractDialogDirective<any, DemoGuestbookEntryPopupComponentConfig> {

get guestbookEntryDocumentStore(): GuestbookEntryDocumentStore {
return this.data.guestbookEntryDocumentStore;
}

get exists$() {
return this.guestbookEntryDocumentStore.exists$;
}

static openPopup(matDialog: MatDialog, config: DemoGuestbookEntryPopupComponentConfig) {
return matDialog.open(DemoGuestbookEntryPopupComponent, {
data: config
});
}

readonly handleFormAction: HandleActionFunction = (value: DemoGuestbookEntryFormValue) => {
console.log('save.');
return of(false);
}

}
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
<dbx-loading [linear]="true" [context]="context">
<dbx-loading [linear]="true" [context]="context" #guestbookEntry="guestbookEntry" demoGuestbookEntryDocument
dbxFirebaseDocumentAuthId>
<dbx-content-container>
<dbx-two-block class="guestbook-view">
<div top class="guestbook-view-header">
<h2>{{ name$ | async }}</h2>
<div [ngSwitch]="guestbookEntry.exists$ | async">
<div *ngSwitchCase="true">
<dbx-button [raised]="true" color="primary" text="Edit Entry" (buttonClick)="openEntry()"></dbx-button>
</div>
<div *ngSwitchCase="false">
<p class="dbx-note">You have not created an entry in this guest book.</p>
<dbx-button [raised]="true" color="primary" text="Create Entry" (buttonClick)="openEntry()"></dbx-button>
</div>
</div>
</div>
<div class="guestbook-view-content">
<p></p>
<mat-divider></mat-divider>
<div>

</div>
<div>
<demo-guestbook-entry-list dbxFirebaseCollectionList demoGuestbookEntryCollection>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
import { Component, OnDestroy } from '@angular/core';
import { Component, OnDestroy, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { loadingStateContext } from '@dereekb/rxjs';
import { map } from 'rxjs';
import { GuestbookDocumentStore } from '../../../../shared/modules/guestbook/store/guestbook.document.store';
import { GuestbookEntryDocumentStore } from '../../../../shared/modules/guestbook/store/guestbook.entry.document.store';
import { DemoGuestbookEntryPopupComponent } from './guestbook.entry.popup.component';

@Component({
selector: 'demo-guestbook-view',
templateUrl: './guestbook.view.component.html'
})
export class DemoGuestbookViewComponent implements OnDestroy {

@ViewChild(GuestbookEntryDocumentStore)
readonly documentStore!: GuestbookEntryDocumentStore;

readonly context = loadingStateContext({ obs: this.guestbookStore.dataLoadingState$ });
readonly data$ = this.guestbookStore.data$;

readonly name$ = this.data$.pipe(map(x => x?.name));

constructor(readonly guestbookStore: GuestbookDocumentStore) { }
constructor(readonly guestbookStore: GuestbookDocumentStore, readonly matDialog: MatDialog) { }

ngOnDestroy(): void {
this.context.destroy();
}

openEntry() {
DemoGuestbookEntryPopupComponent.openPopup(this.matDialog, {
guestbookEntryDocumentStore: this.documentStore
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { DemoGuestbookListPageRightComponent } from './container/list.right.comp
import { DemoGuestbookListPageComponent } from './container/list.component';
import { DemoGuestbookLayoutComponent } from './container/layout.component';
import { DemoGuestbookViewComponent } from './container/guestbook.view.component';
import { DemoGuestbookEntryPopupComponent } from './container/guestbook.entry.popup.component';

@NgModule({
imports: [
Expand All @@ -15,6 +16,7 @@ import { DemoGuestbookViewComponent } from './container/guestbook.view.component
})
],
declarations: [
DemoGuestbookEntryPopupComponent,
DemoGuestbookViewComponent,
DemoGuestbookLayoutComponent,
DemoGuestbookListPageComponent,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Component } from "@angular/core";
import { ProvideFormlyContext, AbstractSyncFormlyFormDirective } from "@dereekb/dbx-form";
import { GuestbookEntry } from "@dereekb/demo-firebase";
import { FormlyFieldConfig } from "@ngx-formly/core";
import { guestbookEntryFields } from "./guestbook.entry.form";

export interface DemoGuestbookEntryFormValue extends Pick<GuestbookEntry, 'message' | 'signed'> { }

@Component({
template: `<dbx-formly></dbx-formly>`,
selector: 'demo-guestbook-entry-form',
providers: [ProvideFormlyContext()]
})
export class DemoGuestbookEntryFormComponent extends AbstractSyncFormlyFormDirective<DemoGuestbookEntryFormValue> {

readonly fields: FormlyFieldConfig[] = guestbookEntryFields();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { textAreaField, textField } from "@dereekb/dbx-form";
import { GUESTBOOK_ENTRY_MESSAGE_MAX_LENGTH, GUESTBOOK_ENTRY_SIGNED_MAX_LENGTH } from "@dereekb/demo-firebase";

export function guestbookEntryFields() {
return [
guestbookEntryMessageField(),
guestbookEntrySignedField()
];
}

export function guestbookEntryMessageField() {
return textAreaField({ key: 'message', label: 'Message', maxLength: GUESTBOOK_ENTRY_MESSAGE_MAX_LENGTH, required: true });
}

export function guestbookEntrySignedField() {
return textField({ key: 'signed', label: 'Signed', maxLength: GUESTBOOK_ENTRY_SIGNED_MAX_LENGTH, required: true });
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,36 @@ import { DemoGuestbookEntryListComponent, DemoGuestbookEntryListViewComponent, D
import { DemoGuestbookCollectionStoreDirective } from './store/guestbook.collection.store.directive';
import { DemoGuestbookDocumentStoreDirective } from './store/guestbook.document.store.directive';
import { DemoGuestbookEntryCollectionStoreDirective } from './store/guestbook.entry.collection.store.directive';
import { DemoGuestbookEntryDocumentStoreDirective } from './store/guestbook.entry.document.store.directive';
import { DemoGuestbookEntryFormComponent } from './component/guestbook.entry.form.component';

@NgModule({
imports: [
AppSharedModule
],
declarations: [
DemoGuestbookCollectionStoreDirective,
DemoGuestbookDocumentStoreDirective,
DemoGuestbookEntryCollectionStoreDirective,
// component
DemoGuestbookEntryFormComponent,
DemoGuestbookListComponent,
DemoGuestbookListViewComponent,
DemoGuestbookListViewItemComponent,
DemoGuestbookEntryListComponent,
DemoGuestbookEntryListViewComponent,
DemoGuestbookEntryListViewItemComponent
DemoGuestbookEntryListViewItemComponent,
// store
DemoGuestbookCollectionStoreDirective,
DemoGuestbookDocumentStoreDirective,
DemoGuestbookEntryCollectionStoreDirective,
DemoGuestbookEntryDocumentStoreDirective,
],
exports: [
DemoGuestbookEntryFormComponent,
DemoGuestbookListComponent,
DemoGuestbookEntryListComponent,
DemoGuestbookCollectionStoreDirective,
DemoGuestbookDocumentStoreDirective,
DemoGuestbookEntryCollectionStoreDirective,
DemoGuestbookListComponent,
DemoGuestbookEntryListComponent
DemoGuestbookEntryDocumentStoreDirective
]
})
export class DemoSharedGuestbookModule { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Directive } from "@angular/core";
import { DbxFirebaseDocumentStoreDirective, provideDbxFirebaseDocumentStoreDirective } from "@dereekb/dbx-firebase";
import { GuestbookEntry, GuestbookEntryDocument } from "@dereekb/demo-firebase";
import { GuestbookEntryDocumentStore } from "./guestbook.entry.document.store";

@Directive({
exportAs: 'guestbookEntry',
selector: '[demoGuestbookEntryDocument]',
providers: provideDbxFirebaseDocumentStoreDirective(DemoGuestbookEntryDocumentStoreDirective, GuestbookEntryDocumentStore)
})
export class DemoGuestbookEntryDocumentStoreDirective extends DbxFirebaseDocumentStoreDirective<GuestbookEntry, GuestbookEntryDocument, GuestbookEntryDocumentStore> {

constructor(store: GuestbookEntryDocumentStore) {
super(store);
}

}
Loading

0 comments on commit 3d1bc69

Please sign in to comment.