Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Organisation admin role #1126

Merged
merged 23 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
fb7bfb1
dependencies to cehck if user_exists
nrjadkry Jan 22, 2024
ad36a70
endpoint to add organisation admin
nrjadkry Jan 22, 2024
984198d
added approved field in organisation model
nrjadkry Jan 22, 2024
ffaafd5
fix: table name in migration file for organisations
nrjadkry Jan 22, 2024
8b06c5f
feat: organisations list api updated according to role
nrjadkry Jan 22, 2024
8a689bb
feat: endpoint to approve organisations
nrjadkry Jan 22, 2024
f5329f5
update: get organisation endpoint for filtering approval
nrjadkry Jan 22, 2024
00221ed
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 22, 2024
cdc7140
added docstrings
nrjadkry Jan 22, 2024
e38f3d1
fix: pre-commit linting errors
nrjadkry Jan 22, 2024
89700ad
added docstring in user_deps file
nrjadkry Jan 22, 2024
2ec8980
build: add default odk credentials to organisations
spwoodcock Jan 21, 2024
12acd85
build: merge migrations to organisations table
spwoodcock Jan 22, 2024
8bb9efb
refactor: fix linting errors
spwoodcock Jan 22, 2024
879f173
refactor: remove subscription_tier field for orgs
spwoodcock Jan 22, 2024
9f3351b
build: add public.organisation.approved field to base schema
spwoodcock Jan 22, 2024
70b505c
refactor: remove extra url field from DbOrganisation
spwoodcock Jan 23, 2024
e56cbac
refactor: fix organizationModel dir --> organisation for import
spwoodcock Jan 23, 2024
2395b56
fix: remove router get_db global dependency (on routes)
spwoodcock Jan 23, 2024
b9b3230
fix: use separate super_admin + check_super_admin deps
spwoodcock Jan 23, 2024
8d4f4d0
fix: update org_admin to also allow super_admin
spwoodcock Jan 23, 2024
931f8e9
refactor: remove missed log.warning from organisations endpoint
spwoodcock Jan 23, 2024
7b8d654
fix: separate Depends from logic, working org approval
spwoodcock Jan 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/backend/app/db/db_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ class DbOrganisation(Base):
description = Column(String)
url = Column(String)
type = Column(Enum(OrganisationType), default=OrganisationType.FREE, nullable=False)
approved = Column(Boolean, default=False)
# subscription_tier = Column(Integer)

managers = relationship(
Expand Down
60 changes: 55 additions & 5 deletions src/backend/app/organisations/organisation_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,28 @@
from sqlalchemy import update
from sqlalchemy.orm import Session

from app.auth.osm import AuthUser
from app.config import settings
from app.db import db_models
from app.models.enums import HTTPStatus
from app.models.enums import HTTPStatus, UserRole
from app.organisations.organisation_deps import (
get_organisation_by_name,
)
from app.organisations.organisation_schemas import OrganisationEdit, OrganisationIn
from app.s3 import add_obj_to_bucket
from app.users import user_crud


def get_organisations(
db: Session,
):
def get_organisations(db: Session, current_user: AuthUser, approved: bool):
"""Get all orgs."""
return db.query(db_models.DbOrganisation).all()
if (
db.query(db_models.DbUser)
.filter_by(id=current_user["id"], role=UserRole.ADMIN)
.first()
):
return db.query(db_models.DbOrganisation).filter_by(approved=approved).all()

return db.query(db_models.DbOrganisation).filter_by(approved=True).all()


async def upload_logo_to_s3(
Expand Down Expand Up @@ -186,3 +193,46 @@ async def delete_organisation(
db.commit()

return Response(status_code=HTTPStatus.NO_CONTENT)


async def add_organisation_admin(
db: Session, user_id: int, organization: db_models.DbOrganisation
):
"""Adds a user as an admin to the specified organisation.

Args:
db (Session): The database session.
user_id (int): The ID of the user to be added as an admin.
organization (DbOrganisation): The organisation model instance.

Returns:
Response: The HTTP response with status code 200.
"""
# get the user model instance
user_model_instance = await user_crud.get_user(db, user_id)

# add data to the managers field in organisation model
organization.managers.append(user_model_instance)
db.commit()

return Response(status_code=HTTPStatus.OK)


async def approve_organisation(db, organisation_id):
"""Approves an oranisation request made by the user .

Args:
db: The database session.
organisation_id: The ID of the organisation to be approved.

Returns:
Response: An HTTP response with the status code 200.
"""
db_org = (
db.query(db_models.DbOrganisation)
.filter(db_models.DbOrganisation.id == organisation_id)
.first()
)
db_org.approved = True
db.commit()
return Response(status_code=HTTPStatus.OK)
3 changes: 2 additions & 1 deletion src/backend/app/organisations/organisation_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ async def get_organisation_by_name(db: Session, org_name: str) -> DbOrganisation
"""
return (
db.query(DbOrganisation)
.filter_by(approved=True)
.filter(func.lower(DbOrganisation.name).like(func.lower(f"%{org_name}%")))
.first()
)
Expand All @@ -58,7 +59,7 @@ async def get_organisation_by_id(db: Session, org_id: int) -> DbOrganisation:
Returns:
DbOrganisation: organisation with the given id
"""
return db.query(DbOrganisation).filter(DbOrganisation.id == org_id).first()
return db.query(DbOrganisation).filter_by(id=org_id, approved=True).first()


async def org_exists(
Expand Down
39 changes: 38 additions & 1 deletion src/backend/app/organisations/organisation_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@
)
from sqlalchemy.orm import Session

from app.auth.osm import AuthUser, login_required
from app.auth.roles import org_admin, super_admin
from app.db import database
from app.db.db_models import DbOrganisation
from app.organisations import organisation_crud, organisation_schemas
from app.organisations.organisation_deps import org_exists
from app.users.user_deps import user_exists

router = APIRouter(
prefix="/organisation",
Expand All @@ -41,9 +44,11 @@
@router.get("/", response_model=list[organisation_schemas.OrganisationOut])
def get_organisations(
db: Session = Depends(database.get_db),
current_user: AuthUser = Depends(login_required),
approved: bool = True,
) -> list[organisation_schemas.OrganisationOut]:
"""Get a list of all organisations."""
return organisation_crud.get_organisations(db)
return organisation_crud.get_organisations(db, current_user, approved)


@router.get("/{org_id}", response_model=organisation_schemas.OrganisationOut)
Expand Down Expand Up @@ -85,3 +90,35 @@ async def delete_organisations(
):
"""Delete an organisation."""
return await organisation_crud.delete_organisation(db, organisation)


@router.post("/approve/")
async def approve_organisation(
organisation_id: int,
db: Session = Depends(database.get_db),
current_user: AuthUser = Depends(login_required),
):
"""Approve the organisation request made by the user.

The logged in user must be super admin to perform this action .
"""
# check if the current_user is the super admin
super_admin(db, current_user)
nrjadkry marked this conversation as resolved.
Show resolved Hide resolved
return await organisation_crud.approve_organisation(db, organisation_id)


@router.post("/add_admin/")
async def add_new_organisation_admin(
db: Session = Depends(database.get_db),
current_user: AuthUser = Depends(login_required),
user: AuthUser = Depends(user_exists),
nrjadkry marked this conversation as resolved.
Show resolved Hide resolved
organisation: DbOrganisation = Depends(org_exists),
):
"""Add a new organisation admin.

The logged in user must be either the owner of the organisation or a super admin.
"""
# check if the current_user is the organisation admin
org_admin(db, organisation.id, current_user)

return await organisation_crud.add_organisation_admin(db, user, organization)
70 changes: 70 additions & 0 deletions src/backend/app/users/user_deps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Copyright (c) 2022, 2023 Humanitarian OpenStreetMap Team
#
# This file is part of FMTM.
#
# FMTM is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# FMTM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with FMTM. If not, see <https:#www.gnu.org/licenses/>.
#

"""User dependencies for use in Depends."""


from typing import Union

from fastapi import Depends
from fastapi.exceptions import HTTPException
from loguru import logger as log
from sqlalchemy.orm import Session

from app.db.database import get_db
from app.db.db_models import DbUser
from app.models.enums import HTTPStatus
from app.users.user_crud import get_user, get_user_by_username


async def user_exists(
user_id: Union[str, int],
db: Session = Depends(get_db),
) -> DbUser:
"""Check if a user exists, else Error.

Args:
user_id (Union[str, int]): The user ID (integer) or username (string) to check.
db (Session, optional): The SQLAlchemy database session.

Returns:
DbUser: The user if found.

Raises:
HTTPException: Raised with a 404 status code if the user is not found.
"""
try:
user_id = int(user_id)
except ValueError:
pass

if isinstance(user_id, int):
log.debug(f"Getting user by ID: {user_id}")
db_user = await get_user(db, user_id)

if isinstance(user_id, str):
log.debug(f"Getting user by username: {user_id}")
db_user = await get_user_by_username(db, user_id)

if not db_user:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail=f"User {user_id} does not exist",
)

return db_user
2 changes: 1 addition & 1 deletion src/backend/migrations/002-add-profile-img.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ BEGIN;
ALTER TABLE IF EXISTS public.users
ADD COLUMN IF NOT EXISTS profile_img VARCHAR;
-- Commit the transaction
COMMIT;
COMMIT;
2 changes: 1 addition & 1 deletion src/backend/migrations/003-project-roles.sql
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ ALTER TABLE public.user_roles ALTER COLUMN "role" TYPE public.projectrole USING
ALTER TYPE public.projectrole OWNER TO fmtm;

-- Commit the transaction
COMMIT;
COMMIT;
14 changes: 14 additions & 0 deletions src/backend/migrations/004-organisation-odk-creds.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- ## Migration to:
-- * Add odk central credentials (str) to organisations table.
-- * Add the approved (bool) field to organisations table.

-- Start a transaction
BEGIN;

ALTER TABLE IF EXISTS public.organisations
ADD COLUMN IF NOT EXISTS approved BOOLEAN DEFAULT false,
ADD COLUMN IF NOT EXISTS odk_central_url VARCHAR,
ADD COLUMN IF NOT EXISTS odk_central_user VARCHAR,
ADD COLUMN IF NOT EXISTS odk_central_password VARCHAR;
-- Commit the transaction
COMMIT;
5 changes: 4 additions & 1 deletion src/backend/migrations/init/fmtm_base_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,10 @@ CREATE TABLE public.organisations (
logo character varying,
description character varying,
url character varying,
type public.organisationtype NOT NULL
type public.organisationtype NOT NULL,
odk_central_url character varying,
odk_central_user character varying,
odk_central_password character varying
);
ALTER TABLE public.organisations OWNER TO fmtm;
CREATE SEQUENCE public.organisations_id_seq
Expand Down
2 changes: 1 addition & 1 deletion src/backend/migrations/revert/002-add-profile-img.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ ALTER TABLE IF EXISTS public.users
DROP COLUMN IF EXISTS profile_img;

-- Commit the transaction
COMMIT;
COMMIT;
13 changes: 13 additions & 0 deletions src/backend/migrations/revert/004-organisation-odk-creds.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- Start a transaction
BEGIN;

-- Remove the odk central credentials columns and approved column
--- from the public.organisations table
ALTER TABLE IF EXISTS public.organisations
DROP COLUMN IF EXISTS approved,
DROP COLUMN IF EXISTS odk_central_url CASCADE,
DROP COLUMN IF EXISTS odk_central_user CASCADE,
DROP COLUMN IF EXISTS odk_central_password CASCADE;

-- Commit the transaction
COMMIT;