List and Read on frontend, fullecrud on back
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,2 @@
|
|||||||
.idea/
|
.idea/
|
||||||
__pycache__
|
*/__pycache__
|
||||||
|
|||||||
160
back/.gitignore
vendored
Normal file
160
back/.gitignore
vendored
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
|
.pdm.toml
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,7 +1,11 @@
|
|||||||
from beanie import PydanticObjectId
|
from beanie import PydanticObjectId
|
||||||
|
from beanie.odm.enums import SortDirection
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from typing import TypeVar, List, Generic
|
from fastapi_paginate import Page, Params, add_pagination
|
||||||
|
from fastapi_paginate.ext.motor import paginate
|
||||||
|
|
||||||
|
from typing import TypeVar, List, Generic, Any, Dict
|
||||||
|
|
||||||
|
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
@@ -10,7 +14,21 @@ V = TypeVar('V')
|
|||||||
W = TypeVar('W')
|
W = TypeVar('W')
|
||||||
|
|
||||||
|
|
||||||
|
def parse_sort(sort_by):
|
||||||
|
fields = []
|
||||||
|
for field in sort_by.split(','):
|
||||||
|
dir, col = field.split('(')
|
||||||
|
fields.append((col[:-1], 1 if dir == 'asc' else -1))
|
||||||
|
|
||||||
|
return fields
|
||||||
|
|
||||||
|
|
||||||
|
def parse_query(query) -> Dict[Any, Any]:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def get_crud_router(model, model_create, model_read, model_update):
|
def get_crud_router(model, model_create, model_read, model_update):
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
@router.post("/", response_description="{} added to the database".format(model.__name__))
|
@router.post("/", response_description="{} added to the database".format(model.__name__))
|
||||||
@@ -22,12 +40,17 @@ def get_crud_router(model, model_create, model_read, model_update):
|
|||||||
@router.get("/{id}", response_description="{} record retrieved".format(model.__name__))
|
@router.get("/{id}", response_description="{} record retrieved".format(model.__name__))
|
||||||
async def read_id(id: PydanticObjectId) -> model_read:
|
async def read_id(id: PydanticObjectId) -> model_read:
|
||||||
item = await model.get(id)
|
item = await model.get(id)
|
||||||
return item
|
return model_read(**item.dict())
|
||||||
|
|
||||||
@router.get("/", response_description="{} records retrieved".format(model.__name__))
|
@router.get("/", response_model=Page[model_read], response_description="{} records retrieved".format(model.__name__))
|
||||||
async def read_list() -> List[model_read]:
|
async def read_list(size: int = 50, page: int = 1, sort_by: str = None, query: str = None) -> Page[model_read]:
|
||||||
item = await model.find_all().to_list()
|
sort = parse_sort(sort_by)
|
||||||
return item
|
query = parse_query(query)
|
||||||
|
# limit=limit, skip=offset,
|
||||||
|
|
||||||
|
collection = model.get_motor_collection()
|
||||||
|
items = paginate(collection, query, Params(**{'size': size, 'page': page}), sort=sort)
|
||||||
|
return await items
|
||||||
|
|
||||||
@router.put("/{id}", response_description="{} record updated".format(model.__name__))
|
@router.put("/{id}", response_description="{} record updated".format(model.__name__))
|
||||||
async def update(id: PydanticObjectId, req: model_update) -> model_read:
|
async def update(id: PydanticObjectId, req: model_update) -> model_read:
|
||||||
@@ -44,7 +67,7 @@ def get_crud_router(model, model_create, model_read, model_update):
|
|||||||
)
|
)
|
||||||
|
|
||||||
await item.update(update_query)
|
await item.update(update_query)
|
||||||
return item
|
return model_read(**item.dict())
|
||||||
|
|
||||||
@router.delete("/{id}", response_description="{} record deleted from the database".format(model.__name__))
|
@router.delete("/{id}", response_description="{} record deleted from the database".format(model.__name__))
|
||||||
async def delete(id: PydanticObjectId) -> dict:
|
async def delete(id: PydanticObjectId) -> dict:
|
||||||
@@ -61,4 +84,7 @@ def get_crud_router(model, model_create, model_read, model_update):
|
|||||||
"message": "{} deleted successfully".format(model.__name__)
|
"message": "{} deleted successfully".format(model.__name__)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_pagination(router)
|
||||||
return router
|
return router
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -18,3 +18,6 @@ class Entity(Document):
|
|||||||
address: str
|
address: str
|
||||||
created_at: datetime = Field(default=datetime.utcnow(), nullable=False)
|
created_at: datetime = Field(default=datetime.utcnow(), nullable=False)
|
||||||
updated_at: datetime = Field(default_factory=datetime.utcnow, nullable=False)
|
updated_at: datetime = Field(default_factory=datetime.utcnow, nullable=False)
|
||||||
|
#
|
||||||
|
# class Settings:
|
||||||
|
# name = "entities"
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ from datetime import datetime
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from .models import Entity, EntityType
|
from .models import Entity, EntityType
|
||||||
|
from ..core.schemas import Writer
|
||||||
|
|
||||||
|
|
||||||
class EntityRead(Entity):
|
class EntityRead(Entity):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class EntityCreate(BaseModel):
|
class EntityCreate(Writer):
|
||||||
type: EntityType
|
type: EntityType
|
||||||
name: str
|
name: str
|
||||||
address: str
|
address: str
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi import Depends, Request
|
|
||||||
|
|
||||||
from .contract import contract_router
|
from .contract import contract_router
|
||||||
from .db import init_db
|
from .db import init_db
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,10 +1,14 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from typing import Any, Dict, Generic, Optional
|
from typing import Any, Dict, Generic, Optional
|
||||||
|
from bson import ObjectId
|
||||||
|
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
from fastapi_users import BaseUserManager, UUIDIDMixin, models, exceptions, schemas
|
from fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin, models, exceptions, schemas
|
||||||
|
from fastapi_users.authentication import BearerTransport, AuthenticationBackend
|
||||||
|
from fastapi_users.authentication.strategy.db import AccessTokenDatabase, DatabaseStrategy
|
||||||
|
|
||||||
|
from .models import User, get_user_db, AccessToken, get_access_token_db
|
||||||
|
|
||||||
from .models import User, get_user_db
|
|
||||||
SECRET = "SECRET"
|
SECRET = "SECRET"
|
||||||
|
|
||||||
|
|
||||||
@@ -66,6 +70,44 @@ class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):
|
|||||||
|
|
||||||
return created_user
|
return created_user
|
||||||
|
|
||||||
|
def parse_id(self, value: Any) -> uuid.UUID:
|
||||||
|
if isinstance(value, ObjectId):
|
||||||
|
return value
|
||||||
|
if isinstance(value, uuid.UUID):
|
||||||
|
return value
|
||||||
|
try:
|
||||||
|
return uuid.UUID(value)
|
||||||
|
except ValueError as e:
|
||||||
|
raise exceptions.InvalidID() from e
|
||||||
|
|
||||||
|
|
||||||
async def get_user_manager(user_db=Depends(get_user_db)):
|
async def get_user_manager(user_db=Depends(get_user_db)):
|
||||||
yield UserManager(user_db)
|
yield UserManager(user_db)
|
||||||
|
|
||||||
|
|
||||||
|
def get_database_strategy(
|
||||||
|
access_token_db: AccessTokenDatabase[AccessToken] = Depends(get_access_token_db),
|
||||||
|
) -> DatabaseStrategy:
|
||||||
|
return DatabaseStrategy(access_token_db, lifetime_seconds=3600)
|
||||||
|
|
||||||
|
|
||||||
|
bearer_transport = BearerTransport(tokenUrl="auth/jwt/login")
|
||||||
|
|
||||||
|
|
||||||
|
auth_backend = AuthenticationBackend(
|
||||||
|
name="db",
|
||||||
|
transport=bearer_transport,
|
||||||
|
get_strategy=get_database_strategy,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
fastapi_users = FastAPIUsers[User, uuid.UUID](
|
||||||
|
get_user_manager,
|
||||||
|
[auth_backend],
|
||||||
|
)
|
||||||
|
|
||||||
|
get_current_user = fastapi_users.current_user(active=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_auth_router():
|
||||||
|
return fastapi_users.get_auth_router(auth_backend)
|
||||||
|
|||||||
@@ -1,45 +1,13 @@
|
|||||||
import uuid
|
from fastapi import Depends
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import Depends, Request
|
|
||||||
from fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin, models, exceptions
|
|
||||||
from fastapi_users.authentication import BearerTransport, AuthenticationBackend
|
|
||||||
from fastapi_users.authentication.strategy.db import AccessTokenDatabase, DatabaseStrategy
|
|
||||||
|
|
||||||
from beanie import PydanticObjectId
|
from beanie import PydanticObjectId
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from .models import User, AccessToken, get_user_db, get_access_token_db
|
from .models import User
|
||||||
from .schemas import UserRead, UserUpdate, UserCreate
|
from .schemas import UserRead, UserUpdate, UserCreate
|
||||||
from .manager import get_user_manager
|
from .manager import get_user_manager, get_current_user, get_auth_router
|
||||||
|
|
||||||
|
|
||||||
def get_database_strategy(
|
|
||||||
access_token_db: AccessTokenDatabase[AccessToken] = Depends(get_access_token_db),
|
|
||||||
) -> DatabaseStrategy:
|
|
||||||
return DatabaseStrategy(access_token_db, lifetime_seconds=3600)
|
|
||||||
|
|
||||||
|
|
||||||
bearer_transport = BearerTransport(tokenUrl="auth/jwt/login")
|
|
||||||
|
|
||||||
|
|
||||||
auth_backend = AuthenticationBackend(
|
|
||||||
name="db",
|
|
||||||
transport=bearer_transport,
|
|
||||||
get_strategy=get_database_strategy,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
fastapi_users = FastAPIUsers[User, uuid.UUID](
|
|
||||||
get_user_manager,
|
|
||||||
[auth_backend],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_auth_router():
|
|
||||||
return fastapi_users.get_auth_router(auth_backend)
|
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
@@ -51,10 +19,16 @@ async def create(user: UserCreate, user_manager=Depends(get_user_manager)) -> di
|
|||||||
return {"message": "User added successfully"}
|
return {"message": "User added successfully"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/me", response_description="User record retrieved")
|
||||||
|
async def read_me(user=Depends(get_current_user)) -> UserRead:
|
||||||
|
user = await User.get(user.id)
|
||||||
|
return UserRead(**user.dict())
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{id}", response_description="User record retrieved")
|
@router.get("/{id}", response_description="User record retrieved")
|
||||||
async def read_id(id: PydanticObjectId) -> UserRead:
|
async def read_id(id: PydanticObjectId) -> UserRead:
|
||||||
user = await User.get(id)
|
user = await User.get(id)
|
||||||
return user
|
return UserRead(**user.dict())
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_model=List[UserRead], response_description="User records retrieved")
|
@router.get("/", response_model=List[UserRead], response_description="User records retrieved")
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ fastapi==0.88.0
|
|||||||
fastapi_users==10.2.1
|
fastapi_users==10.2.1
|
||||||
fastapi_users_db_beanie==1.1.2
|
fastapi_users_db_beanie==1.1.2
|
||||||
motor==3.1.1
|
motor==3.1.1
|
||||||
|
fastapi-paginate==0.1.0
|
||||||
uvicorn
|
uvicorn
|
||||||
@@ -23,7 +23,7 @@ export function TranslateHttpLoaderFactory(http: HttpClient) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
import { LoginService } from '@core/authentication/login.service';
|
import { LoginService } from '@core/authentication/login.service';
|
||||||
import { FakeLoginService } from './fake-login.service';
|
import { ChtloginService } from '@core/../custom/chtlogin.service';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -49,7 +49,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
|||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: BASE_URL, useValue: environment.baseUrl },
|
{ provide: BASE_URL, useValue: environment.baseUrl },
|
||||||
{ provide: LoginService, useClass: FakeLoginService }, // <= Remove it in the real APP
|
{ provide: LoginService, useClass: ChtloginService }, // <= Remove it in the real APP
|
||||||
httpInterceptorProviders,
|
httpInterceptorProviders,
|
||||||
appInitializerProviders,
|
appInitializerProviders,
|
||||||
],
|
],
|
||||||
|
|||||||
41
front/app/src/app/custom/chtlogin.service.ts
Normal file
41
front/app/src/app/custom/chtlogin.service.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpHeaders, HttpParams } from '@angular/common/http';
|
||||||
|
import { Token, User } from '../core/authentication/interface';
|
||||||
|
import { Menu } from '@core';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { LoginService } from '../core/authentication/login.service'
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class ChtloginService extends LoginService {
|
||||||
|
|
||||||
|
login(username: string, password: string, rememberMe = false) {
|
||||||
|
const body = new HttpParams()
|
||||||
|
.set('username', username)
|
||||||
|
.set('password', password);
|
||||||
|
|
||||||
|
return this.http.post<Token>('/api/v1/auth/login', body.toString(), {
|
||||||
|
headers: new HttpHeaders()
|
||||||
|
.set('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh(params: Record<string, any>) {
|
||||||
|
return this.http.post<Token>('/api/v1/auth/refresh', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
return this.http.post<any>('/api/v1/auth/logout', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
me() {
|
||||||
|
return this.http.get<User>('/api/v1/users/me');
|
||||||
|
}
|
||||||
|
|
||||||
|
menu() {
|
||||||
|
return this.http
|
||||||
|
.get<{ menu: Menu[] }>('assets/data/menu.json?_t=' + Date.now())
|
||||||
|
.pipe(map(res => res.menu));
|
||||||
|
}
|
||||||
|
}
|
||||||
28
front/app/src/app/routes/clients/card/card.component.html
Normal file
28
front/app/src/app/routes/clients/card/card.component.html
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<page-header></page-header>
|
||||||
|
<button routerLink="../../list" mat-fab extended>
|
||||||
|
<mat-icon>arrow_back</mat-icon>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<form [formGroup]="cardForm" (ngSubmit)="onSubmit()">
|
||||||
|
<mat-form-field appearance="fill" formControlName="name">
|
||||||
|
<mat-label>Name</mat-label>
|
||||||
|
<input matInput [value]="this.item.name">
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="fill" formControlName="address">
|
||||||
|
<mat-label>Address</mat-label>
|
||||||
|
<input matInput [value]="this.item.address">
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="fill" formControlName="type">
|
||||||
|
<mat-label>Type</mat-label>
|
||||||
|
<mat-select [value]="this.item.type">
|
||||||
|
<mat-option value="individual">Particulier</mat-option>
|
||||||
|
<mat-option value="corporation">Entreprise</mat-option>
|
||||||
|
<mat-option value="institution">Institution</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<button class="button" type="submit">Modifier</button>
|
||||||
|
</form>
|
||||||
25
front/app/src/app/routes/clients/card/card.component.spec.ts
Normal file
25
front/app/src/app/routes/clients/card/card.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ClientsCardComponent } from './card.component';
|
||||||
|
|
||||||
|
describe('ClientsCardComponent', () => {
|
||||||
|
let component: ClientsCardComponent;
|
||||||
|
let fixture: ComponentFixture<ClientsCardComponent>;
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ ClientsCardComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ClientsCardComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
48
front/app/src/app/routes/clients/card/card.component.ts
Normal file
48
front/app/src/app/routes/clients/card/card.component.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
import { FormBuilder } from '@angular/forms';
|
||||||
|
import { ClientsService } from '../clients.service'
|
||||||
|
|
||||||
|
export interface Client {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
address: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-clients-card',
|
||||||
|
templateUrl: './card.component.html',
|
||||||
|
styleUrls: ['./card.component.css']
|
||||||
|
})
|
||||||
|
export class ClientsCardComponent implements OnInit {
|
||||||
|
@Input() id: string = "";
|
||||||
|
|
||||||
|
item: Client = {_id: '', name: '', address: '', type: ''};
|
||||||
|
|
||||||
|
cardForm = this.formBuilder.group({
|
||||||
|
name: '',
|
||||||
|
address: '',
|
||||||
|
type: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor(private formBuilder: FormBuilder, private clientsService: ClientsService) {
|
||||||
|
if (this.id == "") {
|
||||||
|
const url_parts = window.location.href.split('/')
|
||||||
|
this.id = url_parts[url_parts.length - 1];
|
||||||
|
}
|
||||||
|
this.item = {_id: '', name: '', address: '', type: ''}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getData() {
|
||||||
|
this.clientsService.get(this.id).subscribe((data: any) => {
|
||||||
|
this.item = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
}
|
||||||
|
}
|
||||||
14
front/app/src/app/routes/clients/clients-routing.module.ts
Normal file
14
front/app/src/app/routes/clients/clients-routing.module.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
|
import { ClientsListComponent } from './list/list.component';
|
||||||
|
import { ClientsCardComponent } from './card/card.component';
|
||||||
|
|
||||||
|
const routes: Routes = [{ path: 'list', component: ClientsListComponent },
|
||||||
|
{ path: 'card/:id', component: ClientsCardComponent }
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class ClientsRoutingModule { }
|
||||||
20
front/app/src/app/routes/clients/clients.module.ts
Normal file
20
front/app/src/app/routes/clients/clients.module.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { SharedModule } from '@shared/shared.module';
|
||||||
|
import { ClientsRoutingModule } from './clients-routing.module';
|
||||||
|
import { ClientsListComponent } from './list/list.component';
|
||||||
|
import { ClientsCardComponent } from './card/card.component';
|
||||||
|
|
||||||
|
const COMPONENTS: any[] = [ClientsListComponent, ClientsCardComponent];
|
||||||
|
const COMPONENTS_DYNAMIC: any[] = [];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
SharedModule,
|
||||||
|
ClientsRoutingModule
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
...COMPONENTS,
|
||||||
|
...COMPONENTS_DYNAMIC
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class ClientsModule { }
|
||||||
23
front/app/src/app/routes/clients/clients.service.ts
Normal file
23
front/app/src/app/routes/clients/clients.service.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import {Client} from "./list/list.component";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ClientsService {
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) { }
|
||||||
|
|
||||||
|
public getList(page: number, size: number, sortColumn: string, sortDirection: string) {
|
||||||
|
return this.http.get<{ menu: Client[] }>(
|
||||||
|
`/api/v1/entity/?size=${size}&page=${page + 1}&sort_by=${sortDirection}(${sortColumn})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(id: string) {
|
||||||
|
return this.http.get<{ menu: Client[] }>(
|
||||||
|
`/api/v1/entity/${id}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
43
front/app/src/app/routes/clients/list/list.component.html
Normal file
43
front/app/src/app/routes/clients/list/list.component.html
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<page-header></page-header>
|
||||||
|
|
||||||
|
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Filter</mat-label>
|
||||||
|
<input matInput (keyup)="applyFilter($event)" placeholder="Ex. Mia" #input>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8 items" matSort (matSortChange)="handleSortChange($event)">
|
||||||
|
<ng-container matColumnDef="_id">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> ID </th>
|
||||||
|
<td mat-cell *matCellDef="let item"> {{item._id}} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="name">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> Nom </th>
|
||||||
|
<td mat-cell *matCellDef="let item"> {{item.name}} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="type">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> Type </th>
|
||||||
|
<td mat-cell *matCellDef="let item"> {{item.type}} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="address">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> Adresse </th>
|
||||||
|
<td mat-cell *matCellDef="let item"> {{item.address}} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
|
<tr mat-row
|
||||||
|
routerLink="../card/{{row._id}}"
|
||||||
|
*matRowDef="let row; columns: displayedColumns;"
|
||||||
|
></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<mat-paginator (page)="handlePageEvent($event)"
|
||||||
|
[pageSizeOptions]="[5, 10, 15, 25, 50]"
|
||||||
|
[pageSize]="pageSize"
|
||||||
|
[length]="length"
|
||||||
|
showFirstLastButtons
|
||||||
|
aria-label="Select page">
|
||||||
|
</mat-paginator>
|
||||||
25
front/app/src/app/routes/clients/list/list.component.spec.ts
Normal file
25
front/app/src/app/routes/clients/list/list.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ClientsListComponent } from './list.component';
|
||||||
|
|
||||||
|
describe('ClientsListComponent', () => {
|
||||||
|
let component: ClientsListComponent;
|
||||||
|
let fixture: ComponentFixture<ClientsListComponent>;
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ ClientsListComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ClientsListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
84
front/app/src/app/routes/clients/list/list.component.ts
Normal file
84
front/app/src/app/routes/clients/list/list.component.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
||||||
|
import { MatSort, Sort} from '@angular/material/sort';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Menu } from "@core";
|
||||||
|
import { BehaviorSubject, forkJoin, fromEvent, Observable } from "rxjs";
|
||||||
|
import { map, take } from "rxjs/operators";
|
||||||
|
import { ClientsService } from '../clients.service'
|
||||||
|
import {Location} from '@angular/common';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
export interface Client {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
address: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-clients-list',
|
||||||
|
templateUrl: './list.component.html',
|
||||||
|
styleUrls: ['./list.component.css']
|
||||||
|
})
|
||||||
|
export class ClientsListComponent implements OnInit {
|
||||||
|
|
||||||
|
displayedColumns: string[] = ['type', 'name', 'address', '_id',];
|
||||||
|
dataSource: Client[] = [];
|
||||||
|
|
||||||
|
length: number = 0;
|
||||||
|
pageIndex: number = 0;
|
||||||
|
pageSize: number = 15;
|
||||||
|
|
||||||
|
sortColumn: string = "name";
|
||||||
|
sortDirection: string = "asc";
|
||||||
|
|
||||||
|
//@ViewChild(MatSort) sort: MatSort;
|
||||||
|
|
||||||
|
constructor(private location: Location, private clientsService: ClientsService, private router: Router) { }
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePageEvent(e: PageEvent) {
|
||||||
|
this.length = e.length;
|
||||||
|
this.pageSize = e.pageSize;
|
||||||
|
this.pageIndex = e.pageIndex;
|
||||||
|
this.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSortChange(s: Sort) {
|
||||||
|
this.sortColumn = s.active;
|
||||||
|
this.sortDirection = s.direction;
|
||||||
|
this.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClickRow(row: any) {
|
||||||
|
window.location.href=window.location.href.replace('list', `card/${row._id}`)
|
||||||
|
this.router.navigateByUrl("card");
|
||||||
|
}
|
||||||
|
|
||||||
|
private getData() {
|
||||||
|
this.clientsService.getList(this.pageIndex, this.pageSize, this.sortColumn, this.sortDirection).subscribe((data: any) => {
|
||||||
|
this.dataSource = data.items;
|
||||||
|
this.length = data.total;
|
||||||
|
this.pageSize = data.size;
|
||||||
|
this.pageIndex = data.page - 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
applyFilter(event: Event) {
|
||||||
|
// const filterValue = (event.target as HTMLInputElement).value;
|
||||||
|
// this.dataSource.filter = filterValue.trim().toLowerCase();
|
||||||
|
//
|
||||||
|
// if (this.dataSource.paginator) {
|
||||||
|
// this.dataSource.paginator.firstPage();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ const routes: Routes = [
|
|||||||
{ path: '403', component: Error403Component },
|
{ path: '403', component: Error403Component },
|
||||||
{ path: '404', component: Error404Component },
|
{ path: '404', component: Error404Component },
|
||||||
{ path: '500', component: Error500Component },
|
{ path: '500', component: Error500Component },
|
||||||
|
{ path: 'clients', loadChildren: () => import('./clients/clients.module').then(m => m.ClientsModule) },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
{
|
{
|
||||||
"menu": [
|
"menu": [
|
||||||
|
{
|
||||||
|
"route": "clients/list",
|
||||||
|
"name": "clients",
|
||||||
|
"type": "link",
|
||||||
|
"icon": "group"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"route": "dashboard",
|
"route": "dashboard",
|
||||||
"name": "dashboard",
|
"name": "dashboard",
|
||||||
|
|||||||
@@ -34,5 +34,13 @@ http {
|
|||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Host $server_name;
|
proxy_set_header X-Forwarded-Host $server_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /ng-cli-ws {
|
||||||
|
proxy_pass http://docker-front ;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "Upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user