def delete(self, organisation_id, campaign_id): """ Unassigns an organization from an campaign --- tags: - campaigns 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: Unique organisation ID required: true type: integer default: 1 - name: campaign_id in: path description: Unique campaign ID required: true type: integer default: 1 responses: 200: description: Organisation and campaign unassociated successfully 401: description: Unauthorized - Invalid credentials 403: description: Forbidden - users have submitted mapping 404: description: Project not found 500: description: Internal Server Error """ try: if OrganisationService.can_user_manage_organisation( organisation_id, token_auth.current_user() ): CampaignService.delete_organisation_campaign( organisation_id, campaign_id ) return ( {"Success": "Organisation and campaign unassociated successfully"}, 200, ) else: return {"Error": "User is not a manager of the organisation"}, 403 except NotFound: return {"Error": "Organisation Campaign Not Found"}, 404 except Exception as e: error_msg = f"Organisation Campaigns DELETE - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": error_msg}, 500
def delete(self, campaign_id): """ Deletes an existing campaign --- tags: - campaigns produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token type: string required: true default: Token sessionTokenHere== - in: header name: Accept-Language description: Language user is requesting type: string required: true default: en - name: campaign_id in: path description: Campaign ID required: true type: integer default: 1 responses: 200: description: Campaign deleted successfully 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 500: description: Internal Server Error """ try: orgs_dto = OrganisationService.get_organisations_managed_by_user_as_dto( token_auth.current_user() ) if len(orgs_dto.organisations) < 1: raise ValueError("User not a Org Manager") except ValueError as e: error_msg = f"CampaignsRestAPI DELETE: {str(e)}" return {"Error": error_msg}, 403 try: campaign = CampaignService.get_campaign(campaign_id) CampaignService.delete_campaign(campaign.id) return {"Success": "Campaign deleted"}, 200 except NotFound: return {"Error": "Campaign not found"}, 404 except Exception as e: error_msg = f"Campaign DELETE - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": error_msg}, 500
def delete(self, project_id, campaign_id): """ Delete a campaign for a project --- tags: - campaigns produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - name: project_id in: path description: Unique project ID required: true type: integer default: 1 - name: campaign_id in: path description: Unique campaign ID required: true type: integer default: 1 responses: 200: description: Campaign assigned successfully 400: description: Client Error - Invalid Request 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 500: description: Internal Server Error """ try: ProjectAdminService.is_user_action_permitted_on_project( tm.authenticated_user_id, project_id) except ValueError as e: error_msg = f"ProjectsCampaignsAPI DELETE: {str(e)}" return {"Error": error_msg}, 403 try: CampaignService.delete_project_campaign(project_id, campaign_id) return {"Success": "Campaigns Deleted"}, 200 except NotFound: return {"Error": "Campaign Not Found"}, 404 except Exception as e: error_msg = f"ProjectsCampaignsAPI DELETE - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": error_msg}, 500
def get(self, campaign_id): """ Get an active campaign's information --- tags: - campaigns produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token type: string default: Token sessionTokenHere== - in: header name: Accept-Language description: Language user is requesting type: string required: true default: en - name: campaign_id in: path description: Campaign ID required: true type: integer default: 1 responses: 200: description: Campaign found 404: description: No Campaign found 500: description: Internal Server Error """ try: authenticated_user_id = token_auth.current_user() if authenticated_user_id: campaign = CampaignService.get_campaign_as_dto( campaign_id, authenticated_user_id ) else: campaign = CampaignService.get_campaign_as_dto(campaign_id, 0) return campaign.to_primitive(), 200 except NotFound: return {"Error": "No campaign found"}, 404 except Exception as e: error_msg = f"Campaign GET - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": error_msg}, 500
def get(self, project_id): """ Gets all campaigns for a project --- tags: - campaigns produces: - application/json parameters: - name: project_id in: path description: Unique project ID required: true type: integer default: 1 responses: 200: description: Campaign list returned successfully 400: description: Client Error - Invalid Request 401: description: Unauthorized - Invalid credentials 500: description: Internal Server Error """ try: campaigns = CampaignService.get_project_campaigns_as_dto( project_id) return campaigns.to_primitive(), 200 except NotFound: return {"Error": "No campaign found"}, 404 except Exception as e: error_msg = f"Messages GET - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": error_msg}, 500
def get(self): """ Get all active campaigns --- tags: - campaigns produces: - application/json responses: 200: description: All Campaigns returned successfully 500: description: Internal Server Error """ try: campaigns = CampaignService.get_all_campaigns() return campaigns.to_primitive(), 200 except Exception as e: error_msg = f"User GET - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": error_msg}, 500
def get(self, organisation_id): """ Returns all campaigns related to an organisation --- tags: - campaigns produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: false type: string default: Token sessionTokenHere== - name: organisation_id in: path description: Unique project ID required: true type: integer default: 1 responses: 200: description: Success 404: description: Organisation not found 500: description: Internal Server Error """ try: campaigns = CampaignService.get_organisation_campaigns_as_dto( organisation_id ) return campaigns.to_primitive(), 200 except NotFound: return {"Error": "No campaign found"}, 404 except Exception as e: error_msg = f"Organisation Campaigns GET - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": error_msg}, 500
def post(self, project_id, campaign_id): """ Assign a campaign for a project --- tags: - campaigns produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - name: project_id in: path description: Unique project ID required: true type: integer default: 1 - name: campaign_id in: path description: Unique campaign ID required: true type: integer default: 1 responses: 201: description: Campaign assigned successfully 400: description: Client Error - Invalid Request 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 500: description: Internal Server Error """ try: ProjectAdminService.is_user_action_permitted_on_project( tm.authenticated_user_id, project_id) except ValueError as e: error_msg = f"ProjectsCampaignsAPI POST: {str(e)}" return {"Error": error_msg}, 403 try: campaign_project_dto = CampaignProjectDTO() campaign_project_dto.campaign_id = campaign_id campaign_project_dto.project_id = project_id campaign_project_dto.validate() except DataError as e: current_app.logger.error(f"error validating request: {str(e)}") return str(e), 400 try: CampaignService.create_campaign_project(campaign_project_dto) message = "campaign with id {} assigned successfully for project with id {}".format( campaign_id, project_id) return ({"Success": message}, 200) except Exception as e: error_msg = f"ProjectsCampaignsAPI POST - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": error_msg}, 500
def patch(self, campaign_id): """ Updates an existing campaign --- tags: - campaigns produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token type: string required: true default: Token sessionTokenHere== - in: header name: Accept-Language description: Language user is requesting type: string required: true default: en - name: campaign_id in: path description: Campaign ID required: true type: integer default: 1 - in: body name: body required: true description: JSON object for updating a Campaign schema: properties: name: type: string example: HOT Campaign logo: type: string example: https://tasks.hotosm.org/assets/img/hot-tm-logo.svg url: type: string example: https://hotosm.org organisations: type: array items: type: integer default: [ 1 ] responses: 200: description: Campaign updated successfully 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 409: description: Resource duplication 500: description: Internal Server Error """ try: orgs_dto = OrganisationService.get_organisations_managed_by_user_as_dto( token_auth.current_user()) if len(orgs_dto.organisations) < 1: raise ValueError("User not a Org Manager") except ValueError as e: error_msg = f"CampaignsRestAPI PATCH: {str(e)}" return {"Error": error_msg}, 403 try: campaign_dto = CampaignDTO(request.get_json()) campaign_dto.validate() except DataError as e: current_app.logger.error(f"error validating request: {str(e)}") return str(e), 400 try: campaign = CampaignService.update_campaign(campaign_dto, campaign_id) return {"Success": "Campaign {} updated".format(campaign.id)}, 200 except NotFound: return {"Error": "Campaign not found"}, 404 except ValueError: error_msg = "Campaign PATCH - name already exists" return {"Error": error_msg}, 409 except Exception as e: error_msg = f"Campaign PATCH - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": error_msg}, 500
def post(self, organisation_id, campaign_id): """ Assigns a campaign to an organisation --- tags: - campaigns 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: Unique organisation ID required: true type: integer default: 1 - name: campaign_id in: path description: Unique campaign ID required: true type: integer default: 1 responses: 200: description: Organisation and campaign assigned successfully 401: description: Unauthorized - Invalid credentials 403: description: Forbidden - users have submitted mapping 404: description: Project not found 500: description: Internal Server Error """ try: if OrganisationService.can_user_manage_organisation( organisation_id, token_auth.current_user() ): if CampaignService.campaign_organisation_exists( campaign_id, organisation_id ): message = ( "Campaign {} is already assigned to organisation {}.".format( campaign_id, organisation_id ) ) return {"Error": message}, 400 CampaignService.create_campaign_organisation( organisation_id, campaign_id ) message = ( "campaign with id {} assigned for organisation with id {}".format( campaign_id, organisation_id ) ) return {"Success": message}, 200 else: return {"Error": "User is not a manager of the organisation"}, 403 except Exception as e: error_msg = f"Campaign Organisation POST - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": error_msg}, 500
def post(self): """ Creates a new campaign --- tags: - campaigns produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token type: string required: true default: Token sessionTokenHere== - in: header name: Accept-Language description: Language user is requesting type: string required: true default: en - in: body name: body required: true description: JSON object for creating a new Campaign schema: properties: name: type: string example: HOT Campaign logo: type: string example: https://tasks.hotosm.org/assets/img/hot-tm-logo.svg url: type: string example: https://hotosm.org organisations: type: array items: type: integer default: [ 1 ] responses: 200: description: New campaign created successfully 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 500: description: Internal Server Error """ try: orgs_dto = OrganisationService.get_organisations_managed_by_user_as_dto( tm.authenticated_user_id ) if len(orgs_dto.organisations) < 1: raise ValueError("User not a Org Manager") except ValueError as e: error_msg = f"CampaignsAllAPI POST: {str(e)}" return {"Error": error_msg}, 403 try: campaign_dto = NewCampaignDTO(request.get_json()) campaign_dto.validate() except DataError as e: current_app.logger.error(f"error validating request: {str(e)}") return str(e), 400 try: campaign = CampaignService.create_campaign(campaign_dto) return {"campaignId": campaign.id}, 200 except Exception as e: error_msg = f"Campaign POST - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": error_msg}, 500
def get_task_stats( start_date, end_date, org_id, org_name, campaign, project_id, country ): """ Creates tasks stats for a period using the TaskStatsDTO """ query = ( db.session.query( TaskHistory.task_id, TaskHistory.project_id, TaskHistory.action_text, func.DATE(TaskHistory.action_date).label("day"), ) .distinct( tuple_( TaskHistory.project_id, TaskHistory.task_id, TaskHistory.action_text ) ) .filter( TaskHistory.action == "STATE_CHANGE", or_( TaskHistory.action_text == "MAPPED", TaskHistory.action_text == "VALIDATED", TaskHistory.action_text == "BADIMAGERY", ), ) .order_by( TaskHistory.project_id, TaskHistory.task_id, TaskHistory.action_text, TaskHistory.action_date, ) ) if org_id: query = query.join(Project, Project.id == TaskHistory.project_id).filter( Project.organisation_id == org_id ) if org_name: try: organisation_id = OrganisationService.get_organisation_by_name( org_name ).id except NotFound: organisation_id = None query = query.join(Project, Project.id == TaskHistory.project_id).filter( Project.organisation_id == organisation_id ) if campaign: try: campaign_id = CampaignService.get_campaign_by_name(campaign).id except NotFound: campaign_id = None query = query.join( campaign_projects, campaign_projects.c.project_id == TaskHistory.project_id, ).filter(campaign_projects.c.campaign_id == campaign_id) if project_id: query = query.filter(TaskHistory.project_id.in_(project_id)) if country: # Unnest country column array. sq = Project.query.with_entities( Project.id, func.unnest(Project.country).label("country") ).subquery() query = query.filter(sq.c.country.ilike("%{}%".format(country))).filter( TaskHistory.project_id == sq.c.id ) query = query.subquery() date_query = db.session.query( func.DATE( func.generate_series(start_date, end_date, timedelta(days=1)) ).label("d_day") ).subquery() grouped_dates = ( db.session.query( date_query.c.d_day, query.c.action_text, func.count(query.c.action_text).label("cnt"), ) .join(date_query, date_query.c.d_day == query.c.day) .group_by(date_query.c.d_day, query.c.action_text) .order_by(date_query.c.d_day) ).subquery() mapped = ( db.session.query( grouped_dates.c.d_day, grouped_dates.c.action_text, grouped_dates.c.cnt ) .select_from(grouped_dates) .filter(grouped_dates.c.action_text == "MAPPED") .subquery() ) validated = ( db.session.query( grouped_dates.c.d_day, grouped_dates.c.action_text, grouped_dates.c.cnt ) .select_from(grouped_dates) .filter(grouped_dates.c.action_text == "VALIDATED") .subquery() ) badimagery = ( db.session.query( grouped_dates.c.d_day, grouped_dates.c.action_text, grouped_dates.c.cnt ) .select_from(grouped_dates) .filter(grouped_dates.c.action_text == "BADIMAGERY") .subquery() ) result = ( db.session.query( func.to_char(grouped_dates.c.d_day, "YYYY-MM-DD"), func.coalesce(mapped.c.cnt, 0).label("mapped"), func.coalesce(validated.c.cnt, 0).label("validated"), func.coalesce(badimagery.c.cnt, 0).label("badimagery"), ) .select_from(grouped_dates) .distinct(grouped_dates.c.d_day) .filter(grouped_dates.c.d_day is not None) .outerjoin(mapped, mapped.c.d_day == grouped_dates.c.d_day) .outerjoin(validated, validated.c.d_day == grouped_dates.c.d_day) .outerjoin(badimagery, badimagery.c.d_day == grouped_dates.c.d_day) ) day_stats_dto = list(map(StatsService.set_task_stats, result)) results_dto = TaskStatsDTO() results_dto.stats = day_stats_dto return results_dto