Compare commits

..

27 Commits
1.3.6 ... 1.5.1

Author SHA1 Message Date
528176fc0d Merge pull request 'add confirm mail' (#47) from hotfix/template-mail into master
Reviewed-on: #47
2025-02-10 22:15:02 +00:00
e677261be5 add confirm mail 2025-02-10 23:14:00 +01:00
0a5ff67da5 Merge pull request 'more detail for profile creation' (#46) from feature/create-user-post into master
Reviewed-on: #46
2025-02-02 17:41:21 +00:00
3f720e7279 more detail for profile creation 2025-02-02 18:26:24 +01:00
ea6f9790d9 Merge pull request 'add status changing' (#45) from feature/create-user-post into master
Reviewed-on: #45
2025-01-26 20:25:28 +00:00
cfa1402984 add status changing 2025-01-26 21:10:06 +01:00
c22af013fd Merge pull request 'add update other informations' (#44) from feature/add-condition into master
Reviewed-on: #44
2025-01-06 23:32:39 +01:00
a6b11a8096 add update other informations 2025-01-06 23:30:23 +01:00
16949c83d7 Merge pull request 'remove update roles' (#43) from feature/add-condition into master
Reviewed-on: #43
2025-01-06 23:13:08 +01:00
5b347a020a remove update roles 2025-01-06 23:04:58 +01:00
e5128a9182 Merge pull request 'check condition' (#42) from feature/add-condition into master
Reviewed-on: #42
2025-01-05 21:43:42 +01:00
579ac3d303 check condition 2025-01-05 21:17:08 +01:00
ce24b50b8b Merge pull request 'change type datetime' (#41) from hotfix/birth into master
Reviewed-on: #41
2025-01-03 14:51:50 +01:00
587763d808 change type datetime 2025-01-03 14:47:31 +01:00
531656c169 Merge pull request 'add tag' (#40) from hotfix/options-regex into master
Reviewed-on: #40
2024-12-28 14:04:51 +01:00
7096c1d507 add tag 2024-12-28 14:02:17 +01:00
1ea5948ed0 Merge pull request 'fix options regex' (#39) from hotfix/options-regex into master
Reviewed-on: #39
2024-12-28 13:53:37 +01:00
216b4bb3f2 fix options regex 2024-12-28 00:19:43 +01:00
7a502d2bf9 Merge pull request 'fix tags' (#38) from hotfix/fix-tags-put into master
Reviewed-on: #38
2024-12-22 22:28:14 +01:00
c3fe5dad79 fix tags 2024-12-22 22:26:09 +01:00
1ae360b071 Merge pull request 'add conition for put' (#37) from hotfix/fix-doublon into master
Reviewed-on: #37
2024-12-22 18:27:46 +01:00
94bcc9ee5b add conition for put 2024-12-22 18:21:26 +01:00
ed1334f00a Merge pull request 'feature/tags-collection' (#36) from feature/tags-collection into master
Reviewed-on: #36
2024-12-21 23:06:40 +01:00
2a194c163a add tag in put endpiint 2024-12-21 23:05:24 +01:00
a893e86cdf add tags rouger work 2024-12-21 22:38:21 +01:00
ca983f1199 add routers tags 2024-12-21 21:38:14 +01:00
1afcd626ea add models tags 2024-12-21 20:59:43 +01:00
9 changed files with 310 additions and 22 deletions

View File

@@ -1,7 +1,7 @@
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from .routers import users, token, mail, events from .routers import users, token, mail, events, tags
from .dependencies import user_add from .dependencies import user_add
import os import os
@@ -29,6 +29,7 @@ app.include_router(users.router)
app.include_router(token.router) app.include_router(token.router)
app.include_router(mail.router) app.include_router(mail.router)
app.include_router(events.router) app.include_router(events.router)
app.include_router(tags.router)
@app.on_event("startup") @app.on_event("startup")

24
app/models/tags.py Normal file
View File

@@ -0,0 +1,24 @@
from pydantic import BaseModel, EmailStr
from pydantic_mongo import AbstractRepository, ObjectIdField
from datetime import datetime, date
class Tags(BaseModel):
id: ObjectIdField = None
name: str
created_at: datetime = datetime.today()
class TagsOut(BaseModel):
id: ObjectIdField = None
name: str
class TagsIn(BaseModel):
name: str
class TagsIDS(BaseModel):
ids: list[str]
class TagsRepository(AbstractRepository[Tags]):
class Meta:
collection_name = "tags"

View File

@@ -11,7 +11,7 @@ class User(BaseModel):
roles: str = "User" roles: str = "User"
status: int = 0 status: int = 0
email: EmailStr email: EmailStr
birth: str | None = None birth: datetime | None = None
created_at: datetime = datetime.today() created_at: datetime = datetime.today()
connected_at: datetime | None = None connected_at: datetime | None = None
updated_at: datetime | None = None updated_at: datetime | None = None
@@ -42,7 +42,9 @@ class UserCreate(BaseModel):
username: str username: str
password: str password: str
email: EmailStr email: EmailStr
birth: str
firstName: str
name: str
class UserInDB(User): class UserInDB(User):
password: str password: str

View File

@@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, HTTPException, status, Response
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from datetime import datetime from datetime import datetime
from ..dependencies import users_token, permissions_checker, database from ..dependencies import users_token, permissions_checker, database
from ..models import events, users from ..models import events, users, tags
from pydantic import EmailStr from pydantic import EmailStr
from typing import Annotated, Union from typing import Annotated, Union
from bson import ObjectId from bson import ObjectId
@@ -330,8 +330,12 @@ async def update_events(authorize: Annotated[bool, Depends(permissions_checker.P
detail="Body request is empty" detail="Body request is empty"
) )
event_repository = events.EventRepository(database=database.database) event_repository = events.EventRepository(database=database.database)
tags_repository = tags.TagsRepository(database=database.database)
event = event_repository.find_one_by({"name": {'$eq': eventSingle.name}}) event = event_repository.find_one_by( {"$and": [
{"start_date": {"$eq": eventSingle.start_date}}, # Already started
{"name": {"$eq": eventSingle.name}},
]})
if event is not None: if event is not None:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_204_NO_CONTENT, status_code=status.HTTP_204_NO_CONTENT,
@@ -347,6 +351,11 @@ async def update_events(authorize: Annotated[bool, Depends(permissions_checker.P
event.longitude = eventSingle.longitude event.longitude = eventSingle.longitude
event.imgUrl = eventSingle.imgUrl event.imgUrl = eventSingle.imgUrl
event.tags = eventSingle.tags event.tags = eventSingle.tags
for tag_name in eventSingle.tags:
tag = tags_repository.find_one_by({"name": {'$eq': tag_name}})
if tag is None:
tag = tags.Tags(name=tag_name)
tags_repository.save(tag)
event.status = 1 event.status = 1
event.created_at = datetime.today() event.created_at = datetime.today()
event_repository.save(event) event_repository.save(event)
@@ -363,7 +372,7 @@ async def update_events_id(item_id: str, authorize: Annotated[bool, Depends(perm
detail="Body request is empty" detail="Body request is empty"
) )
event_repository = events.EventRepository(database=database.database) event_repository = events.EventRepository(database=database.database)
tags_repository = tags.TagsRepository(database=database)
event = event_repository.find_one_by({"id": {'$eq': ObjectId(item_id)}}) event = event_repository.find_one_by({"id": {'$eq': ObjectId(item_id)}})
if event is None: if event is None:
raise HTTPException( raise HTTPException(
@@ -378,6 +387,11 @@ async def update_events_id(item_id: str, authorize: Annotated[bool, Depends(perm
event.end_date = eventSingle.end_date event.end_date = eventSingle.end_date
event.organizers = eventSingle.organizers event.organizers = eventSingle.organizers
event.tags = eventSingle.tags event.tags = eventSingle.tags
for tag_name in eventSingle:
tag = tags_repository.find_one_by({"name": {'$eq': tag_name}})
if tag is None:
tag = Tags(name=tag_name)
tags_repository.save(tag)
event.latitude = eventSingle.latitude event.latitude = eventSingle.latitude
event.longitude = eventSingle.longitude event.longitude = eventSingle.longitude
event.updated_at = datetime.today() event.updated_at = datetime.today()

View File

@@ -1,12 +1,18 @@
from fastapi import APIRouter, HTTPException, status from fastapi import APIRouter, HTTPException, status, Request
from fastapi.templating import Jinja2Templates
from ..dependencies import users_token, database, mail from ..dependencies import users_token, database, mail
from ..models import users, email from ..models import users, email
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse, HTMLResponse
from fastapi_mail import MessageSchema, MessageType, FastMail from fastapi_mail import MessageSchema, MessageType, FastMail
import random import random, os
router = APIRouter() router = APIRouter()
# Assurer que le chemin vers "templates" est correct
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
templates = Jinja2Templates(directory=os.path.join(BASE_DIR, "templates"))
@router.post("/mail",tags=["mail"]) @router.post("/mail",tags=["mail"])
async def create_user(userSingle: users.UserCreate | None = None): async def create_user(userSingle: users.UserCreate | None = None):
if userSingle is None: if userSingle is None:
@@ -19,7 +25,7 @@ async def create_user(userSingle: users.UserCreate | None = None):
if user is not None: if user is not None:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_204_NO_CONTENT, status_code=status.HTTP_204_NO_CONTENT,
detail="User is already exist" detail="User already exists"
) )
fm = FastMail(mail.conf) fm = FastMail(mail.conf)
@@ -35,26 +41,32 @@ async def create_user(userSingle: users.UserCreate | None = None):
) )
await fm.send_message(message, template_name="mailer.html") await fm.send_message(message, template_name="mailer.html")
current_user = users.User(username=userSingle.username, password=users_token.get_password_hash(userSingle.password), email=userSingle.email) current_user = users.User(username=userSingle.username, password=users_token.get_password_hash(userSingle.password), email=userSingle.email, name=userSingle.name, firstName=userSingle.firstName)
current_user.status = 0
user_repository.save(current_user) user_repository.save(current_user)
database.connect_redis.set(userSingle.username, key_hashed) database.connect_redis.set(userSingle.username, key_hashed)
return JSONResponse(status_code=status.HTTP_200_OK, content={"message": "email has been sent"}) return JSONResponse(status_code=status.HTTP_200_OK, content={"message": "email has been sent"})
@router.get("/mail",tags=["mail"]) @router.get("/mail", tags=["mail"])
async def confirm_user(key: str | None = None, username: str | None = None): async def confirm_user(request: Request, key: str | None = None, username: str | None = None):
if key is None or username is None: if key is None or username is None:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
detail="Parameter key or/and username is empty" detail="Parameter key or/and username is empty"
) )
user_repository = users.UserRepository(database=database.database) user_repository = users.UserRepository(database=database.database)
user = user_repository.find_one_by({"username": {'$eq': username}}) user = user_repository.find_one_by({"username": {'$eq': username}})
key_hashed = database.connect_redis.get(username) key_hashed = database.connect_redis.get(username)
if key_hashed != key: if key_hashed != key:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
detail="Key is invalid" detail="Key is invalid"
) )
user.confirmed = True
user.status = 1
user_repository.save(user) user_repository.save(user)
return JSONResponse(status_code=status.HTTP_200_OK, content={"message": "user account confirmed"})
# Rendre la page HTML avec Jinja2 et passer la variable username
return templates.TemplateResponse("confirm.html", {"request": request, "username": username})

119
app/routers/tags.py Normal file
View File

@@ -0,0 +1,119 @@
from fastapi import APIRouter, Depends, HTTPException, status, Response
from fastapi.responses import JSONResponse
from datetime import datetime
from ..dependencies import users_token, permissions_checker, database
from ..models import tags, users
from pydantic import EmailStr
from typing import Annotated, Union
from bson import ObjectId
from datetime import datetime
router = APIRouter()
@router.get("/tags", tags=["tags"], response_model=list[tags.TagsOut])
async def read_tags(
authorize: Annotated[bool, Depends(permissions_checker.PermissionChecker(roles=["Admin", "User"]))],
skip: int = 0,
limit: int = 20,
id_tags: str | None = None,
name: str | None = None
):
# Validate `skip` and `limit`
if limit < 1 or skip < 0 or limit < skip:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="`skip` should be >= 0 and `limit` should be > 0 and greater than `skip`.",
)
limit = limit + skip
# Initialize filters
filters = []
if name:
filters.append({"name": {"$regex": name, "$options": "i"}})
# Add ID filter
if id_tags:
try:
tags_id = ObjectId(id_tags)
filters.append({"_id": {"$eq": event_id}})
except Exception:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid event ID format.")
# Combine all filters
object_search = {"$and": filters} if filters else {}
# Fetch and return results
tags_repository = tags.TagsRepository(database=database.database)
list_tags = []
for tag_index in tags_repository.find_by(object_search, limit=limit, skip=skip):
tag = tags.TagsOut(
id=tag_index.id,
name=tag_index.name
)
list_tags.append(tag)
return list_tags
@router.get("/tags/count", tags=["tags"])
async def read_tags_count(authorize: Annotated[bool, Depends(permissions_checker.PermissionChecker(roles=["Admin", "User"]))]):
count = database.database.get_collection("tags").estimated_document_count()
content = {"count":count}
response = JSONResponse(content=content)
return response
@router.get("/tags/{item_id}", tags=["tags"], response_model=tags.Tags)
async def read_tags_id(item_id : str, authorize: Annotated[bool, Depends(permissions_checker.PermissionChecker(roles=["Admin"]))]):
tags_repository = tags.TagsRepository(database=database.database)
tag = tags_repository.find_one_by_id(ObjectId(item_id))
return tag
@router.delete("/tags/groups",tags=["tags"])
async def delete_tags_groups(authorize: Annotated[bool, Depends(permissions_checker.PermissionChecker(roles=["Admin"]))], tagsids: tags.TagsIDS | None = None):
if len(tagsids.ids) == 0:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="eventids should be greater than 0"
)
tag_repository = tags.TagsRepository(database=database.database)
for i in tagsids.ids:
tag = tag_repository.find_one_by_id(ObjectId(i))
tag_repository.delete_one(tag)
content = {"message": "tags removed"}
response = JSONResponse(content=content)
return response
@router.delete("/tags/{item_id}", tags=["tags"])
async def delete_tags_id(item_id : str, authorize: Annotated[bool, Depends(permissions_checker.PermissionChecker(roles=["Admin"]))]):
tag_repository = tags.TagsRepository(database=database.database)
tag = tag_repository.find_one_by_id(ObjectId(item_id))
event_repository.delete_one(event)
content = {"message": "tags delete"}
response = JSONResponse(content=content)
return responsed
@router.put("/tags", tags=["tags"], status_code=status.HTTP_201_CREATED)
async def update_tags(authorize: Annotated[bool, Depends(permissions_checker.PermissionChecker(roles=["Admin"]))], tagSingle: tags.TagsIn | None = None):
if tagSingle is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Body request is empty"
)
tag_repository = tags.TagsRepository(database=database.database)
tag = tag_repository.find_one_by({"name": {'$eq': tagSingle.name}})
if tag is not None:
raise HTTPException(
status_code=status.HTTP_204_NO_CONTENT,
detail="name"
)
tag = tags.Tags(name=tagSingle.name)
tag.created_at = datetime.today()
tag_repository.save(event)
content = {"message": "tags created"}
response = JSONResponse(content=content, status_code=status.HTTP_201_CREATED)
return response

View File

@@ -139,13 +139,17 @@ async def delete_users_id(item_id : str, authorize: Annotated[bool, Depends(perm
response = JSONResponse(content=content) response = JSONResponse(content=content)
return response return response
@router.put("/users/me",tags=["users"]) @router.put("/users/me",tags=["users"])
async def update_users_me(current_user: Annotated[users.User, Depends(users_token.get_current_active_user)], authorize: Annotated[bool, Depends(permissions_checker.PermissionChecker(roles=["Admin", "User"]))], userSingle: users.UserIn | None = None): async def update_users_me(current_user: Annotated[users.User, Depends(users_token.get_current_active_user)], authorize: Annotated[bool, Depends(permissions_checker.PermissionChecker(roles=["Admin", "User"]))], userSingle: users.UserIn | None = None):
user_repository = users.UserRepository(database=database.database) user_repository = users.UserRepository(database=database.database)
current_user.username = userSingle.username current_user.username = userSingle.username
current_user.password = user_token.get_password_hash(userSingle.password) if len(userSingle.password) > 0:
current_user.roles = userSingle.roles current_user.password = user_token.get_password_hash(userSingle.password)
current_user.email = userSingle.email current_user.email = userSingle.email
current_user.name = userSingle.name
current_user.firstName = userSingle.firstName
current_user.birth = userSingle.birth
user_repository.save(current_user) user_repository.save(current_user)
content = {"message": "user is updated"} content = {"message": "user is updated"}
response = JSONResponse(content=content) response = JSONResponse(content=content)
@@ -202,7 +206,8 @@ async def update_users_id(item_id: str, authorize: Annotated[bool, Depends(permi
) )
user.username = userSingle.username user.username = userSingle.username
user.password = users_token.get_password_hash(userSingle.password) if len(userSingle.password) > 0:
user.password = users_token.get_password_hash(userSingle.password)
user.roles = userSingle.roles user.roles = userSingle.roles
user.email = userSingle.email user.email = userSingle.email
user.firstName = userSingle.firstName user.firstName = userSingle.firstName

View File

@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Votre compte est activé</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
}
.container {
max-width: 600px;
margin: 20px auto;
background: #ffffff;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
text-align: center;
}
h2 {
color: #333;
}
p {
color: #666;
font-size: 16px;
}
.button {
display: inline-block;
background: #007BFF;
color: white;
padding: 12px 20px;
text-decoration: none;
border-radius: 5px;
font-size: 16px;
font-weight: bold;
margin-top: 20px;
}
.button:hover {
background: #0056b3;
}
.footer {
margin-top: 20px;
font-size: 12px;
color: #888;
}
</style>
</head>
<body>
<div class="container">
<h2>Félicitations, {{ username }} ! 🎉</h2>
<p>Votre compte a été activé avec succès.</p>
<p>Vous pouvez maintenant vous connecter et profiter pleinement de nos services.</p>
<p class="footer">Si vous avez des questions, n'hésitez pas à nous contacter.</p>
</div>
</body>
</html>

View File

@@ -1,6 +1,59 @@
<!DOCTYPE html>
<html> <html>
<head><title>Email</title></head> <head>
<body><p>Voici un lien https://localhost:8080/api/mail?key={{ key }}&username={{ username }} <meta charset="UTF-8">
</p></body> <title>Confirmation de votre compte</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
}
.container {
max-width: 600px;
margin: 20px auto;
background: #ffffff;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
text-align: center;
}
h2 {
color: #333;
}
p {
color: #666;
font-size: 16px;
}
.button {
display: inline-block;
background: #28a745;
color: white;
padding: 12px 20px;
text-decoration: none;
border-radius: 5px;
font-size: 16px;
font-weight: bold;
margin-top: 20px;
}
.button:hover {
background: #218838;
}
.footer {
margin-top: 20px;
font-size: 12px;
color: #888;
}
</style>
</head>
<body>
<div class="container">
<h2>Bienvenue, {{ username }} ! 🎉</h2>
<p>Merci de vous être inscrit sur notre plateforme.</p>
<p>Pour finaliser votre inscription, veuillez confirmer votre compte en cliquant sur le bouton ci-dessous :</p>
<a href="https://backend.valczeryba.ovh/api/confirm?key={{ key }}&username={{ username }}" class="button">Confirmer mon compte</a>
<p class="footer">Si vous n'êtes pas à l'origine de cette inscription, ignorez simplement cet email.</p>
</div>
</body>
</html> </html>