Termék és felhasználó listázása

This commit is contained in:
Norbi Peti 2023-05-06 23:17:00 +02:00
parent e8ccd3fff8
commit d2018fa8b9
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
26 changed files with 284 additions and 138 deletions

View file

@ -20,3 +20,7 @@ npm install
npm start
```
Ezután a http://localhost:3000/ cimen érhető el az oldal.
## Használat
* Felhasználónév: admin
* Jelszó: admin123

View file

@ -0,0 +1,38 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthCheck } from './auth-check';
import { ProductListComponent } from './product-list/product-list.component';
import { LoginComponent } from './login/login.component';
import { UserListComponent } from './user-list/user-list.component';
const routes: Routes = [
{
path: '',
canActivate: [AuthCheck],
children: [
{
path: 'users',
component: UserListComponent
},
{
path: '',
component: ProductListComponent
}
]
},
{
path: '',
children: [
{path: '', component: LoginComponent}
]
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}
export type RouteData = { title: string; };

View file

@ -13,9 +13,12 @@
<input matInput type="password" formControlName="password">
</mat-form-field>
<button mat-button class="mat-primary" (click)="doLogin()">Bejelentkezés</button>
<button mat-button>Regisztráció</button>
<button mat-button (click)="doRegister()">Regisztráció</button>
</form>
</div>
</ng-template>
<h1>Termékek</h1>
<div>
<button mat-button class="mat-primary" (click)="router.navigate(['/'])">Termékek</button>
<button mat-button class="mat-primary" (click)="router.navigate(['/users'])">Felhasználók</button>
</div>
<router-outlet></router-outlet>

View file

@ -14,7 +14,9 @@ export class AppComponent implements OnInit {
password: new FormControl()
});
constructor(public userService: UserService, private router: Router) {
toggle = false;
constructor(public userService: UserService, public router: Router) {
}
ngOnInit() {
@ -31,4 +33,7 @@ export class AppComponent implements OnInit {
alert("Hiba: " + (e?.error?.error ?? JSON.stringify(e)));
}
}
async doRegister() {
}
}

View file

@ -12,11 +12,20 @@ import { MatTableModule } from '@angular/material/table';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { LoginComponent } from './login/login.component';
import { UserListComponent } from './user-list/user-list.component';
import { AuthCheck } from './auth-check';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { RegisterComponent } from './register/register.component';
@NgModule({
declarations: [
AppComponent,
ProductListComponent
ProductListComponent,
LoginComponent,
UserListComponent,
RegisterComponent
],
imports: [
BrowserModule,
@ -28,9 +37,13 @@ import { HttpClientModule } from '@angular/common/http';
MatTableModule,
MatPaginatorModule,
MatSortModule,
HttpClientModule
HttpClientModule,
AppRoutingModule,
MatSlideToggleModule
],
providers: [
AuthCheck
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View file

@ -0,0 +1,13 @@
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { Injectable } from '@angular/core';
import { UserService } from './services/user.service';
@Injectable()
export class AuthCheck implements CanActivate {
constructor(private userService: UserService) {
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
return !!this.userService.user;
}
}

View file

View file

@ -0,0 +1 @@
<h1>Jelentkezz be</h1>

View file

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

View file

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent {
}

View file

@ -0,0 +1,6 @@
export interface Product {
name: string;
price: number;
description: string;
id: string;
}

View file

@ -1,111 +0,0 @@
import { DataSource } from '@angular/cdk/collections';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { map } from 'rxjs/operators';
import { Observable, of as observableOf, merge } from 'rxjs';
// TODO: Replace this with your own data model type
export interface ProductListItem {
name: string;
id: number;
}
// TODO: replace this with real data from your application
const EXAMPLE_DATA: ProductListItem[] = [
{id: 1, name: 'Hydrogen'},
{id: 2, name: 'Helium'},
{id: 3, name: 'Lithium'},
{id: 4, name: 'Beryllium'},
{id: 5, name: 'Boron'},
{id: 6, name: 'Carbon'},
{id: 7, name: 'Nitrogen'},
{id: 8, name: 'Oxygen'},
{id: 9, name: 'Fluorine'},
{id: 10, name: 'Neon'},
{id: 11, name: 'Sodium'},
{id: 12, name: 'Magnesium'},
{id: 13, name: 'Aluminum'},
{id: 14, name: 'Silicon'},
{id: 15, name: 'Phosphorus'},
{id: 16, name: 'Sulfur'},
{id: 17, name: 'Chlorine'},
{id: 18, name: 'Argon'},
{id: 19, name: 'Potassium'},
{id: 20, name: 'Calcium'},
];
/**
* Data source for the ProductList view. This class should
* encapsulate all logic for fetching and manipulating the displayed data
* (including sorting, pagination, and filtering).
*/
export class ProductListDataSource extends DataSource<ProductListItem> {
data: ProductListItem[] = EXAMPLE_DATA;
paginator: MatPaginator | undefined;
sort: MatSort | undefined;
constructor() {
super();
}
/**
* Connect this data source to the table. The table will only update when
* the returned stream emits new items.
* @returns A stream of the items to be rendered.
*/
connect(): Observable<ProductListItem[]> {
if (this.paginator && this.sort) {
// Combine everything that affects the rendered data into one update
// stream for the data-table to consume.
return merge(observableOf(this.data), this.paginator.page, this.sort.sortChange)
.pipe(map(() => {
return this.getPagedData(this.getSortedData([...this.data ]));
}));
} else {
throw Error('Please set the paginator and sort on the data source before connecting.');
}
}
/**
* Called when the table is being destroyed. Use this function, to clean up
* any open connections or free any held resources that were set up during connect.
*/
disconnect(): void {}
/**
* Paginate the data (client-side). If you're using server-side pagination,
* this would be replaced by requesting the appropriate data from the server.
*/
private getPagedData(data: ProductListItem[]): ProductListItem[] {
if (this.paginator) {
const startIndex = this.paginator.pageIndex * this.paginator.pageSize;
return data.splice(startIndex, this.paginator.pageSize);
} else {
return data;
}
}
/**
* Sort the data (client-side). If you're using server-side sorting,
* this would be replaced by requesting the appropriate data from the server.
*/
private getSortedData(data: ProductListItem[]): ProductListItem[] {
if (!this.sort || !this.sort.active || this.sort.direction === '') {
return data;
}
return data.sort((a, b) => {
const isAsc = this.sort?.direction === 'asc';
switch (this.sort?.active) {
case 'name': return compare(a.name, b.name, isAsc);
case 'id': return compare(+a.id, +b.id, isAsc);
default: return 0;
}
});
}
}
/** Simple sort comparator for example ID/Name columns (for client-side sorting). */
function compare(a: string | number, b: string | number, isAsc: boolean): number {
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
}

View file

@ -1,15 +1,18 @@
<div class="mat-elevation-z8">
<table mat-table class="full-width-table" matSort aria-label="Elements">
<!-- Id Column -->
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Id</th>
<td mat-cell *matCellDef="let row">{{row.id}}</td>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Név</th>
<td mat-cell *matCellDef="let row">{{row.name}}</td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
<td mat-cell *matCellDef="let row">{{row.name}}</td>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Leirás</th>
<td mat-cell *matCellDef="let row">{{row.description}}</td>
</ng-container>
<ng-container matColumnDef="price">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Ár</th>
<td mat-cell *matCellDef="let row">{{row.price}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
@ -17,7 +20,7 @@
</table>
<mat-paginator #paginator
[length]="dataSource?.data?.length"
[length]="(products | async)?.length"
[pageIndex]="0"
[pageSize]="10"
[pageSizeOptions]="[5, 10, 20]"

View file

@ -2,7 +2,10 @@ import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { MatTable } from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { ProductListDataSource, ProductListItem } from './product-list-datasource';
import { ProductService } from '../services/product.service';
import { Product } from '../model/product.model';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
@Component({
selector: 'app-product-list',
@ -12,19 +15,18 @@ import { ProductListDataSource, ProductListItem } from './product-list-datasourc
export class ProductListComponent implements AfterViewInit {
@ViewChild(MatPaginator) paginator!: MatPaginator;
@ViewChild(MatSort) sort!: MatSort;
@ViewChild(MatTable) table!: MatTable<ProductListItem>;
dataSource: ProductListDataSource;
@ViewChild(MatTable) table!: MatTable<Product>;
products: Observable<Product[]>
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
displayedColumns = ['id', 'name'];
displayedColumns = ['name', 'description', 'price'];
constructor() {
this.dataSource = new ProductListDataSource();
constructor(private service: ProductService) {
this.products = service.getList().pipe(map(value => value as Product[]));
}
ngAfterViewInit(): void {
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
this.table.dataSource = this.dataSource;
this.table.dataSource = this.products;
}
}

View file

@ -0,0 +1 @@
<p>register works!</p>

View file

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

View file

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.css']
})
export class RegisterComponent {
}

View file

@ -0,0 +1,13 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class ProductService {
constructor(private http: HttpClient) { }
getList() {
return this.http.get('/api/products')
}
}

View file

@ -32,4 +32,8 @@ export class UserService {
this._user = undefined;
}
}
getList() {
return this.http.get('/api/users')
}
}

View file

@ -0,0 +1,29 @@
<div class="mat-elevation-z8">
<table mat-table class="full-width-table" matSort aria-label="Elements">
<ng-container matColumnDef="username">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Felhasználónév</th>
<td mat-cell *matCellDef="let row">{{row.username}}</td>
</ng-container>
<ng-container matColumnDef="accessLevel">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Hozzáférési szint</th>
<td mat-cell *matCellDef="let row">{{row.accessLevel}}</td>
</ng-container>
<ng-container matColumnDef="birthdate">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Születési dátum</th>
<td mat-cell *matCellDef="let row">{{row.birthdate | date}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator #paginator
[length]="(users | async)?.length"
[pageIndex]="0"
[pageSize]="10"
[pageSizeOptions]="[5, 10, 20]"
aria-label="Select page">
</mat-paginator>
</div>

View file

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

View file

@ -0,0 +1,32 @@
import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { MatTable } from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { UserService } from '../services/user.service';
import { User } from '../model/user.model';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
@Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html',
styleUrls: ['./user-list.component.css']
})
export class UserListComponent implements AfterViewInit {
@ViewChild(MatPaginator) paginator!: MatPaginator;
@ViewChild(MatSort) sort!: MatSort;
@ViewChild(MatTable) table!: MatTable<User>;
users: Observable<User[]>
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
displayedColumns = ['username', 'accessLevel', 'birthdate'];
constructor(private service: UserService) {
this.users = service.getList().pipe(map(value => value as User[]));
}
ngAfterViewInit(): void {
this.table.dataSource = this.users;
}
}

View file

@ -58,6 +58,13 @@ app.use(passport.session({}));
app.use('/api/users', require('./usersRouter'))
app.use('/api/products', require('./productsRouter'))
app.use(function (req, _, next) {
if (!req.url.startsWith('/api') && req.url.indexOf('.') === -1) {
req.url = '/';
}
next();
})
app.use('', express.static('public'))
app.listen(3000, () => {

View file

@ -29,7 +29,7 @@ router.get('/', async (req, res) => {
}
try {
const products = await Product.find();
res.status(200).json(products);
res.status(200).json(products.map(product => getProductData(product)));
} catch (error) {
res.status(500).json({ message: error.message });
}
@ -40,7 +40,7 @@ router.get('/:id', getProduct, (req, res) => {
if (!req.isAuthenticated()) {
return res.status(403).json({ message: "Unauthenticated" });
}
res.json(res.product);
res.json(getProductData(res.product));
});
// POST /products - új termék létrehozása