Browse Source

add user endpoints

merge-requests/160/head
pollev 2 months ago
parent
commit
94339d5f9f
  1. 116
      tildes/openapi_beta.yaml
  2. 3
      tildes/tildes/routes.py
  3. 215
      tildes/tildes/views/api/beta/user.py

116
tildes/openapi_beta.yaml

@ -93,10 +93,118 @@ paths:
type: array
items:
$ref: '#/components/schemas/Comment'
"400":
$ref: "#/components/responses/ValidationError"
/user/{username}:
get:
summary: Get a user along with their history
parameters:
- in: path
name: username
schema:
type: string
required: true
description: The username of the user to retrieve.
- $ref: '#/components/parameters/paginationLimit'
- $ref: '#/components/parameters/paginationBefore'
- $ref: '#/components/parameters/paginationAfter'
responses:
"200":
description: Basic user information and their post/comment history
content:
application/json:
schema:
type: object
required:
- user
- history
- pagination
properties:
user:
$ref: '#/components/schemas/User'
history:
type: array
items:
anyOf:
- $ref: '#/components/schemas/Topic'
- $ref: '#/components/schemas/Comment'
pagination:
$ref: '#/components/schemas/Pagination'
"400":
$ref: "#/components/responses/ValidationError"
"403":
$ref: "#/components/responses/AuthorizationError"
/user/{username}/comments:
get:
summary: Get comments made by a user
parameters:
- in: path
name: username
schema:
type: string
required: true
description: The username of the user for whom to retrieve comments.
- $ref: '#/components/parameters/paginationLimit'
- $ref: '#/components/parameters/paginationBefore'
- $ref: '#/components/parameters/paginationAfter'
responses:
"200":
description: A list of comments made by the user
content:
application/json:
schema:
type: object
required:
- comments
- pagination
properties:
comments:
type: array
items:
$ref: '#/components/schemas/Comment'
pagination:
$ref: '#/components/schemas/Pagination'
"400":
$ref: "#/components/responses/ValidationError"
"403":
$ref: "#/components/responses/AuthorizationError"
/user/{username}/topics:
get:
summary: Get topics made by a user
parameters:
- in: path
name: username
schema:
type: string
required: true
description: The username of the user for whom to retrieve topics.
- $ref: '#/components/parameters/paginationLimit'
- $ref: '#/components/parameters/paginationBefore'
- $ref: '#/components/parameters/paginationAfter'
responses:
"200":
description: A list of topics made by the user
content:
application/json:
schema:
type: object
required:
- topics
- pagination
properties:
topics:
type: array
items:
$ref: '#/components/schemas/Topic'
pagination:
$ref: '#/components/schemas/Pagination'
"400":
$ref: "#/components/responses/ValidationError"
"403":
$ref: "#/components/responses/AuthorizationError"
components:
parameters:
@ -289,6 +397,14 @@ components:
items:
$ref: '#/components/schemas/Comment'
User:
type: object
required:
- username
properties:
username:
type: string
Pagination:
type: object
required:

3
tildes/tildes/routes.py

@ -136,6 +136,9 @@ def includeme(config: Configurator) -> None:
with config.route_prefix_context("/api/beta"):
config.add_route("apibeta.topics", "/topics")
config.add_route("apibeta.topic", "/topic/{topic_id36}")
config.add_route("apibeta.user", "/user/{username}")
config.add_route("apibeta.user_comments", "/user/{username}/comments")
config.add_route("apibeta.user_topics", "/user/{username}/topics")
def add_intercooler_routes(config: Configurator) -> None:

215
tildes/tildes/views/api/beta/user.py

@ -0,0 +1,215 @@
# Copyright (c) 2018 Tildes contributors <code@tildes.net>
# SPDX-License-Identifier: AGPL-3.0-or-later
"""JSON API endpoints related to users."""
from pyramid.request import Request
from pyramid.view import view_config
from tildes.models.user.user import User
from tildes.models.comment import Comment
from tildes.models.pagination import MixedPaginatedResults
from tildes.models.topic import Topic
from tildes.views.api.beta.api_utils import (
build_error_response,
get_next_and_prev_link,
query_apply_pagination,
)
from tildes.views.api.beta.comment import comment_to_dict
from tildes.views.api.beta.topic import topic_to_dict
def _user_to_dict(user: User) -> dict:
"""Convert a User object to a dictionary for JSON serialization."""
return {
"username": user.username,
"joined_at": user.created_time.isoformat(),
"bio_rendered_html": user.bio_rendered_html,
}
@view_config(route_name="apibeta.user", openapi=True, renderer="json")
def get_user(request: Request) -> dict: # noqa
"""Get a single user with their comment and post history."""
username = request.openapi_validated.parameters.path.get("username")
limit = request.openapi_validated.parameters.query.get("limit", 20)
before = request.openapi_validated.parameters.query.get("before", None)
after = request.openapi_validated.parameters.query.get("after", None)
# Maximum number of items to return without history permission
max_items_no_permission = 20
try:
query = request.query(User).include_deleted().filter(User.username == username)
user = query.one_or_none()
if not user:
raise ValueError(f"User with name {username} not found")
except ValueError as exc:
return build_error_response(str(exc), field="username")
if not request.has_permission("view_history", user) and (
limit > max_items_no_permission or before or after
):
return build_error_response(
f"You do not have permission to view this user's history after "
f"the first {max_items_no_permission} items. "
f"Please resubmit your request without pagination parameters. "
f"If you submit a limit, it must be less "
f"than or equal to {max_items_no_permission}.",
status=403,
field="limit/before/after",
error_type="AuthorizationError",
)
result_sets = []
# For the main user API endpoint, combine topics and comments
for type_to_query in [Topic, Comment]:
query = request.query(type_to_query).filter(type_to_query.user == user)
try:
query = query_apply_pagination(
query, before, after, error_if_no_anchor=True
)
except ValueError as exc:
return build_error_response(str(exc), field="pagination")
# include removed posts if the viewer has permission
if request.has_permission("view_removed_posts", user):
query = query.include_removed()
query = query.join_all_relationships()
result_sets.append(query.get_page(limit))
combined_results = MixedPaginatedResults(result_sets)
# Build the JSON history data
processed_results = []
for item in combined_results.results:
if isinstance(item, Topic):
processed_results.append(topic_to_dict(item))
elif isinstance(item, Comment):
processed_results.append(comment_to_dict(request, item))
# Construct the paging next and previous link if there are more topics
(next_link, prev_link) = get_next_and_prev_link(request, combined_results)
# Construct the final response JSON object
response = {
"user": _user_to_dict(user),
"history": processed_results,
"pagination": {
"num_items": len(processed_results),
"next_link": next_link,
"prev_link": prev_link,
},
}
return response
@view_config(route_name="apibeta.user_comments", openapi=True, renderer="json")
def get_user_comments(request: Request) -> dict:
"""Get comments made by a user."""
username = request.openapi_validated.parameters.path.get("username")
limit = request.openapi_validated.parameters.query.get("limit", 50)
before = request.openapi_validated.parameters.query.get("before", None)
after = request.openapi_validated.parameters.query.get("after", None)
try:
query = request.query(User).include_deleted().filter(User.username == username)
user = query.one_or_none()
if not user:
raise ValueError(f"User with name {username} not found")
except ValueError as exc:
return build_error_response(str(exc), field="username")
if not request.has_permission("view_history", user):
return build_error_response(
"You do not have permission to view this user's comments.",
status=403,
field="N/A",
error_type="AuthorizationError",
)
query = request.query(Comment).filter(Comment.user == user)
try:
query = query_apply_pagination(query, before, after, error_if_no_anchor=False)
except ValueError as exc:
return build_error_response(str(exc), field="pagination")
# include removed posts if the viewer has permission
if request.has_permission("view_removed_posts", user):
query = query.include_removed()
query = query.join_all_relationships()
query_result = query.get_page(limit)
# Build the JSON history data
processed_comments = []
for comment in query_result.results:
processed_comments.append(comment_to_dict(request, comment))
# Construct the paging next and previous link if there are more comments
(next_link, prev_link) = get_next_and_prev_link(request, query_result)
# Construct the final response JSON object
response = {
"comments": processed_comments,
"pagination": {
"num_items": len(processed_comments),
"next_link": next_link,
"prev_link": prev_link,
},
}
return response
@view_config(route_name="apibeta.user_topics", openapi=True, renderer="json")
def get_user_topics(request: Request) -> dict:
"""Get topics made by a user."""
username = request.openapi_validated.parameters.path.get("username")
limit = request.openapi_validated.parameters.query.get("limit", 50)
before = request.openapi_validated.parameters.query.get("before", None)
after = request.openapi_validated.parameters.query.get("after", None)
try:
query = request.query(User).include_deleted().filter(User.username == username)
user = query.one_or_none()
if not user:
raise ValueError(f"User with name {username} not found")
except ValueError as exc:
return build_error_response(str(exc), field="username")
if not request.has_permission("view_history", user):
return build_error_response(
"You do not have permission to view this user's topics.",
status=403,
field="N/A",
error_type="AuthorizationError",
)
query = request.query(Topic).filter(Topic.user == user)
try:
query = query_apply_pagination(query, before, after, error_if_no_anchor=False)
except ValueError as exc:
return build_error_response(str(exc), field="pagination")
# include removed posts if the viewer has permission
if request.has_permission("view_removed_posts", user):
query = query.include_removed()
query = query.join_all_relationships()
query_result = query.get_page(limit)
# Build the JSON history data
processed_topics = []
for topic in query_result.results:
processed_topics.append(topic_to_dict(topic))
# Construct the paging next and previous link if there are more topics
(next_link, prev_link) = get_next_and_prev_link(request, query_result)
# Construct the final response JSON object
response = {
"topics": processed_topics,
"pagination": {
"num_items": len(processed_topics),
"next_link": next_link,
"prev_link": prev_link,
},
}
return response
Loading…
Cancel
Save