backend/api/organisations/resources.py (161 lines of code) (raw):
from distutils.util import strtobool
from flask_restful import Resource, request, current_app
from schematics.exceptions import DataError
from backend.models.dtos.organisation_dto import (
NewOrganisationDTO,
UpdateOrganisationDTO,
)
from backend.models.postgis.user import User
from backend.services.organisation_service import (
OrganisationService,
OrganisationServiceError,
NotFound,
)
from backend.models.postgis.statuses import OrganisationType
from backend.services.users.authentication_service import token_auth
class OrganisationsBySlugRestAPI(Resource):
def get(self, slug):
"""
Retrieves an organisation
---
tags:
- organisations
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
type: string
default: Token sessionTokenHere==
- name: slug
in: path
description: The unique organisation slug
required: true
type: string
default: hot
- in: query
name: omitManagerList
type: boolean
description: Set it to true if you don't want the managers list on the response.
default: False
responses:
200:
description: Organisation found
401:
description: Unauthorized - Invalid credentials
404:
description: Organisation not found
500:
description: Internal Server Error
"""
try:
authenticated_user_id = token_auth.current_user()
if authenticated_user_id is None:
user_id = 0
else:
user_id = authenticated_user_id
# Validate abbreviated.
omit_managers = strtobool(request.args.get("omitManagerList", "false"))
organisation_dto = OrganisationService.get_organisation_by_slug_as_dto(
slug, user_id, omit_managers
)
return organisation_dto.to_primitive(), 200
except NotFound:
return {"Error": "Organisation Not Found"}, 404
except Exception as e:
error_msg = f"Organisation GET - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": error_msg}, 500
class OrganisationsRestAPI(Resource):
@token_auth.login_required
def post(self):
"""
Creates a new organisation
---
tags:
- organisations
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- in: body
name: body
required: true
description: JSON object for creating organisation
schema:
properties:
name:
type: string
default: HOT
slug:
type: string
default: hot
logo:
type: string
default: https://cdn.hotosm.org/tasking-manager/uploads/1588741335578_hot-logo.png
url:
type: string
default: https://hotosm.org
managers:
type: array
items:
type: string
default: [
user_1,
user_2
]
responses:
201:
description: Organisation created successfully
400:
description: Client Error - Invalid Request
401:
description: Unauthorized - Invalid credentials
403:
description: Forbidden
402:
description: Duplicate Name - Organisation name already exists
500:
description: Internal Server Error
"""
request_user = User.get_by_id(token_auth.current_user())
if request_user.role != 1:
return {"Error": "Only admin users can create organisations."}, 403
try:
organisation_dto = NewOrganisationDTO(request.get_json())
if request_user.username not in organisation_dto.managers:
organisation_dto.managers.append(request_user.username)
organisation_dto.validate()
except DataError as e:
current_app.logger.error(f"error validating request: {str(e)}")
return str(e), 400
try:
org_id = OrganisationService.create_organisation(organisation_dto)
return {"organisationId": org_id}, 201
except OrganisationServiceError as e:
return str(e), 400
except Exception as e:
error_msg = f"Organisation PUT - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": error_msg}, 500
@token_auth.login_required
def delete(self, organisation_id):
"""
Deletes an organisation
---
tags:
- organisations
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- name: organisation_id
in: path
description: The unique organisation ID
required: true
type: integer
default: 1
responses:
200:
description: Organisation deleted
401:
description: Unauthorized - Invalid credentials
403:
description: Forbidden
404:
description: Organisation not found
500:
description: Internal Server Error
"""
if not OrganisationService.can_user_manage_organisation(
organisation_id, token_auth.current_user()
):
return {"Error": "User is not an admin for the org"}, 403
try:
OrganisationService.delete_organisation(organisation_id)
return {"Success": "Organisation deleted"}, 200
except OrganisationServiceError:
return {"Error": "Organisation has some projects"}, 403
except NotFound:
return {"Error": "Organisation Not Found"}, 404
except Exception as e:
error_msg = f"Organisation DELETE - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": error_msg}, 500
def get(self, organisation_id):
"""
Retrieves an organisation
---
tags:
- organisations
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
type: string
default: Token sessionTokenHere==
- name: organisation_id
in: path
description: The unique organisation ID
required: true
type: integer
default: 1
- in: query
name: omitManagerList
type: boolean
description: Set it to true if you don't want the managers list on the response.
default: False
responses:
200:
description: Organisation found
401:
description: Unauthorized - Invalid credentials
404:
description: Organisation not found
500:
description: Internal Server Error
"""
try:
authenticated_user_id = token_auth.current_user()
if authenticated_user_id is None:
user_id = 0
else:
user_id = authenticated_user_id
# Validate abbreviated.
omit_managers = strtobool(request.args.get("omitManagerList", "false"))
organisation_dto = OrganisationService.get_organisation_by_id_as_dto(
organisation_id, user_id, omit_managers
)
return organisation_dto.to_primitive(), 200
except NotFound:
return {"Error": "Organisation Not Found"}, 404
except Exception as e:
error_msg = f"Organisation GET - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": error_msg}, 500
@token_auth.login_required
def patch(self, organisation_id):
"""
Updates an organisation
---
tags:
- organisations
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- name: organisation_id
in: path
description: The unique organisation ID
required: true
type: integer
default: 1
- in: body
name: body
required: true
description: JSON object for updating an organisation
schema:
properties:
name:
type: string
default: HOT
slug:
type: string
default: HOT
logo:
type: string
default: https://tasks.hotosm.org/assets/img/hot-tm-logo.svg
url:
type: string
default: https://hotosm.org
managers:
type: array
items:
type: string
default: [
user_1,
user_2
]
responses:
201:
description: Organisation updated successfully
400:
description: Client Error - Invalid Request
401:
description: Unauthorized - Invalid credentials
403:
description: Forbidden
500:
description: Internal Server Error
"""
if not OrganisationService.can_user_manage_organisation(
organisation_id, token_auth.current_user()
):
return {"Error": "User is not an admin for the org"}, 403
try:
organisation_dto = UpdateOrganisationDTO(request.get_json())
organisation_dto.organisation_id = organisation_id
# Don't update organisation type and subscription_tier if request user is not an admin
if User.get_by_id(token_auth.current_user()).role != 1:
org = OrganisationService.get_organisation_by_id(organisation_id)
organisation_dto.type = OrganisationType(org.type).name
organisation_dto.subscription_tier = org.subscription_tier
organisation_dto.validate()
except DataError as e:
current_app.logger.error(f"error validating request: {str(e)}")
return str(e), 400
try:
OrganisationService.update_organisation(organisation_dto)
return {"Status": "Updated"}, 200
except NotFound as e:
return {"Error": str(e)}, 404
except OrganisationServiceError as e:
return str(e), 402
except Exception as e:
error_msg = f"Organisation PATCH - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": error_msg}, 500
class OrganisationsStatsAPI(Resource):
def get(self, organisation_id):
"""
Return statistics about projects and active tasks of an organisation
---
tags:
- organisations
produces:
- application/json
parameters:
- name: organisation_id
in: path
description: The unique organisation ID
required: true
type: integer
default: 1
responses:
200:
description: Organisation found
404:
description: Organisation not found
500:
description: Internal Server Error
"""
try:
OrganisationService.get_organisation_by_id(organisation_id)
organisation_dto = OrganisationService.get_organisation_stats(
organisation_id
)
return organisation_dto.to_primitive(), 200
except NotFound:
return {"Error": "Organisation Not Found"}, 404
except Exception as e:
error_msg = f"Organisation GET - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": error_msg}, 500
class OrganisationsAllAPI(Resource):
@token_auth.login_required(optional=True)
def get(self):
"""
List all organisations
---
tags:
- organisations
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
type: string
default: Token sessionTokenHere==
- name: manager_user_id
in: query
description: Filter projects on managers with this user_id
required: false
type: integer
- in: query
name: omitManagerList
type: boolean
description: Set it to true if you don't want the managers list on the response.
default: False
responses:
200:
description: Organisations found
400:
description: Client Error - Invalid Request
401:
description: Unauthorized - Invalid credentials
403:
description: Unauthorized - Not allowed
404:
description: Organisations not found
500:
description: Internal Server Error
"""
# Restrict some of the parameters to some permissions
authenticated_user_id = token_auth.current_user()
try:
manager_user_id = int(request.args.get("manager_user_id"))
except Exception:
manager_user_id = None
if manager_user_id is not None and not authenticated_user_id:
return (
{
"Error": "Unauthorized - Filter by manager_user_id is not allowed to unauthenticated requests"
},
403,
)
# Validate abbreviated.
omit_managers = strtobool(request.args.get("omitManagerList", "false"))
# Obtain organisations
try:
results_dto = OrganisationService.get_organisations_as_dto(
manager_user_id, authenticated_user_id, omit_managers
)
return results_dto.to_primitive(), 200
except NotFound:
return {"Error": "No organisations found"}, 404
except Exception as e:
error_msg = f"Organisations GET - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": error_msg}, 500