From 014cb2b38c86b9fe207788200fbf50a35831072e Mon Sep 17 00:00:00 2001 From: Jason Teplitz Date: Wed, 30 Mar 2016 10:50:16 -0700 Subject: [PATCH 1/3] chore(angularfire): Bump angularfire version to alpha.13 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 339a638..b320b8f 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "dependencies": { "angular2": "2.0.0-beta.7", "angular2-universal-preview": "0.55.4", - "angularfire2": "2.0.0-alpha.9", + "angularfire2": "2.0.0-alpha.13", "css": "2.2.1", "es6-promise": "3.1.2", "es6-shim": "0.33.13", From 8b19346ece02e28d9e5724c70791ce5ee0809141 Mon Sep 17 00:00:00 2001 From: Jason Teplitz Date: Tue, 8 Mar 2016 11:03:46 -0800 Subject: [PATCH 2/3] feat(auth): Add github auth --- package.json | 2 +- src/css/core/_header.scss | 4 ++++ src/main-server.ts | 2 ++ src/shared-providers.ts | 16 +++++++++++---- src/ui/main_ui.ts | 12 ++++++++++++ src/worker/components/header.ts | 19 ++++++++++++++++-- src/worker/containers/createQuestion.ts | 26 +++++++++++++++---------- src/worker/containers/questions.ts | 8 +++++--- src/worker/main_worker.ts | 2 ++ 9 files changed, 71 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index b320b8f..35de632 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,6 @@ "ts-node": "0.5.4", "typescript": "1.7.5", "typescript-node": "0.1.3", - "typings": "^0.6.8" + "typings": "^0.6.9" } } diff --git a/src/css/core/_header.scss b/src/css/core/_header.scss index 9975a06..2fd01e3 100755 --- a/src/css/core/_header.scss +++ b/src/css/core/_header.scss @@ -58,4 +58,8 @@ margin: 0; color: #FFF; text-decoration: none; + padding-right: 20px; + &:last-child { + padding-right: 0; + } } diff --git a/src/main-server.ts b/src/main-server.ts index 45338fa..9024049 100644 --- a/src/main-server.ts +++ b/src/main-server.ts @@ -7,6 +7,7 @@ import { ng2engine, REQUEST_URL } from 'angular2-universal-preview/dist/server'; +import {FIREBASE_PROVIDERS} from 'angularfire2'; import {provide} from 'angular2/core'; import {APP_BASE_HREF, ROUTER_PROVIDERS} from 'angular2/router'; @@ -32,6 +33,7 @@ app.use('/', (req, res) => { res.render('index', { App, providers: [ ROUTER_PROVIDERS, SERVER_LOCATION_PROVIDERS, + FIREBASE_PROVIDERS, provide(REQUEST_URL, {useValue: req.originalUrl}), provide(APP_BASE_HREF, {useValue: `http://localhost:3000${req.baseUrl}`}), provide(REQUEST_URL, {useValue: 'http://localhost:3000'}), diff --git a/src/shared-providers.ts b/src/shared-providers.ts index ef52dda..ba39607 100644 --- a/src/shared-providers.ts +++ b/src/shared-providers.ts @@ -1,14 +1,22 @@ import {provide} from 'angular2/core'; -import {FIREBASE_PROVIDERS, defaultFirebase} from 'angularfire2'; +import { + defaultFirebase, + firebaseAuthConfig, + AuthMethods, + AuthProviders +} from 'angularfire2'; import {AuthService} from './worker/services/Auth'; import {Backend, BackendConfig} from './worker/services/Backend'; import {QuestionService} from './worker/services/QuestionService'; - export const SHARED_PROVIDERS = [ AuthService, QuestionService, - FIREBASE_PROVIDERS, - defaultFirebase('answers-mobile.firebaseio.com') + defaultFirebase('answers-mobile.firebaseio.com'), + firebaseAuthConfig({ + method: AuthMethods.Redirect, + provider: AuthProviders.Github + }) ]; + diff --git a/src/ui/main_ui.ts b/src/ui/main_ui.ts index ac56238..2cd13d6 100644 --- a/src/ui/main_ui.ts +++ b/src/ui/main_ui.ts @@ -7,9 +7,18 @@ import { } from 'angular2/platform/worker_render'; import {platform, provide} from 'angular2/core'; import {BOOTSTRAP_CHANNEL} from '../shared/channels'; +import { + defaultFirebase, +} from 'angularfire2'; +import {WORKER_RENDER_FIREBASE_PROVIDERS} from 'angularfire2/angularfire2_worker_render'; +// Need to import from providers directly +// until https://github.com/angular/angularfire2/pull/111 is merged +import {MessageBasedFirebaseAuth} from 'angularfire2/providers/web_workers/ui/auth'; let appRef = platform([WORKER_RENDER_PLATFORM]) .application([ + WORKER_RENDER_FIREBASE_PROVIDERS, + defaultFirebase('answers-mobile.firebaseio.com'), WORKER_RENDER_APPLICATION, WORKER_RENDER_ROUTER, provide(WORKER_SCRIPT, {useValue: '/loader.js'}) @@ -22,3 +31,6 @@ bus.from(BOOTSTRAP_CHANNEL).subscribe((message: string) => { ( window).preboot.complete(); } }); + +// Need to manually call start until https://github.com/angular/angular/issues/7420 is implemented +appRef.injector.get(MessageBasedFirebaseAuth).start(); diff --git a/src/worker/components/header.ts b/src/worker/components/header.ts index 15318c7..ddfaa10 100644 --- a/src/worker/components/header.ts +++ b/src/worker/components/header.ts @@ -1,6 +1,7 @@ import {Component} from 'angular2/core'; import {Nav} from '../services/Nav'; import {ROUTER_DIRECTIVES} from 'angular2/router'; +import {FirebaseAuth} from 'angularfire2'; @Component({ selector: 'app-header', @@ -8,15 +9,29 @@ import {ROUTER_DIRECTIVES} from 'angular2/router'; toggle_nav Angular Answers + Login + Logout New Question `, directives: [ROUTER_DIRECTIVES] }) export class AppHeader { - constructor(private nav: Nav){ - + constructor(private nav: Nav, public auth: FirebaseAuth){ + this.auth.subscribe((data) => { + console.log('auth data', data); + }); } openSideNav(){ this.nav.open() } + + login() { + this.auth.login() + .then(() => console.log("Success!"), + (err) => console.error(err)); + } + + logout() { + this.auth.logout(); + } } diff --git a/src/worker/containers/createQuestion.ts b/src/worker/containers/createQuestion.ts index 39d96de..b80ebed 100644 --- a/src/worker/containers/createQuestion.ts +++ b/src/worker/containers/createQuestion.ts @@ -1,21 +1,26 @@ import {Component} from 'angular2/core'; import {QuestionService} from '../services/QuestionService'; import {Router, RouteConfig} from 'angular2/router' +import {FirebaseAuth} from 'angularfire2'; @Component({ selector: 'create-question-container', template: ` -
-

Ask a new question

-
+
+ +

Please login to continue.

+
+ +

Ask a new question

+
+ + + - - - -
- +
+ +
- `, styles: [ `.new-question-container { @@ -26,7 +31,8 @@ import {Router, RouteConfig} from 'angular2/router' }) export class CreateQuestionContainer { newQuestion = {}; - constructor(private questionService:QuestionService, private router:Router){} + constructor(private questionService:QuestionService, private router:Router, + public auth: FirebaseAuth){} addQuestion(){ this.questionService.addQuestion(this.newQuestion); this.router.navigate(['../Questions']); diff --git a/src/worker/containers/questions.ts b/src/worker/containers/questions.ts index 7a0da67..d9412e1 100644 --- a/src/worker/containers/questions.ts +++ b/src/worker/containers/questions.ts @@ -1,13 +1,15 @@ import {Component, ChangeDetectionStrategy} from 'angular2/core'; import {Nav} from '../services/Nav'; import {QuestionService} from '../services/QuestionService'; -import {QuestionList} from '../components/QuestionList' +import {QuestionList} from '../components/QuestionList'; +import {FirebaseAuth} from 'angularfire2'; @Component({ selector: 'home-container', template: `
-

Recent Questions

+

Recent Questions

+

Hi {{(auth | async).github.displayName}}, here are some recent questions

`, @@ -15,5 +17,5 @@ import {QuestionList} from '../components/QuestionList' changeDetection: ChangeDetectionStrategy.OnPush }) export class QuestionsContainer { - constructor(private questionService:QuestionService){} + constructor(private questionService:QuestionService, public auth: FirebaseAuth){} } diff --git a/src/worker/main_worker.ts b/src/worker/main_worker.ts index e698648..7d6de96 100644 --- a/src/worker/main_worker.ts +++ b/src/worker/main_worker.ts @@ -8,6 +8,7 @@ import {platform, provide, ComponentRef, Injector} from 'angular2/core'; import {APP_BASE_HREF} from 'angular2/router'; import {BOOTSTRAP_CHANNEL} from '../shared/channels'; import {SHARED_PROVIDERS} from '../shared-providers'; +import {WORKER_APP_FIREBASE_PROVIDERS} from 'angularfire2/angularfire2_worker_app'; import {App} from './app/app'; @@ -15,6 +16,7 @@ platform([WORKER_APP_PLATFORM]) .asyncApplication(null, [ WORKER_APP_ROUTER, WORKER_APP_APPLICATION, + WORKER_APP_FIREBASE_PROVIDERS, provide(APP_BASE_HREF, {useValue: '/'}), SHARED_PROVIDERS ]).then((appRef) => appRef.bootstrap(App).then((compRef: ComponentRef) => { From a58857d6a5ffef75c6d5009046df82b13497780b Mon Sep 17 00:00:00 2001 From: Jason Teplitz Date: Tue, 8 Mar 2016 11:08:59 -0800 Subject: [PATCH 3/3] feat(answers): Add CreateAnswer, AnswerList components, and AnswerService --- src/shared-providers.ts | 2 + src/worker/components/AnswerList.ts | 32 +++++++++++++++ src/worker/components/CreateAnswer.ts | 52 +++++++++++++++++++++++++ src/worker/components/header.ts | 3 +- src/worker/containers/questionDetail.ts | 18 +++++++-- src/worker/services/AnswerService.ts | 37 ++++++++++++++++++ 6 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 src/worker/components/AnswerList.ts create mode 100644 src/worker/components/CreateAnswer.ts create mode 100644 src/worker/services/AnswerService.ts diff --git a/src/shared-providers.ts b/src/shared-providers.ts index ba39607..e0e4ab3 100644 --- a/src/shared-providers.ts +++ b/src/shared-providers.ts @@ -9,10 +9,12 @@ import { import {AuthService} from './worker/services/Auth'; import {Backend, BackendConfig} from './worker/services/Backend'; import {QuestionService} from './worker/services/QuestionService'; +import {AnswerService} from './worker/services/AnswerService'; export const SHARED_PROVIDERS = [ AuthService, QuestionService, + AnswerService, defaultFirebase('answers-mobile.firebaseio.com'), firebaseAuthConfig({ method: AuthMethods.Redirect, diff --git a/src/worker/components/AnswerList.ts b/src/worker/components/AnswerList.ts new file mode 100644 index 0000000..5c3083c --- /dev/null +++ b/src/worker/components/AnswerList.ts @@ -0,0 +1,32 @@ +import {Component, Input} from 'angular2/core'; +import {ROUTER_DIRECTIVES} from 'angular2/router'; +import {Answer} from '../services/AnswerService'; + +const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', + 'September', 'October', 'November', 'December']; + +@Component({ + selector: 'answer-list', + template: ` +
+

{{ answer.text }}

+

Answered by {{ answer.username }} on {{ timestampToDate (answer.timestamp) }}.

+
+ `, + directives: [ROUTER_DIRECTIVES], + styles: [` + .username { + font-size: 12pt; + text-align: right; + } + `] +}) +export class AnswerList { + @Input() answers: Answer[]; + + timestampToDate (timestamp: number): string { + let date = new Date(timestamp); + let month = months[date.getMonth()]; + return `${month} ${date.getDate()}`; + } +} diff --git a/src/worker/components/CreateAnswer.ts b/src/worker/components/CreateAnswer.ts new file mode 100644 index 0000000..6807530 --- /dev/null +++ b/src/worker/components/CreateAnswer.ts @@ -0,0 +1,52 @@ +import {Component, Input, ChangeDetectionStrategy} from 'angular2/core'; +import {AnswerService, Answer} from '../services/AnswerService'; +import {FirebaseAuth} from 'angularfire2'; + +import * as Firebase from 'firebase'; + +@Component({ + selector: 'create-answer', + template: ` +
+ +

You must login to answer this question.

+
+ +

Add Answer

+
+ +
+ +
+
+ `, + styles: [ + `.new-answer-container { + display: flex; + flex-direction: column + }` + ] +}) +export class CreateAnswer { + @Input() questionId: string; + newAnswer: Answer = { + timestamp: Firebase.ServerValue.TIMESTAMP, + text: '', + uid: '', + username: '' + }; + + constructor(private _answerService: AnswerService, public auth: FirebaseAuth){ + this.auth.subscribe ((authData) => { + if (authData != null) { + this.newAnswer.uid = authData.uid; + this.newAnswer.username = authData.github.username; + } + }); + } + + addAnswer(){ + this._answerService.addAnswer(this.questionId, this.newAnswer); + this.newAnswer.text = ''; + } +} diff --git a/src/worker/components/header.ts b/src/worker/components/header.ts index ddfaa10..1009d16 100644 --- a/src/worker/components/header.ts +++ b/src/worker/components/header.ts @@ -1,4 +1,4 @@ -import {Component} from 'angular2/core'; +import {Component, ChangeDetectionStrategy} from 'angular2/core'; import {Nav} from '../services/Nav'; import {ROUTER_DIRECTIVES} from 'angular2/router'; import {FirebaseAuth} from 'angularfire2'; @@ -13,6 +13,7 @@ import {FirebaseAuth} from 'angularfire2'; Logout New Question `, + changeDetection: ChangeDetectionStrategy.OnPush, directives: [ROUTER_DIRECTIVES] }) export class AppHeader { diff --git a/src/worker/containers/questionDetail.ts b/src/worker/containers/questionDetail.ts index c8237a9..d70bfd3 100644 --- a/src/worker/containers/questionDetail.ts +++ b/src/worker/containers/questionDetail.ts @@ -1,6 +1,9 @@ import {Component, ChangeDetectionStrategy} from 'angular2/core'; import {QuestionService} from '../services/QuestionService'; import {RouteParams} from 'angular2/router'; +import {AnswerList} from '../components/AnswerList'; +import {CreateAnswer} from '../components/CreateAnswer'; +import {AnswerService} from '../services/AnswerService'; @Component({ selector: 'question-detail-container', @@ -9,12 +12,21 @@ import {RouteParams} from 'angular2/router';

{{ (question | async)?.title }}

{{ (question | async)?.text }}

+ + `, - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, + directives: [AnswerList, CreateAnswer] }) export class QuestionDetailContainer { question: any; - constructor(private questionService:QuestionService, params:RouteParams){ - this.question = questionService.getQuestionById(params.get('id')); + id: string; + answers: any; + + constructor(private _questionService:QuestionService, private _answerService: AnswerService, + params:RouteParams){ + this.id = params.get('id'); + this.question = this._questionService.getQuestionById(this.id); + this.answers = this._answerService.getAnswersByQuestionId(this.id); } } diff --git a/src/worker/services/AnswerService.ts b/src/worker/services/AnswerService.ts new file mode 100644 index 0000000..9b17259 --- /dev/null +++ b/src/worker/services/AnswerService.ts @@ -0,0 +1,37 @@ +import {AngularFire, FirebaseListObservable} from 'angularfire2'; +import {Injectable} from 'angular2/core'; +const ANSWERS_PATH = '/answers'; + +@Injectable() +export class AnswerService { + private _answersList: {[key: string]: FirebaseListObservable} = {}; + + constructor (private _angularFire: AngularFire) {} + + getAnswersByQuestionId(id:string) { + return this._lookupByQuestionId (id) + .map(l => l.map(v => v.val())); + } + + addAnswer(questionId: string, newAnswer: any) { + this._lookupByQuestionId(questionId).add(newAnswer); + } + + private _lookupByQuestionId(id: string): FirebaseListObservable { + let observable: FirebaseListObservable = null; + if (this._answersList[id]) + observable = this._answersList[id]; + else { + observable = this._angularFire.list(ANSWERS_PATH + `/${id}`, {preserveSnapshot: true}); + this._answersList[id] = observable; + } + return observable; + } +} + +export interface Answer { + text: string; + uid: string; + timestamp: number; + username: string; +}