add crud for forms, templates, shipment, users and auth with keycloak
This commit is contained in:
14
.env.example
14
.env.example
@@ -1,4 +1,12 @@
|
|||||||
POSTGRES_USER=postgres
|
DB_USER=postgres
|
||||||
POSTGRES_PASSWORD=postgres
|
DB_PASS=postgres
|
||||||
POSTGRES_DB=amap
|
DB_NAME=amap
|
||||||
|
DB_HOST=localhost
|
||||||
|
ORIGINS=http://localhost:8000
|
||||||
|
SECRET_KEY=
|
||||||
ROOT_FQDN=http://localhost
|
ROOT_FQDN=http://localhost
|
||||||
|
KEYCLOAK_SERVER=
|
||||||
|
KEYCLOAK_REALM=
|
||||||
|
KEYCLOAK_CLIENT_ID=
|
||||||
|
KEYCLOAK_CLIENT_SECRET=
|
||||||
|
KEYCLOAK_REDIRECT_URI=
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
auth {
|
auth {
|
||||||
mode: bearer
|
mode: none
|
||||||
}
|
|
||||||
|
|
||||||
auth:bearer {
|
|
||||||
token: eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJtNUtSQkp1T3VqMnFiUElySlRldFVISGVWMWRTLTEzUG5saU1PSWRLcWFvIn0.eyJleHAiOjE3NzA2NTQwMzYsImlhdCI6MTc3MDY1MzczNiwiYXV0aF90aW1lIjoxNzcwNjUzMDU2LCJqdGkiOiJvbnJ0YWM6YmFiMDZiNGMtMjM5ZC00NzM3LTliYWEtNjE2MjBmMzVjM2NhIiwiaXNzIjoiaHR0cHM6Ly9rZXljbG9hay5hbGRvbi5mci9yZWFsbXMvYWxkb24uZnIiLCJhdWQiOlsibmV4dGNsb3VkIiwiYWNjb3VudCJdLCJzdWIiOiJlM2VkN2NiNC1iMDYxLTRiZWQtYTY5YS1lMGQ5ZWJmNDJhZWIiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhbWFwIiwic2lkIjoiMTkwY2I4YzAtMGYxZC00MWJiLTkwYWYtZjNmMGJkZWMzYjA3IiwiYWNyIjoiMCIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgwMDAiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtZ2lyYXNvbCIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJuZXh0Y2xvdWQiOnsicm9sZXMiOlsicGVsYXJnbyIsImZlZmFuIiwiYWRtaW4iLCJnaXJhc29sIl19LCJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiSnVsaWVuIEFsZG9uIiwicHJlZmVycmVkX3VzZXJuYW1lIjoianVsaWVuIiwiZ2l2ZW5fbmFtZSI6Ikp1bGllbiIsImZhbWlseV9uYW1lIjoiQWxkb24iLCJlbWFpbCI6Imp1bGllbi5hbGRvbkB3YW5hZG9vLmZyIn0.bq-EUtK_UqsIOwI6KDHB8eELMirWPDfTMta904XNeffj_v_ptEnHbecCf1OG6zzwanrBUyl_On7z95zVvVuKX6fQM9iaqxDqm7VlAK1O6n97367evTjQTOggkl3eTgX3xkfbCjJyzP_8RhTPXBsL_Nao8h5kgCnDwUHKEZ547oeoPKVEzlc82SgPi2rsiTVyvznJxGyJkQOTcDDMqTUxj4OVqWD5FMEDCfLnisUNPADhq0Umyw8hU4YwtI1-3hn6aXbnVcDekk2oWVli_6MeJHyejI8_yPnnQMvcp9OqciXRMtCGml1vMHcb5kUh4U9OeAhewzBFb_Mk9KDOspktSQ
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vars:pre-request {
|
vars:pre-request {
|
||||||
|
|||||||
20
amapcontract/forms/Create.bru
Normal file
20
amapcontract/forms/Create.bru
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
meta {
|
||||||
|
name: Create
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{Service}}/{{Route}}
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{{ExamplePOSTBody}}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
16
amapcontract/forms/Delete one.bru
Normal file
16
amapcontract/forms/Delete one.bru
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
meta {
|
||||||
|
name: Delete one
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
delete {
|
||||||
|
url: {{Service}}/{{Route}}/2
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
16
amapcontract/forms/Get all.bru
Normal file
16
amapcontract/forms/Get all.bru
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
meta {
|
||||||
|
name: Get all
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{Service}}/{{Route}}
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
16
amapcontract/forms/Get one.bru
Normal file
16
amapcontract/forms/Get one.bru
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
meta {
|
||||||
|
name: Get one
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{Service}}/{{Route}}/1
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
20
amapcontract/forms/Update one.bru
Normal file
20
amapcontract/forms/Update one.bru
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
meta {
|
||||||
|
name: Update one
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
put {
|
||||||
|
url: {{Service}}/{{Route}}/1
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{{ExamplePUTBody}}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
31
amapcontract/forms/folder.bru
Normal file
31
amapcontract/forms/folder.bru
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
meta {
|
||||||
|
name: forms
|
||||||
|
}
|
||||||
|
|
||||||
|
auth {
|
||||||
|
mode: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
vars:pre-request {
|
||||||
|
Route: forms
|
||||||
|
ExamplePOSTBody: '''
|
||||||
|
{
|
||||||
|
"productor_id": 1,
|
||||||
|
"referer_id": 1,
|
||||||
|
"season": "Hiver-2026",
|
||||||
|
"shipments": 5,
|
||||||
|
"start": "2026-01-10",
|
||||||
|
"end": "2026-05-10"
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
ExamplePUTBody: '''
|
||||||
|
{
|
||||||
|
"productor_id": 1,
|
||||||
|
"referer_id": 1,
|
||||||
|
"season": "updatedHiver-2026",
|
||||||
|
"shipments": 7,
|
||||||
|
"start": "2026-01-10",
|
||||||
|
"end": "2026-05-10"
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ meta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete {
|
delete {
|
||||||
url: {{Service}}/{{Route}}/2
|
url: {{Service}}/{{Route}}/6
|
||||||
body: none
|
body: none
|
||||||
auth: inherit
|
auth: inherit
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ meta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
put {
|
put {
|
||||||
url: {{Service}}/{{Route}}/1
|
url: {{Service}}/{{Route}}/3
|
||||||
body: json
|
body: json
|
||||||
auth: inherit
|
auth: inherit
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,28 @@ auth {
|
|||||||
|
|
||||||
vars:pre-request {
|
vars:pre-request {
|
||||||
Route: products
|
Route: products
|
||||||
ExamplePOSTBody: {"name": "test", "unit": "KILO", "price": 3.50, "price_kg": 3.50, "weight": "1.0", "productor_id": 1}
|
ExamplePOSTBody: '''
|
||||||
ExamplePUTBody: {"name": "updatetestt", "address": "updatetestt"}
|
{
|
||||||
|
"name": "test",
|
||||||
|
"unit": 1,
|
||||||
|
"price": 3.50,
|
||||||
|
"price_kg": 3.50,
|
||||||
|
"weight": "1.0",
|
||||||
|
"productor_id": 1,
|
||||||
|
"type": 2,
|
||||||
|
"shipment_ids": []
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
ExamplePUTBody: '''
|
||||||
|
{
|
||||||
|
"name": "test",
|
||||||
|
"unit": 1,
|
||||||
|
"price": 3.50,
|
||||||
|
"price_kg": 3.50,
|
||||||
|
"weight": "1.0",
|
||||||
|
"productor_id": 1,
|
||||||
|
"type": 2,
|
||||||
|
"shipment_ids": [1]
|
||||||
|
}
|
||||||
|
'''
|
||||||
}
|
}
|
||||||
|
|||||||
20
amapcontract/shipments/Create.bru
Normal file
20
amapcontract/shipments/Create.bru
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
meta {
|
||||||
|
name: Create
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{Service}}/{{Route}}
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{{ExamplePOSTBody}}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
16
amapcontract/shipments/Delete one.bru
Normal file
16
amapcontract/shipments/Delete one.bru
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
meta {
|
||||||
|
name: Delete one
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
delete {
|
||||||
|
url: {{Service}}/{{Route}}/2
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
16
amapcontract/shipments/Get all.bru
Normal file
16
amapcontract/shipments/Get all.bru
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
meta {
|
||||||
|
name: Get all
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{Service}}/{{Route}}
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
16
amapcontract/shipments/Get one.bru
Normal file
16
amapcontract/shipments/Get one.bru
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
meta {
|
||||||
|
name: Get one
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{Service}}/{{Route}}/1
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
20
amapcontract/shipments/Update one.bru
Normal file
20
amapcontract/shipments/Update one.bru
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
meta {
|
||||||
|
name: Update one
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
put {
|
||||||
|
url: {{Service}}/{{Route}}/1
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{{ExamplePUTBody}}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
26
amapcontract/shipments/folder.bru
Normal file
26
amapcontract/shipments/folder.bru
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
meta {
|
||||||
|
name: shipments
|
||||||
|
}
|
||||||
|
|
||||||
|
auth {
|
||||||
|
mode: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
vars:pre-request {
|
||||||
|
Route: shipments
|
||||||
|
ExamplePOSTBody: '''
|
||||||
|
{
|
||||||
|
"name": "test",
|
||||||
|
"date": "2026-01-10",
|
||||||
|
"product_ids": [1],
|
||||||
|
"form_id": 3
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
ExamplePUTBody: '''
|
||||||
|
{
|
||||||
|
"name": "updatedtestt",
|
||||||
|
"date": "2026-01-10",
|
||||||
|
"product_ids": [2]
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
}
|
||||||
20
amapcontract/users/Create.bru
Normal file
20
amapcontract/users/Create.bru
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
meta {
|
||||||
|
name: Create
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{Service}}/{{Route}}
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{{ExamplePOSTBody}}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
16
amapcontract/users/Delete one.bru
Normal file
16
amapcontract/users/Delete one.bru
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
meta {
|
||||||
|
name: Delete one
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
delete {
|
||||||
|
url: {{Service}}/{{Route}}/2
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
16
amapcontract/users/Get all.bru
Normal file
16
amapcontract/users/Get all.bru
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
meta {
|
||||||
|
name: Get all
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{Service}}/{{Route}}
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
16
amapcontract/users/Get one.bru
Normal file
16
amapcontract/users/Get one.bru
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
meta {
|
||||||
|
name: Get one
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{Service}}/{{Route}}/1
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
20
amapcontract/users/Update one.bru
Normal file
20
amapcontract/users/Update one.bru
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
meta {
|
||||||
|
name: Update one
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
put {
|
||||||
|
url: {{Service}}/{{Route}}/1
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{{ExamplePUTBody}}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
13
amapcontract/users/folder.bru
Normal file
13
amapcontract/users/folder.bru
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
meta {
|
||||||
|
name: users
|
||||||
|
}
|
||||||
|
|
||||||
|
auth {
|
||||||
|
mode: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
vars:pre-request {
|
||||||
|
Route: users
|
||||||
|
ExamplePOSTBody: {"name": "test", "email": "test@test.test"}
|
||||||
|
ExamplePUTBody: {"name": "updatedtest", "email": "updatedtest@test.test"}
|
||||||
|
}
|
||||||
@@ -22,9 +22,9 @@ classifiers = [
|
|||||||
"Programming Language :: Python :: Implementation :: PyPy",
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastapi",
|
"fastapi[standard]",
|
||||||
"sqlmodel",
|
"sqlmodel",
|
||||||
"psycopg2",
|
"psycopg2-binary",
|
||||||
"PyJWT",
|
"PyJWT",
|
||||||
"cryptography",
|
"cryptography",
|
||||||
"requests"
|
"requests"
|
||||||
@@ -43,7 +43,7 @@ extra-dependencies = [
|
|||||||
"mypy>=1.0.0",
|
"mypy>=1.0.0",
|
||||||
]
|
]
|
||||||
[tool.hatch.envs.types.scripts]
|
[tool.hatch.envs.types.scripts]
|
||||||
check = "mypy --install-types --non-interactive {args:src/backend tests}"
|
check = "mypy --install-types --non-interactive {args:src tests}"
|
||||||
|
|
||||||
[tool.coverage.run]
|
[tool.coverage.run]
|
||||||
source_pkgs = ["backend", "tests"]
|
source_pkgs = ["backend", "tests"]
|
||||||
@@ -63,3 +63,7 @@ exclude_lines = [
|
|||||||
"if __name__ == .__main__.:",
|
"if __name__ == .__main__.:",
|
||||||
"if TYPE_CHECKING:",
|
"if TYPE_CHECKING:",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["src"]
|
||||||
|
include = ["src/**/*.py"]
|
||||||
@@ -1,7 +1,13 @@
|
|||||||
from fastapi import APIRouter, Security, HTTPException
|
from fastapi import APIRouter, Security, HTTPException, Depends
|
||||||
from fastapi.responses import RedirectResponse
|
from fastapi.responses import RedirectResponse
|
||||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||||
from src.secrets import CLIENT_ID, REDIRECT_URI, AUTH_URL, CLIENT_SECRET, TOKEN_URL, JWKS_URL, ISSUER
|
from sqlmodel import Session
|
||||||
|
|
||||||
|
from src.settings import AUTH_URL, TOKEN_URL, JWKS_URL, ISSUER, settings
|
||||||
|
import src.users.service as service
|
||||||
|
from src.database import get_session
|
||||||
|
from src.models import UserCreate
|
||||||
|
|
||||||
import secrets
|
import secrets
|
||||||
import jwt
|
import jwt
|
||||||
from jwt import PyJWKClient
|
from jwt import PyJWKClient
|
||||||
@@ -15,26 +21,24 @@ security = HTTPBearer()
|
|||||||
@router.get('/login')
|
@router.get('/login')
|
||||||
def login():
|
def login():
|
||||||
state = secrets.token_urlsafe(16)
|
state = secrets.token_urlsafe(16)
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
"client_id": CLIENT_ID,
|
"client_id": settings.keycloak_client_id,
|
||||||
"response_type": "code",
|
"response_type": "code",
|
||||||
"scope": "openid",
|
"scope": "openid",
|
||||||
"redirect_uri": REDIRECT_URI,
|
"redirect_uri": settings.keycloak_redirect_uri,
|
||||||
"state": state,
|
"state": state,
|
||||||
}
|
}
|
||||||
|
|
||||||
request_url = requests.Request('GET', AUTH_URL, params=params).prepare().url
|
request_url = requests.Request('GET', AUTH_URL, params=params).prepare().url
|
||||||
return RedirectResponse(request_url)
|
return RedirectResponse(request_url)
|
||||||
|
|
||||||
@router.get("/callback")
|
@router.get("/callback")
|
||||||
def callback(code: str):
|
def callback(code: str, session: Session = Depends(get_session)):
|
||||||
data = {
|
data = {
|
||||||
"grant_type": "authorization_code",
|
"grant_type": "authorization_code",
|
||||||
"code": code,
|
"code": code,
|
||||||
"redirect_uri": REDIRECT_URI,
|
"redirect_uri": settings.keycloak_redirect_uri,
|
||||||
"client_id": CLIENT_ID,
|
"client_id": settings.keycloak_client_id,
|
||||||
"client_secret": CLIENT_SECRET,
|
"client_secret": settings.keycloak_client_secret,
|
||||||
}
|
}
|
||||||
headers = {
|
headers = {
|
||||||
"Content-Type": "application/x-www-form-urlencoded"
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
@@ -45,7 +49,17 @@ def callback(code: str):
|
|||||||
{"error": "Failed to get token"},
|
{"error": "Failed to get token"},
|
||||||
status_code=400
|
status_code=400
|
||||||
)
|
)
|
||||||
|
|
||||||
token_data = response.json()
|
token_data = response.json()
|
||||||
|
|
||||||
|
id_token = token_data["id_token"]
|
||||||
|
decoded_token = jwt.decode(id_token, options={"verify_signature": False})
|
||||||
|
user_create = UserCreate(
|
||||||
|
email=decoded_token.get("email"),
|
||||||
|
name=decoded_token.get("preferred_username")
|
||||||
|
)
|
||||||
|
print(user_create)
|
||||||
|
user = service.get_or_create_user(session, user_create)
|
||||||
return {
|
return {
|
||||||
"access_token": token_data["access_token"],
|
"access_token": token_data["access_token"],
|
||||||
"id_token": token_data["id_token"],
|
"id_token": token_data["id_token"],
|
||||||
@@ -56,20 +70,16 @@ def verify_token(token: str):
|
|||||||
try:
|
try:
|
||||||
signing_key = jwk_client.get_signing_key_from_jwt(token)
|
signing_key = jwk_client.get_signing_key_from_jwt(token)
|
||||||
decoded = jwt.decode(token, options={"verify_signature": False})
|
decoded = jwt.decode(token, options={"verify_signature": False})
|
||||||
print(decoded, ISSUER)
|
|
||||||
print(decoded["exp"])
|
|
||||||
payload = jwt.decode(
|
payload = jwt.decode(
|
||||||
token,
|
token,
|
||||||
signing_key.key,
|
signing_key.key,
|
||||||
algorithms=["RS256"],
|
algorithms=["RS256"],
|
||||||
audience=CLIENT_ID,
|
audience=settings.keycloak_client_id,
|
||||||
issuer=ISSUER,
|
issuer=ISSUER,
|
||||||
)
|
)
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
except jwt.ExpiredSignatureError:
|
except jwt.ExpiredSignatureError:
|
||||||
raise HTTPException(status_code=401, detail="Token expired")
|
raise HTTPException(status_code=401, detail="Token expired")
|
||||||
|
|
||||||
except jwt.InvalidTokenError:
|
except jwt.InvalidTokenError:
|
||||||
raise HTTPException(status_code=401, detail="Invalid token")
|
raise HTTPException(status_code=401, detail="Invalid token")
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
from sqlmodel import create_engine, SQLModel, Session
|
from sqlmodel import create_engine, SQLModel, Session
|
||||||
from src.secrets import dbname, dbhost, dbuser, dbpass
|
from src.settings import settings
|
||||||
|
|
||||||
engine = create_engine(f'postgresql://{dbuser}:{dbpass}@{dbhost}:54321/{dbname}')
|
engine = create_engine(f'postgresql://{settings.db_user}:{settings.db_pass}@{settings.db_host}:54321/{settings.db_name}')
|
||||||
# SQLModel.metadata.create_all(engine)
|
|
||||||
|
|
||||||
def get_session():
|
def get_session():
|
||||||
with Session(engine) as session:
|
with Session(engine) as session:
|
||||||
yield session
|
yield session
|
||||||
|
|
||||||
|
def create_all_tables():
|
||||||
|
SQLModel.metadata.create_all(engine)
|
||||||
|
|||||||
@@ -1,19 +1,37 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter, HTTPException, Depends
|
||||||
|
import src.messages as messages
|
||||||
|
import src.models as models
|
||||||
|
from src.database import get_session
|
||||||
|
from sqlmodel import Session
|
||||||
|
import src.forms.service as service
|
||||||
|
|
||||||
router = APIRouter(prefix='/forms')
|
router = APIRouter(prefix='/forms')
|
||||||
|
|
||||||
@router.get('/')
|
@router.get('/', response_model=list[models.FormPublic])
|
||||||
def get_forms():
|
def get_forms(session: Session = Depends(get_session)):
|
||||||
return []
|
return service.get_all(session)
|
||||||
|
|
||||||
@router.post('/')
|
@router.get('/{id}', response_model=models.FormPublic)
|
||||||
def create_form():
|
def get_forms(id: int, session: Session = Depends(get_session)):
|
||||||
return {}
|
result = service.get_one(session, id)
|
||||||
|
if result is None:
|
||||||
|
raise HTTPException(status_code=404, detail=messages.notfound)
|
||||||
|
return result
|
||||||
|
|
||||||
@router.put('/')
|
@router.post('/', response_model=models.FormPublic)
|
||||||
def update_form():
|
def create_form(form: models.FormCreate, session: Session = Depends(get_session)):
|
||||||
return {}
|
return service.create_one(session, form)
|
||||||
|
|
||||||
@router.delete('/')
|
@router.put('/{id}', response_model=models.FormPublic)
|
||||||
def delete_form():
|
def update_form(id: int, form: models.FormUpdate, session: Session = Depends(get_session)):
|
||||||
return {}
|
result = service.update_one(session, id, form)
|
||||||
|
if result is None:
|
||||||
|
raise HTTPException(status_code=404, detail=messages.notfound)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@router.delete('/{id}', response_model=models.FormPublic)
|
||||||
|
def delete_form(id: int, session: Session = Depends(get_session)):
|
||||||
|
result = service.delete_one(session, id)
|
||||||
|
if result is None:
|
||||||
|
raise HTTPException(status_code=404, detail=messages.notfound)
|
||||||
|
return result
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
from sqlmodel import Field, SQLModel
|
|
||||||
form src.productors.model import Productor
|
|
||||||
|
|
||||||
class Form(SQLModel, table=True):
|
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
|
||||||
name: str
|
|
||||||
productor_id: int | None = Field(default=None, foreign_key="productor.id")
|
|
||||||
shipment_number: int
|
|
||||||
season: str
|
|
||||||
|
|
||||||
42
backend/src/forms/service.py
Normal file
42
backend/src/forms/service.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
from sqlmodel import Session, select
|
||||||
|
import src.models as models
|
||||||
|
|
||||||
|
def get_all(session: Session) -> list[models.FormPublic]:
|
||||||
|
statement = select(models.Form)
|
||||||
|
return session.exec(statement).all()
|
||||||
|
|
||||||
|
def get_one(session: Session, form_id: int) -> models.FormPublic:
|
||||||
|
return session.get(models.Form, form_id)
|
||||||
|
|
||||||
|
def create_one(session: Session, form: models.FormCreate) -> models.FormPublic:
|
||||||
|
form_create = form.model_dump(exclude_unset=True)
|
||||||
|
new_form = models.Form(**form_create)
|
||||||
|
session.add(new_form)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(new_form)
|
||||||
|
return new_form
|
||||||
|
|
||||||
|
def update_one(session: Session, id: int, form: models.FormUpdate) -> models.FormPublic:
|
||||||
|
statement = select(models.Form).where(models.Form.id == id)
|
||||||
|
result = session.exec(statement)
|
||||||
|
new_form = result.first()
|
||||||
|
if not new_form:
|
||||||
|
return None
|
||||||
|
form_updates = form.model_dump(exclude_unset=True)
|
||||||
|
for key, value in form_updates.items():
|
||||||
|
setattr(new_form, key, value)
|
||||||
|
session.add(new_form)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(new_form)
|
||||||
|
return new_form
|
||||||
|
|
||||||
|
def delete_one(session: Session, id: int) -> models.FormPublic:
|
||||||
|
statement = select(models.Form).where(models.Form.id == id)
|
||||||
|
result = session.exec(statement)
|
||||||
|
form = result.first()
|
||||||
|
if not form:
|
||||||
|
return None
|
||||||
|
result = models.FormPublic.model_validate(form)
|
||||||
|
session.delete(form)
|
||||||
|
session.commit()
|
||||||
|
return result
|
||||||
@@ -10,14 +10,15 @@ from src.productors.productors import router as productors_router
|
|||||||
from src.products.products import router as products_router
|
from src.products.products import router as products_router
|
||||||
from src.users.users import router as users_router
|
from src.users.users import router as users_router
|
||||||
from src.auth.auth import router as auth_router
|
from src.auth.auth import router as auth_router
|
||||||
from src.secrets import origins
|
from src.shipments.shipments import router as shipment_router
|
||||||
|
from src.settings import settings
|
||||||
from src.database import engine
|
from src.database import engine
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=origins,
|
allow_origins=[settings.origins],
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
@@ -32,5 +33,6 @@ app.include_router(productors_router)
|
|||||||
app.include_router(products_router)
|
app.include_router(products_router)
|
||||||
app.include_router(users_router)
|
app.include_router(users_router)
|
||||||
app.include_router(auth_router)
|
app.include_router(auth_router)
|
||||||
|
app.include_router(shipment_router)
|
||||||
|
|
||||||
SQLModel.metadata.create_all(engine)
|
SQLModel.metadata.create_all(engine)
|
||||||
@@ -3,85 +3,6 @@ from enum import Enum
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
class Unit(Enum):
|
|
||||||
GRAMS = 1
|
|
||||||
KILO = 2
|
|
||||||
|
|
||||||
class ProductBase(SQLModel):
|
|
||||||
name: str
|
|
||||||
unit: Unit
|
|
||||||
price: float
|
|
||||||
price_kg: float | None
|
|
||||||
weight: float
|
|
||||||
productor_id: int | None = Field(default=None, foreign_key="productor.id")
|
|
||||||
|
|
||||||
class ProductPublic(ProductBase):
|
|
||||||
id: int
|
|
||||||
|
|
||||||
class Product(ProductBase, table=True):
|
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
|
||||||
|
|
||||||
productor: Optional['Productor'] = Relationship(back_populates="products")
|
|
||||||
|
|
||||||
class ProductUpdate(SQLModel):
|
|
||||||
name: str | None
|
|
||||||
unit: Unit | None
|
|
||||||
price: float | None
|
|
||||||
price_kg: float | None
|
|
||||||
weight: float | None
|
|
||||||
productor_id: int | None = Field(default=None, foreign_key="productor.id")
|
|
||||||
|
|
||||||
|
|
||||||
class ProductCreate(ProductBase):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class ProductorBase(SQLModel):
|
|
||||||
name: str
|
|
||||||
address: str
|
|
||||||
payment: str
|
|
||||||
|
|
||||||
class ProductorPublic(ProductorBase):
|
|
||||||
id: int
|
|
||||||
products: list[Product] = []
|
|
||||||
|
|
||||||
class Productor(ProductorBase, table=True):
|
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
|
||||||
|
|
||||||
products: list[Product] = Relationship(back_populates='productor')
|
|
||||||
|
|
||||||
class ProductorUpdate(SQLModel):
|
|
||||||
name: str | None
|
|
||||||
address: str | None
|
|
||||||
payment: str | None
|
|
||||||
|
|
||||||
class ProductorCreate(ProductorBase):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class FormBase(SQLModel):
|
|
||||||
productor_id: int | None = Field(default=None, foreign_key="productor.id")
|
|
||||||
referer_id: int | None = Field(default=None, foreign_key="referer.id")
|
|
||||||
season: str
|
|
||||||
shipments: int
|
|
||||||
start: datetime.date
|
|
||||||
end: datetime.date
|
|
||||||
|
|
||||||
class FormPublic(FormBase):
|
|
||||||
id: int
|
|
||||||
|
|
||||||
class Form(FormBase, table=True):
|
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
|
||||||
|
|
||||||
class FormUpdate(SQLModel):
|
|
||||||
productor_id: int | None = Field(default=None, foreign_key="productor.id")
|
|
||||||
referer_id: int | None = Field(default=None, foreign_key="user.id")
|
|
||||||
season: str | None
|
|
||||||
shipments: int | None
|
|
||||||
start: datetime.date | None
|
|
||||||
end: datetime.date | None
|
|
||||||
|
|
||||||
class FormCreate(FormBase):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class UserBase(SQLModel):
|
class UserBase(SQLModel):
|
||||||
name: str
|
name: str
|
||||||
email: str
|
email: str
|
||||||
@@ -98,3 +19,149 @@ class UserUpdate(SQLModel):
|
|||||||
|
|
||||||
class UserCreate(UserBase):
|
class UserCreate(UserBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class ProductorBase(SQLModel):
|
||||||
|
name: str
|
||||||
|
address: str
|
||||||
|
payment: str
|
||||||
|
|
||||||
|
class ProductorPublic(ProductorBase):
|
||||||
|
id: int
|
||||||
|
products: list["Product"] = []
|
||||||
|
|
||||||
|
class Productor(ProductorBase, table=True):
|
||||||
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
|
|
||||||
|
products: list["Product"] = Relationship(back_populates='productor')
|
||||||
|
|
||||||
|
class ProductorUpdate(SQLModel):
|
||||||
|
name: str | None
|
||||||
|
address: str | None
|
||||||
|
payment: str | None
|
||||||
|
|
||||||
|
class ProductorCreate(ProductorBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Unit(Enum):
|
||||||
|
GRAMS = 1
|
||||||
|
KILO = 2
|
||||||
|
|
||||||
|
class ProductType(Enum):
|
||||||
|
PLANNED = 1
|
||||||
|
RECCURENT = 2
|
||||||
|
|
||||||
|
class ShipmentProductLink(SQLModel, table=True):
|
||||||
|
shipment_id: Optional[int] = Field(default=None, foreign_key="shipment.id", primary_key=True)
|
||||||
|
product_id: Optional[int] = Field(default=None, foreign_key="product.id", primary_key=True)
|
||||||
|
|
||||||
|
class ProductBase(SQLModel):
|
||||||
|
name: str
|
||||||
|
unit: Unit
|
||||||
|
price: float
|
||||||
|
price_kg: float | None
|
||||||
|
weight: float
|
||||||
|
type: ProductType
|
||||||
|
productor_id: int | None = Field(default=None, foreign_key="productor.id")
|
||||||
|
|
||||||
|
class ProductPublic(ProductBase):
|
||||||
|
id: int
|
||||||
|
productor: Productor | None
|
||||||
|
shipments: list["Shipment"] | None
|
||||||
|
|
||||||
|
class Product(ProductBase, table=True):
|
||||||
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
|
shipments: list["Shipment"] = Relationship(back_populates="products", link_model=ShipmentProductLink)
|
||||||
|
productor: Optional[Productor] = Relationship(back_populates="products")
|
||||||
|
|
||||||
|
class ProductUpdate(SQLModel):
|
||||||
|
name: str | None
|
||||||
|
unit: Unit | None
|
||||||
|
price: float | None
|
||||||
|
price_kg: float | None
|
||||||
|
weight: float | None
|
||||||
|
productor_id: int | None
|
||||||
|
shipment_ids: list[int] | None
|
||||||
|
|
||||||
|
class ProductCreate(ProductBase):
|
||||||
|
shipment_ids: list[int] | None
|
||||||
|
|
||||||
|
class FormBase(SQLModel):
|
||||||
|
name: str
|
||||||
|
productor_id: int | None = Field(default=None, foreign_key="productor.id")
|
||||||
|
referer_id: int | None = Field(default=None, foreign_key="user.id")
|
||||||
|
season: str
|
||||||
|
start: datetime.date
|
||||||
|
end: datetime.date
|
||||||
|
|
||||||
|
class FormPublic(FormBase):
|
||||||
|
id: int
|
||||||
|
productor: ProductorPublic | None
|
||||||
|
referer: User
|
||||||
|
shipments: list["Shipment"] = []
|
||||||
|
|
||||||
|
class Form(FormBase, table=True):
|
||||||
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
|
productor: Optional['Productor'] = Relationship()
|
||||||
|
referer: Optional['User'] = Relationship()
|
||||||
|
shipments: list["Shipment"] = Relationship()
|
||||||
|
|
||||||
|
class FormUpdate(SQLModel):
|
||||||
|
productor_id: int | None
|
||||||
|
referer_id: int | None
|
||||||
|
season: str | None
|
||||||
|
start: datetime.date | None
|
||||||
|
end: datetime.date | None
|
||||||
|
|
||||||
|
class FormCreate(FormBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TemplateBase(SQLModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TemplatePublic(TemplateBase):
|
||||||
|
id: int
|
||||||
|
|
||||||
|
class Template(TemplateBase, table=True):
|
||||||
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
|
|
||||||
|
class TemplateUpdate(SQLModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TemplateCreate(TemplateBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ContractBase(SQLModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ContractPublic(ContractBase):
|
||||||
|
id: int
|
||||||
|
|
||||||
|
class Contract(ContractBase, table=True):
|
||||||
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
|
|
||||||
|
class ContractUpdate(SQLModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ContractCreate(ContractBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ShipmentBase(SQLModel):
|
||||||
|
name: str
|
||||||
|
date: datetime.date
|
||||||
|
form_id: int | None = Field(default=None, foreign_key="form.id")
|
||||||
|
|
||||||
|
class ShipmentPublic(ShipmentBase):
|
||||||
|
id: int
|
||||||
|
products: list[Product] = []
|
||||||
|
|
||||||
|
class Shipment(ShipmentBase, table=True):
|
||||||
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
|
products: list[Product] = Relationship(back_populates="shipments", link_model=ShipmentProductLink)
|
||||||
|
|
||||||
|
class ShipmentUpdate(SQLModel):
|
||||||
|
name: str | None
|
||||||
|
date: str | None
|
||||||
|
product_ids: list[int]
|
||||||
|
|
||||||
|
class ShipmentCreate(ShipmentBase):
|
||||||
|
product_ids: list[int]
|
||||||
@@ -1,23 +1,23 @@
|
|||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
from src.models import Productor
|
import src.models as models
|
||||||
|
|
||||||
def get_all(session: Session) -> list[Productor]:
|
def get_all(session: Session) -> list[models.ProductorPublic]:
|
||||||
statement = select(Productor)
|
statement = select(models.Productor)
|
||||||
return session.exec(statement).all()
|
return session.exec(statement).all()
|
||||||
|
|
||||||
def get_one(session: Session, productor_id: int) -> Productor:
|
def get_one(session: Session, productor_id: int) -> models.ProductorPublic:
|
||||||
return session.get(Productor, productor_id)
|
return session.get(models.Productor, productor_id)
|
||||||
|
|
||||||
def create_one(session: Session, productor: Productor) -> Productor:
|
def create_one(session: Session, productor: models.ProductorCreate) -> models.ProductorPublic:
|
||||||
productor_create = productor.model_dump(exclude_unset=True)
|
productor_create = productor.model_dump(exclude_unset=True)
|
||||||
new_productor = Productor(**productor_create)
|
new_productor = models.Productor(**productor_create)
|
||||||
session.add(new_productor)
|
session.add(new_productor)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(new_productor)
|
session.refresh(new_productor)
|
||||||
return new_productor
|
return new_productor
|
||||||
|
|
||||||
def update_one(session: Session, id: int, productor: Productor) -> Productor:
|
def update_one(session: Session, id: int, productor: models.ProductorUpdate) -> models.ProductorPublic:
|
||||||
statement = select(Productor).where(Productor.id == id)
|
statement = select(models.Productor).where(models.Productor.id == id)
|
||||||
result = session.exec(statement)
|
result = session.exec(statement)
|
||||||
new_productor = result.first()
|
new_productor = result.first()
|
||||||
if not new_productor:
|
if not new_productor:
|
||||||
@@ -30,12 +30,13 @@ def update_one(session: Session, id: int, productor: Productor) -> Productor:
|
|||||||
session.refresh(new_productor)
|
session.refresh(new_productor)
|
||||||
return new_productor
|
return new_productor
|
||||||
|
|
||||||
def delete_one(session: Session, id: int) -> Productor:
|
def delete_one(session: Session, id: int) -> models.ProductorPublic:
|
||||||
statement = select(Productor).where(Productor.id == id)
|
statement = select(models.Productor).where(models.Productor.id == id)
|
||||||
result = session.exec(statement)
|
result = session.exec(statement)
|
||||||
productor = result.first()
|
productor = result.first()
|
||||||
if not productor:
|
if not productor:
|
||||||
return None
|
return None
|
||||||
|
result = models.ProductorPublic.model_validate(productor)
|
||||||
session.delete(productor)
|
session.delete(productor)
|
||||||
session.commit()
|
session.commit()
|
||||||
return productor
|
return result
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ from sqlmodel import Session
|
|||||||
import src.products.service as service
|
import src.products.service as service
|
||||||
from src.auth.auth import get_current_user
|
from src.auth.auth import get_current_user
|
||||||
router = APIRouter(prefix='/products')
|
router = APIRouter(prefix='/products')
|
||||||
|
#user=Depends(get_current_user)
|
||||||
@router.get('/', response_model=list[models.ProductPublic], )
|
@router.get('/', response_model=list[models.ProductPublic], )
|
||||||
def get_products(session: Session = Depends(get_session), user=Depends(get_current_user)):
|
def get_products(session: Session = Depends(get_session)):
|
||||||
return service.get_all(session)
|
return service.get_all(session)
|
||||||
|
|
||||||
@router.get('/{id}', response_model=models.ProductPublic)
|
@router.get('/{id}', response_model=models.ProductPublic)
|
||||||
|
|||||||
@@ -1,41 +1,51 @@
|
|||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
from src.models import Product
|
import src.models as models
|
||||||
|
|
||||||
def get_all(session: Session) -> list[Product]:
|
def get_all(session: Session) -> list[models.ProductPublic]:
|
||||||
statement = select(Product)
|
statement = select(models.Product)
|
||||||
return session.exec(statement).all()
|
return session.exec(statement).all()
|
||||||
|
|
||||||
def get_one(session: Session, product_id: int) -> Product:
|
def get_one(session: Session, product_id: int) -> models.ProductPublic:
|
||||||
return session.get(Product, product_id)
|
return session.get(models.Product, product_id)
|
||||||
|
|
||||||
def create_one(session: Session, product: Product) -> Product:
|
def create_one(session: Session, product: models.ProductCreate) -> models.ProductPublic:
|
||||||
product_create = product.model_dump(exclude_unset=True)
|
shipments = session.exec(select(models.Shipment).where(models.Shipment.id.in_(product.shipment_ids))).all()
|
||||||
new_product = Product(**product_create)
|
|
||||||
|
product_create = product.model_dump(exclude_unset=True, exclude={'shipment_ids'})
|
||||||
|
new_product = models.Product(**product_create, shipments=shipments)
|
||||||
session.add(new_product)
|
session.add(new_product)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(new_product)
|
session.refresh(new_product)
|
||||||
return new_product
|
return new_product
|
||||||
|
|
||||||
def update_one(session: Session, id: int, product: Product) -> Product:
|
def update_one(session: Session, id: int, product: models.ProductUpdate) -> models.ProductPublic:
|
||||||
statement = select(Product).where(Product.id == id)
|
statement = select(models.Product).where(models.Product.id == id)
|
||||||
result = session.exec(statement)
|
result = session.exec(statement)
|
||||||
new_product = result.first()
|
new_product = result.first()
|
||||||
if not new_product:
|
if not new_product:
|
||||||
return None
|
return None
|
||||||
product_updates = product.model_dump(exclude_unset=True)
|
|
||||||
|
shipments_to_add = session.exec(select(models.Shipment).where(models.Shipment.id.in_(product.shipment_ids))).all()
|
||||||
|
new_product.shipments.clear()
|
||||||
|
for add in shipments_to_add:
|
||||||
|
new_product.shipments.append(add)
|
||||||
|
|
||||||
|
product_updates = product.model_dump(exclude_unset=True, exclude={"shipment_ids"})
|
||||||
for key, value in product_updates.items():
|
for key, value in product_updates.items():
|
||||||
setattr(new_product, key, value)
|
setattr(new_product, key, value)
|
||||||
|
|
||||||
session.add(new_product)
|
session.add(new_product)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(new_product)
|
session.refresh(new_product)
|
||||||
return new_product
|
return new_product
|
||||||
|
|
||||||
def delete_one(session: Session, id: int) -> Product:
|
def delete_one(session: Session, id: int) -> models.ProductPublic:
|
||||||
statement = select(Product).where(Product.id == id)
|
statement = select(models.Product).where(models.Product.id == id)
|
||||||
result = session.exec(statement)
|
result = session.exec(statement)
|
||||||
product = result.first()
|
product = result.first()
|
||||||
if not product:
|
if not product:
|
||||||
return None
|
return None
|
||||||
|
result = models.ProductPublic.model_validate(product)
|
||||||
session.delete(product)
|
session.delete(product)
|
||||||
session.commit()
|
session.commit()
|
||||||
return product
|
return result
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
origins = [
|
|
||||||
os.environ.get('SERVICE_ORIGIN') or 'http://localhost'
|
|
||||||
]
|
|
||||||
|
|
||||||
dbhost = os.environ.get('DB_HOST') or 'localhost'
|
|
||||||
dbuser = os.environ.get('PGSQL_USER') or 'postgres'
|
|
||||||
dbpass = os.environ.get('PGSQL_PASSWORD') or 'postgres'
|
|
||||||
dbname = os.environ.get('PGSQL_DATABASE') or 'amap'
|
|
||||||
|
|
||||||
# openssl rand -hex 32
|
|
||||||
SECRET_KEY = os.environ.get('SERVICE_SECRET_KEY') or 'test'
|
|
||||||
ALGORITHM = 'HS256'
|
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES = 600
|
|
||||||
|
|
||||||
|
|
||||||
KEYCLOAK_SERVER = ""
|
|
||||||
REALM = ""
|
|
||||||
CLIENT_ID = ""
|
|
||||||
CLIENT_SECRET = ""
|
|
||||||
|
|
||||||
REDIRECT_URI = "http://localhost:8000/auth/callback"
|
|
||||||
|
|
||||||
AUTH_URL = f"{KEYCLOAK_SERVER}/realms/{REALM}/protocol/openid-connect/auth"
|
|
||||||
TOKEN_URL = f"{KEYCLOAK_SERVER}/realms/{REALM}/protocol/openid-connect/token"
|
|
||||||
|
|
||||||
ISSUER = f"{KEYCLOAK_SERVER}/realms/{REALM}"
|
|
||||||
JWKS_URL = f"{ISSUER}/protocol/openid-connect/certs"
|
|
||||||
25
backend/src/settings.py
Normal file
25
backend/src/settings.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from pydantic_settings import BaseSettings
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
origins: str
|
||||||
|
db_host: str
|
||||||
|
db_user: str
|
||||||
|
db_pass: str
|
||||||
|
db_name: str
|
||||||
|
secret_key: str
|
||||||
|
keycloak_server: str
|
||||||
|
keycloak_realm: str
|
||||||
|
keycloak_client_id: str
|
||||||
|
keycloak_client_secret: str
|
||||||
|
keycloak_redirect_uri: str
|
||||||
|
root_fqdn: str
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
env_file = "../.env"
|
||||||
|
|
||||||
|
settings = Settings()
|
||||||
|
|
||||||
|
AUTH_URL = f"{settings.keycloak_server}/realms/{settings.keycloak_realm}/protocol/openid-connect/auth"
|
||||||
|
TOKEN_URL = f"{settings.keycloak_server}/realms/{settings.keycloak_realm}/protocol/openid-connect/token"
|
||||||
|
ISSUER = f"{settings.keycloak_server}/realms/{settings.keycloak_realm}"
|
||||||
|
JWKS_URL = f"{ISSUER}/protocol/openid-connect/certs"
|
||||||
3
backend/src/shipments/__init__.py
Normal file
3
backend/src/shipments/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2026-present Julien Aldon <julien.aldon@wanadoo.fr>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
50
backend/src/shipments/service.py
Normal file
50
backend/src/shipments/service.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
from sqlmodel import Session, select
|
||||||
|
import src.models as models
|
||||||
|
|
||||||
|
def get_all(session: Session) -> list[models.ShipmentPublic]:
|
||||||
|
statement = select(models.Shipment)
|
||||||
|
return session.exec(statement).all()
|
||||||
|
|
||||||
|
def get_one(session: Session, shipment_id: int) -> models.ShipmentPublic:
|
||||||
|
return session.get(models.Shipment, shipment_id)
|
||||||
|
|
||||||
|
def create_one(session: Session, shipment: models.ShipmentCreate) -> models.ShipmentPublic:
|
||||||
|
products = session.exec(select(models.Product).where(models.Product.id.in_(shipment.product_ids))).all()
|
||||||
|
shipment_create = shipment.model_dump(exclude_unset=True, exclude={'product_ids'})
|
||||||
|
new_shipment = models.Shipment(**shipment_create, products=products)
|
||||||
|
session.add(new_shipment)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(new_shipment)
|
||||||
|
return new_shipment
|
||||||
|
|
||||||
|
def update_one(session: Session, id: int, shipment: models.ShipmentUpdate) -> models.ShipmentPublic:
|
||||||
|
statement = select(models.Shipment).where(models.Shipment.id == id)
|
||||||
|
result = session.exec(statement)
|
||||||
|
new_shipment = result.first()
|
||||||
|
if not new_shipment:
|
||||||
|
return None
|
||||||
|
|
||||||
|
products_to_add = session.exec(select(models.Product).where(models.Product.id.in_(shipment.product_ids))).all()
|
||||||
|
new_shipment.products.clear()
|
||||||
|
for add in products_to_add:
|
||||||
|
new_shipment.products.append(add)
|
||||||
|
|
||||||
|
shipment_updates = shipment.model_dump(exclude_unset=True, exclude={"product_ids"})
|
||||||
|
for key, value in shipment_updates.items():
|
||||||
|
setattr(new_shipment, key, value)
|
||||||
|
|
||||||
|
session.add(new_shipment)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(new_shipment)
|
||||||
|
return new_shipment
|
||||||
|
|
||||||
|
def delete_one(session: Session, id: int) -> models.ShipmentPublic:
|
||||||
|
statement = select(models.Shipment).where(models.Shipment.id == id)
|
||||||
|
result = session.exec(statement)
|
||||||
|
shipment = result.first()
|
||||||
|
if not shipment:
|
||||||
|
return None
|
||||||
|
result = models.ShipmentPublic.model_validate(shipment)
|
||||||
|
session.delete(shipment)
|
||||||
|
session.commit()
|
||||||
|
return result
|
||||||
37
backend/src/shipments/shipments.py
Normal file
37
backend/src/shipments/shipments.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException, Depends
|
||||||
|
import src.messages as messages
|
||||||
|
import src.models as models
|
||||||
|
from src.database import get_session
|
||||||
|
from sqlmodel import Session
|
||||||
|
import src.shipments.service as service
|
||||||
|
from src.auth.auth import get_current_user
|
||||||
|
router = APIRouter(prefix='/shipments')
|
||||||
|
|
||||||
|
@router.get('/', response_model=list[models.ShipmentPublic], )
|
||||||
|
def get_shipments(session: Session = Depends(get_session)):
|
||||||
|
return service.get_all(session)
|
||||||
|
|
||||||
|
@router.get('/{id}', response_model=models.ShipmentPublic)
|
||||||
|
def get_shipment(id: int, session: Session = Depends(get_session)):
|
||||||
|
result = service.get_one(session, id)
|
||||||
|
if result is None:
|
||||||
|
raise HTTPException(status_code=404, detail=messages.notfound)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@router.post('/', response_model=models.ShipmentPublic)
|
||||||
|
def create_shipment(shipment: models.ShipmentCreate, session: Session = Depends(get_session)):
|
||||||
|
return service.create_one(session, shipment)
|
||||||
|
|
||||||
|
@router.put('/{id}', response_model=models.ShipmentPublic)
|
||||||
|
def update_shipment(id: int, shipment: models.ShipmentUpdate, session: Session = Depends(get_session)):
|
||||||
|
result = service.update_one(session, id, shipment)
|
||||||
|
if result is None:
|
||||||
|
raise HTTPException(status_code=404, detail=messages.notfound)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@router.delete('/{id}', response_model=models.ShipmentPublic)
|
||||||
|
def delete_shipment(id: int, session: Session = Depends(get_session)):
|
||||||
|
result = service.delete_one(session, id)
|
||||||
|
if result is None:
|
||||||
|
raise HTTPException(status_code=404, detail=messages.notfound)
|
||||||
|
return result
|
||||||
42
backend/src/templates/service.py
Normal file
42
backend/src/templates/service.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
from sqlmodel import Session, select
|
||||||
|
import src.models as models
|
||||||
|
|
||||||
|
def get_all(session: Session) -> list[models.TemplatePublic]:
|
||||||
|
statement = select(models.Template)
|
||||||
|
return session.exec(statement).all()
|
||||||
|
|
||||||
|
def get_one(session: Session, template_id: int) -> models.TemplatePublic:
|
||||||
|
return session.get(models.Template, template_id)
|
||||||
|
|
||||||
|
def create_one(session: Session, template: models.TemplateCreate) -> models.TemplatePublic:
|
||||||
|
template_create = template.model_dump(exclude_unset=True)
|
||||||
|
new_template = models.Template(**template_create)
|
||||||
|
session.add(new_template)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(new_template)
|
||||||
|
return new_template
|
||||||
|
|
||||||
|
def update_one(session: Session, id: int, template: models.TemplateUpdate) -> models.TemplatePublic:
|
||||||
|
statement = select(models.Template).where(models.Template.id == id)
|
||||||
|
result = session.exec(statement)
|
||||||
|
new_template = result.first()
|
||||||
|
if not new_template:
|
||||||
|
return None
|
||||||
|
template_updates = template.model_dump(exclude_unset=True)
|
||||||
|
for key, value in template_updates.items():
|
||||||
|
setattr(new_template, key, value)
|
||||||
|
session.add(new_template)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(new_template)
|
||||||
|
return new_template
|
||||||
|
|
||||||
|
def delete_one(session: Session, id: int) -> models.TemplatePublic:
|
||||||
|
statement = select(models.Template).where(models.Template.id == id)
|
||||||
|
result = session.exec(statement)
|
||||||
|
template = result.first()
|
||||||
|
if not template:
|
||||||
|
return None
|
||||||
|
result = models.TemplatePublic.model_validate(template)
|
||||||
|
session.delete(template)
|
||||||
|
session.commit()
|
||||||
|
return result
|
||||||
@@ -1,19 +1,37 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter, HTTPException, Depends
|
||||||
|
import src.messages as messages
|
||||||
|
import src.models as models
|
||||||
|
from src.database import get_session
|
||||||
|
from sqlmodel import Session
|
||||||
|
import src.templates.service as service
|
||||||
|
|
||||||
router = APIRouter(prefix='/templates')
|
router = APIRouter(prefix='/templates')
|
||||||
|
|
||||||
@router.get('/')
|
@router.get('/', response_model=list[models.TemplatePublic])
|
||||||
def get_templates():
|
def get_templates(session: Session = Depends(get_session)):
|
||||||
return []
|
return service.get_all(session)
|
||||||
|
|
||||||
@router.post('/')
|
@router.get('/{id}', response_model=models.TemplatePublic)
|
||||||
def create_template():
|
def get_templates(id: int, session: Session = Depends(get_session)):
|
||||||
return {}
|
result = service.get_one(session, id)
|
||||||
|
if result is None:
|
||||||
|
raise HTTPException(status_code=404, detail=messages.notfound)
|
||||||
|
return result
|
||||||
|
|
||||||
@router.put('/')
|
@router.post('/', response_model=models.TemplatePublic)
|
||||||
def update_template():
|
def create_template(template: models.TemplateCreate, session: Session = Depends(get_session)):
|
||||||
return {}
|
return service.create_one(session, template)
|
||||||
|
|
||||||
@router.delete('/')
|
@router.put('/{id}', response_model=models.TemplatePublic)
|
||||||
def delete_template():
|
def update_template(id: int, template: models.TemplateUpdate, session: Session = Depends(get_session)):
|
||||||
return {}
|
result = service.update_one(session, id, template)
|
||||||
|
if result is None:
|
||||||
|
raise HTTPException(status_code=404, detail=messages.notfound)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@router.delete('/{id}', response_model=models.TemplatePublic)
|
||||||
|
def delete_template(id: int, session: Session = Depends(get_session)):
|
||||||
|
result = service.delete_one(session, id)
|
||||||
|
if result is None:
|
||||||
|
raise HTTPException(status_code=404, detail=messages.notfound)
|
||||||
|
return result
|
||||||
|
|||||||
3
backend/src/users/__init__.py
Normal file
3
backend/src/users/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2026-present Julien Aldon <julien.aldon@wanadoo.fr>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
50
backend/src/users/service.py
Normal file
50
backend/src/users/service.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
from sqlmodel import Session, select
|
||||||
|
import src.models as models
|
||||||
|
|
||||||
|
def get_all(session: Session) -> list[models.UserPublic]:
|
||||||
|
statement = select(models.User)
|
||||||
|
return session.exec(statement).all()
|
||||||
|
|
||||||
|
def get_one(session: Session, user_id: int) -> models.UserPublic:
|
||||||
|
return session.get(models.User, user_id)
|
||||||
|
|
||||||
|
def get_or_create_user(session: Session, user_create: models.UserCreate):
|
||||||
|
statement = select(models.User).where(models.User.email == user_create.email)
|
||||||
|
user = session.exec(statement).first()
|
||||||
|
if user:
|
||||||
|
return user
|
||||||
|
user = create_one(session, user_create)
|
||||||
|
return user
|
||||||
|
|
||||||
|
def create_one(session: Session, user: models.UserCreate) -> models.UserPublic:
|
||||||
|
user_create = user.model_dump(exclude_unset=True)
|
||||||
|
new_user = models.User(**user_create)
|
||||||
|
session.add(new_user)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(new_user)
|
||||||
|
return new_user
|
||||||
|
|
||||||
|
def update_one(session: Session, id: int, user: models.UserUpdate) -> models.UserPublic:
|
||||||
|
statement = select(models.User).where(models.User.id == id)
|
||||||
|
result = session.exec(statement)
|
||||||
|
new_user = result.first()
|
||||||
|
if not new_user:
|
||||||
|
return None
|
||||||
|
user_updates = user.model_dump(exclude_unset=True)
|
||||||
|
for key, value in user_updates.items():
|
||||||
|
setattr(new_user, key, value)
|
||||||
|
session.add(new_user)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(new_user)
|
||||||
|
return new_user
|
||||||
|
|
||||||
|
def delete_one(session: Session, id: int) -> models.UserPublic:
|
||||||
|
statement = select(models.User).where(models.User.id == id)
|
||||||
|
result = session.exec(statement)
|
||||||
|
user = result.first()
|
||||||
|
if not user:
|
||||||
|
return None
|
||||||
|
result = models.UserPublic.model_validate(user)
|
||||||
|
session.delete(user)
|
||||||
|
session.commit()
|
||||||
|
return result
|
||||||
@@ -1,19 +1,37 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter, HTTPException, Depends
|
||||||
|
import src.messages as messages
|
||||||
|
import src.models as models
|
||||||
|
from src.database import get_session
|
||||||
|
from sqlmodel import Session
|
||||||
|
import src.users.service as service
|
||||||
|
|
||||||
router = APIRouter(prefix='/users')
|
router = APIRouter(prefix='/users')
|
||||||
|
|
||||||
@router.get('/')
|
@router.get('/', response_model=list[models.UserPublic])
|
||||||
def get_users():
|
def get_users(session: Session = Depends(get_session)):
|
||||||
return []
|
return service.get_all(session)
|
||||||
|
|
||||||
@router.post('/')
|
@router.get('/{id}', response_model=models.UserPublic)
|
||||||
def create_user():
|
def get_users(id: int, session: Session = Depends(get_session)):
|
||||||
return {}
|
result = service.get_one(session, id)
|
||||||
|
if result is None:
|
||||||
|
raise HTTPException(status_code=404, detail=messages.notfound)
|
||||||
|
return result
|
||||||
|
|
||||||
@router.put('/')
|
@router.post('/', response_model=models.UserPublic)
|
||||||
def update_user():
|
def create_user(user: models.UserCreate, session: Session = Depends(get_session)):
|
||||||
return {}
|
return service.create_one(session, user)
|
||||||
|
|
||||||
@router.delete('/')
|
@router.put('/{id}', response_model=models.UserPublic)
|
||||||
def delete_user():
|
def update_user(id: int, user: models.UserUpdate, session: Session = Depends(get_session)):
|
||||||
return {}
|
result = service.update_one(session, id, user)
|
||||||
|
if result is None:
|
||||||
|
raise HTTPException(status_code=404, detail=messages.notfound)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@router.delete('/{id}', response_model=models.UserPublic)
|
||||||
|
def delete_user(id: int, session: Session = Depends(get_session)):
|
||||||
|
result = service.delete_one(session, id)
|
||||||
|
if result is None:
|
||||||
|
raise HTTPException(status_code=404, detail=messages.notfound)
|
||||||
|
return result
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
shm_size: 128mb
|
shm_size: 128mb
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: ${POSTGRES_USER}
|
POSTGRES_USER: ${DB_USER}
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
POSTGRES_PASSWORD: ${DB_PASS}
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
POSTGRES_DB: ${DB_NAME}
|
||||||
ROOT_FQDN: ${ROOT_FQDN}
|
ROOT_FQDN: ${ROOT_FQDN}
|
||||||
ports:
|
ports:
|
||||||
- "54321:5432"
|
- "54321:5432"
|
||||||
|
|||||||
Reference in New Issue
Block a user