@@ -39,7 +39,15 @@
|
|
39 |
"styles": [
|
40 |
"src/styles.css"
|
41 |
],
|
42 |
-
"scripts": []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
},
|
44 |
"configurations": {
|
45 |
"production": {
|
39 |
"styles": [
|
40 |
"src/styles.css"
|
41 |
],
|
42 |
+
"scripts": [],
|
43 |
+
"server": "src/main.server.ts",
|
44 |
+
"prerender": {
|
45 |
+
"discoverRoutes": false,
|
46 |
+
"routesFile": "routes.txt"
|
47 |
+
},
|
48 |
+
"ssr": {
|
49 |
+
"entry": "server.ts"
|
50 |
+
}
|
51 |
},
|
52 |
"configurations": {
|
53 |
"production": {
|
@@ -7,7 +7,8 @@
|
|
7 |
"build": "ng build",
|
8 |
"watch": "ng build --watch --configuration development",
|
9 |
"test": "ng test",
|
10 |
-
"lint": "ng lint"
|
|
|
11 |
},
|
12 |
"private": true,
|
13 |
"dependencies": {
|
@@ -19,9 +20,12 @@
|
|
19 |
"@angular/forms": "^18.2.0",
|
20 |
"@angular/platform-browser": "^18.2.0",
|
21 |
"@angular/platform-browser-dynamic": "^18.2.0",
|
|
|
22 |
"@angular/router": "^18.2.0",
|
|
|
23 |
"angular-date-value-accessor": "^3.0.0",
|
24 |
"book-monkey5-styles": "^1.0.4",
|
|
|
25 |
"rxjs": "~7.8.0",
|
26 |
"tslib": "^2.3.0",
|
27 |
"zone.js": "~0.14.10"
|
@@ -30,7 +34,9 @@
|
|
30 |
"@angular-devkit/build-angular": "^18.2.1",
|
31 |
"@angular/cli": "^18.2.1",
|
32 |
"@angular/compiler-cli": "^18.2.0",
|
|
|
33 |
"@types/jasmine": "~5.1.0",
|
|
|
34 |
"angular-eslint": "18.3.0",
|
35 |
"eslint": "^9.9.0",
|
36 |
"jasmine-core": "~5.2.0",
|
@@ -42,4 +48,4 @@
|
|
42 |
"typescript": "~5.5.2",
|
43 |
"typescript-eslint": "8.1.0"
|
44 |
}
|
45 |
-
}
|
7 |
"build": "ng build",
|
8 |
"watch": "ng build --watch --configuration development",
|
9 |
"test": "ng test",
|
10 |
+
"lint": "ng lint",
|
11 |
+
"serve:ssr:book-monkey": "node dist/book-monkey/server/server.mjs"
|
12 |
},
|
13 |
"private": true,
|
14 |
"dependencies": {
|
20 |
"@angular/forms": "^18.2.0",
|
21 |
"@angular/platform-browser": "^18.2.0",
|
22 |
"@angular/platform-browser-dynamic": "^18.2.0",
|
23 |
+
"@angular/platform-server": "^18.2.0",
|
24 |
"@angular/router": "^18.2.0",
|
25 |
+
"@angular/ssr": "^18.2.1",
|
26 |
"angular-date-value-accessor": "^3.0.0",
|
27 |
"book-monkey5-styles": "^1.0.4",
|
28 |
+
"express": "^4.18.2",
|
29 |
"rxjs": "~7.8.0",
|
30 |
"tslib": "^2.3.0",
|
31 |
"zone.js": "~0.14.10"
|
34 |
"@angular-devkit/build-angular": "^18.2.1",
|
35 |
"@angular/cli": "^18.2.1",
|
36 |
"@angular/compiler-cli": "^18.2.0",
|
37 |
+
"@types/express": "^4.17.17",
|
38 |
"@types/jasmine": "~5.1.0",
|
39 |
+
"@types/node": "^18.18.0",
|
40 |
"angular-eslint": "18.3.0",
|
41 |
"eslint": "^9.9.0",
|
42 |
"jasmine-core": "~5.2.0",
|
48 |
"typescript": "~5.5.2",
|
49 |
"typescript-eslint": "8.1.0"
|
50 |
}
|
51 |
+
}
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/
|
2 |
+
/home
|
3 |
+
/books
|
4 |
+
/books/9783864909467
|
5 |
+
/books/9783864907791
|
6 |
+
/books/9783864906466
|
7 |
+
/books/9783864903571
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { APP_BASE_HREF } from '@angular/common';
|
2 |
+
import { CommonEngine } from '@angular/ssr';
|
3 |
+
import express from 'express';
|
4 |
+
import { fileURLToPath } from 'node:url';
|
5 |
+
import { dirname, join, resolve } from 'node:path';
|
6 |
+
import AppServerModule from './src/main.server';
|
7 |
+
|
8 |
+
// The Express app is exported so that it can be used by serverless Functions.
|
9 |
+
export function app(): express.Express {
|
10 |
+
const server = express();
|
11 |
+
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
|
12 |
+
const browserDistFolder = resolve(serverDistFolder, '../browser');
|
13 |
+
const indexHtml = join(serverDistFolder, 'index.server.html');
|
14 |
+
|
15 |
+
const commonEngine = new CommonEngine();
|
16 |
+
|
17 |
+
server.set('view engine', 'html');
|
18 |
+
server.set('views', browserDistFolder);
|
19 |
+
|
20 |
+
// Example Express Rest API endpoints
|
21 |
+
// server.get('/api/**', (req, res) => { });
|
22 |
+
// Serve static files from /browser
|
23 |
+
server.get('**', express.static(browserDistFolder, {
|
24 |
+
maxAge: '1y',
|
25 |
+
index: 'index.html',
|
26 |
+
}));
|
27 |
+
|
28 |
+
// All regular routes use the Angular engine
|
29 |
+
server.get('**', (req, res, next) => {
|
30 |
+
const { protocol, originalUrl, baseUrl, headers } = req;
|
31 |
+
|
32 |
+
commonEngine
|
33 |
+
.render({
|
34 |
+
bootstrap: AppServerModule,
|
35 |
+
documentFilePath: indexHtml,
|
36 |
+
url: `${protocol}://${headers.host}${originalUrl}`,
|
37 |
+
publicPath: browserDistFolder,
|
38 |
+
providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
|
39 |
+
})
|
40 |
+
.then((html) => res.send(html))
|
41 |
+
.catch((err) => next(err));
|
42 |
+
});
|
43 |
+
|
44 |
+
return server;
|
45 |
+
}
|
46 |
+
|
47 |
+
function run(): void {
|
48 |
+
const port = process.env['PORT'] || 4000;
|
49 |
+
|
50 |
+
// Start up the Node server
|
51 |
+
const server = app();
|
52 |
+
server.listen(port, () => {
|
53 |
+
console.log(`Node Express server listening on http://localhost:${port}`);
|
54 |
+
});
|
55 |
+
}
|
56 |
+
|
57 |
+
run();
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
2 |
+
|
3 |
+
import { BookCreateComponent } from './book-create.component';
|
4 |
+
|
5 |
+
describe('BookCreateComponent', () => {
|
6 |
+
let component: BookCreateComponent;
|
7 |
+
let fixture: ComponentFixture<BookCreateComponent>;
|
8 |
+
|
9 |
+
beforeEach(async () => {
|
10 |
+
await TestBed.configureTestingModule({
|
11 |
+
declarations: [ BookCreateComponent ]
|
12 |
+
})
|
13 |
+
.compileComponents();
|
14 |
+
|
15 |
+
fixture = TestBed.createComponent(BookCreateComponent);
|
16 |
+
component = fixture.componentInstance;
|
17 |
+
fixture.detectChanges();
|
18 |
+
});
|
19 |
+
|
20 |
+
it('should create', () => {
|
21 |
+
expect(component).toBeTruthy();
|
22 |
+
});
|
23 |
+
});
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
2 |
+
|
3 |
+
import { BookEditComponent } from './book-edit.component';
|
4 |
+
|
5 |
+
describe('BookEditComponent', () => {
|
6 |
+
let component: BookEditComponent;
|
7 |
+
let fixture: ComponentFixture<BookEditComponent>;
|
8 |
+
|
9 |
+
beforeEach(async () => {
|
10 |
+
await TestBed.configureTestingModule({
|
11 |
+
declarations: [ BookEditComponent ]
|
12 |
+
})
|
13 |
+
.compileComponents();
|
14 |
+
|
15 |
+
fixture = TestBed.createComponent(BookEditComponent);
|
16 |
+
component = fixture.componentInstance;
|
17 |
+
fixture.detectChanges();
|
18 |
+
});
|
19 |
+
|
20 |
+
it('should create', () => {
|
21 |
+
expect(component).toBeTruthy();
|
22 |
+
});
|
23 |
+
});
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
2 |
+
|
3 |
+
import { BookFormComponent } from './book-form.component';
|
4 |
+
|
5 |
+
describe('BookFormComponent', () => {
|
6 |
+
let component: BookFormComponent;
|
7 |
+
let fixture: ComponentFixture<BookFormComponent>;
|
8 |
+
|
9 |
+
beforeEach(async () => {
|
10 |
+
await TestBed.configureTestingModule({
|
11 |
+
declarations: [ BookFormComponent ]
|
12 |
+
})
|
13 |
+
.compileComponents();
|
14 |
+
|
15 |
+
fixture = TestBed.createComponent(BookFormComponent);
|
16 |
+
component = fixture.componentInstance;
|
17 |
+
fixture.detectChanges();
|
18 |
+
});
|
19 |
+
|
20 |
+
it('should create', () => {
|
21 |
+
expect(component).toBeTruthy();
|
22 |
+
});
|
23 |
+
});
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
2 |
+
|
3 |
+
import { FormErrorsComponent } from './form-errors.component';
|
4 |
+
|
5 |
+
describe('FormErrorsComponent', () => {
|
6 |
+
let component: FormErrorsComponent;
|
7 |
+
let fixture: ComponentFixture<FormErrorsComponent>;
|
8 |
+
|
9 |
+
beforeEach(async () => {
|
10 |
+
await TestBed.configureTestingModule({
|
11 |
+
declarations: [ FormErrorsComponent ]
|
12 |
+
})
|
13 |
+
.compileComponents();
|
14 |
+
|
15 |
+
fixture = TestBed.createComponent(FormErrorsComponent);
|
16 |
+
component = fixture.componentInstance;
|
17 |
+
fixture.detectChanges();
|
18 |
+
});
|
19 |
+
|
20 |
+
it('should create', () => {
|
21 |
+
expect(component).toBeTruthy();
|
22 |
+
});
|
23 |
+
});
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { TestBed } from '@angular/core/testing';
|
2 |
+
|
3 |
+
import { AsyncValidatorsService } from './async-validators.service';
|
4 |
+
|
5 |
+
describe('AsyncValidatorsService', () => {
|
6 |
+
let service: AsyncValidatorsService;
|
7 |
+
|
8 |
+
beforeEach(() => {
|
9 |
+
TestBed.configureTestingModule({});
|
10 |
+
service = TestBed.inject(AsyncValidatorsService);
|
11 |
+
});
|
12 |
+
|
13 |
+
it('should be created', () => {
|
14 |
+
expect(service).toBeTruthy();
|
15 |
+
});
|
16 |
+
});
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { NgModule } from '@angular/core';
|
2 |
+
import { ServerModule } from '@angular/platform-server';
|
3 |
+
|
4 |
+
import { AppModule } from './app.module';
|
5 |
+
import { AppComponent } from './app.component';
|
6 |
+
|
7 |
+
@NgModule({
|
8 |
+
imports: [
|
9 |
+
AppModule,
|
10 |
+
ServerModule,
|
11 |
+
],
|
12 |
+
bootstrap: [AppComponent],
|
13 |
+
})
|
14 |
+
export class AppServerModule {}
|
@@ -1,6 +1,6 @@
|
|
1 |
import { NgModule } from '@angular/core';
|
2 |
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
3 |
-
import { BrowserModule } from '@angular/platform-browser';
|
4 |
|
5 |
import { AppRoutingModule } from './app-routing.module';
|
6 |
import { AppComponent } from './app.component';
|
@@ -24,7 +24,8 @@ import { AuthInterceptor } from './shared/auth.interceptor';
|
|
24 |
provide: HTTP_INTERCEPTORS,
|
25 |
useClass: AuthInterceptor,
|
26 |
multi: true
|
27 |
-
}
|
|
|
28 |
],
|
29 |
bootstrap: [AppComponent]
|
30 |
})
|
1 |
import { NgModule } from '@angular/core';
|
2 |
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
3 |
+
import { BrowserModule, provideClientHydration } from '@angular/platform-browser';
|
4 |
|
5 |
import { AppRoutingModule } from './app-routing.module';
|
6 |
import { AppComponent } from './app.component';
|
24 |
provide: HTTP_INTERCEPTORS,
|
25 |
useClass: AuthInterceptor,
|
26 |
multi: true
|
27 |
+
},
|
28 |
+
provideClientHydration()
|
29 |
],
|
30 |
bootstrap: [AppComponent]
|
31 |
})
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
2 |
+
|
3 |
+
import { BookDetailsComponent } from './book-details.component';
|
4 |
+
|
5 |
+
describe('BookDetailsComponent', () => {
|
6 |
+
let component: BookDetailsComponent;
|
7 |
+
let fixture: ComponentFixture<BookDetailsComponent>;
|
8 |
+
|
9 |
+
beforeEach(async () => {
|
10 |
+
await TestBed.configureTestingModule({
|
11 |
+
declarations: [ BookDetailsComponent ]
|
12 |
+
})
|
13 |
+
.compileComponents();
|
14 |
+
|
15 |
+
fixture = TestBed.createComponent(BookDetailsComponent);
|
16 |
+
component = fixture.componentInstance;
|
17 |
+
fixture.detectChanges();
|
18 |
+
});
|
19 |
+
|
20 |
+
it('should create', () => {
|
21 |
+
expect(component).toBeTruthy();
|
22 |
+
});
|
23 |
+
});
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
2 |
+
|
3 |
+
import { BookListComponent } from './book-list.component';
|
4 |
+
|
5 |
+
describe('BookListComponent', () => {
|
6 |
+
let component: BookListComponent;
|
7 |
+
let fixture: ComponentFixture<BookListComponent>;
|
8 |
+
|
9 |
+
beforeEach(async () => {
|
10 |
+
await TestBed.configureTestingModule({
|
11 |
+
declarations: [ BookListComponent ]
|
12 |
+
})
|
13 |
+
.compileComponents();
|
14 |
+
|
15 |
+
fixture = TestBed.createComponent(BookListComponent);
|
16 |
+
component = fixture.componentInstance;
|
17 |
+
fixture.detectChanges();
|
18 |
+
});
|
19 |
+
|
20 |
+
it('should create', () => {
|
21 |
+
expect(component).toBeTruthy();
|
22 |
+
});
|
23 |
+
});
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { LoggedinOnlyDirective } from './loggedin-only.directive';
|
2 |
+
|
3 |
+
describe('LoggedinOnlyDirective', () => {
|
4 |
+
it('should create an instance', () => {
|
5 |
+
const directive = new LoggedinOnlyDirective();
|
6 |
+
expect(directive).toBeTruthy();
|
7 |
+
});
|
8 |
+
});
|
@@ -1,4 +1,3 @@
|
|
1 |
-
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
2 |
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
3 |
|
4 |
import { HomeComponent } from './home.component';
|
@@ -9,8 +8,7 @@ describe('HomeComponent', () => {
|
|
9 |
|
10 |
beforeEach(async () => {
|
11 |
await TestBed.configureTestingModule({
|
12 |
-
declarations: [ HomeComponent ]
|
13 |
-
schemas: [NO_ERRORS_SCHEMA]
|
14 |
})
|
15 |
.compileComponents();
|
16 |
|
|
|
1 |
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
2 |
|
3 |
import { HomeComponent } from './home.component';
|
8 |
|
9 |
beforeEach(async () => {
|
10 |
await TestBed.configureTestingModule({
|
11 |
+
declarations: [ HomeComponent ]
|
|
|
12 |
})
|
13 |
.compileComponents();
|
14 |
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
2 |
+
|
3 |
+
import { SearchComponent } from './search.component';
|
4 |
+
|
5 |
+
describe('SearchComponent', () => {
|
6 |
+
let component: SearchComponent;
|
7 |
+
let fixture: ComponentFixture<SearchComponent>;
|
8 |
+
|
9 |
+
beforeEach(async () => {
|
10 |
+
await TestBed.configureTestingModule({
|
11 |
+
declarations: [ SearchComponent ]
|
12 |
+
})
|
13 |
+
.compileComponents();
|
14 |
+
|
15 |
+
fixture = TestBed.createComponent(SearchComponent);
|
16 |
+
component = fixture.componentInstance;
|
17 |
+
fixture.detectChanges();
|
18 |
+
});
|
19 |
+
|
20 |
+
it('should create', () => {
|
21 |
+
expect(component).toBeTruthy();
|
22 |
+
});
|
23 |
+
});
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { TestBed } from '@angular/core/testing';
|
2 |
+
|
3 |
+
import { BookStoreService } from './book-store.service';
|
4 |
+
|
5 |
+
describe('BookStoreService', () => {
|
6 |
+
let service: BookStoreService;
|
7 |
+
|
8 |
+
beforeEach(() => {
|
9 |
+
TestBed.configureTestingModule({});
|
10 |
+
service = TestBed.inject(BookStoreService);
|
11 |
+
});
|
12 |
+
|
13 |
+
it('should be created', () => {
|
14 |
+
expect(service).toBeTruthy();
|
15 |
+
});
|
16 |
+
});
|
@@ -0,0 +1 @@
|
|
|
1 |
+
export { AppServerModule as default } from './app/app.module.server';
|
@@ -4,10 +4,14 @@
|
|
4 |
"extends": "./tsconfig.json",
|
5 |
"compilerOptions": {
|
6 |
"outDir": "./out-tsc/app",
|
7 |
-
"types": [
|
|
|
|
|
8 |
},
|
9 |
"files": [
|
10 |
-
"src/main.ts"
|
|
|
|
|
11 |
],
|
12 |
"include": [
|
13 |
"src/**/*.d.ts"
|
4 |
"extends": "./tsconfig.json",
|
5 |
"compilerOptions": {
|
6 |
"outDir": "./out-tsc/app",
|
7 |
+
"types": [
|
8 |
+
"node"
|
9 |
+
]
|
10 |
},
|
11 |
"files": [
|
12 |
+
"src/main.ts",
|
13 |
+
"src/main.server.ts",
|
14 |
+
"server.ts"
|
15 |
],
|
16 |
"include": [
|
17 |
"src/**/*.d.ts"
|