Initial commit
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
__pycache__
|
||||||
|
bin
|
||||||
|
include
|
||||||
|
lib
|
||||||
|
lib64
|
||||||
|
pyvenv.cfg
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# ENVIRONMENT (development, production)
|
||||||
|
ENVIRONMENT=Development
|
||||||
|
DOMAIN_URL=https://studio.dotapi.net/proxy/8000/
|
||||||
|
SUPPORT_EMAIL=support@dotapi.net
|
||||||
|
|
||||||
|
# TOKEN
|
||||||
|
TOKEN_SECRET=Desarrollado.por.Albert.Gutiérrez.Muñoz@2026
|
||||||
|
TOKEN_ALGORITHM=HS256
|
||||||
|
TOKEN_LIVE=60
|
||||||
|
|
||||||
|
# SESSION
|
||||||
|
USER_LIMIT_ATTEMPTS=3
|
||||||
|
|
||||||
|
# MAILING
|
||||||
|
MAILING_SMTP_SERVER=smtp.gmail.com
|
||||||
|
MAILING_SMTP_PORT=465
|
||||||
|
MAILING_SMTP_USER=albertgmu@gmail.com
|
||||||
|
MAILING_SMTP_PASSWORD=faqz vrws suaf sqtd
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
from abc import ABC
|
||||||
|
|
||||||
|
class Controller(ABC):
|
||||||
|
def _OnProcessParams(self, params: str):
|
||||||
|
result = {}
|
||||||
|
params = params.strip()
|
||||||
|
params = params.split(',')
|
||||||
|
for param in params:
|
||||||
|
field, value = param.split('=')
|
||||||
|
result[field.lower()] = value
|
||||||
|
|
||||||
|
return result
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class AlertLevelEnum(int, Enum):
|
||||||
|
Info=0
|
||||||
|
Low=1
|
||||||
|
Medium=2
|
||||||
|
High=3
|
||||||
|
Ultra=4
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
class AnimalSexEnum(int, Enum):
|
||||||
|
Female=0
|
||||||
|
Male=1
|
||||||
|
Unknown=2
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
class ErrorTypeEnum(int, Enum):
|
||||||
|
USER_EMAIL_EXISTS = -1
|
||||||
|
USER_ACCOUNT_EXISTS = -2
|
||||||
|
USER_ID_NOT_EXISTS = -3
|
||||||
|
USER_ACCOUNT_LOCKED = -4
|
||||||
|
USER_ACCOUNT_WRONG_PASSWORD = -5
|
||||||
|
USER_SESSION_NOT_EXISTS = -6
|
||||||
|
USER_SESSION_EXPIRED = -7
|
||||||
|
USER_ACCOUNT_NO_PASSWORD = -8
|
||||||
|
SETTLEMENT_EXISTS = -9
|
||||||
|
SETTLEMENT_ID_NOT_EXISTS = -10
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
class MessageTypeEnum(int, Enum):
|
||||||
|
Information=0
|
||||||
|
Alert=1
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
from fastapi import FastAPI, File, HTTPException, Response, UploadFile, status, Header, Depends
|
||||||
|
|
||||||
|
from backend.code.services.environment import EnvironmentService
|
||||||
|
from backend.code.middlewares.private_middleware import PrivateMiddleware
|
||||||
|
|
||||||
|
from backend.code.modules.miscellaneous import router as router_miscellaneous
|
||||||
|
from backend.code.modules.sessions import router as router_sessions
|
||||||
|
from backend.code.modules.users import router as router_users
|
||||||
|
from backend.code.modules.animals import router as router_animals
|
||||||
|
from backend.code.modules.animal_comments import router as router_animal_comments
|
||||||
|
from backend.code.modules.animal_images import router as router_animal_images
|
||||||
|
from backend.code.modules.animal_alerts import router as router_animal_alerts
|
||||||
|
from backend.code.modules.settlements import router as router_settlements
|
||||||
|
|
||||||
|
#
|
||||||
|
# LOAD CONFIGURATION
|
||||||
|
#
|
||||||
|
EnvironmentService().LoadEnvFile('config.env')
|
||||||
|
|
||||||
|
#
|
||||||
|
# MIDDLEWARES
|
||||||
|
#
|
||||||
|
token_middleware = PrivateMiddleware()
|
||||||
|
|
||||||
|
app = FastAPI(root_path="/proxy/8000")
|
||||||
|
|
||||||
|
#
|
||||||
|
# ENDPOINTS
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# Session [OK]
|
||||||
|
#
|
||||||
|
# app.include_router(router_sessions.routes)
|
||||||
|
|
||||||
|
#
|
||||||
|
# USERS [OK]
|
||||||
|
#
|
||||||
|
# app.include_router(router_users.routes, dependencies=[Depends(token_middleware.HasToken)])
|
||||||
|
|
||||||
|
#
|
||||||
|
# SETTLEMENTS
|
||||||
|
#
|
||||||
|
app.include_router(router_settlements.routes, dependencies=[Depends(token_middleware.HasToken)])
|
||||||
|
|
||||||
|
# #
|
||||||
|
# # ANIMALS
|
||||||
|
# #
|
||||||
|
# app.include_router(router_animals.routes, dependencies=[Depends(token_middleware.HasToken)])
|
||||||
|
|
||||||
|
# #
|
||||||
|
# # ANIMAL COMMENTS
|
||||||
|
# #
|
||||||
|
# app.include_router(router_animal_comments.routes)
|
||||||
|
|
||||||
|
# #
|
||||||
|
# # ANIMAL IMAGES
|
||||||
|
# #
|
||||||
|
# app.include_router(router_animal_images.routes)
|
||||||
|
|
||||||
|
# #
|
||||||
|
# # ANIMAL ALERTS
|
||||||
|
# #
|
||||||
|
# app.include_router(router_animal_alerts.routes)
|
||||||
|
|
||||||
|
# #
|
||||||
|
# # Miscellaneous
|
||||||
|
# #
|
||||||
|
# app.include_router(router_miscellaneous.routes)
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import jwt
|
||||||
|
from fastapi import Header, status, HTTPException
|
||||||
|
|
||||||
|
from backend.code.services.token import TokenService
|
||||||
|
|
||||||
|
class PrivateMiddleware:
|
||||||
|
def __init__(self):
|
||||||
|
self._tokenService = TokenService()
|
||||||
|
|
||||||
|
def HasToken(self, token: str = Header(...)):
|
||||||
|
if token is None:
|
||||||
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
if self._tokenService.ValidateToken(token) == False:
|
||||||
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
class AccountChangePassword(BaseModel):
|
||||||
|
OldPassword: str
|
||||||
|
NewPassword: str
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class AccountLoginRequest(BaseModel):
|
||||||
|
Username: str
|
||||||
|
Password: str
|
||||||
|
Domain: Optional[str] = None
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class AccountRecoverRequest(BaseModel):
|
||||||
|
Username: str
|
||||||
|
Domain: Optional[str]
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
class AccountResetPassword(BaseModel):
|
||||||
|
TokenRequest: str
|
||||||
|
Password: str
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
class SettlementEditRequest(BaseModel):
|
||||||
|
Name: str
|
||||||
|
Address: str
|
||||||
|
Active: bool
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class SettlementRegisterRequest(BaseModel):
|
||||||
|
Name: str
|
||||||
|
Address: str
|
||||||
|
Active: Optional[bool]
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
class SettlementRegisterDoneRequest(BaseModel):
|
||||||
|
DoneAt: datetime
|
||||||
|
DoneBy: int
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class UserAssociateAccount(BaseModel):
|
||||||
|
Username: str
|
||||||
|
Active: Optional[bool] = True
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class UserAssociatePermissions(BaseModel):
|
||||||
|
Permissions: Optional[dict[int]] = None
|
||||||
|
Groups: Optional[dict[int]] = None
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class UserEditRequest(BaseModel):
|
||||||
|
Name: Optional[str] = None
|
||||||
|
Surnames: Optional[str] = None
|
||||||
|
Email: Optional[str] = None
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class UserRegisterRequest(BaseModel):
|
||||||
|
Name: str
|
||||||
|
Surnames: str
|
||||||
|
Email: str
|
||||||
|
Username: Optional[str]
|
||||||
|
Locked: Optional[bool]
|
||||||
|
Active: Optional[bool]
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class UserFilter(BaseModel):
|
||||||
|
Name: Optional[str] = None
|
||||||
|
Surnames: Optional[str] = None
|
||||||
|
Email: Optional[str] = None
|
||||||
|
Username: Optional[str] = None
|
||||||
|
Locked: Optional[bool] = None
|
||||||
|
Active: Optional[bool] = None
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
class SessionTokenResponse(BaseModel):
|
||||||
|
Token: str
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class SettlementDetailResponse(BaseModel):
|
||||||
|
Id: int
|
||||||
|
Name: str
|
||||||
|
Address: str
|
||||||
|
LastDone: Optional[datetime]
|
||||||
|
LastDoneBy: Optional[int]
|
||||||
|
Active: bool
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class UserCreatedResponse(BaseModel):
|
||||||
|
Id: int
|
||||||
|
Name: str
|
||||||
|
Surnames: str
|
||||||
|
Email: str
|
||||||
|
Username: Optional[str]
|
||||||
|
Locked: Optional[bool]
|
||||||
|
Active: Optional[bool]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class UserDetailResponse(BaseModel):
|
||||||
|
Id: int
|
||||||
|
Name: str
|
||||||
|
Surnames: str
|
||||||
|
Email: str
|
||||||
|
Username: Optional[str]
|
||||||
|
Locked: Optional[bool]
|
||||||
|
Active: Optional[bool]
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
from backend.code.modules.database.database import DatabaseModule
|
||||||
|
|
||||||
|
class AccountRepository():
|
||||||
|
def __init__(self, repo: DatabaseModule):
|
||||||
|
self.conn = repo.conn
|
||||||
|
self.cursor = repo.cursor
|
||||||
|
|
||||||
|
def SelectByUsername(self, username: str):
|
||||||
|
sql = '''
|
||||||
|
SELECT *
|
||||||
|
FROM user_accounts
|
||||||
|
WHERE username = ?
|
||||||
|
'''
|
||||||
|
self.cursor.execute(sql, [username])
|
||||||
|
return self.cursor.fetchone()
|
||||||
|
|
||||||
|
def InsertAccount(self, user_id: int, username: str, locked: bool = False, active: bool = True):
|
||||||
|
sql = '''
|
||||||
|
INSERT INTO user_accounts(user_id, username, locked, active)
|
||||||
|
VALUES(?, ?, ?, ?)
|
||||||
|
'''
|
||||||
|
self.cursor.execute(sql, [user_id, username, locked, active])
|
||||||
|
self.conn.commit()
|
||||||
|
return self.cursor.lastrowid
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
routes = APIRouter(prefix="/animals/{animalId}/alerts", tags=['Animal alert'])
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("")
|
||||||
|
def ListAnimalAlerts(animalId: str):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@routes.post("")
|
||||||
|
def RegisterAnimalAlert(animalId: str, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@routes.put("/{id}")
|
||||||
|
def EditAnimalAlert(animalId: str, id: str, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@routes.delete("/{id}")
|
||||||
|
def DisableAnimalAlert(animalId: str, id: str):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
routes = APIRouter(prefix="/animals/{animalId}/comments", tags=['Animal comment'])
|
||||||
|
|
||||||
|
@routes.get("")
|
||||||
|
def ListAnimalComments(animalId: str):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@routes.post("")
|
||||||
|
def RegisterAnimalComment(animalId: str, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@routes.put("/{id}")
|
||||||
|
def EditAnimalComment(animalId: str, id: str, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@routes.delete("/{id}")
|
||||||
|
def DisableAnimalComment(animalId: str, id: str):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
from fastapi import APIRouter, UploadFile, File, HTTPException
|
||||||
|
|
||||||
|
ALLOWED_IMAGES = {
|
||||||
|
"image/jpeg",
|
||||||
|
"image/png",
|
||||||
|
"image/webp",
|
||||||
|
}
|
||||||
|
|
||||||
|
routes = APIRouter(prefix="/animals/{animalId}/images", tags=['Animal image'])
|
||||||
|
|
||||||
|
@routes.get("")
|
||||||
|
def ListAnimalImages(animalId: str):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@routes.post("")
|
||||||
|
def RegisterAnimalImage(animalId: str, file: UploadFile = File(...)):
|
||||||
|
if file.content_type not in ALLOWED_IMAGES:
|
||||||
|
raise HTTPException(status_code=400, detail="File must be an image")
|
||||||
|
|
||||||
|
return {"data": file.filename}
|
||||||
|
|
||||||
|
@routes.put("/{id}")
|
||||||
|
def EditAnimalImage(animalId: str, id: str, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@routes.delete("/{id}")
|
||||||
|
def DisableAnimalImage(animalId: str, id: str):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
from fastapi import APIRouter, status
|
||||||
|
|
||||||
|
routes = APIRouter(prefix="/animals", tags=['Animal'])
|
||||||
|
|
||||||
|
@routes.get("", status_code = status.HTTP_200_OK)
|
||||||
|
def FilterAnimals(data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@routes.post("", status_code = status.HTTP_201_CREATED)
|
||||||
|
def RegisterAnimal(data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@routes.put("/{id}", status_code = status.HTTP_204_NO_CONTENT)
|
||||||
|
def EditAnimal(id: str, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@routes.delete("/{id}", status_code = status.HTTP_204_NO_CONTENT)
|
||||||
|
def DisableAnimal(id: str):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
class DatabaseModule(ABC):
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def SelectById(self):
|
||||||
|
raise NotImplementedError("Select by id method not implemented")
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def SelectByCode(self):
|
||||||
|
raise NotImplementedError("Select by code method not implemented")
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def Filter(self, data):
|
||||||
|
raise NotImplementedError("Filter method not implemented")
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def Register(self, data):
|
||||||
|
raise NotImplementedError("Register method not implemented")
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def Edit(self, register, data):
|
||||||
|
raise NotImplementedError("Edit method not implemented")
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def Disable(self, register):
|
||||||
|
raise NotImplementedError("Disable method not implemented")
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import os
|
||||||
|
from backend.code.modules.database.database import DatabaseModule
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
class DatabaseSqliteModule(DatabaseModule):
|
||||||
|
def __init__(self):
|
||||||
|
self.database = '/projects/database/settlements.db'
|
||||||
|
self.schema = '/projects/database/1_schema_sqlite.sql'
|
||||||
|
self.basic_data = '/projects/database/2_population_sqlite.sql'
|
||||||
|
self.Initialize()
|
||||||
|
|
||||||
|
def Initialize(self):
|
||||||
|
exists = os.path.isfile(self.database)
|
||||||
|
self.conn = sqlite3.connect(self.database, check_same_thread=False)
|
||||||
|
self.cursor = self.conn.cursor()
|
||||||
|
if exists is False:
|
||||||
|
self.GenerateSchema()
|
||||||
|
self.PopulateDatabase()
|
||||||
|
|
||||||
|
def GenerateSchema(self):
|
||||||
|
with open(self.schema, 'r', encoding='utf-8') as file:
|
||||||
|
sql = file.read()
|
||||||
|
self.cursor.executescript(sql)
|
||||||
|
self.conn.commit()
|
||||||
|
|
||||||
|
def PopulateDatabase(self):
|
||||||
|
with open(self.basic_data, 'r', encoding='utf-8') as file:
|
||||||
|
sql = file.read()
|
||||||
|
self.cursor.executescript(sql)
|
||||||
|
self.conn.commit()
|
||||||
|
|
||||||
|
def SelectById(self):
|
||||||
|
raise NotImplementedError("Select by id method not implemented")
|
||||||
|
|
||||||
|
def SelectByCode(self):
|
||||||
|
raise NotImplementedError("Select by code method not implemented")
|
||||||
|
|
||||||
|
def Filter(self, data):
|
||||||
|
raise NotImplementedError("Filter method not implemented")
|
||||||
|
|
||||||
|
def Register(self, data):
|
||||||
|
raise NotImplementedError("Register method not implemented")
|
||||||
|
|
||||||
|
def Edit(self, register, data):
|
||||||
|
raise NotImplementedError("Edit method not implemented")
|
||||||
|
|
||||||
|
def Disable(self, register):
|
||||||
|
raise NotImplementedError("Disable method not implemented")
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from fastapi import APIRouter, status, HTTPException
|
||||||
|
|
||||||
|
routes = APIRouter(prefix="", tags=['Miscellaneous'])
|
||||||
|
|
||||||
|
@routes.api_route('/', methods=['GET', 'POST', 'PUT', 'DELETE'], status_code=status.HTTP_400_BAD_REQUEST)
|
||||||
|
def RootPathLocked():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@routes.get('/isalive', status_code=status.HTTP_200_OK)
|
||||||
|
def CheckServerRunning():
|
||||||
|
now = datatime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
return {'data': f'Last server update: {now}'}
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
import os
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from backend.code.modules.database.database_sqlite import DatabaseSqliteModule
|
||||||
|
from backend.code.modules.sessions.repository import SessionRepository
|
||||||
|
from backend.code.services.token import TokenService
|
||||||
|
from backend.code.services.mailing import MailingService
|
||||||
|
from backend.code.enums.errors_enum import ErrorTypeEnum
|
||||||
|
from backend.code.models.responses.session_token import SessionTokenResponse
|
||||||
|
|
||||||
|
|
||||||
|
from backend.code.modules.users.repository import UserRepository
|
||||||
|
|
||||||
|
class SessionsController():
|
||||||
|
def __init__(self):
|
||||||
|
database = DatabaseSqliteModule()
|
||||||
|
self.repo = SessionRepository(database)
|
||||||
|
self.token_service = TokenService()
|
||||||
|
self.mailing_service = MailingService()
|
||||||
|
|
||||||
|
def Login(self, user: str, password: str, domain: str = None):
|
||||||
|
account = self.repo.SelectByUsername(user)
|
||||||
|
if account is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Array to properties
|
||||||
|
account_id = account[0]
|
||||||
|
user_id = account[1]
|
||||||
|
_password = account[3]
|
||||||
|
attempts = account[4]
|
||||||
|
locked = account[5]
|
||||||
|
name = account[7]
|
||||||
|
surnames = account[8]
|
||||||
|
email = account[9]
|
||||||
|
|
||||||
|
# Validations
|
||||||
|
if locked:
|
||||||
|
return ErrorTypeEnum.USER_ACCOUNT_LOCKED
|
||||||
|
|
||||||
|
# Default user has no password
|
||||||
|
if _password is None:
|
||||||
|
return ErrorTypeEnum.USER_ACCOUNT_NO_PASSWORD
|
||||||
|
|
||||||
|
if password != _password:
|
||||||
|
attempts = attempts + 1
|
||||||
|
self._OnUpdateAttempts(account_id, attempts)
|
||||||
|
return ErrorTypeEnum.USER_ACCOUNT_WRONG_PASSWORD
|
||||||
|
|
||||||
|
# Reset when password is correct and registered some attempts
|
||||||
|
if attempts > 0:
|
||||||
|
self._OnUpdateAttempts(account_id, 0)
|
||||||
|
|
||||||
|
# Generate token
|
||||||
|
payload = {
|
||||||
|
'UserId': user_id,
|
||||||
|
'Name': name,
|
||||||
|
'Surnames': surnames,
|
||||||
|
'Email': email
|
||||||
|
}
|
||||||
|
token = self.token_service.GenerateToken(payload)
|
||||||
|
|
||||||
|
session = self.repo.RegisterSession(account_id, token)
|
||||||
|
return SessionTokenResponse(Token=token)
|
||||||
|
|
||||||
|
def ValidateToken(self, token: str):
|
||||||
|
exists = self.repo.SelectByToken(token)
|
||||||
|
if exists is None:
|
||||||
|
return ErrorTypeEnum.USER_SESSION_NOT_EXISTS
|
||||||
|
|
||||||
|
# Array to properties
|
||||||
|
account_id = exists[0]
|
||||||
|
user_id = exists[1]
|
||||||
|
|
||||||
|
# Get metadata token
|
||||||
|
payload = self.token_service.ValidateToken(token)
|
||||||
|
if payload == False:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check token integrity
|
||||||
|
if payload['UserId'] != user_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
autorefresh = self._OnCheckTokenAutoRefresh(payload['exp'])
|
||||||
|
if autorefresh:
|
||||||
|
token = self._OnRefreshToken(account_id, token, payload)
|
||||||
|
|
||||||
|
return token
|
||||||
|
|
||||||
|
def Logout(self, token: str):
|
||||||
|
exists = self.repo.SelectByToken(token)
|
||||||
|
if exists is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
disabled = self.repo.DisableToken(token)
|
||||||
|
if disabled is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
return disabled == 1
|
||||||
|
|
||||||
|
def RecoverAccount(self, username: str, domain: str = None):
|
||||||
|
account = self.repo.SelectByUsername(username)
|
||||||
|
if account is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Array to properties
|
||||||
|
AccountId = account[0]
|
||||||
|
Fullname = f'{account[7]} {account[8]}'
|
||||||
|
Email = account[9]
|
||||||
|
now = datetime.now()
|
||||||
|
|
||||||
|
# Generate token
|
||||||
|
payload = {'Account': AccountId}
|
||||||
|
token = self.token_service.GenerateToken(payload)
|
||||||
|
url = f'{os.getenv('DOMAIN_URL')}/restore/{token}'
|
||||||
|
|
||||||
|
registered = self.repo.InsertRecoverRequest(AccountId, token)
|
||||||
|
if registered is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Prepare notification
|
||||||
|
template = '''
|
||||||
|
Hola {},
|
||||||
|
|
||||||
|
Has pedido restaurar tu contraseña de {}. Accede al siguiente enlace para establecer una nueva contraseña:
|
||||||
|
{}
|
||||||
|
|
||||||
|
Si no has realizado esta petición de restauración de la cuenta, envia un correo a: {}
|
||||||
|
'''
|
||||||
|
message = template.format(Fullname, '[APP_NAME]', url, os.getenv('SUPPORT_EMAIL'))
|
||||||
|
|
||||||
|
# Send notification
|
||||||
|
self.mailing_service.SendMessage(
|
||||||
|
Email,
|
||||||
|
'Petición restaurar cuenta [APP_NAME]',
|
||||||
|
message
|
||||||
|
)
|
||||||
|
|
||||||
|
def ValidateTokenRecoverAccount(self, token: str):
|
||||||
|
validated = self.token_service.ValidateToken(token)
|
||||||
|
if validated is False:
|
||||||
|
return
|
||||||
|
|
||||||
|
exists = self.repo.SelectRecoverRequest(validated['Account'], token)
|
||||||
|
if exists is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
disabled = self.repo.DisableRecoverRequest(token)
|
||||||
|
if disabled == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
# region Utilities
|
||||||
|
def _OnUpdateAttempts(self, account_id: int, attempts: int):
|
||||||
|
locked = False
|
||||||
|
limit = int(os.getenv('USER_LIMIT_ATTEMPTS'))
|
||||||
|
|
||||||
|
if attempts > limit:
|
||||||
|
attempts = limit
|
||||||
|
locked = True
|
||||||
|
|
||||||
|
return self.repo.RefreshAttempts(account_id, attempts, locked)
|
||||||
|
|
||||||
|
def _OnCheckTokenAutoRefresh(self, expiration: int):
|
||||||
|
limit_minutes_left = 10
|
||||||
|
now = datetime.now()
|
||||||
|
expires_at = datetime.fromtimestamp(expiration)
|
||||||
|
difference = ((expires_at - now).total_seconds()) / 60 # seconds to minutes
|
||||||
|
return difference <= limit_minutes_left
|
||||||
|
|
||||||
|
def _OnRefreshToken(self, account_id: int, token: str, payload: dict):
|
||||||
|
old_disabled = self.repo.DisableToken(token)
|
||||||
|
if old_disabled is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
new_token = self.token_service.GenerateToken(payload)
|
||||||
|
|
||||||
|
new_registered = self.repo.RegisterSession(account_id, new_token)
|
||||||
|
if new_registered is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
return new_token
|
||||||
|
|
||||||
|
# endregion
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
from backend.code.modules.database.database import DatabaseModule
|
||||||
|
|
||||||
|
class SessionRepository():
|
||||||
|
def __init__(self, repo: DatabaseModule):
|
||||||
|
self.conn = repo.conn
|
||||||
|
self.cursor = repo.cursor
|
||||||
|
|
||||||
|
def SelectByUsername(self, username: str):
|
||||||
|
sql = '''
|
||||||
|
SELECT a.*, u.name, u.surnames, u.email
|
||||||
|
FROM user_accounts a
|
||||||
|
JOIN users u ON u.id = a.user_id
|
||||||
|
WHERE a.username = ?
|
||||||
|
AND a.active
|
||||||
|
'''
|
||||||
|
self.cursor.execute(sql, [username])
|
||||||
|
return self.cursor.fetchone()
|
||||||
|
|
||||||
|
def SelectByToken(self, token: str):
|
||||||
|
sql = '''
|
||||||
|
SELECT uas.account_id, u.id
|
||||||
|
FROM user_account_sessions uas
|
||||||
|
JOIN user_accounts ua ON uas.account_id = ua.id
|
||||||
|
JOIN users u ON ua.user_id = u.id
|
||||||
|
WHERE token = ?
|
||||||
|
AND uas.active
|
||||||
|
'''
|
||||||
|
self.cursor.execute(sql, [token])
|
||||||
|
return self.cursor.fetchone()
|
||||||
|
|
||||||
|
def RefreshAttempts(self, account_id: int, attempts: int, locked: bool):
|
||||||
|
sql = '''
|
||||||
|
UPDATE user_accounts
|
||||||
|
SET attempts = ?, locked = ?
|
||||||
|
WHERE id = ?
|
||||||
|
'''
|
||||||
|
self.cursor.execute(sql, [attempts, locked, account_id])
|
||||||
|
self.conn.commit()
|
||||||
|
return self.cursor.rowcount
|
||||||
|
|
||||||
|
def RegisterSession(self, accountId: int, token: str):
|
||||||
|
sql = '''
|
||||||
|
INSERT INTO user_account_sessions(account_id, token)
|
||||||
|
VALUES (?, ?)
|
||||||
|
'''
|
||||||
|
self.cursor.execute(sql, [accountId, token])
|
||||||
|
self.conn.commit()
|
||||||
|
return self.cursor.rowcount
|
||||||
|
|
||||||
|
def DisableToken(self, token: str):
|
||||||
|
sql = '''
|
||||||
|
UPDATE user_account_sessions
|
||||||
|
SET active = false
|
||||||
|
WHERE token = ?
|
||||||
|
'''
|
||||||
|
self.cursor.execute(sql, [token])
|
||||||
|
self.conn.commit()
|
||||||
|
return self.cursor.rowcount
|
||||||
|
|
||||||
|
def InsertRecoverRequest(self, accountId: int, token: str):
|
||||||
|
sql = '''
|
||||||
|
INSERT INTO user_account_recover_requests(account_id, token)
|
||||||
|
VALUES(?, ?)
|
||||||
|
'''
|
||||||
|
self.cursor.execute(sql, [accountId, token])
|
||||||
|
self.conn.commit()
|
||||||
|
return self.cursor.rowcount
|
||||||
|
|
||||||
|
def SelectRecoverRequest(self, accountId: int, token: str):
|
||||||
|
sql = '''
|
||||||
|
SELECT *
|
||||||
|
FROM user_account_recover_requests
|
||||||
|
WHERE account_id = ?
|
||||||
|
AND token = ?
|
||||||
|
AND active
|
||||||
|
'''
|
||||||
|
self.cursor.execute(sql, [accountId, token])
|
||||||
|
return self.cursor.fetchone()
|
||||||
|
|
||||||
|
def DisableRecoverRequest(self, token: str):
|
||||||
|
sql = '''
|
||||||
|
UPDATE user_account_recover_requests
|
||||||
|
SET active = false
|
||||||
|
WHERE token = ?
|
||||||
|
AND active
|
||||||
|
'''
|
||||||
|
self.cursor.execute(sql, [token])
|
||||||
|
self.conn.commit()
|
||||||
|
self.cursor.rowcount
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
from fastapi import APIRouter, status, Depends, HTTPException, Header
|
||||||
|
from backend.code.middlewares.private_middleware import PrivateMiddleware
|
||||||
|
from backend.code.models.requests.account_login import AccountLoginRequest
|
||||||
|
from backend.code.models.requests.account_recover import AccountRecoverRequest
|
||||||
|
from backend.code.models.responses.session_token import SessionTokenResponse
|
||||||
|
from backend.code.modules.sessions.controller import SessionsController
|
||||||
|
|
||||||
|
routes = APIRouter(prefix="/account", tags=['Session'])
|
||||||
|
middleware = PrivateMiddleware()
|
||||||
|
controller = SessionsController()
|
||||||
|
|
||||||
|
@routes.post('/login', status_code = status.HTTP_200_OK, response_model = SessionTokenResponse)
|
||||||
|
def Login(data: AccountLoginRequest):
|
||||||
|
session = controller.Login(data.Username, data.Password, data.Domain)
|
||||||
|
if type(session) is not SessionTokenResponse:
|
||||||
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
return session
|
||||||
|
|
||||||
|
@routes.post('/token/validate', status_code = status.HTTP_200_OK, response_model = SessionTokenResponse)
|
||||||
|
def ValidateToken(token: str = Depends(middleware.HasToken)):
|
||||||
|
validation = controller.ValidateToken(token)
|
||||||
|
if type(validation) is not SessionTokenResponse:
|
||||||
|
raise HTTPException(status_code = status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
return validation
|
||||||
|
|
||||||
|
@routes.post('/logout', status_code = status.HTTP_204_NO_CONTENT)
|
||||||
|
def Logout(token: str = Depends(middleware.HasToken)):
|
||||||
|
closed = controller.Logout(token)
|
||||||
|
if closed is None:
|
||||||
|
raise HTTPException(status_code = status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
@routes.post('/recover', status_code = status.HTTP_200_OK)
|
||||||
|
def RecoverAccount(data: AccountRecoverRequest):
|
||||||
|
recovered = controller.RecoverAccount(data.Username, data.Domain)
|
||||||
|
if recovered is None:
|
||||||
|
raise HTTPException(status_code = status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
|
||||||
|
@routes.post("/recover/validate/{token}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
def ValidateTokenRecoverAccount(token: str):
|
||||||
|
account = controller.ValidateTokenRecoverAccount(token)
|
||||||
|
if account is None:
|
||||||
|
raise HTTPException(status_code = status.HTTP_404_NOT_FOUND)
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
from backend.code.enums.errors_enum import ErrorTypeEnum
|
||||||
|
from backend.code.core.controller import Controller
|
||||||
|
from backend.code.enums.errors_enum import ErrorTypeEnum
|
||||||
|
from backend.code.modules.database.database_sqlite import DatabaseSqliteModule
|
||||||
|
from backend.code.modules.settlements.repository import SettlementRepository
|
||||||
|
from backend.code.models.responses.settlement_detail import SettlementDetailResponse
|
||||||
|
|
||||||
|
class SettlementController(Controller):
|
||||||
|
def __init__(self):
|
||||||
|
database = DatabaseSqliteModule()
|
||||||
|
self.repo = SettlementRepository(database)
|
||||||
|
|
||||||
|
def ListSettlements(self, params: str = None):
|
||||||
|
result = []
|
||||||
|
|
||||||
|
if params is not None:
|
||||||
|
params = self._OnProcessParams(params)
|
||||||
|
|
||||||
|
settlements = self.repo.FilterSettlements(params)
|
||||||
|
if len(settlements) == 0:
|
||||||
|
return result
|
||||||
|
|
||||||
|
for _settlement in settlements:
|
||||||
|
settlement = SettlementDetailResponse(
|
||||||
|
Id = _settlement[0],
|
||||||
|
Name = _settlement[1],
|
||||||
|
Address = _settlement[2],
|
||||||
|
LastDone = _settlement[4],
|
||||||
|
LastDoneBy = _settlement[5],
|
||||||
|
Active = _settlement[6]
|
||||||
|
)
|
||||||
|
result.append(settlement)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def SelectSettlement(self, id: int):
|
||||||
|
settlement = self.repo.SelectSettlementById(id)
|
||||||
|
if settlement is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
return SettlementDetailResponse(
|
||||||
|
Id = settlement[0],
|
||||||
|
Name = settlement[1],
|
||||||
|
Address = settlement[2],
|
||||||
|
LastDone = settlement[4],
|
||||||
|
LastDoneBy = settlement[5],
|
||||||
|
Active = settlement[6]
|
||||||
|
)
|
||||||
|
|
||||||
|
def RegisterSettlement(self, name: str, address: str, active: bool = True):
|
||||||
|
exists = self.repo.FilterSettlements({'name': name, 'address': address})
|
||||||
|
if len(exists) > 0:
|
||||||
|
return ErrorTypeEnum.SETTLEMENT_EXISTS
|
||||||
|
|
||||||
|
settlement_id = self.repo.InsertSettlement(name, address, active)
|
||||||
|
if settlement_id is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
return SettlementDetailResponse(
|
||||||
|
Id = settlement_id,
|
||||||
|
Name = name,
|
||||||
|
Address = address,
|
||||||
|
LastDone = None,
|
||||||
|
LastDoneBy = None,
|
||||||
|
Active = active
|
||||||
|
)
|
||||||
|
|
||||||
|
def EditSettlement(self, id: int, name: str, address: str, active: bool = True):
|
||||||
|
exists = self.repo.SelectSettlementById(id)
|
||||||
|
if exists is None:
|
||||||
|
return ErrorTypeEnum.SETTLEMENT_ID_NOT_EXISTS
|
||||||
|
|
||||||
|
edited = self.repo.UpdateSettlement(id, name, address, active)
|
||||||
|
if edited == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def RegisterSettlementDone(self, id: int, doneAt: datetime, doneBy: int):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def DeleteSettlement(self, id: int):
|
||||||
|
exists = self.repo.SelectSettlementById(id)
|
||||||
|
if exists is None:
|
||||||
|
return ErrorTypeEnum.SETTLEMENT_ID_NOT_EXISTS
|
||||||
|
|
||||||
|
deleted = self.repo.DeleteSettlement(id)
|
||||||
|
if deleted == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
return True
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
from backend.code.modules.database.database import DatabaseModule
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class SettlementRepository():
|
||||||
|
def __init__(self, repo: DatabaseModule):
|
||||||
|
self.conn = repo.conn
|
||||||
|
self.cursor = repo.cursor
|
||||||
|
|
||||||
|
def FilterSettlements(self, filters: dict = None):
|
||||||
|
params = list()
|
||||||
|
|
||||||
|
sql = '''
|
||||||
|
SELECT *
|
||||||
|
FROM settlements
|
||||||
|
WHERE 1=1
|
||||||
|
'''
|
||||||
|
|
||||||
|
if filters is not None:
|
||||||
|
if 'name' in filters:
|
||||||
|
sql = sql + ' AND name LIKE ?'
|
||||||
|
params.append(filters['name'])
|
||||||
|
|
||||||
|
if 'address' in filters:
|
||||||
|
sql = sql + ' AND address LIKE ?'
|
||||||
|
params.append(filters['address'])
|
||||||
|
|
||||||
|
if 'last_done_by' in filters:
|
||||||
|
sql = sql + ' AND last_done_by LIKE ?'
|
||||||
|
params.append(filters['last_done_by'])
|
||||||
|
|
||||||
|
if 'last_done' in filters:
|
||||||
|
sql = sql + ' AND last_done = ?'
|
||||||
|
params.append(filters['last_done'])
|
||||||
|
|
||||||
|
# print(sql)
|
||||||
|
|
||||||
|
self.cursor.execute(sql, params)
|
||||||
|
return self.cursor.fetchall()
|
||||||
|
|
||||||
|
def SelectSettlementById(self, id: int):
|
||||||
|
sql = '''
|
||||||
|
SELECT *
|
||||||
|
FROM settlements
|
||||||
|
WHERE id = ?
|
||||||
|
AND active
|
||||||
|
'''
|
||||||
|
self.cursor.execute(sql, [id])
|
||||||
|
return self.cursor.fetchone()
|
||||||
|
|
||||||
|
def InsertSettlement(self, name: str, address: str, active: bool = True):
|
||||||
|
sql = '''
|
||||||
|
INSERT INTO settlements(name, address, active)
|
||||||
|
VALUES(?, ?, ?)
|
||||||
|
'''
|
||||||
|
self.cursor.execute(sql, [name, address, active])
|
||||||
|
self.conn.commit()
|
||||||
|
return self.cursor.lastrowid
|
||||||
|
|
||||||
|
def UpdateSettlement(self, id: int, name: str, address: str, active: bool = True):
|
||||||
|
sql = '''
|
||||||
|
UPDATE settlements
|
||||||
|
set name = ?, address = ?, active = ?
|
||||||
|
WHERE id = ?
|
||||||
|
'''
|
||||||
|
self.cursor.execute(sql, [name, address, active, id])
|
||||||
|
self.conn.commit()
|
||||||
|
return self.cursor.rowcount
|
||||||
|
|
||||||
|
def RegisterSettlementDone(self, id: int, user_done: int, done_at: datetime = datetime.now()):
|
||||||
|
sql = '''
|
||||||
|
UPDATE settlements
|
||||||
|
SET last_done_by = ?, last_done = ?
|
||||||
|
WHERE id = ?
|
||||||
|
'''
|
||||||
|
self.cursor.execute(sql, [done_at, user_done, id])
|
||||||
|
self.conn.commit()
|
||||||
|
return self.cursor.rowcount
|
||||||
|
|
||||||
|
def DeleteSettlement(self, id: int):
|
||||||
|
sql = '''
|
||||||
|
UPDATE settlements
|
||||||
|
SET active = false
|
||||||
|
WHERE id = ?
|
||||||
|
'''
|
||||||
|
self.cursor.execute(sql, [id])
|
||||||
|
self.conn.commit()
|
||||||
|
return self.cursor.rowcount
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
from fastapi import APIRouter, Query, status, Depends, Response, HTTPException
|
||||||
|
|
||||||
|
from backend.code.modules.settlements.controller import SettlementController
|
||||||
|
from backend.code.models.responses.settlement_detail import SettlementDetailResponse
|
||||||
|
from backend.code.models.requests.settlement_register import SettlementRegisterRequest
|
||||||
|
from backend.code.models.requests.settlement_edit import SettlementEditRequest
|
||||||
|
from backend.code.models.requests.settlement_register_done import SettlementRegisterDoneRequest
|
||||||
|
|
||||||
|
routes = APIRouter(prefix="/settlements", tags=['Settlement'])
|
||||||
|
controller = SettlementController()
|
||||||
|
|
||||||
|
@routes.get("", status_code = status.HTTP_200_OK, response_model = list[SettlementDetailResponse])
|
||||||
|
def ListSettlements(params: str | None = Query(default = None, alias = 'query')):
|
||||||
|
settlements = controller.ListSettlements(params)
|
||||||
|
if settlements is None:
|
||||||
|
raise HTTPException(status_code = status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
if len(settlements) == 0:
|
||||||
|
return Response(status_code = status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
return settlements
|
||||||
|
|
||||||
|
@routes.get("/{id}", status_code = status.HTTP_200_OK, response_model = SettlementDetailResponse)
|
||||||
|
def GetSettlement(id: int):
|
||||||
|
settlement = controller.SelectSettlement(id)
|
||||||
|
if settlement is None:
|
||||||
|
raise HTTPException(status_code = status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
return settlement
|
||||||
|
|
||||||
|
@routes.post('', status_code = status.HTTP_201_CREATED, response_model = SettlementDetailResponse)
|
||||||
|
def RegisterSettlement(data: SettlementRegisterRequest):
|
||||||
|
created = controller.RegisterSettlement(data.Name, data.Address, data.Active)
|
||||||
|
if created is None:
|
||||||
|
raise HTTPException(status_code = status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
if created == -9:
|
||||||
|
raise HTTPException(status_code = status.HTTP_409_CONFLICT, detail = 'Settlement name or address exists')
|
||||||
|
|
||||||
|
return created
|
||||||
|
|
||||||
|
@routes.put("/{id}", status_code = status.HTTP_204_NO_CONTENT)
|
||||||
|
def EditSettlement(id: str, data: SettlementEditRequest):
|
||||||
|
edited = controller.EditSettlement(id, data.Name, data.Address, data.Active)
|
||||||
|
if edited is None:
|
||||||
|
raise HTTPException(status_code = status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
if deleted == -10:
|
||||||
|
raise HTTPException(status_code = status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
@routes.post('/done', status_code = status.HTTP_200_OK)
|
||||||
|
def RegisterSettlementDone(data: SettlementRegisterDoneRequest):
|
||||||
|
registered = controller.
|
||||||
|
|
||||||
|
@routes.delete("/{id}", status_code = status.HTTP_204_NO_CONTENT)
|
||||||
|
def DisableSettlement(id: str):
|
||||||
|
deleted = controller.DeleteSettlement(id)
|
||||||
|
if deleted is None:
|
||||||
|
raise HTTPException(status_code = status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
if deleted == -10:
|
||||||
|
raise HTTPException(status_code = status.HTTP_404_NOT_FOUND)
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
from backend.code.enums.errors_enum import ErrorTypeEnum
|
||||||
|
from backend.code.modules.database.database_sqlite import DatabaseSqliteModule
|
||||||
|
from backend.code.modules.users.repository import UserRepository
|
||||||
|
from backend.code.modules.accounts.repository import AccountRepository
|
||||||
|
from backend.code.models.responses.user_detail import UserDetailResponse
|
||||||
|
|
||||||
|
class UserController():
|
||||||
|
def __init__(self):
|
||||||
|
database = DatabaseSqliteModule()
|
||||||
|
self.repo = UserRepository(database)
|
||||||
|
self.account_repo = AccountRepository(database)
|
||||||
|
|
||||||
|
def ListUsers(self, params: str = None):
|
||||||
|
result = []
|
||||||
|
|
||||||
|
# Process filters
|
||||||
|
if params is not None:
|
||||||
|
params = self._OnProcessParams(params)
|
||||||
|
|
||||||
|
users = self.repo.FilterUsers(params)
|
||||||
|
if len(users) == 0:
|
||||||
|
return result
|
||||||
|
|
||||||
|
for _user in users:
|
||||||
|
user = UserDetailResponse(
|
||||||
|
Id = _user[0],
|
||||||
|
Name = _user[1],
|
||||||
|
Surnames = _user[2],
|
||||||
|
Email = _user[3],
|
||||||
|
Username = _user[4],
|
||||||
|
Locked = _user[5],
|
||||||
|
Active = _user[6]
|
||||||
|
)
|
||||||
|
result.append(user)
|
||||||
|
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def SelectUser(self, id: int):
|
||||||
|
user = self.repo.SelectUserById(id)
|
||||||
|
if user is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
return UserDetailResponse(
|
||||||
|
Id = user[0],
|
||||||
|
Name = user[1],
|
||||||
|
Surnames = user[2],
|
||||||
|
Email = user[3],
|
||||||
|
Username = user[4],
|
||||||
|
Locked = user[5],
|
||||||
|
Active = user[6]
|
||||||
|
)
|
||||||
|
|
||||||
|
def RegisterUser(self, name: str, surnames: str, email: str, username: str = None, locked: bool = False, active: bool = True):
|
||||||
|
create_account = (username is not None)
|
||||||
|
|
||||||
|
# Check not exists
|
||||||
|
user_exists = self.repo.FilterUsers({'email': email})
|
||||||
|
if len(user_exists) > 0:
|
||||||
|
return ErrorTypeEnum.USER_EMAIL_EXISTS
|
||||||
|
|
||||||
|
if create_account:
|
||||||
|
account_exists = self.account_repo.SelectByUsername(username)
|
||||||
|
if account_exists is not None:
|
||||||
|
return ErrorTypeEnum.USER_ACCOUNT_EXISTS
|
||||||
|
|
||||||
|
# Register
|
||||||
|
user_id = self.repo.InsertUser(name, surnames, email)
|
||||||
|
if user_id is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if create_account:
|
||||||
|
account_id = self.account_repo.InsertAccount(user_id, username, locked, active)
|
||||||
|
if account_id is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
return UserDetailResponse(
|
||||||
|
Id = user_id,
|
||||||
|
Name = name,
|
||||||
|
Surnames = surnames,
|
||||||
|
Email = email,
|
||||||
|
Username = username,
|
||||||
|
Locked = locked,
|
||||||
|
Active = active
|
||||||
|
)
|
||||||
|
|
||||||
|
def EditUser(self, id: int, name: str, surnames: str, email: str):
|
||||||
|
exists = self.repo.SelectUserById(id)
|
||||||
|
if exists is None:
|
||||||
|
return ErrorTypeEnum.USER_ID_NOT_EXISTS
|
||||||
|
|
||||||
|
edited = self.repo.UpdateUser(id, name, surnames, email)
|
||||||
|
if edited == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def DeleteUser(self, id: int):
|
||||||
|
exists = self.repo.SelectUserById(id)
|
||||||
|
if exists is None:
|
||||||
|
return ErrorTypeEnum.USER_ID_NOT_EXISTS
|
||||||
|
|
||||||
|
deleted = self.repo.DeleteUser(id)
|
||||||
|
if deleted == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
#region Utilities
|
||||||
|
def _OnProcessParams(self, params: str):
|
||||||
|
result = {}
|
||||||
|
params = params.strip()
|
||||||
|
params = params.split(',')
|
||||||
|
for param in params:
|
||||||
|
field,value=param.split('=')
|
||||||
|
result[field.lower()]=value
|
||||||
|
return result
|
||||||
|
#endregion
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
from backend.code.modules.database.database import DatabaseModule
|
||||||
|
|
||||||
|
class UserRepository():
|
||||||
|
def __init__(self, repo: DatabaseModule):
|
||||||
|
self.conn = repo.conn
|
||||||
|
self.cursor = repo.cursor
|
||||||
|
|
||||||
|
def FilterUsers(self, filters: dict = None):
|
||||||
|
params = list()
|
||||||
|
|
||||||
|
sql = '''
|
||||||
|
SELECT u.*, a.username, a.locked, a.active
|
||||||
|
FROM users u
|
||||||
|
LEFT JOIN user_accounts a ON a.user_id = u.id
|
||||||
|
WHERE 1=1
|
||||||
|
'''
|
||||||
|
|
||||||
|
if filters is not None:
|
||||||
|
if 'name' in filters:
|
||||||
|
sql = sql + " AND u.name LIKE ?"
|
||||||
|
params.append(filters['name'])
|
||||||
|
|
||||||
|
if 'surnames' in filters:
|
||||||
|
sql = sql + " AND u.surnames LIKE ?"
|
||||||
|
params.append(filters['surnames'])
|
||||||
|
|
||||||
|
if 'email' in filters:
|
||||||
|
sql = sql + " AND u.email LIKE ?"
|
||||||
|
params.append(filters['email'])
|
||||||
|
|
||||||
|
if 'username' in filters:
|
||||||
|
sql = sql + " AND a.username LIKE ?"
|
||||||
|
params.append(filters['username'])
|
||||||
|
|
||||||
|
# print(sql)
|
||||||
|
|
||||||
|
self.cursor.execute(sql, params)
|
||||||
|
return self.cursor.fetchall()
|
||||||
|
|
||||||
|
def SelectUserById(self, id: int):
|
||||||
|
sql = '''
|
||||||
|
SELECT u.*, a.username, a.locked, a.active
|
||||||
|
FROM users u
|
||||||
|
LEFT JOIN user_accounts a ON a.user_id = u.id
|
||||||
|
WHERE u.id = ?
|
||||||
|
'''
|
||||||
|
self.cursor.execute(sql, [id])
|
||||||
|
return self.cursor.fetchone()
|
||||||
|
|
||||||
|
def InsertUser(self, name: str, surnames: str, email: str):
|
||||||
|
sql = '''
|
||||||
|
INSERT INTO users(name, surnames, email)
|
||||||
|
VALUES(?, ?, ?)
|
||||||
|
'''
|
||||||
|
self.cursor.execute(sql, [name, surnames, email])
|
||||||
|
self.conn.commit()
|
||||||
|
return self.cursor.lastrowid
|
||||||
|
|
||||||
|
def UpdateUser(self, id: int, name: str, surnames: str, email: str):
|
||||||
|
sql = '''
|
||||||
|
UPDATE users
|
||||||
|
SET name = ?, surnames = ?, email = ?
|
||||||
|
WHERE id = ?
|
||||||
|
'''
|
||||||
|
self.cursor.execute(sql, [name, surnames, email, id])
|
||||||
|
self.conn.commit()
|
||||||
|
return self.cursor.rowcount
|
||||||
|
|
||||||
|
def DeleteUser(self, id: int):
|
||||||
|
sql = '''
|
||||||
|
DELETE FROM users
|
||||||
|
WHERE id = ?
|
||||||
|
'''
|
||||||
|
self.cursor.execute(sql, [id])
|
||||||
|
self.conn.commit()
|
||||||
|
return self.cursor.rowcount
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
from fastapi import Query, APIRouter, status, Depends, Response, HTTPException
|
||||||
|
from backend.code.modules.users.controller import UserController
|
||||||
|
|
||||||
|
from backend.code.models.requests.user_register import UserRegisterRequest
|
||||||
|
from backend.code.models.requests.user_edit import UserEditRequest
|
||||||
|
from backend.code.models.responses.user_detail import UserDetailResponse
|
||||||
|
from backend.code.models.responses.user_created import UserCreatedResponse
|
||||||
|
|
||||||
|
routes = APIRouter(prefix="/users", tags=["User"])
|
||||||
|
controller = UserController()
|
||||||
|
|
||||||
|
@routes.get('', status_code = status.HTTP_200_OK, response_model = list[UserDetailResponse])
|
||||||
|
def ListUsers(params: str | None = Query(default = None, alias = "query")):
|
||||||
|
users = controller.ListUsers(params)
|
||||||
|
if users is None:
|
||||||
|
raise HTTPException(status_code = status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
if len(users) == 0:
|
||||||
|
return Response(status_code = status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
return users
|
||||||
|
|
||||||
|
@routes.get('/{id}', status_code = status.HTTP_200_OK, response_model = UserDetailResponse)
|
||||||
|
def GetUser(id: int):
|
||||||
|
user = controller.SelectUser(id)
|
||||||
|
if user is None:
|
||||||
|
raise HTTPException(status_code = status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
@routes.post('', status_code = status.HTTP_201_CREATED, response_model = UserCreatedResponse)
|
||||||
|
def RegisterUser(data: UserRegisterRequest):
|
||||||
|
created = controller.RegisterUser(data.Name, data.Surnames, data.Email, data.Username, data.Locked, data.Active)
|
||||||
|
if created is None:
|
||||||
|
raise HTTPException(status_code = status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
if created == -1:
|
||||||
|
raise HTTPException(status_code = status.HTTP_409_CONFLICT, detail = 'Email exists')
|
||||||
|
elif created == -2:
|
||||||
|
raise HTTPException(status_code = status.HTTP_409_CONFLICT, detail = 'Username exists')
|
||||||
|
|
||||||
|
return created
|
||||||
|
|
||||||
|
@routes.put('/{id}', status_code = status.HTTP_204_NO_CONTENT)
|
||||||
|
def EditUser(id: int, data: UserEditRequest):
|
||||||
|
edited = controller.EditUser(id, data.Name, data.Surnames, data.Email)
|
||||||
|
if edited is None:
|
||||||
|
raise HTTPException(status_code = status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
if deleted == -3:
|
||||||
|
raise HTTPException(status_code = status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
@routes.delete('/{id}', status_code = status.HTTP_204_NO_CONTENT)
|
||||||
|
def DeleteUser(id: int):
|
||||||
|
deleted = controller.DeleteUser(id)
|
||||||
|
if deleted is None:
|
||||||
|
raise HTTPException(status_code = status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
if deleted == -3:
|
||||||
|
raise HTTPException(status_code = status.HTTP_404_NOT_FOUND)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
class EnvironmentService:
|
||||||
|
def __init__(self):
|
||||||
|
self.LINE_COMMENTED = '#'
|
||||||
|
self.VALUE_SEPARATOR = '='
|
||||||
|
|
||||||
|
def LoadEnvFile(self, filepath:str):
|
||||||
|
with open(filepath, 'r') as file:
|
||||||
|
for line in file:
|
||||||
|
line = line.strip()
|
||||||
|
if line and not line.startswith(self.LINE_COMMENTED) and self.VALUE_SEPARATOR in line:
|
||||||
|
key,value = line.split(self.VALUE_SEPARATOR, 1)
|
||||||
|
self.Set(key, value) # os.environ[key] = value
|
||||||
|
|
||||||
|
def Get(self, key:str):
|
||||||
|
return os.getenv(key)
|
||||||
|
|
||||||
|
def Set(self, key: str, value):
|
||||||
|
os.environ[key] = value
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import os
|
||||||
|
import smtplib
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
|
||||||
|
# https://realpython.com/python-send-email/
|
||||||
|
class MailingService:
|
||||||
|
def __init__(self):
|
||||||
|
self.server = os.getenv('MAILING_SMTP_SERVER')
|
||||||
|
self.port = os.getenv('MAILING_SMTP_PORT')
|
||||||
|
self.user = os.getenv('MAILING_SMTP_USER')
|
||||||
|
self.password = os.getenv('MAILING_SMTP_PASSWORD')
|
||||||
|
|
||||||
|
def SendMessage(self, address: str, subject: str, content: str):
|
||||||
|
try:
|
||||||
|
message = MIMEMultipart()
|
||||||
|
message['From'] = self.user
|
||||||
|
message['To'] = address
|
||||||
|
message['Subject'] = subject
|
||||||
|
message.attach(MIMEText(content, 'plain'))
|
||||||
|
|
||||||
|
with smtplib.SMTP_SSL(self.server, self.port) as server:
|
||||||
|
server.login(self.user, self.password)
|
||||||
|
server.send_message(message)
|
||||||
|
server.quit()
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import os
|
||||||
|
import jwt
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
class TokenService:
|
||||||
|
def __init__(self):
|
||||||
|
self.SECRET = os.getenv('TOKEN_SECRET')
|
||||||
|
self.ALGORITHM = os.getenv('TOKEN_ALGORITHM')
|
||||||
|
self.LIVE_HOUR_MINUTES = os.getenv('TOKEN_LIVE')
|
||||||
|
|
||||||
|
def GenerateToken(self, payload):
|
||||||
|
payload['exp'] = datetime.now() + timedelta(minutes = int(self.LIVE_HOUR_MINUTES))
|
||||||
|
return jwt.encode(payload, self.SECRET, self.ALGORITHM)
|
||||||
|
|
||||||
|
def ValidateToken(self, token: str):
|
||||||
|
try:
|
||||||
|
if os.getenv('ENVIRONMENT') == 'Development' and token == "1":
|
||||||
|
return True
|
||||||
|
|
||||||
|
return jwt.decode(token, self.SECRET, algorithms = self.ALGORITHM)
|
||||||
|
except jwt.InvalidTokenError as e:
|
||||||
|
return False
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
agent-detector==2.0.0
|
||||||
|
annotated-doc==0.0.5
|
||||||
|
annotated-types==0.8.0
|
||||||
|
anyio==4.15.1
|
||||||
|
certifi==2026.10.23
|
||||||
|
charset-normalizer==3.5.2
|
||||||
|
click==8.5.0
|
||||||
|
colorama==0.4.6
|
||||||
|
detect-installer==0.2.1
|
||||||
|
dnspython==2.9.0
|
||||||
|
email-validator==2.3.0
|
||||||
|
fastapi==0.162.0
|
||||||
|
fastapi-cli==0.0.32
|
||||||
|
fastapi-cloud-cli==0.26.0
|
||||||
|
fastar==0.12.0
|
||||||
|
googleapis-common-protos==1.76.0
|
||||||
|
h11==0.16.0
|
||||||
|
httpcore==1.0.9
|
||||||
|
httpcore2==2.16.0
|
||||||
|
httptools==0.9.0
|
||||||
|
httpx==0.28.1
|
||||||
|
httpx2==2.16.0
|
||||||
|
idna==3.20
|
||||||
|
Jinja2==3.1.6
|
||||||
|
markdown-it-py==4.2.0
|
||||||
|
MarkupSafe==3.0.4
|
||||||
|
mdurl==0.1.2
|
||||||
|
opentelemetry-api==1.45.1
|
||||||
|
opentelemetry-exporter-http-transport==0.66b1
|
||||||
|
opentelemetry-exporter-otlp-common==0.66b1
|
||||||
|
opentelemetry-exporter-otlp-proto-common==1.45.1
|
||||||
|
opentelemetry-exporter-otlp-proto-http==1.45.1
|
||||||
|
opentelemetry-proto==1.45.1
|
||||||
|
opentelemetry-sdk==1.45.1
|
||||||
|
opentelemetry-semantic-conventions==0.66b1
|
||||||
|
protobuf==7.36.2
|
||||||
|
pydantic==2.14.1
|
||||||
|
pydantic-extra-types==2.11.1
|
||||||
|
pydantic-settings==2.16.1
|
||||||
|
pydantic_core==2.50.1
|
||||||
|
Pygments==2.21.0
|
||||||
|
PyJWT==2.15.1
|
||||||
|
pysqlite3==0.6.1
|
||||||
|
python-dotenv==1.2.4
|
||||||
|
python-multipart==0.0.32
|
||||||
|
PyYAML==6.0.3
|
||||||
|
requests==2.34.2
|
||||||
|
rich==15.0.0
|
||||||
|
rich-toolkit==0.20.6
|
||||||
|
rignore==0.8.1
|
||||||
|
sentry-sdk==2.73.0
|
||||||
|
shellingham==1.5.4
|
||||||
|
starlette==1.10.0
|
||||||
|
truststore==0.10.5
|
||||||
|
typer==0.27.3
|
||||||
|
typing-inspection==0.4.4
|
||||||
|
typing_extensions==4.16.0
|
||||||
|
urllib3==2.8.0
|
||||||
|
uvicorn==0.54.0
|
||||||
|
uvloop==0.23.0
|
||||||
|
watchfiles==1.3.0
|
||||||
|
websockets==17.3
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
-- drop database if exists settlementsint
|
||||||
|
-- create database settlements;
|
||||||
|
|
||||||
|
create table users (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
name varchar,
|
||||||
|
surnames varchar,
|
||||||
|
email varchar
|
||||||
|
);
|
||||||
|
|
||||||
|
create table user_accounts (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
user_id bigint references users (id) unique,
|
||||||
|
username varchar unique not null,
|
||||||
|
password varchar default null,
|
||||||
|
attempts smallint default 0,
|
||||||
|
locked bool default false,
|
||||||
|
active bool default true
|
||||||
|
);
|
||||||
|
|
||||||
|
create table permissions (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
code varchar unique not null,
|
||||||
|
description varchar default null
|
||||||
|
);
|
||||||
|
|
||||||
|
create table groups (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
code varchar unique not null,
|
||||||
|
description varchar default null
|
||||||
|
);
|
||||||
|
|
||||||
|
create table user_account_groups (
|
||||||
|
account_id bigint references user_accounts (id),
|
||||||
|
group_id bigint references groups (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table user_account_permissions (
|
||||||
|
account_id bigint references user_accounts (id),
|
||||||
|
permission_id bigint references permissions (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table group_permissions (
|
||||||
|
group_id bigint references groups (id),
|
||||||
|
permission_id bigint references permissions (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table user_account_sessions (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
account_id bigint not null references user_accounts (id),
|
||||||
|
token varchar not null unique,
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
active bool default true
|
||||||
|
);
|
||||||
|
|
||||||
|
create table user_account_recover_requests (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
account_id bigint not null references user_accounts (id),
|
||||||
|
token varchar not null unique,
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
active bool default true
|
||||||
|
);
|
||||||
|
|
||||||
|
create table settlements (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
name varchar not null unique,
|
||||||
|
address varchar not null unique,
|
||||||
|
created_at timestamp not null default current_timestamp,
|
||||||
|
last_done timestamp default null,
|
||||||
|
last_done_by bigint references users (id),
|
||||||
|
active bool default true
|
||||||
|
);
|
||||||
|
|
||||||
|
create table animal_coats (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
code varchar unique not null,
|
||||||
|
description varchar default null
|
||||||
|
);
|
||||||
|
|
||||||
|
create table animals (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
name varchar not null,
|
||||||
|
microchip varchar default null,
|
||||||
|
coat bigint not null references animal_coats (id),
|
||||||
|
sex int not null check(sex in ('female', 'male', 'unknown')) default 'unknown',
|
||||||
|
settlement_id bigint references settlements (id),
|
||||||
|
has_ear_mark bool default false,
|
||||||
|
controlled bool default false,
|
||||||
|
castrated bool default false,
|
||||||
|
adopted bool default false,
|
||||||
|
friendly bool default null,
|
||||||
|
friendly_children bool default null,
|
||||||
|
friendly_dogs bool default null,
|
||||||
|
first_view timestamp default null,
|
||||||
|
last_view timestamp default null,
|
||||||
|
created_at timestamp not null default current_timestamp,
|
||||||
|
active bool default true
|
||||||
|
);
|
||||||
|
|
||||||
|
create table animal_comments (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
animal_id bigint not null references animals (id),
|
||||||
|
_type int not null check(_type in ('info', 'alert')) default 'info',
|
||||||
|
message varchar not null,
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
public bool default false,
|
||||||
|
active bool default true
|
||||||
|
);
|
||||||
|
|
||||||
|
create table animal_alerts (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
animal_id bigint not null references animals (id),
|
||||||
|
_type int not null check(_type in ('info', 'low', 'medium', 'high', 'ultra')) default 'high',
|
||||||
|
comment varchar not null,
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
active bool default true
|
||||||
|
);
|
||||||
|
|
||||||
|
create table animal_images (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
animal_id bigint not null references animals (id),
|
||||||
|
image varchar not null unique,
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
is_primary bool default false,
|
||||||
|
public bool default false,
|
||||||
|
active bool default true
|
||||||
|
);
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
insert into
|
||||||
|
animal_coats (code)
|
||||||
|
values
|
||||||
|
('NEGRO'),
|
||||||
|
('NARANJA'),
|
||||||
|
('BLANCO'),
|
||||||
|
('CHOCOLATE');
|
||||||
|
|
||||||
|
insert into
|
||||||
|
animal_coats (code, description)
|
||||||
|
values
|
||||||
|
('CAREY', 'Mezcla irregular de negro y rojo'),
|
||||||
|
('AZUL', 'Gris diluido'),
|
||||||
|
('CANELA', 'Marrón claro con toque dorado'),
|
||||||
|
('LILA', 'Crema con tono lavanda'),
|
||||||
|
('CREMA', 'Naranja pálido'),
|
||||||
|
('CALICO', 'Combina blanco, negro y naranja'),
|
||||||
|
('ATIGRADO', 'Con rayas o espirales definidas'),
|
||||||
|
(
|
||||||
|
'COLORPOINT',
|
||||||
|
'Cuerpo claro con extremidades oscuras'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'AHUMADO',
|
||||||
|
'Pelaje blanco en la base con puntas de color'
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into
|
||||||
|
permissions (code)
|
||||||
|
values
|
||||||
|
('LOGIN'),
|
||||||
|
('USERS_ADMINISTRATE'),
|
||||||
|
('USERS_CREATE'),
|
||||||
|
('USERS_EDIT'),
|
||||||
|
('USERS_DISABLE'),
|
||||||
|
('USERS_FORCE_RESET_PASSWORD'),
|
||||||
|
('ANIMALS_ADMINISTRATE'),
|
||||||
|
('ANIMALS_CREATE'),
|
||||||
|
('ANIMALS_EDIT'),
|
||||||
|
('ANIMALS_DISABLE'),
|
||||||
|
('ANIMALS_SET_PUBLIC'),
|
||||||
|
('ANIMAL_IMAGES_ADMINISTRATE'),
|
||||||
|
('ANIMAL_IMAGES_UPLOAD'),
|
||||||
|
('ANIMAL_IMAGES_EDIT'),
|
||||||
|
('ANIMAL_IMAGES_DISABLE'),
|
||||||
|
('ANIMAL_IMAGES_SET_PUBLIC'),
|
||||||
|
('ANIMAL_COMMENTS_ADMINISTRATE'),
|
||||||
|
('ANIMAL_COMMENTS_CREATE'),
|
||||||
|
('ANIMAL_COMMENTS_EDIT'),
|
||||||
|
('ANIMAL_COMMENTS_DISABLE'),
|
||||||
|
('ANIMAL_COMMENTS_SET_PUBLIC'),
|
||||||
|
('ANIMAL_ALERTS_ADMINISTRATE'),
|
||||||
|
('ANIMAL_ALERTS_CREATE'),
|
||||||
|
('ANIMAL_ALERTS_EDIT'),
|
||||||
|
('ANIMAL_ALERTS_DISABLE'),
|
||||||
|
('SETTLEMENTS_ADMINISTRATE'),
|
||||||
|
('SETTLEMENTS_CREATE'),
|
||||||
|
('SETTLEMENTS_EDIT'),
|
||||||
|
('SETTLEMENTS_DISABLE');
|
||||||
|
|
||||||
|
insert into
|
||||||
|
groups (code)
|
||||||
|
values
|
||||||
|
('ADMINISTRATORS'),
|
||||||
|
('WORKERS'),
|
||||||
|
('VOLUNTEERS'),
|
||||||
|
('ADOPTERS');
|
||||||
|
|
||||||
|
insert into
|
||||||
|
users (id, name, surnames, email)
|
||||||
|
values
|
||||||
|
(
|
||||||
|
1,
|
||||||
|
'Albert',
|
||||||
|
'Gutiérrez Muñoz',
|
||||||
|
'albertgmu@gmail.com'
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into
|
||||||
|
user_accounts (user_id, username, password)
|
||||||
|
values
|
||||||
|
(1, 'devadmin', 'fe452152faec60d73c3f00c3bb985fe9');
|
||||||
Binary file not shown.
@@ -0,0 +1,170 @@
|
|||||||
|
-- drop database if exists settlements;
|
||||||
|
-- create database settlements;
|
||||||
|
drop type if exists alert_types cascade;
|
||||||
|
|
||||||
|
create type alert_types as enum ('info', 'low', 'medium', 'high', 'ultra');
|
||||||
|
|
||||||
|
drop type if exists message_types cascade;
|
||||||
|
|
||||||
|
create type message_types as enum ('info', 'alert');
|
||||||
|
|
||||||
|
drop type if exists animal_sexs cascade;
|
||||||
|
|
||||||
|
create type animal_sexs as enum ('female', 'male', 'unknown');
|
||||||
|
|
||||||
|
drop table if exists users cascade;
|
||||||
|
|
||||||
|
drop table if exists user_accounts cascade;
|
||||||
|
|
||||||
|
drop table if exists permissions cascade;
|
||||||
|
|
||||||
|
drop table if exists groups cascade;
|
||||||
|
|
||||||
|
drop table if exists user_groups cascade;
|
||||||
|
|
||||||
|
drop table if exists user_permissions cascade;
|
||||||
|
|
||||||
|
drop table if exists group_permissions cascade;
|
||||||
|
|
||||||
|
drop table if exists user_groups cascade;
|
||||||
|
|
||||||
|
drop table if exists user_sessions cascade;
|
||||||
|
|
||||||
|
drop table if exists user_recover_requests cascade;
|
||||||
|
|
||||||
|
drop table if exists settlements cascade;
|
||||||
|
|
||||||
|
drop table if exists animals cascade;
|
||||||
|
|
||||||
|
drop table if exists animal_comments cascade;
|
||||||
|
|
||||||
|
drop table if exists animal_alerts cascade;
|
||||||
|
|
||||||
|
drop table if exists animal_images cascade;
|
||||||
|
|
||||||
|
drop table if exists animal_coats cascade;
|
||||||
|
|
||||||
|
create table users (
|
||||||
|
id bigint generated by default as identity primary key,
|
||||||
|
name varchar,
|
||||||
|
surnames varchar,
|
||||||
|
email varchar
|
||||||
|
);
|
||||||
|
|
||||||
|
create table user_accounts (
|
||||||
|
id bigint generated by default as identity primary key,
|
||||||
|
user_id bigint references users (id),
|
||||||
|
username varchar unique not null,
|
||||||
|
password varchar not null,
|
||||||
|
attempts smallint default 0,
|
||||||
|
locked bool default false,
|
||||||
|
active bool default true
|
||||||
|
);
|
||||||
|
|
||||||
|
create table permissions (
|
||||||
|
id bigint generated by default as identity primary key,
|
||||||
|
code varchar unique not null,
|
||||||
|
description varchar default null
|
||||||
|
);
|
||||||
|
|
||||||
|
create table groups (
|
||||||
|
id bigint generated by default as identity primary key,
|
||||||
|
code varchar unique not null,
|
||||||
|
description varchar default null
|
||||||
|
);
|
||||||
|
|
||||||
|
create table user_groups (
|
||||||
|
user_id bigint references users (id),
|
||||||
|
group_id bigint references groups (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table user_permissions (
|
||||||
|
user_id bigint references users (id),
|
||||||
|
permission_id bigint references permissions (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table group_permissions (
|
||||||
|
group_id bigint references groups (id),
|
||||||
|
permission_id bigint references permissions (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table user_sessions (
|
||||||
|
id bigint generated by default as identity primary key,
|
||||||
|
user_id bigint not null references users (id),
|
||||||
|
token varchar not null unique,
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
active bool default true
|
||||||
|
);
|
||||||
|
|
||||||
|
create table user_recover_requests (
|
||||||
|
id bigint generated by default as identity primary key,
|
||||||
|
user_id bigint not null references users (id),
|
||||||
|
token varchar not null unique,
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
active bool default true
|
||||||
|
);
|
||||||
|
|
||||||
|
create table settlements (
|
||||||
|
id bigint generated by default as identity primary key,
|
||||||
|
name varchar not null unique,
|
||||||
|
address varchar not null unique,
|
||||||
|
created_at timestamp not null default current_timestamp,
|
||||||
|
last_done timestamp default null,
|
||||||
|
last_done_by bigint references users (id),
|
||||||
|
active bool default true
|
||||||
|
);
|
||||||
|
|
||||||
|
create table animal_coats (
|
||||||
|
id bigint generated by default as identity primary key,
|
||||||
|
code varchar unique not null,
|
||||||
|
description varchar default null
|
||||||
|
);
|
||||||
|
|
||||||
|
create table animals (
|
||||||
|
id bigint generated by default as identity primary key,
|
||||||
|
name varchar not null,
|
||||||
|
microchip varchar default null,
|
||||||
|
coat bigint not null references animal_coats (id),
|
||||||
|
sex animal_sexs default 'unknown',
|
||||||
|
settlement_id bigint references settlements (id),
|
||||||
|
has_ear_mark bool default false,
|
||||||
|
controlled bool default false,
|
||||||
|
castrated bool default false,
|
||||||
|
adopted bool default false,
|
||||||
|
friendly bool default null,
|
||||||
|
friendly_children bool default null,
|
||||||
|
friendly_dogs bool default null,
|
||||||
|
first_view timestamp default null,
|
||||||
|
last_view timestamp default null,
|
||||||
|
created_at timestamp not null default current_timestamp,
|
||||||
|
active bool default true
|
||||||
|
);
|
||||||
|
|
||||||
|
create table animal_comments (
|
||||||
|
id bigint generated by default as identity primary key,
|
||||||
|
animal_id bigint not null references animals (id),
|
||||||
|
type message_types default 'info',
|
||||||
|
message varchar not null,
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
public bool default false,
|
||||||
|
active bool default true
|
||||||
|
);
|
||||||
|
|
||||||
|
create table animal_alerts (
|
||||||
|
id bigint generated by default as identity primary key,
|
||||||
|
animal_id bigint not null references animals (id),
|
||||||
|
type alert_types default 'high',
|
||||||
|
comment varchar not null,
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
active bool default true
|
||||||
|
);
|
||||||
|
|
||||||
|
create table animal_images (
|
||||||
|
id bigint generated by default as identity primary key,
|
||||||
|
animal_id bigint not null references animals (id),
|
||||||
|
image varchar not null unique,
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
is_primary bool default false,
|
||||||
|
public bool default false,
|
||||||
|
active bool default true
|
||||||
|
);
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
truncate table animal_coats cascade;
|
||||||
|
|
||||||
|
truncate table permissions cascade;
|
||||||
|
|
||||||
|
truncate table groups cascade;
|
||||||
|
|
||||||
|
truncate table users cascade;
|
||||||
|
|
||||||
|
truncate table user_accounts cascade;
|
||||||
|
|
||||||
|
insert into
|
||||||
|
animal_coats (code)
|
||||||
|
values
|
||||||
|
('NEGRO'),
|
||||||
|
('NARANJA'),
|
||||||
|
('BLANCO'),
|
||||||
|
('CHOCOLATE');
|
||||||
|
|
||||||
|
insert into
|
||||||
|
animal_coats (code, description)
|
||||||
|
values
|
||||||
|
('CAREY', 'Mezcla irregular de negro y rojo'),
|
||||||
|
('AZUL', 'Gris diluido'),
|
||||||
|
('CANELA', 'Marrón claro con toque dorado'),
|
||||||
|
('LILA', 'Crema con tono lavanda'),
|
||||||
|
('CREMA', 'Naranja pálido'),
|
||||||
|
('CALICO', 'Combina blanco, negro y naranja'),
|
||||||
|
('ATIGRADO', 'Con rayas o espirales definidas'),
|
||||||
|
(
|
||||||
|
'COLORPOINT',
|
||||||
|
'Cuerpo claro con extremidades oscuras'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'AHUMADO',
|
||||||
|
'Pelaje blanco en la base con puntas de color'
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into
|
||||||
|
permissions (code)
|
||||||
|
values
|
||||||
|
('LOGIN'),
|
||||||
|
('USERS_ADMINISTRATE'),
|
||||||
|
('USERS_CREATE'),
|
||||||
|
('USERS_EDIT'),
|
||||||
|
('USERS_DISABLE'),
|
||||||
|
('USERS_FORCE_RESET_PASSWORD'),
|
||||||
|
('ANIMALS_ADMINISTRATE'),
|
||||||
|
('ANIMALS_CREATE'),
|
||||||
|
('ANIMALS_EDIT'),
|
||||||
|
('ANIMALS_DISABLE'),
|
||||||
|
('ANIMALS_SET_PUBLIC'),
|
||||||
|
('ANIMAL_IMAGES_ADMINISTRATE'),
|
||||||
|
('ANIMAL_IMAGES_UPLOAD'),
|
||||||
|
('ANIMAL_IMAGES_EDIT'),
|
||||||
|
('ANIMAL_IMAGES_DISABLE'),
|
||||||
|
('ANIMAL_IMAGES_SET_PUBLIC'),
|
||||||
|
('ANIMAL_COMMENTS_ADMINISTRATE'),
|
||||||
|
('ANIMAL_COMMENTS_CREATE'),
|
||||||
|
('ANIMAL_COMMENTS_EDIT'),
|
||||||
|
('ANIMAL_COMMENTS_DISABLE'),
|
||||||
|
('ANIMAL_COMMENTS_SET_PUBLIC'),
|
||||||
|
('ANIMAL_ALERTS_ADMINISTRATE'),
|
||||||
|
('ANIMAL_ALERTS_CREATE'),
|
||||||
|
('ANIMAL_ALERTS_EDIT'),
|
||||||
|
('ANIMAL_ALERTS_DISABLE'),
|
||||||
|
('SETTLEMENTS_ADMINISTRATE'),
|
||||||
|
('SETTLEMENTS_CREATE'),
|
||||||
|
('SETTLEMENTS_EDIT'),
|
||||||
|
('SETTLEMENTS_DISABLE');
|
||||||
|
|
||||||
|
insert into
|
||||||
|
groups (code)
|
||||||
|
values
|
||||||
|
('ADMINISTRATORS'),
|
||||||
|
('WORKERS'),
|
||||||
|
('VOLUNTEERS'),
|
||||||
|
('ADOPTERS');
|
||||||
|
|
||||||
|
insert into
|
||||||
|
users (id, name, surnames, email)
|
||||||
|
values
|
||||||
|
(
|
||||||
|
1,
|
||||||
|
'Albert',
|
||||||
|
'Gutiérrez Muñoz',
|
||||||
|
'albertgmu@gmail.com'
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into
|
||||||
|
user_accounts (user_id, username, password)
|
||||||
|
values
|
||||||
|
(1, 'devadmin', encode (sha256 (''), 'hex'));
|
||||||
Reference in New Issue
Block a user