funny merge

This commit is contained in:
RubenCGomes 2024-11-17 00:54:27 +00:00
commit 096e086e14
11 changed files with 359 additions and 20 deletions

View File

@ -1,6 +1,7 @@
from flask import Flask import sqlalchemy.exc
from flask import Flask, request, jsonify
from routes import org_bp, user_bp, file_bp from routes import org_bp, user_bp, file_bp
from database import db_connection from database import db_connection, db
from models import Organization, User, File, Session from models import Organization, User, File, Session
app = Flask(__name__) app = Flask(__name__)
@ -10,18 +11,35 @@ app.config["SQLALCHEMY_AUTOCOMMIT"] = False
app.config["SQLALCHEMY_AUTOFLUSH"] = False app.config["SQLALCHEMY_AUTOFLUSH"] = False
db_connection.init_app(app) db_connection.init_app(app)
with app.app_context(): with app.app_context():
try:
db_connection.session.query(Session).delete()
db_connection.session.commit()
except sqlalchemy.exc.OperationalError:
pass
db_connection.create_all() db_connection.create_all()
app.register_blueprint(org_bp, url_prefix="/org") app.register_blueprint(org_bp, url_prefix="/org")
app.register_blueprint(user_bp, url_prefix="/user") app.register_blueprint(user_bp, url_prefix="/user")
app.register_blueprint(file_bp, url_prefix="/file") app.register_blueprint(file_bp, url_prefix="/file")
@app.route("/reset")
@app.route("/", methods=["GET"])
def index():
return jsonify({"message": "Welcome to the API"}), 200
@app.route("/reset", methods=["POST"])
def reset(): def reset():
with app.app_context(): password = request.json["password"]
db_connection.drop_all() if password != "123":
db_connection.create_all() return jsonify({"error": "Invalid password"}), 403
return "Database reset" try:
with app.app_context():
db_connection.drop_all()
db_connection.create_all()
except sqlalchemy.exc.OperationalError:
return jsonify({"error": "Database error"}), 500
return jsonify({"message": "Database reset"}), 200
if __name__ == "__main__": if __name__ == "__main__":
app.run(debug=True) app.run(debug=True)

View File

@ -5,8 +5,9 @@ class Organization(db_connection.Model):
id = db_connection.Column(db_connection.Integer, primary_key=True, index=True) id = db_connection.Column(db_connection.Integer, primary_key=True, index=True)
name = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False) name = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False)
owner_id = db_connection.Column(db_connection.Integer, db_connection.ForeignKey('users.id'), nullable=False) users = db_connection.Column(db_connection.JSON, nullable=False, default=dict)
owner = db_connection.relationship('User', back_populates='orgs') owner_id = db_connection.Column(db_connection.Integer, db_connection.ForeignKey('users.id'))
owner = db_connection.relationship('User', backref=db_connection.backref('owned_organization', uselist=False))
files = db_connection.relationship('File', back_populates='org') files = db_connection.relationship('File', back_populates='org')
def to_dict(self): def to_dict(self):
@ -14,5 +15,11 @@ class Organization(db_connection.Model):
"id": self.id, "id": self.id,
"name": self.name, "name": self.name,
"owner": self.owner.to_dict(), "owner": self.owner.to_dict(),
"users": [{"id": user_id, "user_data": {
"username": user_data["username"],
"full_name": user_data["full_name"],
"email": user_data["email"],
"status": user_data["status"]
}} for user_id, user_data in self.users.items()],
"files": [{"id": file.id, "name": file.name, "file_handle": file.file_handle} for file in self.files] "files": [{"id": file.id, "name": file.name, "file_handle": file.file_handle} for file in self.files]
} }

View File

@ -10,7 +10,8 @@ class User(db_connection.Model):
full_name = db_connection.Column(db_connection.String, nullable=False) full_name = db_connection.Column(db_connection.String, nullable=False)
email = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False) email = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False)
public_keys = db_connection.Column(db_connection.JSON, nullable=False, default=dict) public_keys = db_connection.Column(db_connection.JSON, nullable=False, default=dict)
orgs = db_connection.relationship('Organization', back_populates='owner') orgs = db_connection.Column(db_connection.JSON, nullable=False, default=dict)
files = db_connection.relationship('File', back_populates='creator') files = db_connection.relationship('File', back_populates='creator')
def to_dict(self): def to_dict(self):
@ -19,7 +20,6 @@ class User(db_connection.Model):
"username": self.username, "username": self.username,
"full_name": self.full_name, "full_name": self.full_name,
"email": self.email, "email": self.email,
"public_keys": [{"org_id": org_id, "key": public_key} for org_id, public_key in self.public_keys.items()], "orgs": [{"id": org_id, "name": org_data["name"], "status": org_data["status"]} for org_id, org_data in self.orgs.items()],
"orgs": [{"id": org.id, "name": org.name} for org in self.orgs],
"files": [{"id": file.id, "name": file.name, "file_handle": file.file_handle} for file in self.files] "files": [{"id": file.id, "name": file.name, "file_handle": file.file_handle} for file in self.files]
} }

View File

@ -2,3 +2,13 @@ from flask import Blueprint, request, jsonify
from services import FileService from services import FileService
file_bp = Blueprint("file", __name__) file_bp = Blueprint("file", __name__)
@file_bp.route("/get", methods=["GET"])
def file_get():
data = request.json
file_handle = data["file_handle"]
file = FileService.get_file_by_file_handle(file_handle)
if not file:
return jsonify({"error": "File not found"}), 404
return jsonify(file.to_dict()), 200

View File

@ -1,5 +1,7 @@
import json
from flask import Blueprint, request, jsonify from flask import Blueprint, request, jsonify
from services import UserService, SessionService, OrganizationService from services import UserService, SessionService, OrganizationService
from utils import data_checks
user_bp = Blueprint("user", __name__) user_bp = Blueprint("user", __name__)
@ -23,3 +25,131 @@ def user_login():
session = SessionService.create_session(user, org) session = SessionService.create_session(user, org)
return jsonify(session.to_dict()), 201 return jsonify(session.to_dict()), 201
@user_bp.route("/logout", methods=["POST"])
def user_logout():
data = request.json
session_file = data["session_file"]
session_data = json.loads(session_file)
session_token = session_data["token"]
session = SessionService.get_session(session_token)
if not session:
return jsonify({"error": "Not authenticated"}), 401
SessionService.delete_session(session)
return jsonify({"message": "Logged out"}), 200
@user_bp.route("/list", methods=["GET"])
def user_list():
data = request.json
if "session_file" not in data:
return jsonify({"error": "No session file"}), 400
session_file = data["session_file"]
session_data = json.loads(session_file)
session = data_checks.validate_session_file(session_data)
if isinstance(session, tuple):
return session
org = OrganizationService.get_organization(session.org_id)
if not org:
return jsonify({"error": "Organization not found"}), 404
if "username" in data:
user = UserService.get_user_by_username(data["username"])
if not user:
return jsonify({"error": "User not found"}), 404
return jsonify(user.to_dict()), 200
users = OrganizationService.get_users_in_organization(org)
return jsonify(users), 200
@user_bp.route("/create", methods=["POST"])
def user_create():
data = request.json
if "session_file" not in data or "username" not in data or "full_name" not in data or "email" not in data or "public_key" not in data:
return jsonify({"error": "Missing required fields"}), 400
session_file = data["session_file"]
session_data = json.loads(session_file)
session = data_checks.validate_session_file(session_data)
if isinstance(session, tuple):
return session
org = OrganizationService.get_organization(session.org_id)
if not org:
return jsonify({"error": "Organization not found"}), 404
if org.owner.id != session.user_id:
return jsonify({"error": "Not authorized to create users"}), 403
user = UserService.get_user_by_username(data["username"])
if not user:
user = UserService.create_user(
username=data["username"],
full_name=data["full_name"],
email=data["email"],
public_key=data["public_key"],
org=org
)
return jsonify(user.to_dict()), 201
@user_bp.route("/suspend", methods=["POST"])
def user_suspend():
data = request.json
if "session_file" not in data or "username" not in data:
return jsonify({"error": "Missing required fields"}), 400
session_file = data["session_file"]
session_data = json.loads(session_file)
session = data_checks.validate_session_file(session_data)
if isinstance(session, tuple):
return session
org = OrganizationService.get_organization(session.org_id)
if not org:
return jsonify({"error": "Organization not found"}), 404
if org.owner.id != session.user_id:
return jsonify({"error": "Not authorized to suspend users"}), 403
user = UserService.get_user_by_username(data["username"])
if not user:
return jsonify({"error": "User not found"}), 404
return OrganizationService.suspend_user(org, user)
@user_bp.route("/activate", methods=["POST"])
def user_unsuspend():
data = request.json
if "session_file" not in data or "username" not in data:
return jsonify({"error": "Missing required fields"}), 400
session_file = data["session_file"]
session_data = json.loads(session_file)
session = data_checks.validate_session_file(session_data)
if isinstance(session, tuple):
return session
org = OrganizationService.get_organization(session.org_id)
if not org:
return jsonify({"error": "Organization not found"}), 404
if org.owner.id != session.user_id:
return jsonify({"error": "Not authorized to unsuspend users"}), 403
user = UserService.get_user_by_username(data["username"])
if not user:
return jsonify({"error": "User not found"}), 404
return OrganizationService.activate_user(org, user)

View File

@ -1,7 +1,8 @@
import os.path import os.path
from database import db from database import db
from models import Organization from models import Organization, User
from sqlalchemy.orm.attributes import flag_modified
class OrganizationService: class OrganizationService:
@ -19,8 +20,13 @@ class OrganizationService:
organization = Organization( organization = Organization(
name=name, name=name,
owner_id=user.id, owner=user,
owner=user users={user.id: {
"username": user.username,
"full_name": user.full_name,
"email": user.email,
"status": "active"
}}
) )
db.add(organization) db.add(organization)
@ -32,6 +38,10 @@ class OrganizationService:
return organization return organization
@staticmethod
def list_organizations():
return db.query(Organization).all()
@staticmethod @staticmethod
def get_organization(org_id: int) -> Organization | None: def get_organization(org_id: int) -> Organization | None:
return db.query(Organization).filter(Organization.id == org_id).first() return db.query(Organization).filter(Organization.id == org_id).first()
@ -41,5 +51,49 @@ class OrganizationService:
return db.query(Organization).filter(Organization.name == name).first() return db.query(Organization).filter(Organization.name == name).first()
@staticmethod @staticmethod
def list_organizations(): def get_users_in_organization(org: Organization) -> list[User]:
return db.query(Organization).all() return db.query(Organization).filter(Organization.id == org.id).first().users
@staticmethod
def get_user_status(org: Organization, user_id: int) -> str:
return db.query(Organization).filter(Organization.id == org.id).first().users[str(user_id)]["status"]
@staticmethod
def add_user_to_organization(org: Organization, user: User) -> Organization:
org.users[str(user.id)] = {
"username": user.username,
"full_name": user.full_name,
"email": user.email,
"status": "active"
}
flag_modified(org, "users")
db.commit()
db.refresh(org)
return org
@staticmethod
def suspend_user(org: Organization, user: User) -> tuple:
if OrganizationService.get_user_status(org, user.id) != "active":
return {"error": "User already suspended"}, 400
if org.owner.id == user.id:
return {"error": "Cannot suspend owner"}, 400
org.users[str(user.id)]["status"] = "suspended"
flag_modified(org, "users")
db.commit()
db.refresh(org)
return {"message": "User suspended"}, 200
@staticmethod
def activate_user(org: Organization, user: User) -> tuple:
if OrganizationService.get_user_status(org, user.id) != "suspended":
return {"error": "User already active"}, 400
org.users[str(user.id)]["status"] = "active"
flag_modified(org, "users")
db.commit()
db.refresh(org)
return {"message": "User activated"}, 200

View File

@ -17,7 +17,7 @@ class SessionService:
return session return session
@staticmethod @staticmethod
def get_session_by_token(token: str) -> Session | None: def get_session(token: str) -> Session | None:
return db.query(Session).filter(Session.token == token).first() return db.query(Session).filter(Session.token == token).first()
@staticmethod @staticmethod

View File

@ -5,16 +5,22 @@ from models import User, Organization
class UserService: class UserService:
@staticmethod @staticmethod
def create_user(username: str, full_name: str, email: str, public_key: str, org: Organization = None) -> User: def create_user(username: str, full_name: str, email: str, public_key: str, org: Organization = None) -> User:
from services import OrganizationService
user = User( user = User(
username=username, username=username,
full_name=full_name, full_name=full_name,
email=email, email=email,
public_keys={org.id: public_key} if org else {}, public_keys={org.id: public_key} if org else {},
orgs=[org] if org else [] orgs={org.id: {
"name": org.name,
"status": "active"
}} if org else {}
) )
db.add(user) db.add(user)
db.commit() db.commit()
db.refresh(user) db.refresh(user)
if org:
OrganizationService.add_user_to_organization(org, user)
return user return user
@staticmethod @staticmethod
@ -27,7 +33,12 @@ class UserService:
@staticmethod @staticmethod
def add_org_to_user(user: User, org: Organization) -> User: def add_org_to_user(user: User, org: Organization) -> User:
user.orgs.append(org) orgs = user.orgs.copy()
orgs[org.id] = {
"name": org.name,
"status": "active"
}
user.orgs = orgs
db.commit() db.commit()
db.refresh(user) db.refresh(user)
return user return user

View File

@ -0,0 +1,80 @@
### Reset database
POST http://localhost:5000/reset
Content-Type: application/json
{
"password": "123"
}
### Create a new organization
POST http://localhost:5000/org/create
Content-Type: application/json
{
"name": "org",
"username": "username",
"full_name": "Full Name",
"email": "user@mail.com",
"public_key": "null"
}
### Login
POST http://localhost:5000/user/login
Content-Type: application/json
{
"username": "username",
"org": "org",
"public_key": "null"
}
> {% client.global.set("token", response.body["token"]) %}
### List organizations
GET http://localhost:5000/org/list
### Create a new user
POST http://localhost:5000/user/create
Content-Type: application/json
{
"session_file": "{\"token\":\"{{token}}\"}",
"username": "newuser",
"full_name": "Full Name",
"email": "newuser@mail.com",
"public_key": "null2"
}
### List users
GET http://localhost:5000/user/list
Content-Type: application/json
{
"session_file": "{\"token\":\"{{token}}\"}"
}
### Suspend user
POST http://localhost:5000/user/suspend
Content-Type: application/json
{
"session_file": "{\"token\":\"{{token}}\"}",
"username": "newuser"
}
### Activate user
POST http://localhost:5000/user/activate
Content-Type: application/json
{
"session_file": "{\"token\":\"{{token}}\"}",
"username": "newuser"
}
### Logout
POST http://localhost:5000/user/logout
Content-Type: application/json
{
"session_file": "{\"token\":\"{{token}}\"}"
}

View File

@ -0,0 +1 @@
from .data_checks import validate_session_file

View File

@ -0,0 +1,28 @@
import json
from flask import jsonify
from services import SessionService, OrganizationService
from models import Session
def validate_session_file(data) -> tuple | Session:
"""
Check if the session file is valid, and return the session object if it is
:param data: session file data (json)
:return: Session object or error response
"""
if "token" not in data:
return jsonify({"error": "No session token"}), 400
session_token = data["token"]
session = SessionService.get_session(session_token)
if not session:
return jsonify({"error": "Not authenticated"}), 401
org = OrganizationService.get_organization(session.org_id)
if not org:
return jsonify({"error": "Organization not found"}), 404
status = OrganizationService.get_user_status(org, session.user_id)
if status != "active":
return jsonify({"error": "User is not active"}), 403
return session