aboutsummaryrefslogtreecommitdiffstats
path: root/pysite/views/api
diff options
context:
space:
mode:
Diffstat (limited to 'pysite/views/api')
-rw-r--r--pysite/views/api/__init__.py0
-rw-r--r--pysite/views/api/bot/__init__.py0
-rw-r--r--pysite/views/api/bot/bigbrother.py118
-rw-r--r--pysite/views/api/bot/clean.py48
-rw-r--r--pysite/views/api/bot/doc.py98
-rw-r--r--pysite/views/api/bot/hiphopify.py170
-rw-r--r--pysite/views/api/bot/infractions.py572
-rw-r--r--pysite/views/api/bot/off_topic_names.py108
-rw-r--r--pysite/views/api/bot/settings.py56
-rw-r--r--pysite/views/api/bot/snake_cog/__init__.py0
-rw-r--r--pysite/views/api/bot/snake_cog/snake_facts.py28
-rw-r--r--pysite/views/api/bot/snake_cog/snake_idioms.py28
-rw-r--r--pysite/views/api/bot/snake_cog/snake_names.py48
-rw-r--r--pysite/views/api/bot/snake_cog/snake_quiz.py28
-rw-r--r--pysite/views/api/bot/snake_cog/special_snakes.py28
-rw-r--r--pysite/views/api/bot/tags.py107
-rw-r--r--pysite/views/api/bot/user.py166
-rw-r--r--pysite/views/api/bot/user_complete.py143
-rw-r--r--pysite/views/api/error_view.py40
-rw-r--r--pysite/views/api/healthcheck.py11
-rw-r--r--pysite/views/api/index.py10
-rw-r--r--pysite/views/api/robots_txt.py15
-rw-r--r--pysite/views/api/sitemap_xml.py11
23 files changed, 0 insertions, 1833 deletions
diff --git a/pysite/views/api/__init__.py b/pysite/views/api/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/pysite/views/api/__init__.py
+++ /dev/null
diff --git a/pysite/views/api/bot/__init__.py b/pysite/views/api/bot/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/pysite/views/api/bot/__init__.py
+++ /dev/null
diff --git a/pysite/views/api/bot/bigbrother.py b/pysite/views/api/bot/bigbrother.py
deleted file mode 100644
index 89697811..00000000
--- a/pysite/views/api/bot/bigbrother.py
+++ /dev/null
@@ -1,118 +0,0 @@
-import json
-
-from flask import jsonify
-from schema import And, Optional, Schema
-
-from pysite.base_route import APIView
-from pysite.constants import ValidationTypes
-from pysite.decorators import api_key, api_params
-from pysite.mixins import DBMixin
-
-
-GET_SCHEMA = Schema({
- # This is passed as a GET parameter, so it has to be a string
- Optional('user_id'): And(str, str.isnumeric, error="`user_id` must be a numeric string")
-})
-
-POST_SCHEMA = Schema({
- 'user_id': And(str, str.isnumeric, error="`user_id` must be a numeric string"),
- 'channel_id': And(str, str.isnumeric, error="`channel_id` must be a numeric string")
-})
-
-DELETE_SCHEMA = Schema({
- 'user_id': And(str, str.isnumeric, error="`user_id` must be a numeric string")
-})
-
-
-NOT_A_NUMBER_JSON = json.dumps({
- 'error_message': "The given `user_id` parameter is not a valid number"
-})
-NOT_FOUND_JSON = json.dumps({
- 'error_message': "No entry for the requested user ID could be found."
-})
-
-
-class BigBrotherView(APIView, DBMixin):
- path = '/bot/bigbrother'
- name = 'bot.bigbrother'
- table_name = 'watched_users'
-
- @api_key
- @api_params(schema=GET_SCHEMA, validation_type=ValidationTypes.params)
- def get(self, params):
- """
- Without query parameters, returns a list of all monitored users.
- A parameter `user_id` can be specified to return a single entry,
- or a dictionary with the string field 'error_message' that tells why it failed.
-
- If the returned status is 200, has got either a list of entries
- or a single object (see above).
-
- If the returned status is 400, the `user_id` parameter was incorrectly specified.
- If the returned status is 404, the given `user_id` could not be found.
- See the 'error_message' field in the JSON response for more information.
-
- The user ID must be provided as query parameter.
- API key must be provided as header.
- """
-
- user_id = params.get('user_id')
- if user_id is not None:
- data = self.db.get(self.table_name, user_id)
- if data is None:
- return NOT_FOUND_JSON, 404
- return jsonify(data)
-
- else:
- data = self.db.pluck(self.table_name, ('user_id', 'channel_id')) or []
- return jsonify(data)
-
- @api_key
- @api_params(schema=POST_SCHEMA, validation_type=ValidationTypes.json)
- def post(self, data):
- """
- Adds a new entry to the database.
- Entries take the following form:
- {
- "user_id": ..., # The user ID of the user being monitored, as a string.
- "channel_id": ... # The channel ID that the user's messages will be relayed to, as a string.
- }
-
- If an entry for the given `user_id` already exists, it will be updated with the new channel ID.
-
- Returns 204 (ok, empty response) on success.
-
- Data must be provided as JSON.
- API key must be provided as header.
- """
-
- self.db.insert(
- self.table_name,
- {
- 'user_id': data['user_id'],
- 'channel_id': data['channel_id']
- },
- conflict='update'
- )
-
- return '', 204
-
- @api_key
- @api_params(schema=DELETE_SCHEMA, validation_type=ValidationTypes.params)
- def delete(self, params):
- """
- Removes an entry for the given `user_id`.
-
- Returns 204 (ok, empty response) on success.
- Returns 400 if the given `user_id` is invalid.
-
- The user ID must be provided as query parameter.
- API key must be provided as header.
- """
-
- self.db.delete(
- self.table_name,
- params['user_id']
- )
-
- return '', 204
diff --git a/pysite/views/api/bot/clean.py b/pysite/views/api/bot/clean.py
deleted file mode 100644
index 82d1e735..00000000
--- a/pysite/views/api/bot/clean.py
+++ /dev/null
@@ -1,48 +0,0 @@
-from flask import jsonify
-from schema import Schema
-
-from pysite.base_route import APIView
-from pysite.constants import ValidationTypes
-from pysite.decorators import api_key, api_params
-from pysite.mixins import DBMixin
-
-POST_SCHEMA = Schema({
- 'log_data': [
- {
- "author": str,
- "user_id": str,
- "content": str,
- "role_id": str,
- "timestamp": str,
- "embeds": object,
- "attachments": [str],
- }
- ]
-})
-
-
-class CleanView(APIView, DBMixin):
- path = '/bot/clean'
- name = 'bot.clean'
- table_name = 'clean_logs'
-
- @api_key
- @api_params(schema=POST_SCHEMA, validation_type=ValidationTypes.json)
- def post(self, data):
- """
- Receive some log_data from a bulk deletion,
- and store it in the database.
-
- Returns an ID which can be used to get the data
- from the /bot/clean_logs/<id> endpoint.
- """
-
- # Insert and return the id to use for GET
- insert = self.db.insert(
- self.table_name,
- {
- "log_data": data["log_data"]
- }
- )
-
- return jsonify({"log_id": insert['generated_keys'][0]})
diff --git a/pysite/views/api/bot/doc.py b/pysite/views/api/bot/doc.py
deleted file mode 100644
index c1d6020c..00000000
--- a/pysite/views/api/bot/doc.py
+++ /dev/null
@@ -1,98 +0,0 @@
-from flask import jsonify
-from schema import Optional, Schema
-
-from pysite.base_route import APIView
-from pysite.constants import ValidationTypes
-from pysite.decorators import api_key, api_params
-from pysite.mixins import DBMixin
-
-
-GET_SCHEMA = Schema([
- {
- Optional("package"): str
- }
-])
-
-POST_SCHEMA = Schema([
- {
- "package": str,
- "base_url": str,
- "inventory_url": str
- }
-])
-
-DELETE_SCHEMA = Schema([
- {
- "package": str
- }
-])
-
-
-class DocView(APIView, DBMixin):
- path = "/bot/docs"
- name = "bot.docs"
- table_name = "pydoc_links"
-
- @api_key
- @api_params(schema=GET_SCHEMA, validation_type=ValidationTypes.params)
- def get(self, params=None):
- """
- Fetches documentation metadata from the database.
-
- - If `package` parameters are provided, fetch metadata
- for the given packages, or `[]` if none matched.
-
- - If `package` is not provided, return all
- packages known to the database.
-
- Data must be provided as params.
- API key must be provided as header.
- """
-
- if params:
- packages = (param['package'] for param in params if 'package' in param)
- data = self.db.get_all(self.table_name, *packages, index='package') or []
- else:
- data = self.db.pluck(self.table_name, ("package", "base_url", "inventory_url")) or []
-
- return jsonify(data)
-
- @api_key
- @api_params(schema=POST_SCHEMA, validation_type=ValidationTypes.json)
- def post(self, json_data):
- """
- Adds one or more new documentation metadata objects.
-
- If the `package` passed in the data
- already exists, it will be updated instead.
-
- Data must be provided as JSON.
- API key must be provided as header.
- """
-
- packages_to_insert = (
- {
- "package": json_object["package"],
- "base_url": json_object["base_url"],
- "inventory_url": json_object["inventory_url"]
- } for json_object in json_data
- )
-
- self.db.insert(self.table_name, *packages_to_insert, conflict="update")
- return jsonify({"success": True})
-
- @api_key
- @api_params(schema=DELETE_SCHEMA, validation_type=ValidationTypes.json)
- def delete(self, json_data):
- """
- Deletes a documentation metadata object.
- Expects the `package` to be deleted to
- be specified as a request parameter.
-
- Data must be provided as params.
- API key must be provided as header.
- """
-
- packages = (json_object["package"]for json_object in json_data)
- changes = self.db.delete(self.table_name, *packages, return_changes=True)
- return jsonify(changes)
diff --git a/pysite/views/api/bot/hiphopify.py b/pysite/views/api/bot/hiphopify.py
deleted file mode 100644
index ce4dfa4a..00000000
--- a/pysite/views/api/bot/hiphopify.py
+++ /dev/null
@@ -1,170 +0,0 @@
-import logging
-
-from flask import jsonify
-from schema import Optional, Schema
-
-from pysite.base_route import APIView
-from pysite.constants import ValidationTypes
-from pysite.decorators import api_key, api_params
-from pysite.mixins import DBMixin
-from pysite.utils.time import is_expired, parse_duration
-
-log = logging.getLogger(__name__)
-
-GET_SCHEMA = Schema({
- "user_id": str
-})
-
-POST_SCHEMA = Schema({
- "user_id": str,
- "duration": str,
- Optional("forced_nick"): str
-})
-
-DELETE_SCHEMA = Schema({
- "user_id": str
-})
-
-
-class HiphopifyView(APIView, DBMixin):
- path = "/bot/hiphopify"
- name = "bot.hiphopify"
- prison_table = "hiphopify"
- name_table = "hiphopify_namelist"
-
- @api_key
- @api_params(schema=GET_SCHEMA, validation_type=ValidationTypes.params)
- def get(self, params=None):
- """
- Check if the user is currently in hiphop-prison.
-
- If user is currently servin' his sentence in the big house,
- return the name stored in the forced_nick column of prison_table.
-
- If user cannot be found in prison, or
- if his sentence has expired, return nothing.
-
- Data must be provided as params.
- API key must be provided as header.
- """
-
- user_id = params.get("user_id")
-
- log.debug(f"Checking if user ({user_id}) is permitted to change their nickname.")
- data = self.db.get(self.prison_table, user_id) or {}
-
- if data and data.get("end_timestamp"):
- log.trace("User exists in the prison_table.")
- end_time = data.get("end_timestamp")
- if is_expired(end_time):
- log.trace("...But their sentence has already expired.")
- data = {} # Return nothing if the sentence has expired.
-
- return jsonify(data)
-
- @api_key
- @api_params(schema=POST_SCHEMA, validation_type=ValidationTypes.json)
- def post(self, json_data):
- """
- Imprisons a user in hiphop-prison.
-
- If a forced_nick was provided by the caller, the method will force
- this nick. If not, a random hiphop nick will be selected from the
- name_table.
-
- Data must be provided as JSON.
- API key must be provided as header.
- """
-
- user_id = json_data.get("user_id")
- duration = json_data.get("duration")
- forced_nick = json_data.get("forced_nick")
-
- log.debug(f"Attempting to imprison user ({user_id}).")
-
- # Get random name and picture if no forced_nick was provided.
- if not forced_nick:
- log.trace("No forced_nick provided. Fetching a random rapper name and image.")
- rapper_data = self.db.sample(self.name_table, 1)[0]
- forced_nick = rapper_data.get('name')
-
- # If forced nick was provided, try to look up the forced_nick in the database.
- # If a match cannot be found, just default to Lil' Jon for the image.
- else:
- log.trace(f"Forced nick provided ({forced_nick}). Trying to match it with the database.")
- rapper_data = (
- self.db.get(self.name_table, forced_nick)
- or self.db.get(self.name_table, "Lil' Joseph")
- )
-
- image_url = rapper_data.get('image_url')
- log.trace(f"Using the nickname {forced_nick} and the image_url {image_url}.")
-
- # Convert duration to valid timestamp
- try:
- log.trace("Parsing the duration and converting it to a timestamp")
- end_timestamp = parse_duration(duration)
- except ValueError:
- log.warning(f"The duration could not be parsed, or was invalid. The duration was '{duration}'.")
- return jsonify({
- "success": False,
- "error_message": "Invalid duration"
- })
-
- log.debug("Everything seems to be in order, inserting the data into the prison_table.")
- self.db.insert(
- self.prison_table,
- {
- "user_id": user_id,
- "end_timestamp": end_timestamp,
- "forced_nick": forced_nick
- },
- conflict="update" # If it exists, update it.
- )
-
- return jsonify({
- "success": True,
- "end_timestamp": end_timestamp,
- "forced_nick": forced_nick,
- "image_url": image_url
- })
-
- @api_key
- @api_params(schema=DELETE_SCHEMA, validation_type=ValidationTypes.json)
- def delete(self, json_data):
- """
- Releases a user from hiphop-prison.
-
- Data must be provided as JSON.
- API key must be provided as header.
- """
-
- user_id = json_data.get("user_id")
-
- log.debug(f"Attempting to release user ({user_id}) from hiphop-prison.")
- prisoner_data = self.db.get(self.prison_table, user_id)
- sentence_expired = None
-
- log.trace(f"Checking if the user ({user_id}) is currently in hiphop-prison.")
- if prisoner_data and prisoner_data.get("end_timestamp"):
- sentence_expired = is_expired(prisoner_data['end_timestamp'])
-
- if prisoner_data and not sentence_expired:
- log.debug("User is currently in hiphop-prison. Deleting the record and releasing the prisoner.")
- self.db.delete(
- self.prison_table,
- user_id
- )
- return jsonify({"success": True})
- elif not prisoner_data:
- log.warning(f"User ({user_id}) is not currently in hiphop-prison.")
- return jsonify({
- "success": False,
- "error_message": "User is not currently in hiphop-prison!"
- })
- elif sentence_expired:
- log.warning(f"User ({user_id}) was in hiphop-prison, but has already been released.")
- return jsonify({
- "success": False,
- "error_message": "User has already been released from hiphop-prison!"
- })
diff --git a/pysite/views/api/bot/infractions.py b/pysite/views/api/bot/infractions.py
deleted file mode 100644
index eee40b82..00000000
--- a/pysite/views/api/bot/infractions.py
+++ /dev/null
@@ -1,572 +0,0 @@
-"""
-INFRACTIONS API
-
-"GET" endpoints in this API may take the following optional parameters, depending on the endpoint:
- - active: filters infractions that are active (true), expired (false), or either (not present/any)
- - expand: expands the result data with the information about the users (slower)
- - dangling: filters infractions that are active, or inactive infractions that have not been closed manually.
- - search: filters the "reason" field to match the given RE2 query.
-
-Infraction Schema:
- This schema is used when an infraction's data is returned.
-
- Root object:
- "id" (str): the UUID of the infraction.
- "inserted_at" (str): the date and time of the creation of this infraction (RFC1123 format).
- "expires_at" (str): the date and time of the expiration of this infraction (RC1123 format), may be null.
- The significance of this field being null depends on the type of infraction. Duration-based infractions
- have a "null" expiration if they are permanent. Other infraction types do not have expirations.
- "active" (bool): whether the infraction is still active. Note that the check for expiration of
- duration-based infractions is done by the API, so you should check for expiration using this "active" field.
- "user" (object): the user to which the infraction was applied.
- "user_id" (str): the Discord ID of the user.
- "username" (optional str): the username of the user. This field is only present if the query was expanded.
- "discriminator" (optional int): the username discriminator of the user. This field is only present if the
- query was expanded.
- "avatar" (optional str): the avatar URL of the user. This field is only present if the query was expanded.
- "actor" (object): the user which applied the infraction.
- This object uses the same schema as the "user" field.
- "type" (str): the type of the infraction.
- "reason" (str): the reason for the infraction.
-
-
-Endpoints:
-
- GET /bot/infractions
- Gets a list of all infractions, regardless of type or user.
- Parameters: "active", "expand", "dangling", "search".
- This endpoint returns an array of infraction objects.
-
- GET /bot/infractions/user/<user_id>
- Gets a list of all infractions for a user.
- Parameters: "active", "expand", "search".
- This endpoint returns an array of infraction objects.
-
- GET /bot/infractions/type/<type>
- Gets a list of all infractions of the given type (ban, mute, etc.)
- Parameters: "active", "expand", "search".
- This endpoint returns an array of infraction objects.
-
- GET /bot/infractions/user/<user_id>/<type>
- Gets a list of all infractions of the given type for a user.
- Parameters: "active", "expand", "search".
- This endpoint returns an array of infraction objects.
-
- GET /bot/infractions/user/<user_id>/<type>/current
- Gets the active infraction (if any) of the given type for a user.
- Parameters: "expand".
- This endpoint returns an object with the "infraction" key, which is either set to null (no infraction)
- or the query's corresponding infraction. It will not return an infraction if the type of the infraction
- isn't duration-based (e.g. kick, warning, etc.)
-
- GET /bot/infractions/id/<infraction_id>
- Gets the infraction (if any) for the given ID.
- Parameters: "expand".
- This endpoint returns an object with the "infraction" key, which is either set to null (no infraction)
- or the infraction corresponding to the ID.
-
- POST /bot/infractions
- Creates an infraction for a user.
- Parameters (JSON payload):
- "type" (str): the type of the infraction (must be a valid infraction type).
- "reason" (str): the reason of the infraction.
- "user_id" (str): the Discord ID of the user who is being given the infraction.
- "actor_id" (str): the Discord ID of the user who submitted the infraction.
- "duration" (optional str): the duration of the infraction. This is ignored for infractions
- which are not duration-based. For other infraction types, omitting this field may imply permanence.
- "expand" (optional bool): whether to expand the infraction user data once the infraction is inserted and returned.
-
- PATCH /bot/infractions
- Updates an infractions.
- Parameters (JSON payload):
- "id" (str): the ID of the infraction to update.
- "reason" (optional str): if provided, the new reason for the infraction.
- "duration" (optional str): if provided, updates the expiration of the infraction to the time of UPDATING
- plus the duration. If set to null, the expiration is also set to null (may imply permanence).
- "active" (optional bool): if provided, activates or deactivates the infraction. This does not do anything
- if the infraction isn't duration-based, or if the infraction has already expired. This marks the infraction
- as closed.
- "expand" (optional bool): whether to expand the infraction user data once the infraction is updated and returned.
-"""
-
-import datetime
-from typing import NamedTuple
-
-import rethinkdb
-from flask import jsonify
-from schema import Optional, Or, Schema
-
-from pysite.base_route import APIView
-from pysite.constants import ErrorCodes, ValidationTypes
-from pysite.decorators import api_key, api_params
-from pysite.mixins import DBMixin
-from pysite.utils.time import parse_duration
-
-
-class InfractionType(NamedTuple):
- timed_infraction: bool # whether the infraction is active until it expires.
-
-
-RFC1123_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"
-EXCLUDED_FIELDS = "user_id", "actor_id", "closed", "_timed"
-INFRACTION_ORDER = rethinkdb.desc("active"), rethinkdb.desc("inserted_at")
-
-INFRACTION_TYPES = {
- "warning": InfractionType(timed_infraction=False),
- "mute": InfractionType(timed_infraction=True),
- "ban": InfractionType(timed_infraction=True),
- "kick": InfractionType(timed_infraction=False),
- "superstar": InfractionType(timed_infraction=True) # hiphopify
-}
-
-GET_SCHEMA = Schema({
- Optional("active"): str,
- Optional("expand"): str,
- Optional("dangling"): str,
- Optional("search"): str
-})
-
-GET_ACTIVE_SCHEMA = Schema({
- Optional("expand"): str
-})
-
-CREATE_INFRACTION_SCHEMA = Schema({
- "type": lambda tp: tp in INFRACTION_TYPES,
- "reason": Or(str, None),
- "user_id": str, # Discord user ID
- "actor_id": str, # Discord user ID
- Optional("duration"): str, # If not provided, may imply permanence depending on the infraction
- Optional("expand"): bool
-})
-
-UPDATE_INFRACTION_SCHEMA = Schema({
- "id": str,
- Optional("reason"): Or(str, None),
- Optional("duration"): Or(str, None),
- Optional("active"): bool
-})
-
-IMPORT_INFRACTIONS_SCHEMA = Schema([
- {
- "id": str,
- "active": bool,
- "actor": {
- "id": str
- },
- "created_at": str,
- "expires_at": Or(str, None),
- "reason": Or(str, None),
- "type": {
- "name": str
- },
- "user": {
- "id": str
- }
- }
-], ignore_extra_keys=True)
-
-
-class InfractionsView(APIView, DBMixin):
- path = "/bot/infractions"
- name = "bot.infractions"
- table_name = "bot_infractions"
-
- @api_key
- @api_params(schema=GET_SCHEMA, validation_type=ValidationTypes.params)
- def get(self, params: dict = None):
- if "dangling" in params:
- return _infraction_list_filtered(self, params, {"_timed": True, "closed": False})
- else:
- return _infraction_list_filtered(self, params, {})
-
- @api_key
- @api_params(schema=CREATE_INFRACTION_SCHEMA, validation_type=ValidationTypes.json)
- def post(self, data):
- deactivate_infraction_query = None
-
- infraction_type = data["type"]
- user_id = data["user_id"]
- actor_id = data["actor_id"]
- reason = data["reason"]
- duration_str = data.get("duration")
- expand = data.get("expand")
- expires_at = None
- inserted_at = datetime.datetime.now(tz=datetime.timezone.utc)
-
- if infraction_type not in INFRACTION_TYPES:
- return self.error(ErrorCodes.incorrect_parameters, "Invalid infraction type.")
-
- # check if the user already has an active infraction of this type
- # if so, we need to disable that infraction and create a new infraction
- if INFRACTION_TYPES[infraction_type].timed_infraction:
- active_infraction_query = \
- self.db.query(self.table_name).merge(_merge_active_check()) \
- .filter({"user_id": user_id, "type": infraction_type, "active": True}) \
- .limit(1).nth(0).default(None)
-
- active_infraction = self.db.run(active_infraction_query)
- if active_infraction:
- deactivate_infraction_query = \
- self.db.query(self.table_name) \
- .get(active_infraction["id"]) \
- .update({"active": False, "closed": True})
-
- if duration_str:
- try:
- expires_at = parse_duration(duration_str)
- except ValueError:
- return self.error(
- ErrorCodes.incorrect_parameters,
- "Invalid duration format."
- )
-
- infraction_insert_doc = {
- "actor_id": actor_id,
- "user_id": user_id,
- "type": infraction_type,
- "reason": reason,
- "inserted_at": inserted_at,
- "expires_at": expires_at
- }
-
- infraction_id = self.db.insert(self.table_name, infraction_insert_doc)["generated_keys"][0]
-
- if deactivate_infraction_query:
- self.db.run(deactivate_infraction_query)
-
- query = self.db.query(self.table_name).get(infraction_id) \
- .merge(_merge_expand_users(self, expand)) \
- .merge(_merge_active_check()) \
- .without(*EXCLUDED_FIELDS).default(None)
- return jsonify({
- "infraction": self.db.run(query)
- })
-
- @api_key
- @api_params(schema=UPDATE_INFRACTION_SCHEMA, validation_type=ValidationTypes.json)
- def patch(self, data):
- expand = data.get("expand")
- update_collection = {
- "id": data["id"]
- }
-
- if "reason" in data:
- update_collection["reason"] = data["reason"]
-
- if "active" in data:
- update_collection["active"] = data["active"]
- update_collection["closed"] = not data["active"]
-
- if "duration" in data:
- duration_str = data["duration"]
- if duration_str is None:
- update_collection["expires_at"] = None
- else:
- try:
- update_collection["expires_at"] = parse_duration(duration_str)
- except ValueError:
- return self.error(
- ErrorCodes.incorrect_parameters,
- "Invalid duration format."
- )
-
- query_update = self.db.query(self.table_name).update(update_collection)
- result_update = self.db.run(query_update)
-
- if not result_update["replaced"]:
- return jsonify({
- "success": False,
- "error_message": "Unknown infraction / nothing was changed."
- })
-
- # return the updated infraction
- query = self.db.query(self.table_name).get(data["id"]) \
- .merge(_merge_expand_users(self, expand)) \
- .merge(_merge_active_check()) \
- .without(*EXCLUDED_FIELDS).default(None)
- infraction = self.db.run(query)
-
- return jsonify({
- "infraction": infraction,
- "success": True
- })
-
-
-class InfractionById(APIView, DBMixin):
- path = "/bot/infractions/id/<string:infraction_id>"
- name = "bot.infractions.id"
- table_name = "bot_infractions"
-
- @api_key
- @api_params(schema=GET_ACTIVE_SCHEMA, validation_type=ValidationTypes.params)
- def get(self, params, infraction_id):
- params = params or {}
- expand = parse_bool(params.get("expand"), default=False)
-
- query = self.db.query(self.table_name).get(infraction_id) \
- .merge(_merge_expand_users(self, expand)) \
- .merge(_merge_active_check()) \
- .without(*EXCLUDED_FIELDS).default(None)
- return jsonify({
- "infraction": self.db.run(query)
- })
-
-
-class ListInfractionsByUserView(APIView, DBMixin):
- path = "/bot/infractions/user/<string:user_id>"
- name = "bot.infractions.user"
- table_name = "bot_infractions"
-
- @api_key
- @api_params(schema=GET_SCHEMA, validation_type=ValidationTypes.params)
- def get(self, params, user_id):
- return _infraction_list_filtered(self, params, {
- "user_id": user_id
- })
-
-
-class ListInfractionsByTypeView(APIView, DBMixin):
- path = "/bot/infractions/type/<string:type>"
- name = "bot.infractions.type"
- table_name = "bot_infractions"
-
- @api_key
- @api_params(schema=GET_SCHEMA, validation_type=ValidationTypes.params)
- def get(self, params, type):
- return _infraction_list_filtered(self, params, {
- "type": type
- })
-
-
-class ListInfractionsByTypeAndUserView(APIView, DBMixin):
- path = "/bot/infractions/user/<string:user_id>/<string:type>"
- name = "bot.infractions.user.type"
- table_name = "bot_infractions"
-
- @api_key
- @api_params(schema=GET_SCHEMA, validation_type=ValidationTypes.params)
- def get(self, params, user_id, type):
- return _infraction_list_filtered(self, params, {
- "user_id": user_id,
- "type": type
- })
-
-
-class CurrentInfractionByTypeAndUserView(APIView, DBMixin):
- path = "/bot/infractions/user/<string:user_id>/<string:infraction_type>/current"
- name = "bot.infractions.user.type.current"
- table_name = "bot_infractions"
-
- @api_key
- @api_params(schema=GET_ACTIVE_SCHEMA, validation_type=ValidationTypes.params)
- def get(self, params, user_id, infraction_type):
- params = params or {}
- expand = parse_bool(params.get("expand"), default=False)
-
- query_filter = {
- "user_id": user_id,
- "type": infraction_type
- }
- query = _merged_query(self, expand, query_filter).filter({
- "active": True
- }).order_by(rethinkdb.desc("data")).limit(1).nth(0).default(None)
- return jsonify({
- "infraction": self.db.run(query)
- })
-
-
-class ImportRowboatInfractionsView(APIView, DBMixin):
- path = "/bot/infractions/import"
- name = "bot.infractions.import"
- table_name = "bot_infractions"
-
- @api_key
- @api_params(schema=IMPORT_INFRACTIONS_SCHEMA, validation_type=ValidationTypes.json)
- def post(self, data):
- # keep track of the un-bans, to apply after the import is complete.
- unbans = []
- infractions = []
-
- # previously imported infractions
- imported_infractions = self.db.run(
- self.db.query(self.table_name).filter(
- lambda row: row.has_fields("legacy_rowboat_id")
- ).fold([], lambda acc, row: acc.append(row["legacy_rowboat_id"])).coerce_to("array")
- )
-
- for rowboat_infraction_data in data:
- legacy_rowboat_id = rowboat_infraction_data["id"]
- if legacy_rowboat_id in imported_infractions:
- continue
- infraction_type = rowboat_infraction_data["type"]["name"]
- if infraction_type == "unban":
- unbans.append(rowboat_infraction_data)
- continue
- # adjust infraction types
- if infraction_type == "tempmute":
- infraction_type = "mute"
- if infraction_type == "tempban":
- infraction_type = "ban"
- if infraction_type not in INFRACTION_TYPES:
- # unknown infraction type
- continue
- active = rowboat_infraction_data["active"]
- reason = rowboat_infraction_data["reason"] or "<No reason>"
- user_id = rowboat_infraction_data["user"]["id"]
- actor_id = rowboat_infraction_data["actor"]["id"]
- inserted_at_str = rowboat_infraction_data["created_at"]
- try:
- inserted_at = parse_rfc1123(inserted_at_str)
- except ValueError:
- continue
- expires_at_str = rowboat_infraction_data["expires_at"]
- if expires_at_str is not None:
- try:
- expires_at = parse_rfc1123(expires_at_str)
- except ValueError:
- continue
- else:
- expires_at = None
- infractions.append({
- "legacy_rowboat_id": legacy_rowboat_id,
- "active": active,
- "reason": reason,
- "user_id": user_id,
- "actor_id": actor_id,
- "inserted_at": inserted_at,
- "expires_at": expires_at,
- "type": infraction_type
- })
-
- insertion_query = self.db.query(self.table_name).insert(infractions)
- inserted_count = self.db.run(insertion_query)["inserted"]
-
- # apply unbans
- for unban_data in unbans:
- inserted_at_str = unban_data["created_at"]
- user_id = unban_data["user"]["id"]
- try:
- inserted_at = parse_rfc1123(inserted_at_str)
- except ValueError:
- continue
- self.db.run(
- self.db.query(self.table_name).filter(
- lambda row: (row["user_id"].eq(user_id)) &
- (row["type"].eq("ban")) &
- (row["inserted_at"] < inserted_at)
- ).pluck("id").merge(lambda row: {
- "active": False
- }).coerce_to("array").for_each(lambda doc: self.db.query(self.table_name).get(doc["id"]).update(doc))
- )
-
- return jsonify({
- "success": True,
- "inserted_count": inserted_count
- })
-
-
-def _infraction_list_filtered(view, params=None, query_filter=None):
- params = params or {}
- query_filter = query_filter or {}
- active = parse_bool(params.get("active"))
- expand = parse_bool(params.get("expand"), default=False)
- search = params.get("search")
-
- if active is not None:
- query_filter["active"] = active
-
- query = _merged_query(view, expand, query_filter)
-
- if search is not None:
- query = query.filter(
- lambda row: rethinkdb.branch(
- row["reason"].eq(None),
- False,
- row["reason"].match(search)
- )
- )
-
- query = query.order_by(*INFRACTION_ORDER)
-
- return jsonify(view.db.run(query.coerce_to("array")))
-
-
-def _merged_query(view, expand, query_filter):
- return view.db.query(view.table_name).merge(_merge_active_check()).filter(query_filter) \
- .merge(_merge_expand_users(view, expand)).without(*EXCLUDED_FIELDS)
-
-
-def _merge_active_check():
- # Checks if the "closed" field has been set to true (manual infraction removal).
- # If not, the "active" field is set to whether the infraction has expired.
- def _merge(row):
- return {
- "active":
- rethinkdb.branch(
- _is_timed_infraction(row["type"]),
- rethinkdb.branch(
- (row["closed"].default(False).eq(True)) | (row["active"].default(True).eq(False)),
- False,
- rethinkdb.branch(
- row["expires_at"].eq(None),
- True,
- row["expires_at"] > rethinkdb.now()
- )
- ),
- False
- ),
- "closed": row["closed"].default(False),
- "_timed": _is_timed_infraction(row["type"])
- }
-
- return _merge
-
-
-def _merge_expand_users(view, expand):
- def _do_expand(user_id):
- if not user_id:
- return None
- # Expands the user information, if it is in the database.
-
- if expand:
- return view.db.query("users").get(user_id).default({
- "user_id": user_id
- })
-
- return {
- "user_id": user_id
- }
-
- def _merge(row):
- return {
- "user": _do_expand(row["user_id"].default(None)),
- "actor": _do_expand(row["actor_id"].default(None))
- }
-
- return _merge
-
-
-def _is_timed_infraction(type_var):
- # this method generates an ReQL expression to check if the given type
- # is a "timed infraction" (i.e it can expire or be permanent)
-
- timed_infractions = filter(lambda key: INFRACTION_TYPES[key].timed_infraction, INFRACTION_TYPES.keys())
- expr = rethinkdb.expr(False)
- for infra_type in timed_infractions:
- expr = expr | type_var.eq(infra_type)
- return expr
-
-
-def parse_rfc1123(time_str):
- return datetime.datetime.strptime(time_str, RFC1123_FORMAT).replace(tzinfo=datetime.timezone.utc)
-
-
-def parse_bool(a_string, default=None):
- # Not present, null or any: returns default (defaults to None)
- # false, no, or 0: returns False
- # anything else: True
- if a_string is None or a_string == "null" or a_string == "any":
- return default
- if a_string.lower() == "false" or a_string.lower() == "no" or a_string == "0":
- return False
- return True
diff --git a/pysite/views/api/bot/off_topic_names.py b/pysite/views/api/bot/off_topic_names.py
deleted file mode 100644
index 1c75428e..00000000
--- a/pysite/views/api/bot/off_topic_names.py
+++ /dev/null
@@ -1,108 +0,0 @@
-import random
-
-from flask import jsonify, request
-from schema import And, Schema
-
-from pysite.base_route import APIView
-from pysite.constants import ValidationTypes
-from pysite.decorators import api_key, api_params
-from pysite.mixins import DBMixin
-
-
-OFF_TOPIC_NAME = And(
- str,
- len,
- lambda name: all(c.isalnum() or c == '-' for c in name),
- str.islower,
- lambda name: len(name) <= 96,
- error=(
- "The channel name must be a non-blank string consisting only of"
- " lowercase regular characters and '-' with a maximum length of 96"
- )
-)
-
-DELETE_SCHEMA = Schema({
- 'name': OFF_TOPIC_NAME
-})
-
-POST_SCHEMA = Schema({
- 'name': OFF_TOPIC_NAME
-})
-
-
-class OffTopicNamesView(APIView, DBMixin):
- path = "/bot/off-topic-names"
- name = "bot.off_topic_names"
- table_name = "off_topic_names"
-
- @api_key
- @api_params(schema=DELETE_SCHEMA, validation_type=ValidationTypes.params)
- def delete(self, params):
- """
- Removes a single off-topic name from the database.
- Returns the result of the deletion call.
-
- API key must be provided as header.
- Name to delete must be provided as the `name` query argument.
- """
-
- result = self.db.delete(
- self.table_name,
- params['name'],
- return_changes=True
- )
-
- return jsonify(result)
-
- @api_key
- def get(self):
- """
- Fetch all known off-topic channel names from the database.
- Returns a list of strings, the strings being the off-topic names.
-
- If the query argument `random_items` is provided (a non-negative integer),
- then this view will return `random_items` random names from the database
- instead of returning all items at once.
-
- API key must be provided as header.
- """
-
- names = [
- entry['name'] for entry in self.db.get_all(self.table_name)
- ]
-
- if 'random_items' in request.args:
- random_count = request.args['random_items']
- if not random_count.isdigit():
- response = {'message': "`random_items` must be a valid integer"}
- return jsonify(response), 400
-
- samples = random.sample(names, int(random_count))
- return jsonify(samples)
-
- return jsonify(names)
-
- @api_key
- @api_params(schema=POST_SCHEMA, validation_type=ValidationTypes.params)
- def post(self, data):
- """
- Add a new off-topic channel name to the database.
- Expects the new channel's name as the `name` argument.
- The name must consist only of alphanumeric characters or minus signs,
- and must not be empty or exceed 96 characters.
-
- Data must be provided as params.
- API key must be provided as header.
- """
-
- if self.db.get(self.table_name, data['name']) is not None:
- response = {
- 'message': "An entry with the given name already exists"
- }
- return jsonify(response), 400
-
- self.db.insert(
- self.table_name,
- {'name': data['name']}
- )
- return jsonify({'message': 'ok'})
diff --git a/pysite/views/api/bot/settings.py b/pysite/views/api/bot/settings.py
deleted file mode 100644
index a633a68a..00000000
--- a/pysite/views/api/bot/settings.py
+++ /dev/null
@@ -1,56 +0,0 @@
-from flask import jsonify
-from schema import Optional, Schema
-
-from pysite.base_route import APIView
-from pysite.constants import ValidationTypes
-from pysite.decorators import api_key, api_params
-from pysite.mixins import DBMixin
-
-# todo: type safety
-SETTINGS_KEYS_DEFAULTS = {
- "defcon_enabled": False,
- "defcon_days": 1
-}
-
-GET_SCHEMA = Schema({
- Optional("keys"): str
-})
-
-
-def settings_schema():
- schema_dict = {Optional(key): type(SETTINGS_KEYS_DEFAULTS[key]) for key in SETTINGS_KEYS_DEFAULTS.keys()}
- return Schema(schema_dict)
-
-
-class ServerSettingsView(APIView, DBMixin):
- path = "/bot/settings"
- name = "bot.settings"
-
- @api_key
- @api_params(schema=GET_SCHEMA, validation_type=ValidationTypes.params)
- def get(self, params=None):
- keys_raw = None
- if params:
- keys_raw = params.get("keys")
-
- keys = filter(lambda key: key in SETTINGS_KEYS_DEFAULTS,
- keys_raw.split(",")) if keys_raw else SETTINGS_KEYS_DEFAULTS.keys()
-
- result = {key: (self.db.get("bot_settings", key) or {}).get("value") or SETTINGS_KEYS_DEFAULTS[key] for key in
- keys}
- return jsonify(result)
-
- @api_key
- @api_params(schema=settings_schema(), validation_type=ValidationTypes.json)
- def put(self, json_data):
- # update in database
-
- for key, value in json_data.items():
- self.db.insert("bot_settings", {
- "key": key,
- "value": value
- }, conflict="update")
-
- return jsonify({
- "success": True
- })
diff --git a/pysite/views/api/bot/snake_cog/__init__.py b/pysite/views/api/bot/snake_cog/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/pysite/views/api/bot/snake_cog/__init__.py
+++ /dev/null
diff --git a/pysite/views/api/bot/snake_cog/snake_facts.py b/pysite/views/api/bot/snake_cog/snake_facts.py
deleted file mode 100644
index 4e8c8a5d..00000000
--- a/pysite/views/api/bot/snake_cog/snake_facts.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import logging
-
-from flask import jsonify
-
-from pysite.base_route import APIView
-from pysite.decorators import api_key
-from pysite.mixins import DBMixin
-
-log = logging.getLogger(__name__)
-
-
-class SnakeFactsView(APIView, DBMixin):
- path = "/bot/snake_facts"
- name = "bot.snake_facts"
- table = "snake_facts"
-
- @api_key
- def get(self):
- """
- Returns a random fact from the snake_facts table.
-
- API key must be provided as header.
- """
-
- log.trace("Fetching a random fact from the snake_facts database")
- question = self.db.sample(self.table, 1)[0]["fact"]
-
- return jsonify(question)
diff --git a/pysite/views/api/bot/snake_cog/snake_idioms.py b/pysite/views/api/bot/snake_cog/snake_idioms.py
deleted file mode 100644
index 9d879871..00000000
--- a/pysite/views/api/bot/snake_cog/snake_idioms.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import logging
-
-from flask import jsonify
-
-from pysite.base_route import APIView
-from pysite.decorators import api_key
-from pysite.mixins import DBMixin
-
-log = logging.getLogger(__name__)
-
-
-class SnakeIdiomView(APIView, DBMixin):
- path = "/bot/snake_idioms"
- name = "bot.snake_idioms"
- table = "snake_idioms"
-
- @api_key
- def get(self):
- """
- Returns a random idiom from the snake_idioms table.
-
- API key must be provided as header.
- """
-
- log.trace("Fetching a random idiom from the snake_idioms database")
- question = self.db.sample(self.table, 1)[0]["idiom"]
-
- return jsonify(question)
diff --git a/pysite/views/api/bot/snake_cog/snake_names.py b/pysite/views/api/bot/snake_cog/snake_names.py
deleted file mode 100644
index d9e0c6b8..00000000
--- a/pysite/views/api/bot/snake_cog/snake_names.py
+++ /dev/null
@@ -1,48 +0,0 @@
-import logging
-
-from flask import jsonify
-from schema import Optional, Schema
-
-
-from pysite.base_route import APIView
-from pysite.constants import ValidationTypes
-from pysite.decorators import api_key, api_params
-from pysite.mixins import DBMixin
-
-log = logging.getLogger(__name__)
-
-GET_SCHEMA = Schema([
- {
- Optional("get_all"): str
- }
-])
-
-
-class SnakeNamesView(APIView, DBMixin):
- path = "/bot/snake_names"
- name = "bot.snake_names"
- table = "snake_names"
-
- @api_key
- @api_params(schema=GET_SCHEMA, validation_type=ValidationTypes.params)
- def get(self, params=None):
- """
- Returns all snake names random name from the snake_names table.
-
- API key must be provided as header.
- """
-
- get_all = None
-
- if params:
- get_all = params[0].get("get_all")
-
- if get_all:
- log.trace("Returning all snake names from the snake_names table")
- snake_names = self.db.get_all(self.table)
-
- else:
- log.trace("Fetching a single random snake name from the snake_names table")
- snake_names = self.db.sample(self.table, 1)[0]
-
- return jsonify(snake_names)
diff --git a/pysite/views/api/bot/snake_cog/snake_quiz.py b/pysite/views/api/bot/snake_cog/snake_quiz.py
deleted file mode 100644
index 359077d7..00000000
--- a/pysite/views/api/bot/snake_cog/snake_quiz.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import logging
-
-from flask import jsonify
-
-from pysite.base_route import APIView
-from pysite.decorators import api_key
-from pysite.mixins import DBMixin
-
-log = logging.getLogger(__name__)
-
-
-class SnakeQuizView(APIView, DBMixin):
- path = "/bot/snake_quiz"
- name = "bot.snake_quiz"
- table = "snake_quiz"
-
- @api_key
- def get(self):
- """
- Returns a random question from the snake_quiz table.
-
- API key must be provided as header.
- """
-
- log.trace("Fetching a random question from the snake_quiz database")
- question = self.db.sample(self.table, 1)[0]
-
- return jsonify(question)
diff --git a/pysite/views/api/bot/snake_cog/special_snakes.py b/pysite/views/api/bot/snake_cog/special_snakes.py
deleted file mode 100644
index 294c16c9..00000000
--- a/pysite/views/api/bot/snake_cog/special_snakes.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import logging
-
-from flask import jsonify
-
-from pysite.base_route import APIView
-from pysite.decorators import api_key
-from pysite.mixins import DBMixin
-
-log = logging.getLogger(__name__)
-
-
-class SpecialSnakesView(APIView, DBMixin):
- path = "/bot/special_snakes"
- name = "bot.special_snakes"
- table = "special_snakes"
-
- @api_key
- def get(self):
- """
- Returns all special snake objects from the database
-
- API key must be provided as header.
- """
-
- log.trace("Returning all special snakes in the database")
- snake_names = self.db.get_all(self.table)
-
- return jsonify(snake_names)
diff --git a/pysite/views/api/bot/tags.py b/pysite/views/api/bot/tags.py
deleted file mode 100644
index 4394c224..00000000
--- a/pysite/views/api/bot/tags.py
+++ /dev/null
@@ -1,107 +0,0 @@
-from flask import jsonify
-from schema import Optional, Schema
-
-from pysite.base_route import APIView
-from pysite.constants import ValidationTypes
-from pysite.decorators import api_key, api_params
-from pysite.mixins import DBMixin
-
-GET_SCHEMA = Schema({
- Optional("tag_name"): str
-})
-
-POST_SCHEMA = Schema({
- "tag_name": str,
- "tag_content": str
-})
-
-DELETE_SCHEMA = Schema({
- "tag_name": str
-})
-
-
-class TagsView(APIView, DBMixin):
- path = "/bot/tags"
- name = "bot.tags"
- table_name = "tags"
-
- @api_key
- @api_params(schema=GET_SCHEMA, validation_type=ValidationTypes.params)
- def get(self, params=None):
- """
- Fetches tags from the database.
-
- - If tag_name is provided, it fetches
- that specific tag.
-
- - If tag_category is provided, it fetches
- all tags in that category.
-
- - If nothing is provided, it will
- fetch a list of all tag_names.
-
- Data must be provided as params.
- API key must be provided as header.
- """
-
- tag_name = None
-
- if params:
- tag_name = params.get("tag_name")
-
- if tag_name:
- data = self.db.get(self.table_name, tag_name) or {}
- else:
- data = self.db.pluck(self.table_name, "tag_name") or []
-
- return jsonify(data)
-
- @api_key
- @api_params(schema=POST_SCHEMA, validation_type=ValidationTypes.json)
- def post(self, json_data):
- """
- If the tag_name doesn't exist, this
- saves a new tag in the database.
-
- If the tag_name already exists,
- this will edit the existing tag.
-
- Data must be provided as JSON.
- API key must be provided as header.
- """
-
- tag_name = json_data.get("tag_name")
- tag_content = json_data.get("tag_content")
-
- self.db.insert(
- self.table_name,
- {
- "tag_name": tag_name,
- "tag_content": tag_content
- },
- conflict="update" # If it exists, update it.
- )
-
- return jsonify({"success": True})
-
- @api_key
- @api_params(schema=DELETE_SCHEMA, validation_type=ValidationTypes.json)
- def delete(self, data):
- """
- Deletes a tag from the database.
-
- Data must be provided as JSON.
- API key must be provided as header.
- """
-
- tag_name = data.get("tag_name")
- tag_exists = self.db.get(self.table_name, tag_name)
-
- if tag_exists:
- self.db.delete(
- self.table_name,
- tag_name
- )
- return jsonify({"success": True})
-
- return jsonify({"success": False})
diff --git a/pysite/views/api/bot/user.py b/pysite/views/api/bot/user.py
deleted file mode 100644
index a3a0c7a8..00000000
--- a/pysite/views/api/bot/user.py
+++ /dev/null
@@ -1,166 +0,0 @@
-import logging
-
-import rethinkdb
-from flask import jsonify, request
-from schema import Optional, Schema
-
-from pysite.base_route import APIView
-from pysite.constants import ErrorCodes, ValidationTypes
-from pysite.decorators import api_key, api_params
-from pysite.mixins import DBMixin
-
-SCHEMA = Schema([
- {
- "avatar": str,
- "discriminator": str,
- "roles": [str],
- "user_id": str,
- "username": str
- }
-])
-
-GET_SCHEMA = Schema([
- {
- "user_id": str
- }
-])
-
-DELETE_SCHEMA = Schema([
- {
- "user_id": str,
-
- Optional("avatar"): str,
- Optional("discriminator"): str,
- Optional("roles"): [str],
- Optional("username"): str
- }
-])
-
-BANNABLE_STATES = ("preparing", "running")
-
-
-class UserView(APIView, DBMixin):
- path = "/bot/users"
- name = "bot.users"
-
- chunks_table = "member_chunks"
- infractions_table = "code_jam_infractions"
- jams_table = "code_jams"
- oauth_table_name = "oauth_data"
- participants_table = "code_jam_participants"
- responses_table = "code_jam_responses"
- table_name = "users"
- teams_table = "code_jam_teams"
-
- @api_key
- @api_params(schema=GET_SCHEMA, validation_type=ValidationTypes.params)
- def get(self, data):
- logging.getLogger(__name__).debug(f"Size of request: {len(request.data)} bytes")
-
- if not data:
- return self.error(ErrorCodes.bad_data_format, "No user IDs supplied")
-
- data = [x["user_id"] for x in data]
-
- result = self.db.run(
- self.db.query(self.table_name)
- .filter(lambda document: rethinkdb.expr(data).contains(document["user_id"])),
- coerce=list
- )
-
- return jsonify({"data": result}) # pragma: no cover
-
- @api_key
- @api_params(schema=SCHEMA, validation_type=ValidationTypes.json)
- def post(self, data):
- logging.getLogger(__name__).debug(f"Size of request: {len(request.data)} bytes")
-
- if not data:
- return self.error(ErrorCodes.bad_data_format, "No users supplied")
-
- self.db.insert(self.chunks_table, {"chunk": data})
-
- return jsonify({"success": True}) # pragma: no cover
-
- @api_key
- @api_params(schema=SCHEMA, validation_type=ValidationTypes.json)
- def put(self, data):
- changes = self.db.insert(
- self.table_name, *data,
- conflict="update"
- )
-
- return jsonify(changes) # pragma: no cover
-
- @api_key
- @api_params(schema=DELETE_SCHEMA, validation_type=ValidationTypes.json)
- def delete(self, data):
- user_ids = [user["user_id"] for user in data]
-
- changes = {}
-
- # changes = self.db.run(
- # self.db.query(self.table_name)
- # .get_all(*user_ids)
- # .delete()
- # )
-
- oauth_deletions = self.db.run(
- self.db.query(self.oauth_table_name)
- .get_all(*user_ids, index="snowflake")
- .delete()
- ).get("deleted", 0)
-
- profile_deletions = self.db.run(
- self.db.query(self.participants_table)
- .get_all(*user_ids)
- .delete()
- ).get("deleted", 0)
-
- bans = 0
- response_deletions = 0
-
- for user_id in user_ids:
- banned = False
- responses = self.db.run(self.db.query(self.responses_table).filter({"snowflake": user_id}), coerce=list)
-
- for response in responses:
- jam = response["jam"]
- jam_obj = self.db.get(self.jams_table, jam)
-
- if jam_obj:
- if jam_obj["state"] in BANNABLE_STATES:
- banned = True
-
- self.db.delete(self.responses_table, response["id"])
- response_deletions += 1
-
- teams = self.db.run(
- self.db.query(self.teams_table).filter(lambda row: row["members"].contains(user_id)),
- coerce=list
- )
-
- for team in teams:
- team["members"].remove(user_id)
-
- self.db.insert(self.teams_table, team, conflict="replace", durability="soft")
-
- self.db.sync(self.teams_table)
-
- if banned:
- self.db.insert(
- self.infractions_table, {
- "participant": user_id,
- "reason": "Automatic ban: Removed jammer profile in the middle of a code jam",
- "number": -1,
- "decremented_for": []
- }
- )
- bans += 1
-
- changes["deleted_oauth"] = oauth_deletions
- changes["deleted_jam_profiles"] = profile_deletions
- changes["deleted_responses"] = response_deletions
- changes["jam_bans"] = bans
-
- return jsonify(changes) # pragma: no cover
diff --git a/pysite/views/api/bot/user_complete.py b/pysite/views/api/bot/user_complete.py
deleted file mode 100644
index 877eee34..00000000
--- a/pysite/views/api/bot/user_complete.py
+++ /dev/null
@@ -1,143 +0,0 @@
-import logging
-
-from flask import jsonify, request
-
-from pysite.base_route import APIView
-from pysite.constants import ErrorCodes, ValidationTypes
-from pysite.decorators import api_key, api_params
-from pysite.mixins import DBMixin
-
-
-BANNABLE_STATES = ("preparing", "running")
-
-log = logging.getLogger(__name__)
-
-
-class UserView(APIView, DBMixin):
- path = "/bot/users/complete"
- name = "bot.users.complete"
-
- chunks_table = "member_chunks"
- infractions_table = "code_jam_infractions"
- jams_table = "code_jams"
- oauth_table_name = "oauth_data"
- participants_table = "code_jam_participants"
- responses_table = "code_jam_responses"
- table_name = "users"
- teams_table = "code_jam_teams"
-
- @api_key
- @api_params(validation_type=ValidationTypes.none)
- def post(self, _):
- log.debug(f"Size of request: {len(request.data)} bytes")
-
- documents = self.db.get_all(self.chunks_table)
- chunks = []
-
- for doc in documents:
- log.info(f"Got member chunk with {len(doc['chunk'])} users")
- chunks.append(doc["chunk"])
-
- self.db.delete(self.chunks_table, doc["id"], durability="soft")
- self.db.sync(self.chunks_table)
-
- log.info(f"Got {len(chunks)} member chunks")
-
- data = []
-
- for chunk in chunks:
- data += chunk
-
- log.info(f"Got {len(data)} members")
-
- if not data:
- return self.error(ErrorCodes.bad_data_format, "No users supplied")
-
- deletions = 0
- oauth_deletions = 0
- profile_deletions = 0
- response_deletions = 0
- bans = 0
-
- user_ids = [user["user_id"] for user in data]
-
- all_users = self.db.run(self.db.query(self.table_name), coerce=list)
-
- for user in all_users:
- if user["user_id"] not in user_ids:
- self.db.delete(self.table_name, user["user_id"], durability="soft")
- deletions += 1
-
- all_oauth_data = self.db.run(self.db.query(self.oauth_table_name), coerce=list)
-
- for item in all_oauth_data:
- if item["snowflake"] not in user_ids:
- user_id = item["snowflake"]
-
- oauth_deletions += self.db.delete(
- self.oauth_table_name, item["id"], durability="soft", return_changes=True
- ).get("deleted", 0)
- profile_deletions += self.db.delete(
- self.participants_table, user_id, durability="soft", return_changes=True
- ).get("deleted", 0)
-
- banned = False
- responses = self.db.run(
- self.db.query(self.responses_table).filter({"snowflake": user_id}),
- coerce=list
- )
-
- for response in responses:
- jam = response["jam"]
- jam_obj = self.db.get(self.jams_table, jam)
-
- if jam_obj:
- if jam_obj["state"] in BANNABLE_STATES:
- banned = True
-
- self.db.delete(self.responses_table, response["id"], durability="soft")
- response_deletions += 1
-
- teams = self.db.run(
- self.db.query(self.teams_table).filter(lambda row: row["members"].contains(user_id)),
- coerce=list
- )
-
- for team in teams:
- team["members"].remove(user_id)
-
- self.db.insert(self.teams_table, team, conflict="replace", durability="soft")
-
- if banned:
- self.db.insert(
- self.infractions_table, {
- "participant": user_id,
- "reason": "Automatic ban: Removed jammer profile in the middle of a code jam",
- "number": -1,
- "decremented_for": []
- }, durability="soft"
- )
- bans += 1
-
- del user_ids
-
- changes = self.db.insert(
- self.table_name, *data,
- conflict="update",
- durability="soft"
- )
-
- self.db.sync(self.infractions_table)
- self.db.sync(self.oauth_table_name)
- self.db.sync(self.participants_table)
- self.db.sync(self.responses_table)
- self.db.sync(self.table_name)
- self.db.sync(self.teams_table)
-
- changes["deleted"] = deletions
- changes["deleted_oauth"] = oauth_deletions
- changes["deleted_jam_profiles"] = profile_deletions
- changes["deleted_responses"] = response_deletions
- changes["jam_bans"] = bans
-
- return jsonify(changes) # pragma: no cover
diff --git a/pysite/views/api/error_view.py b/pysite/views/api/error_view.py
deleted file mode 100644
index 89b4d6ad..00000000
--- a/pysite/views/api/error_view.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from flask import jsonify
-from werkzeug.exceptions import HTTPException
-
-from pysite.base_route import ErrorView
-
-
-class APIErrorView(ErrorView):
- name = "api.error_all"
- error_code = range(400, 600)
- register_on_app = False
-
- def __init__(self):
-
- # Direct errors for all methods at self.return_error
- methods = [
- 'get', 'post', 'put',
- 'delete', 'patch', 'connect',
- 'options', 'trace'
- ]
-
- for method in methods:
- setattr(self, method, self.return_error)
-
- def return_error(self, error: HTTPException):
- """
- Return a basic JSON object representing the HTTP error,
- as well as propagating its status code
- """
-
- message = str(error)
- code = 500
-
- if isinstance(error, HTTPException):
- message = error.description
- code = error.code
-
- return jsonify({
- "error_code": -1,
- "error_message": message
- }), code
diff --git a/pysite/views/api/healthcheck.py b/pysite/views/api/healthcheck.py
deleted file mode 100644
index c873d674..00000000
--- a/pysite/views/api/healthcheck.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from flask import jsonify
-
-from pysite.base_route import APIView
-
-
-class HealthCheckView(APIView):
- path = "/healthcheck"
- name = "api.healthcheck"
-
- def get(self):
- return jsonify({"status": "ok"})
diff --git a/pysite/views/api/index.py b/pysite/views/api/index.py
deleted file mode 100644
index 5111162c..00000000
--- a/pysite/views/api/index.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from pysite.base_route import APIView
-from pysite.constants import ErrorCodes
-
-
-class IndexView(APIView):
- path = "/"
- name = "api.index"
-
- def get(self):
- return self.error(ErrorCodes.unknown_route)
diff --git a/pysite/views/api/robots_txt.py b/pysite/views/api/robots_txt.py
deleted file mode 100644
index d4406d54..00000000
--- a/pysite/views/api/robots_txt.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from flask import Response, url_for
-
-from pysite.base_route import RouteView
-
-
-class RobotsTXT(RouteView):
- path = "/robots.txt"
- name = "robots_txt"
-
- def get(self):
- return Response(
- self.render(
- "robots.txt", sitemap_url=url_for("api.sitemap_xml", _external=True), rules={"*": ["/"]}
- ), content_type="text/plain"
- )
diff --git a/pysite/views/api/sitemap_xml.py b/pysite/views/api/sitemap_xml.py
deleted file mode 100644
index 26a786b0..00000000
--- a/pysite/views/api/sitemap_xml.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from flask import Response
-
-from pysite.base_route import RouteView
-
-
-class SitemapXML(RouteView):
- path = "/sitemap.xml"
- name = "sitemap_xml"
-
- def get(self):
- return Response(self.render("sitemap.xml", urls=[]), content_type="application/xml")