Skip to content

Commit 6c1c309

Browse files
authored
Mock backend (#477)
* Added mock backend for demo app
1 parent c2e15b8 commit 6c1c309

File tree

3 files changed

+356
-5
lines changed

3 files changed

+356
-5
lines changed

.gitignore

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@
1919

2020
# IDE - VSCode
2121
.vscode/*
22-
!.vscode/settings.json
23-
!.vscode/tasks.json
24-
!.vscode/launch.json
25-
!.vscode/extensions.json
22+
.vscode/settings.json
23+
.vscode/tasks.json
24+
.vscode/launch.json
25+
.vscode/extensions.json
2626

2727
# misc
2828
/.sass-cache

src/app/app.module.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { AppComponent } from './app.component';
77
import { ExampleModule } from './example/example.module';
88
import { RestrictedModule } from './restricted/restricted.module';
99
import { routes } from './app.routes';
10+
import { fakeBackendProvider } from './fake-backend';
11+
1012

1113
@NgModule({
1214
imports: [
@@ -22,7 +24,8 @@ import { routes } from './app.routes';
2224
})
2325
],
2426
providers: [
25-
AngularTokenModule
27+
AngularTokenModule,
28+
fakeBackendProvider
2629
],
2730
declarations: [ AppComponent ],
2831
bootstrap: [ AppComponent ]

src/app/fake-backend.ts

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
import { Injectable } from '@angular/core';
2+
import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor, HTTP_INTERCEPTORS, HttpHeaders } from '@angular/common/http';
3+
import { Observable, of, throwError } from 'rxjs';
4+
import { delay, mergeMap, materialize, dematerialize } from 'rxjs/operators';
5+
6+
@Injectable()
7+
export class FakeBackendInterceptor implements HttpInterceptor {
8+
9+
users: any[];
10+
11+
constructor() { }
12+
13+
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
14+
15+
// array in local storage for registered users
16+
this.users = JSON.parse(localStorage.getItem('users')) || [];
17+
18+
// wrap in delayed observable to simulate server api call
19+
return of(null).pipe(mergeMap(() => {
20+
21+
/*
22+
*
23+
* Register
24+
*
25+
*/
26+
27+
if (request.url === 'http://localhost:3000/auth' && request.method === 'POST') {
28+
29+
// Get new user object from post body
30+
const body = request.body;
31+
32+
// Check if all inputs provided
33+
if (body.email === null && body.password === null && body.password_confirmation === null) {
34+
return of(this.registerError(
35+
body.email,
36+
'Please submit proper sign up data in request body.'
37+
));
38+
}
39+
40+
// Check if password matches password confimation
41+
if (body.password !== body.password_confirmation) {
42+
return of(this.registerError(
43+
body.email,
44+
{ password_confirmation: ["doesn't match Password"] }
45+
));
46+
}
47+
48+
// Check if login is email
49+
const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
50+
if (!re.test(body.email)) {
51+
return of(this.registerError(
52+
body.email,
53+
{ email: ['is not an email'] },
54+
));
55+
}
56+
57+
// Check if login already exists
58+
const duplicateUser = this.users.filter(user => {
59+
return user.email === body.email;
60+
}).length;
61+
62+
if (duplicateUser) {
63+
return of(this.registerError(
64+
body.email,
65+
{ email: ['has already been taken'] },
66+
));
67+
}
68+
69+
const newUser = {
70+
id: this.users.length + 1,
71+
email: body.email,
72+
password: body.password
73+
};
74+
75+
this.users.push(newUser);
76+
localStorage.setItem('users', JSON.stringify(this.users));
77+
78+
// respond 200 OK
79+
return of(new HttpResponse({
80+
status: 200,
81+
url: 'http://localhost:3000/auth',
82+
body: {
83+
status: 'success',
84+
data: {
85+
uid: body.email,
86+
id: this.users.length + 1,
87+
email: body.email,
88+
provider: 'email',
89+
name: null,
90+
nickname: null,
91+
image: null,
92+
created_at: new Date().toISOString(),
93+
updated_at: new Date().toISOString()
94+
}
95+
}
96+
}));
97+
}
98+
99+
/*
100+
*
101+
* Sign In
102+
*
103+
*/
104+
105+
if (request.url.match('http://localhost:3000/auth/sign_in') && request.method === 'POST') {
106+
107+
const filteredUsers = this.users.filter(user => {
108+
return user.email === request.body.email && user.password === request.body.password;
109+
});
110+
111+
if (filteredUsers.length) {
112+
// if login details are valid return 200 OK with user details and fake jwt token
113+
const body = {
114+
data: {
115+
id: filteredUsers[0].id,
116+
email: filteredUsers[0].email,
117+
provider: 'email',
118+
uid: filteredUsers[0].email,
119+
name: null,
120+
nickname: null,
121+
image: null
122+
}
123+
};
124+
const headers = new HttpHeaders({
125+
'access-token': 'fake-access-token',
126+
'client': 'fake-client-id',
127+
'expiry': '2000000000',
128+
'token-type': 'Bearer',
129+
'uid': filteredUsers[0].email
130+
});
131+
132+
return of(new HttpResponse({
133+
status: 200,
134+
url: 'http://localhost:3000/auth/sign_in',
135+
body: body,
136+
headers: headers
137+
}));
138+
} else {
139+
// else return 400 bad request
140+
return of(new HttpResponse({
141+
status: 401,
142+
url: 'http://localhost:3000/auth/sign_in',
143+
body: {
144+
status: 'false',
145+
errors: ['Invalid login credentials. Please try again.']
146+
}
147+
}));
148+
}
149+
}
150+
151+
/*
152+
*
153+
* Sign Out
154+
*
155+
*/
156+
157+
if (request.url.match('http://localhost:3000/auth/sign_out') && request.method === 'DELETE') {
158+
if (request.headers.get('access-token') === 'fake-access-token') {
159+
return of(new HttpResponse({
160+
status: 200,
161+
url: 'http://localhost:3000/auth/sign_out',
162+
body: {
163+
success: true
164+
}
165+
}));
166+
} else {
167+
return of(new HttpResponse({
168+
status: 404,
169+
url: 'http://localhost:3000/auth/sign_out',
170+
body: {
171+
status: 'false',
172+
errors: ['User was not found or was not logged in.']
173+
}
174+
}));
175+
}
176+
}
177+
178+
/*
179+
*
180+
* Validate Token
181+
*
182+
*/
183+
184+
if (request.url.match('http://localhost:3000/auth/validate_token') && request.method === 'GET') {
185+
186+
const user = this.getAuthUser(request);
187+
188+
if (user) {
189+
return of(new HttpResponse({
190+
status: 200,
191+
url: 'http://localhost:3000/auth/validate_token',
192+
body: {
193+
success: true,
194+
data: {
195+
id: user.id,
196+
provider: 'email',
197+
uid: user.email,
198+
name: null,
199+
nickname: null,
200+
image: null,
201+
email: user.email
202+
}
203+
}
204+
}));
205+
} else {
206+
return of(new HttpResponse({
207+
status: 401,
208+
url: 'http://localhost:3000/auth/validate_token',
209+
body: {
210+
success: false,
211+
errors: ['Invalid login credentials']
212+
}
213+
}));
214+
}
215+
}
216+
217+
/*
218+
*
219+
* Update Password
220+
*
221+
*/
222+
223+
if (request.url.match('http://localhost:3000/auth') && request.method === 'PUT') {
224+
225+
// Check if password matches password confimation
226+
if (request.body.password !== request.body.password_confirmation) {
227+
return of(this.registerError(
228+
request.body.email,
229+
{ password_confirmation: ["doesn't match Password"] }
230+
));
231+
}
232+
233+
const user = this.getAuthUser(request);
234+
235+
if (user && user.password === request.body.password) {
236+
237+
this.users[(user.id - 1)].password = request.body.password;
238+
239+
localStorage.setItem('users', JSON.stringify(this.users));
240+
241+
return of(new HttpResponse({
242+
status: 200,
243+
url: 'http://localhost:3000/auth',
244+
body: {
245+
status: 'success',
246+
data: {
247+
id: user.id,
248+
email: user.email,
249+
uid: user.email,
250+
provider: 'email',
251+
name: null,
252+
nickname: null,
253+
image: null,
254+
created_at: new Date().toISOString(),
255+
updated_at: new Date().toISOString()
256+
}
257+
}
258+
}));
259+
} else {
260+
return of(new HttpResponse({
261+
status: 401,
262+
url: 'http://localhost:3000/auth',
263+
body: {
264+
success: false,
265+
errors: ['Invalid login credentials']
266+
}
267+
}));
268+
}
269+
}
270+
271+
/*
272+
*
273+
* Access Private Resouce
274+
*
275+
*/
276+
277+
if (request.url.match('http://localhost:3000/private_resource') && request.method === 'GET') {
278+
279+
const user = this.getAuthUser(request);
280+
281+
if (user) {
282+
return of(new HttpResponse({
283+
status: 200,
284+
url: 'http://localhost:3000/auth/private_resource',
285+
body: {
286+
data: 'Private Content for ' + user.email
287+
}
288+
}));
289+
} else {
290+
return of(new HttpResponse({
291+
status: 401,
292+
url: 'http://localhost:3000/auth/private_resource',
293+
body: {
294+
success: false,
295+
errors: ['Invalid login credentials']
296+
}
297+
}));
298+
}
299+
}
300+
301+
// pass through any requests not handled above
302+
// return next.handle(request);
303+
}))
304+
305+
// call materialize and dematerialize to ensure delay even if an
306+
// error is thrown (https://github.com/Reactive-Extensions/RxJS/issues/648)
307+
.pipe(materialize())
308+
.pipe(delay(500))
309+
.pipe(dematerialize());
310+
}
311+
312+
getAuthUser(request) {
313+
const filteredUsers = this.users.filter(user => user.email === request.headers.get('uid'));
314+
315+
if (filteredUsers.length && request.headers.get('access-token') === 'fake-access-token') {
316+
return filteredUsers[0];
317+
} else {
318+
return undefined;
319+
}
320+
}
321+
322+
registerError(email: string, errorMsg?) {
323+
return new HttpResponse({
324+
status: 422, url: 'http://localhost:3000/auth', body: {
325+
status: 'error',
326+
data: {
327+
id: null,
328+
provider: 'email',
329+
uid: '',
330+
name: null,
331+
nickname: null,
332+
image: null,
333+
email: email,
334+
created_at: null,
335+
updated_at: null
336+
},
337+
errors: errorMsg
338+
}
339+
});
340+
}
341+
}
342+
343+
export let fakeBackendProvider = {
344+
// use fake backend in place of Http service for backend-less development
345+
provide: HTTP_INTERCEPTORS,
346+
useClass: FakeBackendInterceptor,
347+
multi: true
348+
};

0 commit comments

Comments
 (0)