Browse Source

SP_12062024_AuthorUserInterFace

main
Soumen Pal 7 months ago
parent
commit
a179a764d7
26 changed files with 3027 additions and 224 deletions
  1. +1
    -0
      angular/src/app/app-routing.module.ts
  2. +11
    -0
      angular/src/app/author/author-routing.module.ts
  3. +99
    -0
      angular/src/app/author/author.component.html
  4. +0
    -0
      angular/src/app/author/author.component.scss
  5. +23
    -0
      angular/src/app/author/author.component.spec.ts
  6. +93
    -0
      angular/src/app/author/author.component.ts
  7. +21
    -0
      angular/src/app/author/author.module.ts
  8. +16
    -0
      angular/src/app/book/book.component.html
  9. +59
    -51
      angular/src/app/book/book.component.ts
  10. +10
    -2
      angular/src/app/proxy/books/book.service.ts
  11. +8
    -1
      angular/src/app/proxy/books/models.ts
  12. +182
    -102
      angular/src/app/proxy/generate-proxy.json
  13. +14
    -16
      angular/src/app/route.provider.ts
  14. +12
    -0
      aspnet-core/src/Acme.BookStore.Application.Contracts/Books/AuthorLookupDto.cs
  15. +4
    -0
      aspnet-core/src/Acme.BookStore.Application.Contracts/Books/BookDto.cs
  16. +3
    -0
      aspnet-core/src/Acme.BookStore.Application.Contracts/Books/CreateUpdateBookDto.cs
  17. +2
    -0
      aspnet-core/src/Acme.BookStore.Application.Contracts/Books/IBookAppService.cs
  18. +1
    -0
      aspnet-core/src/Acme.BookStore.Application/BookStoreApplicationAutoMapperProfile.cs
  19. +116
    -24
      aspnet-core/src/Acme.BookStore.Application/Books/BookAppService.cs
  20. +8
    -1
      aspnet-core/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json
  21. +55
    -27
      aspnet-core/src/Acme.BookStore.Domain/BookStoreDataSeederContributor.cs
  22. +2
    -0
      aspnet-core/src/Acme.BookStore.Domain/Books/Book.cs
  23. +2
    -0
      aspnet-core/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs
  24. +2220
    -0
      aspnet-core/src/Acme.BookStore.EntityFrameworkCore/Migrations/20240612062933_Added_AuthorId_To_Book.Designer.cs
  25. +51
    -0
      aspnet-core/src/Acme.BookStore.EntityFrameworkCore/Migrations/20240612062933_Added_AuthorId_To_Book.cs
  26. +14
    -0
      aspnet-core/src/Acme.BookStore.EntityFrameworkCore/Migrations/BookStoreDbContextModelSnapshot.cs

+ 1
- 0
angular/src/app/app-routing.module.ts View File

@ -28,6 +28,7 @@ const routes: Routes = [
{ path: 'books', loadChildren: () => import('./book/book.module').then(m => m.BookModule) },
{ path: 'customer', loadChildren: () => import('./customer/customer.module').then(m => m.CustomerModule) },
{ path: 'book-issue', loadChildren: () => import('./book-issue/book-issue.module').then(m => m.BookIssueModule) },
{ path: 'authors', loadChildren: () => import('./author/author.module').then(m => m.AuthorModule) },
];
@NgModule({


+ 11
- 0
angular/src/app/author/author-routing.module.ts View File

@ -0,0 +1,11 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthorComponent } from './author.component';
const routes: Routes = [{ path: '', component: AuthorComponent }];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AuthorRoutingModule { }

+ 99
- 0
angular/src/app/author/author.component.html View File

@ -0,0 +1,99 @@
<div class="card">
<div class="card-header">
<div class="row">
<div class="col col-md-6">
<h5 class="card-title">
{{ '::Menu:Authors' | abpLocalization }}
</h5>
</div>
<div class="text-end col col-md-6">
<div class="text-lg-end pt-2">
<button *abpPermission="'BookStore.Authors.Create'" id="create" class="btn btn-primary" type="button" (click)="createAuthor()">
<i class="fa fa-plus me-1"></i>
<span>{{ '::NewAuthor' | abpLocalization }}</span>
</button>
</div>
</div>
</div>
</div>
<div class="card-body">
<ngx-datatable [rows]="author.items" [count]="author.totalCount" [list]="list" default>
<ngx-datatable-column
[name]="'::Actions' | abpLocalization"
[maxWidth]="150"
[sortable]="false"
>
<ng-template let-row="row" ngx-datatable-cell-template>
<div ngbDropdown container="body" class="d-inline-block">
<button
class="btn btn-primary btn-sm dropdown-toggle"
data-toggle="dropdown"
aria-haspopup="true"
ngbDropdownToggle
>
<i class="fa fa-cog me-1"></i>{{ '::Actions' | abpLocalization }}
</button>
<div ngbDropdownMenu>
<button *abpPermission="'BookStore.Authors.Edit'" ngbDropdownItem (click)="editAuthor(row.id)">
{{ '::Edit' | abpLocalization }}
</button>
<button *abpPermission="'BookStore.Authors.Delete'" ngbDropdownItem (click)="delete(row.id)">
{{ '::Delete' | abpLocalization }}
</button>
</div>
</div>
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column [name]="'::Name' | abpLocalization" prop="name"></ngx-datatable-column>
<ngx-datatable-column [name]="'::BirthDate' | abpLocalization">
<ng-template let-row="row" ngx-datatable-cell-template>
{{ row.birthDate | date }}
</ng-template>
</ngx-datatable-column>
</ngx-datatable>
</div>
</div>
<abp-modal [(visible)]="isModalOpen">
<ng-template #abpHeader>
<h3>{{ (selectedAuthor.id ? '::Edit' : '::NewAuthor') | abpLocalization }}</h3>
</ng-template>
<ng-template #abpBody>
<form [formGroup]="form" (ngSubmit)="save()">
<div class="form-group">
<label for="author-name">Name</label><span> * </span>
<input type="text" id="author-name" class="form-control" formControlName="name" autofocus />
</div>
<div class="mt-2">
<label>Birth date</label><span> * </span>
<input
#datepicker="ngbDatepicker"
class="form-control"
name="datepicker"
formControlName="birthDate"
ngbDatepicker
(click)="datepicker.toggle()"
/>
</div>
<div class="form-group">
<label for="short-bio">Short Bio</label><span> * </span>
<input type="text" id="short-bio" class="form-control" formControlName="shortBio" />
</div>
</form>
</ng-template>
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" abpClose>
{{ '::Close' | abpLocalization }}
</button>
<button class="btn btn-primary" (click)="save()" [disabled]="form.invalid">
<i class="fa fa-check mr-1"></i>
{{ '::Save' | abpLocalization }}
</button>
</ng-template>
</abp-modal>

+ 0
- 0
angular/src/app/author/author.component.scss View File


+ 23
- 0
angular/src/app/author/author.component.spec.ts View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AuthorComponent } from './author.component';
describe('AuthorComponent', () => {
let component: AuthorComponent;
let fixture: ComponentFixture<AuthorComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AuthorComponent]
})
.compileComponents();
fixture = TestBed.createComponent(AuthorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 93
- 0
angular/src/app/author/author.component.ts View File

@ -0,0 +1,93 @@
import { Component, OnInit } from '@angular/core';
import { ListService, PagedResultDto } from '@abp/ng.core';
import { AuthorService, AuthorDto } from '@proxy/authors';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared';
@Component({
selector: 'app-author',
templateUrl: './author.component.html',
styleUrls: ['./author.component.scss'],
providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
})
export class AuthorComponent implements OnInit {
author = { items: [], totalCount: 0 } as PagedResultDto<AuthorDto>;
isModalOpen = false;
form: FormGroup;
selectedAuthor = {} as AuthorDto;
constructor(
public readonly list: ListService,
private authorService: AuthorService,
private fb: FormBuilder,
private confirmation: ConfirmationService
) {}
ngOnInit(): void {
const authorStreamCreator = (query) => this.authorService.getList(query);
this.list.hookToQuery(authorStreamCreator).subscribe((response) => {
this.author = response;
});
}
createAuthor() {
this.selectedAuthor = {} as AuthorDto;
this.buildForm();
this.isModalOpen = true;
}
editAuthor(id: string) {
this.authorService.get(id).subscribe((author) => {
this.selectedAuthor = author;
this.buildForm();
this.isModalOpen = true;
});
}
buildForm() {
this.form = this.fb.group({
name: [this.selectedAuthor.name || '', Validators.required],
birthDate: [
this.selectedAuthor.birthDate ? new Date(this.selectedAuthor.birthDate) : null,
Validators.required,
],
shortBio:[this.selectedAuthor.shortBio || '', Validators.required],
});
}
save() {
if (this.form.invalid) {
return;
}
if (this.selectedAuthor.id) {
this.authorService
.update(this.selectedAuthor.id, this.form.value)
.subscribe(() => {
this.isModalOpen = false;
this.form.reset();
this.list.get();
});
} else {
this.authorService.create(this.form.value).subscribe(() => {
this.isModalOpen = false;
this.form.reset();
this.list.get();
});
}
}
delete(id: string) {
this.confirmation.warn('::AreYouSureToDelete', '::AreYouSure')
.subscribe((status) => {
if (status === Confirmation.Status.confirm) {
this.authorService.delete(id).subscribe(() => this.list.get());
}
});
}
}

+ 21
- 0
angular/src/app/author/author.module.ts View File

@ -0,0 +1,21 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AuthorRoutingModule } from './author-routing.module';
import { AuthorComponent } from './author.component';
import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap';
import { SharedModule } from '../shared/shared.module';
@NgModule({
declarations: [
AuthorComponent
],
imports: [
CommonModule,
AuthorRoutingModule,
NgbDatepickerModule,
SharedModule
]
})
export class AuthorModule { }

+ 16
- 0
angular/src/app/book/book.component.html View File

@ -63,6 +63,12 @@
{{ row.price | currency }}
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column
[name]="'::Author' | abpLocalization"
prop="authorName"
[sortable]="false"
></ngx-datatable-column>
</ngx-datatable>
</div>
</div>
@ -104,6 +110,16 @@
(click)="datepicker.toggle()"
/>
</div>
<div class="form-group">
<label for="author-id">Author</label><span> * </span>
<select class="form-control" id="author-id" formControlName="authorId">
<option [ngValue]="null">Select author</option>
<option [ngValue]="author.id" *ngFor="let author of authors$ | async">
{{ author.name }}
</option>
</select>
</div>
</form>
</ng-template>


+ 59
- 51
angular/src/app/book/book.component.ts View File

@ -1,32 +1,41 @@
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { BookService, BookDto, bookTypeOptions } from '@proxy/books';
import { BookService, BookDto, bookTypeOptions, AuthorLookupDto } from '@proxy/books';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Component({
selector: 'app-book',
templateUrl: './book.component.html',
styleUrls: ['./book.component.scss'],
providers: [ListService,
{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter } // add this line
],
providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
})
export class BookComponent implements OnInit {
book = { items: [], totalCount: 0 } as PagedResultDto<BookDto>;
selectedBook = {} as BookDto; // declare selectedBook
isModalOpen = false; // add this line
form: FormGroup; // add this line
book = { items: [], totalCount: 0 } as PagedResultDto<BookDto>;
form: FormGroup;
selectedBook = {} as BookDto;
authors$: Observable<AuthorLookupDto[]>;
bookTypes = bookTypeOptions;
constructor(public readonly list: ListService, private bookService: BookService, private fb: FormBuilder,
private confirmation: ConfirmationService
) {}
isModalOpen = false;
constructor(
public readonly list: ListService,
private bookService: BookService,
private fb: FormBuilder,
private confirmation: ConfirmationService
) {
this.authors$ = bookService.getAuthorLookup().pipe(map((r) => r.items));
}
ngOnInit() {
debugger;
const bookStreamCreator = (query) => this.bookService.getList(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
@ -34,55 +43,54 @@ export class BookComponent implements OnInit {
});
}
// add new method
createBook() {
this.buildForm(); // add this line
this.selectedBook = {} as BookDto; // reset the selected book
this.selectedBook = {} as BookDto;
this.buildForm();
this.isModalOpen = true;
}
editBook(id: string) {
this.bookService.get(id).subscribe((book) => {
this.selectedBook = book;
this.buildForm();
this.isModalOpen = true;
});
}
buildForm() {
this.form = this.fb.group({
name: ['', Validators.required],
type: [null, Validators.required],
publishDate: [null, Validators.required],
price: [null, Validators.required],
authorId: [this.selectedBook.authorId || null, Validators.required],
name: [this.selectedBook.name || null, Validators.required],
type: [this.selectedBook.type || null, Validators.required],
publishDate: [
this.selectedBook.publishDate ? new Date(this.selectedBook.publishDate) : null,
Validators.required,
],
price: [this.selectedBook.price || null, Validators.required],
});
}
save() {
if (this.form.invalid) {
return;
}
const request = this.selectedBook.id
? this.bookService.update(this.selectedBook.id, this.form.value)
: this.bookService.create(this.form.value);
request.subscribe(() => {
this.isModalOpen = false;
this.form.reset();
this.list.get();
});
}
? this.bookService.update(this.selectedBook.id, this.form.value)
: this.bookService.create(this.form.value);
// Add editBook method
editBook(id: string) {
this.bookService.get(id).subscribe((book) => {
this.selectedBook = book;
this.buildForm();
this.form.controls["name"].setValue(this.selectedBook.name);
this.form.controls["type"].setValue(this.selectedBook.type);
this.form.controls["publishDate"].setValue(this.selectedBook.publishDate);
this.form.controls["price"].setValue(this.selectedBook.price);
this.isModalOpen = true;
});
}
request.subscribe(() => {
this.isModalOpen = false;
this.form.reset();
this.list.get();
});
}
delete(id: string) {
this.confirmation.warn('::AreYouSureToDelete', '::AreYouSure').subscribe((status) => {
if (status === Confirmation.Status.confirm) {
this.bookService.delete(id).subscribe(() => this.list.get());
}
});
}
delete(id: string) {
this.confirmation.warn('::AreYouSureToDelete', 'AbpAccount::AreYouSure').subscribe((status) => {
if (status === Confirmation.Status.confirm) {
this.bookService.delete(id).subscribe(() => this.list.get());
}
});
}
}

+ 10
- 2
angular/src/app/proxy/books/book.service.ts View File

@ -1,6 +1,6 @@
import type { BookDto, CreateUpdateBookDto } from './models';
import type { AuthorLookupDto, BookDto, CreateUpdateBookDto } from './models';
import { RestService, Rest } from '@abp/ng.core';
import type { PagedAndSortedResultRequestDto, PagedResultDto } from '@abp/ng.core';
import type { ListResultDto, PagedAndSortedResultRequestDto, PagedResultDto } from '@abp/ng.core';
import { Injectable } from '@angular/core';
@Injectable({
@ -35,6 +35,14 @@ export class BookService {
{ apiName: this.apiName,...config });
getAuthorLookup = (config?: Partial<Rest.Config>) =>
this.restService.request<any, ListResultDto<AuthorLookupDto>>({
method: 'GET',
url: '/api/app/book/author-lookup',
},
{ apiName: this.apiName,...config });
getBookDropDown = (config?: Partial<Rest.Config>) =>
this.restService.request<any, BookDto[]>({
method: 'GET',


+ 8
- 1
angular/src/app/proxy/books/models.ts View File

@ -1,11 +1,17 @@
import type { AuditedEntityDto } from '@abp/ng.core';
import type { AuditedEntityDto, EntityDto } from '@abp/ng.core';
import type { BookType } from './book-type.enum';
export interface AuthorLookupDto extends EntityDto<string> {
name?: string;
}
export interface BookDto extends AuditedEntityDto<string> {
name?: string;
type: BookType;
publishDate?: string;
price: number;
authorId?: string;
authorName?: string;
}
export interface CreateUpdateBookDto {
@ -13,4 +19,5 @@ export interface CreateUpdateBookDto {
type: BookType;
publishDate: string;
price: number;
authorId?: string;
}

+ 182
- 102
angular/src/app/proxy/generate-proxy.json View File

@ -1530,6 +1530,14 @@
"typeSimple": "[Acme.BookStore.Books.BookDto]"
}
},
{
"name": "GetAuthorLookupAsync",
"parametersOnMethod": [],
"returnValue": {
"type": "Volo.Abp.Application.Dtos.ListResultDto<Acme.BookStore.Books.AuthorLookupDto>",
"typeSimple": "Volo.Abp.Application.Dtos.ListResultDto<Acme.BookStore.Books.AuthorLookupDto>"
}
},
{
"name": "GetAsync",
"parametersOnMethod": [
@ -1627,6 +1635,119 @@
}
],
"actions": {
"GetAsyncById": {
"uniqueName": "GetAsyncById",
"name": "GetAsync",
"httpMethod": "GET",
"url": "api/app/book/{id}",
"supportedVersions": [],
"parametersOnMethod": [
{
"name": "id",
"typeAsString": "System.Guid, System.Private.CoreLib",
"type": "System.Guid",
"typeSimple": "string",
"isOptional": false,
"defaultValue": null
}
],
"parameters": [
{
"nameOnMethod": "id",
"name": "id",
"jsonName": null,
"type": "System.Guid",
"typeSimple": "string",
"isOptional": false,
"defaultValue": null,
"constraintTypes": [],
"bindingSourceId": "Path",
"descriptorName": ""
}
],
"returnValue": {
"type": "Acme.BookStore.Books.BookDto",
"typeSimple": "Acme.BookStore.Books.BookDto"
},
"allowAnonymous": false,
"implementFrom": "Volo.Abp.Application.Services.IReadOnlyAppService<Acme.BookStore.Books.BookDto,Acme.BookStore.Books.BookDto,System.Guid,Volo.Abp.Application.Dtos.PagedAndSortedResultRequestDto>"
},
"GetListAsyncByInput": {
"uniqueName": "GetListAsyncByInput",
"name": "GetListAsync",
"httpMethod": "GET",
"url": "api/app/book",
"supportedVersions": [],
"parametersOnMethod": [
{
"name": "input",
"typeAsString": "Volo.Abp.Application.Dtos.PagedAndSortedResultRequestDto, Volo.Abp.Ddd.Application.Contracts",
"type": "Volo.Abp.Application.Dtos.PagedAndSortedResultRequestDto",
"typeSimple": "Volo.Abp.Application.Dtos.PagedAndSortedResultRequestDto",
"isOptional": false,
"defaultValue": null
}
],
"parameters": [
{
"nameOnMethod": "input",
"name": "Sorting",
"jsonName": null,
"type": "System.String",
"typeSimple": "string",
"isOptional": false,
"defaultValue": null,
"constraintTypes": null,
"bindingSourceId": "ModelBinding",
"descriptorName": "input"
},
{
"nameOnMethod": "input",
"name": "SkipCount",
"jsonName": null,
"type": "System.Int32",
"typeSimple": "number",
"isOptional": false,
"defaultValue": null,
"constraintTypes": null,
"bindingSourceId": "ModelBinding",
"descriptorName": "input"
},
{
"nameOnMethod": "input",
"name": "MaxResultCount",
"jsonName": null,
"type": "System.Int32",
"typeSimple": "number",
"isOptional": false,
"defaultValue": null,
"constraintTypes": null,
"bindingSourceId": "ModelBinding",
"descriptorName": "input"
}
],
"returnValue": {
"type": "Volo.Abp.Application.Dtos.PagedResultDto<Acme.BookStore.Books.BookDto>",
"typeSimple": "Volo.Abp.Application.Dtos.PagedResultDto<Acme.BookStore.Books.BookDto>"
},
"allowAnonymous": false,
"implementFrom": "Volo.Abp.Application.Services.IReadOnlyAppService<Acme.BookStore.Books.BookDto,Acme.BookStore.Books.BookDto,System.Guid,Volo.Abp.Application.Dtos.PagedAndSortedResultRequestDto>"
},
"GetAuthorLookupAsync": {
"uniqueName": "GetAuthorLookupAsync",
"name": "GetAuthorLookupAsync",
"httpMethod": "GET",
"url": "api/app/book/author-lookup",
"supportedVersions": [],
"parametersOnMethod": [],
"parameters": [],
"returnValue": {
"type": "Volo.Abp.Application.Dtos.ListResultDto<Acme.BookStore.Books.AuthorLookupDto>",
"typeSimple": "Volo.Abp.Application.Dtos.ListResultDto<Acme.BookStore.Books.AuthorLookupDto>"
},
"allowAnonymous": false,
"implementFrom": "Acme.BookStore.Books.IBookAppService"
},
"GetBookDropDown": {
"uniqueName": "GetBookDropDown",
"name": "GetBookDropDown",
@ -1639,7 +1760,7 @@
"type": "System.Collections.Generic.List<Acme.BookStore.Books.BookDto>",
"typeSimple": "[Acme.BookStore.Books.BookDto]"
},
"allowAnonymous": null,
"allowAnonymous": false,
"implementFrom": "Acme.BookStore.Books.IBookAppService"
},
"CreateAsyncByInput": {
@ -1676,7 +1797,7 @@
"type": "Acme.BookStore.Books.BookDto",
"typeSimple": "Acme.BookStore.Books.BookDto"
},
"allowAnonymous": null,
"allowAnonymous": false,
"implementFrom": "Volo.Abp.Application.Services.ICreateAppService<Acme.BookStore.Books.BookDto,Acme.BookStore.Books.CreateUpdateBookDto>"
},
"UpdateAsyncByIdAndInput": {
@ -1733,7 +1854,7 @@
"type": "Acme.BookStore.Books.BookDto",
"typeSimple": "Acme.BookStore.Books.BookDto"
},
"allowAnonymous": null,
"allowAnonymous": false,
"implementFrom": "Volo.Abp.Application.Services.IUpdateAppService<Acme.BookStore.Books.BookDto,System.Guid,Acme.BookStore.Books.CreateUpdateBookDto>"
},
"DeleteAsyncById": {
@ -1770,106 +1891,8 @@
"type": "System.Void",
"typeSimple": "System.Void"
},
"allowAnonymous": null,
"allowAnonymous": false,
"implementFrom": "Volo.Abp.Application.Services.IDeleteAppService<System.Guid>"
},
"GetAsyncById": {
"uniqueName": "GetAsyncById",
"name": "GetAsync",
"httpMethod": "GET",
"url": "api/app/book/{id}",
"supportedVersions": [],
"parametersOnMethod": [
{
"name": "id",
"typeAsString": "System.Guid, System.Private.CoreLib",
"type": "System.Guid",
"typeSimple": "string",
"isOptional": false,
"defaultValue": null
}
],
"parameters": [
{
"nameOnMethod": "id",
"name": "id",
"jsonName": null,
"type": "System.Guid",
"typeSimple": "string",
"isOptional": false,
"defaultValue": null,
"constraintTypes": [],
"bindingSourceId": "Path",
"descriptorName": ""
}
],
"returnValue": {
"type": "Acme.BookStore.Books.BookDto",
"typeSimple": "Acme.BookStore.Books.BookDto"
},
"allowAnonymous": null,
"implementFrom": "Volo.Abp.Application.Services.IReadOnlyAppService<Acme.BookStore.Books.BookDto,Acme.BookStore.Books.BookDto,System.Guid,Volo.Abp.Application.Dtos.PagedAndSortedResultRequestDto>"
},
"GetListAsyncByInput": {
"uniqueName": "GetListAsyncByInput",
"name": "GetListAsync",
"httpMethod": "GET",
"url": "api/app/book",
"supportedVersions": [],
"parametersOnMethod": [
{
"name": "input",
"typeAsString": "Volo.Abp.Application.Dtos.PagedAndSortedResultRequestDto, Volo.Abp.Ddd.Application.Contracts",
"type": "Volo.Abp.Application.Dtos.PagedAndSortedResultRequestDto",
"typeSimple": "Volo.Abp.Application.Dtos.PagedAndSortedResultRequestDto",
"isOptional": false,
"defaultValue": null
}
],
"parameters": [
{
"nameOnMethod": "input",
"name": "Sorting",
"jsonName": null,
"type": "System.String",
"typeSimple": "string",
"isOptional": false,
"defaultValue": null,
"constraintTypes": null,
"bindingSourceId": "ModelBinding",
"descriptorName": "input"
},
{
"nameOnMethod": "input",
"name": "SkipCount",
"jsonName": null,
"type": "System.Int32",
"typeSimple": "number",
"isOptional": false,
"defaultValue": null,
"constraintTypes": null,
"bindingSourceId": "ModelBinding",
"descriptorName": "input"
},
{
"nameOnMethod": "input",
"name": "MaxResultCount",
"jsonName": null,
"type": "System.Int32",
"typeSimple": "number",
"isOptional": false,
"defaultValue": null,
"constraintTypes": null,
"bindingSourceId": "ModelBinding",
"descriptorName": "input"
}
],
"returnValue": {
"type": "Volo.Abp.Application.Dtos.PagedResultDto<Acme.BookStore.Books.BookDto>",
"typeSimple": "Volo.Abp.Application.Dtos.PagedResultDto<Acme.BookStore.Books.BookDto>"
},
"allowAnonymous": null,
"implementFrom": "Volo.Abp.Application.Services.IReadOnlyAppService<Acme.BookStore.Books.BookDto,Acme.BookStore.Books.BookDto,System.Guid,Volo.Abp.Application.Dtos.PagedAndSortedResultRequestDto>"
}
}
},
@ -5036,6 +5059,27 @@
}
]
},
"Acme.BookStore.Books.AuthorLookupDto": {
"baseType": "Volo.Abp.Application.Dtos.EntityDto<System.Guid>",
"isEnum": false,
"enumNames": null,
"enumValues": null,
"genericArguments": null,
"properties": [
{
"name": "Name",
"jsonName": null,
"type": "System.String",
"typeSimple": "string",
"isRequired": false,
"minLength": null,
"maxLength": null,
"minimum": null,
"maximum": null,
"regex": null
}
]
},
"Acme.BookStore.Books.BookDto": {
"baseType": "Volo.Abp.Application.Dtos.AuditedEntityDto<System.Guid>",
"isEnum": false,
@ -5090,6 +5134,30 @@
"minimum": null,
"maximum": null,
"regex": null
},
{
"name": "AuthorId",
"jsonName": null,
"type": "System.Guid",
"typeSimple": "string",
"isRequired": false,
"minLength": null,
"maxLength": null,
"minimum": null,
"maximum": null,
"regex": null
},
{
"name": "AuthorName",
"jsonName": null,
"type": "System.String",
"typeSimple": "string",
"isRequired": false,
"minLength": null,
"maxLength": null,
"minimum": null,
"maximum": null,
"regex": null
}
]
},
@ -5175,6 +5243,18 @@
"minimum": null,
"maximum": null,
"regex": null
},
{
"name": "AuthorId",
"jsonName": null,
"type": "System.Guid",
"typeSimple": "string",
"isRequired": false,
"minLength": null,
"maxLength": null,
"minimum": null,
"maximum": null,
"regex": null
}
]
},


+ 14
- 16
angular/src/app/route.provider.ts View File

@ -14,38 +14,36 @@ function configureRoutes(routesService: RoutesService) {
iconClass: 'fas fa-home',
order: 1,
layout: eLayoutType.application,
},
},
{
path: '/book-store',
name: '::Menu:BookStore',
iconClass: 'fas fa-book',
order: 2,
path: '/authors',
name: '::Menu:Authors',
iconClass: 'fas fa-user-plus',
order:2,
layout: eLayoutType.application,
requiredPolicy: 'BookStore.Books',
requiredPolicy: 'BookStore.Authors',
},
{
path: '/books',
name: '::Menu:Books',
parentName: '::Menu:BookStore',
name: '::Menu:Books',
iconClass: 'fas fa-book',
order: 3,
layout: eLayoutType.application,
requiredPolicy: 'BookStore.Books',
},
},
{
path: '/customer',
name: 'Customer',
name: '::Menu:Customer',
iconClass: 'fas fa-user',
order: 2,
order: 4,
layout: eLayoutType.application,
requiredPolicy: 'BookStore.Customers',
},
{
path: '/book-issue',
name: 'book-issue',
name: '::Menu:BookIssue',
iconClass: 'fas fa-book',
order: 2,
order: 5,
layout: eLayoutType.application,
requiredPolicy: 'BookStore.BookIssued',
},


+ 12
- 0
aspnet-core/src/Acme.BookStore.Application.Contracts/Books/AuthorLookupDto.cs View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
using Volo.Abp.Application.Dtos;
namespace Acme.BookStore.Books
{
public class AuthorLookupDto : EntityDto<Guid>
{
public string Name { get; set; }
}
}

+ 4
- 0
aspnet-core/src/Acme.BookStore.Application.Contracts/Books/BookDto.cs View File

@ -14,5 +14,9 @@ namespace Acme.BookStore.Books
public DateTime PublishDate { get; set; }
public float Price { get; set; }
public Guid AuthorId { get; set; }
public string AuthorName { get; set; }
}
}

+ 3
- 0
aspnet-core/src/Acme.BookStore.Application.Contracts/Books/CreateUpdateBookDto.cs View File

@ -20,5 +20,8 @@ namespace Acme.BookStore.Books
[Required]
public float Price { get; set; }
public Guid AuthorId { get; set; }
}
}

+ 2
- 0
aspnet-core/src/Acme.BookStore.Application.Contracts/Books/IBookAppService.cs View File

@ -15,5 +15,7 @@ namespace Acme.BookStore.Books
CreateUpdateBookDto> //Used to create/update a book
{
Task<List<BookDto>> GetBookDropDown();
Task<ListResultDto<AuthorLookupDto>> GetAuthorLookupAsync();
}
}

+ 1
- 0
aspnet-core/src/Acme.BookStore.Application/BookStoreApplicationAutoMapperProfile.cs View File

@ -17,6 +17,7 @@ public class BookStoreApplicationAutoMapperProfile : Profile
// CreateMap<List<Customer>, List<CustomerDto>>().ReverseMap();
CreateMap<BookIssue, BookIssueDto>().ReverseMap();
CreateMap<BookIssueList, BookIssueListDto>().ReverseMap();
CreateMap<Author, AuthorLookupDto>();
/* You can configure your AutoMapper mapping configuration here.
* Alternatively, you can split your mapping configurations


+ 116
- 24
aspnet-core/src/Acme.BookStore.Application/Books/BookAppService.cs View File

@ -1,43 +1,135 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using Acme.BookStore.Authors;
using Acme.BookStore.Permissions;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Acme.BookStore.Permissions;
namespace Acme.BookStore.Books
namespace Acme.BookStore.Books;
[Authorize(BookStorePermissions.Books.Default)]
public class BookAppService :
CrudAppService<
Book, //The Book entity
BookDto, //Used to show books
Guid, //Primary key of the book entity
PagedAndSortedResultRequestDto, //Used for paging/sorting
CreateUpdateBookDto>, //Used to create/update a book
IBookAppService //implement the IBookAppService
{
private readonly IAuthorRepository _authorRepository;
private readonly IRepository<Book, Guid> _bookRepository;
public BookAppService(
IRepository<Book, Guid> repository,
IAuthorRepository authorRepository)
: base(repository)
{
_authorRepository = authorRepository;
_bookRepository = repository;
GetPolicyName = BookStorePermissions.Books.Default;
GetListPolicyName = BookStorePermissions.Books.Default;
CreatePolicyName = BookStorePermissions.Books.Create;
UpdatePolicyName = BookStorePermissions.Books.Edit;
DeletePolicyName = BookStorePermissions.Books.Delete;
}
public override async Task<BookDto> GetAsync(Guid id)
{
//Get the IQueryable<Book> from the repository
var queryable = await Repository.GetQueryableAsync();
//Prepare a query to join books and authors
var query = from book in queryable
join author in await _authorRepository.GetQueryableAsync() on book.AuthorId equals author.Id
where book.Id == id
select new { book, author };
//Execute the query and get the book with author
var queryResult = await AsyncExecuter.FirstOrDefaultAsync(query);
if (queryResult == null)
{
throw new EntityNotFoundException(typeof(Book), id);
}
var bookDto = ObjectMapper.Map<Book, BookDto>(queryResult.book);
bookDto.AuthorName = queryResult.author.Name;
return bookDto;
}
public override async Task<PagedResultDto<BookDto>> GetListAsync(PagedAndSortedResultRequestDto input)
{
//Get the IQueryable<Book> from the repository
var queryable = await Repository.GetQueryableAsync();
//Prepare a query to join books and authors
var query = from book in queryable
join author in await _authorRepository.GetQueryableAsync() on book.AuthorId equals author.Id
select new { book, author };
public class BookAppService :
CrudAppService<
Book, //The Book entity
BookDto, //Used to show books
Guid, //Primary key of the book entity
PagedAndSortedResultRequestDto, //Used for paging/sorting
CreateUpdateBookDto>, //Used to create/update a book
IBookAppService //implement the IBookAppService
//Paging
query = query
.OrderBy(NormalizeSorting(input.Sorting))
.Skip(input.SkipCount)
.Take(input.MaxResultCount);
//Execute the query and get a list
var queryResult = await AsyncExecuter.ToListAsync(query);
//Convert the query result to a list of BookDto objects
var bookDtos = queryResult.Select(x =>
{
var bookDto = ObjectMapper.Map<Book, BookDto>(x.book);
bookDto.AuthorName = x.author.Name;
return bookDto;
}).ToList();
//Get the total count with another query
var totalCount = await Repository.GetCountAsync();
return new PagedResultDto<BookDto>(
totalCount,
bookDtos
);
}
public async Task<ListResultDto<AuthorLookupDto>> GetAuthorLookupAsync()
{
private readonly IRepository<Book, Guid> _bookRepository;
public BookAppService(IRepository<Book, Guid> repository)
: base(repository)
var authors = await _authorRepository.GetListAsync();
return new ListResultDto<AuthorLookupDto>(
ObjectMapper.Map<List<Author>, List<AuthorLookupDto>>(authors)
);
}
private static string NormalizeSorting(string sorting)
{
if (sorting.IsNullOrEmpty())
{
_bookRepository = repository;
GetPolicyName = BookStorePermissions.Books.Default;
GetListPolicyName = BookStorePermissions.Books.Default;
CreatePolicyName = BookStorePermissions.Books.Create;
UpdatePolicyName = BookStorePermissions.Books.Edit;
DeletePolicyName = BookStorePermissions.Books.Delete;
return $"book.{nameof(Book.Name)}";
}
public async Task<List<BookDto>> GetBookDropDown()
if (sorting.Contains("authorName", StringComparison.OrdinalIgnoreCase))
{
var cus = await _bookRepository.GetListAsync();
cus = cus == null ? new List<Book>() : cus;
return ObjectMapper.Map<List<Book>, List<BookDto>>(cus);
return sorting.Replace(
"authorName",
"author.Name",
StringComparison.OrdinalIgnoreCase
);
}
return $"book.{sorting}";
}
public async Task<List<BookDto>> GetBookDropDown()
{
var cus = await _bookRepository.GetListAsync();
cus = cus == null ? new List<Book>() : cus;
return ObjectMapper.Map<List<Book>, List<BookDto>>(cus);
}
}

+ 8
- 1
aspnet-core/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json View File

@ -6,6 +6,8 @@
"LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.",
"Menu:BookStore": "Book Store",
"Menu:Books": "Books",
"Menu:Customer": "Customer",
"Menu:BookIssue": "Book Issue",
"Actions": "Actions",
"Close": "Close",
"Delete": "Delete",
@ -46,7 +48,12 @@
"Permission:BookIssued": "Book Issued Management",
"Permission:BookIssued.Create": "Creating new Issued Book",
"Permission:BookIssued.Edit": "Editing the Issued Book",
"Permission:BookIssued.Delete": "Deleting the Issued Book"
"Permission:BookIssued.Delete": "Deleting the Issued Book",
"Menu:Authors": "Authors",
"Authors": "Authors",
"AuthorDeletionConfirmationMessage": "Are you sure to delete the author '{0}'?",
"BirthDate": "Birth date",
"NewAuthor": "New author"
}
}


+ 55
- 27
aspnet-core/src/Acme.BookStore.Domain/BookStoreDataSeederContributor.cs View File

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Acme.BookStore.Books;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
@ -23,7 +23,7 @@ namespace Acme.BookStore
private readonly CustomerManager _customerManager;
public BookStoreDataSeederContributor(IRepository<Book, Guid> bookRepository,
IAuthorRepository authorRepository,
IAuthorRepository authorRepository,
AuthorManager authorManager,
ICustomerRepository customerRepository,
CustomerManager customerManager
@ -40,48 +40,76 @@ namespace Acme.BookStore
public async Task SeedAsync(DataSeedContext context)
{
if (await _bookRepository.GetCountAsync() <= 0)
{
await _bookRepository.InsertAsync(
new Book
{
Name = "1984",
Type = BookType.Dystopia,
PublishDate = new DateTime(1949, 6, 8),
Price = 19.84f
},
autoSave: true
);
//if (await _bookRepository.GetCountAsync() <= 0)
//{
// await _bookRepository.InsertAsync(
// new Book
// {
// Name = "1984",
// Type = BookType.Dystopia,
// PublishDate = new DateTime(1949, 6, 8),
// Price = 19.84f
// },
// autoSave: true
// );
await _bookRepository.InsertAsync(
new Book
{
Name = "The Hitchhiker's Guide to the Galaxy",
Type = BookType.ScienceFiction,
PublishDate = new DateTime(1995, 9, 27),
Price = 42.0f
},
autoSave: true
);
}
// await _bookRepository.InsertAsync(
// new Book
// {
// Name = "The Hitchhiker's Guide to the Galaxy",
// Type = BookType.ScienceFiction,
// PublishDate = new DateTime(1995, 9, 27),
// Price = 42.0f
// },
// autoSave: true
// );
//}
if (await _authorRepository.GetCountAsync() <= 0)
{
await _authorRepository.InsertAsync(
var orwell = await _authorRepository.InsertAsync(
await _authorManager.CreateAsync(
"George Orwell",
new DateTime(1903, 06, 25),
"Orwell produced literary criticism and poetry, fiction and polemical journalism; and is best known for the allegorical novella Animal Farm (1945) and the dystopian novel Nineteen Eighty-Four (1949)."
)
);
await _authorRepository.InsertAsync(
var douglas = await _authorRepository.InsertAsync(
await _authorManager.CreateAsync(
"Douglas Adams",
new DateTime(1952, 03, 11),
"Douglas Adams was an English author, screenwriter, essayist, humorist, satirist and dramatist. Adams was an advocate for environmentalism and conservation, a lover of fast cars, technological innovation and the Apple Macintosh, and a self-proclaimed 'radical atheist'."
)
);
if (await _bookRepository.GetCountAsync() <= 0)
{
await _bookRepository.InsertAsync(
new Book
{
AuthorId = orwell.Id, // SET THE AUTHOR
Name = "1984",
Type = BookType.Dystopia,
PublishDate = new DateTime(1949, 6, 8),
Price = 19.84f
},
autoSave: true
);
await _bookRepository.InsertAsync(
new Book
{
AuthorId = douglas.Id, // SET THE AUTHOR
Name = "The Hitchhiker's Guide to the Galaxy",
Type = BookType.ScienceFiction,
PublishDate = new DateTime(1995, 9, 27),
Price = 42.0f
},
autoSave: true
);
}
}
if (await _customerRepository.GetCountAsync() <= 0)


+ 2
- 0
aspnet-core/src/Acme.BookStore.Domain/Books/Book.cs View File

@ -16,5 +16,7 @@ namespace Acme.BookStore.Books
public DateTime PublishDate { get; set; }
public float Price { get; set; }
public Guid AuthorId { get; set; }
}
}

+ 2
- 0
aspnet-core/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs View File

@ -93,6 +93,8 @@ public class BookStoreDbContext :
BookStoreConsts.DbSchema);
b.ConfigureByConvention(); //auto configure for the base class props
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
b.HasOne<Author>().WithMany().HasForeignKey(x => x.AuthorId).IsRequired();
});
builder.Entity<Author>(b =>


+ 2220
- 0
aspnet-core/src/Acme.BookStore.EntityFrameworkCore/Migrations/20240612062933_Added_AuthorId_To_Book.Designer.cs
File diff suppressed because it is too large
View File


+ 51
- 0
aspnet-core/src/Acme.BookStore.EntityFrameworkCore/Migrations/20240612062933_Added_AuthorId_To_Book.cs View File

@ -0,0 +1,51 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Acme.BookStore.Migrations
{
/// <inheritdoc />
public partial class Added_AuthorId_To_Book : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "AuthorId",
table: "AppBooks",
type: "uniqueidentifier",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
migrationBuilder.CreateIndex(
name: "IX_AppBooks_AuthorId",
table: "AppBooks",
column: "AuthorId");
migrationBuilder.AddForeignKey(
name: "FK_AppBooks_AppAuthors_AuthorId",
table: "AppBooks",
column: "AuthorId",
principalTable: "AppAuthors",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_AppBooks_AppAuthors_AuthorId",
table: "AppBooks");
migrationBuilder.DropIndex(
name: "IX_AppBooks_AuthorId",
table: "AppBooks");
migrationBuilder.DropColumn(
name: "AuthorId",
table: "AppBooks");
}
}
}

+ 14
- 0
aspnet-core/src/Acme.BookStore.EntityFrameworkCore/Migrations/BookStoreDbContextModelSnapshot.cs View File

@ -153,6 +153,9 @@ namespace Acme.BookStore.Migrations
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("AuthorId")
.HasColumnType("uniqueidentifier");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.IsRequired()
@ -197,6 +200,8 @@ namespace Acme.BookStore.Migrations
b.HasKey("Id");
b.HasIndex("AuthorId");
b.ToTable("AppBooks", (string)null);
});
@ -2016,6 +2021,15 @@ namespace Acme.BookStore.Migrations
b.ToTable("AbpTenantConnectionStrings", (string)null);
});
modelBuilder.Entity("Acme.BookStore.Books.Book", b =>
{
b.HasOne("Acme.BookStore.Authors.Author", null)
.WithMany()
.HasForeignKey("AuthorId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b =>
{
b.HasOne("Volo.Abp.AuditLogging.AuditLog", null)


Loading…
Cancel
Save