mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-01-28 08:17:58 -05:00
RESTful API for multis.
This commit is contained in:
@@ -309,6 +309,10 @@ def make_map():
|
||||
action='relnote')
|
||||
mc('/api/:action', controller='api')
|
||||
|
||||
mc("/api/multi/mine", controller="multiapi", action="my_multis")
|
||||
mc("/api/multi/*path/r/:sr_name", controller="multiapi", action="multi_subreddit")
|
||||
mc("/api/multi/*path", controller="multiapi", action="multi")
|
||||
|
||||
mc("/api/v1/:action", controller="oauth2frontend",
|
||||
requirements=dict(action="authorize"))
|
||||
mc("/api/v1/:action", controller="oauth2access",
|
||||
|
||||
@@ -39,6 +39,7 @@ api('promotedlink', PromotedLinkJsonTemplate)
|
||||
api('comment', CommentJsonTemplate)
|
||||
api('message', MessageJsonTemplate)
|
||||
api('subreddit', SubredditJsonTemplate)
|
||||
api('labeledmulti', LabeledMultiJsonTemplate)
|
||||
api('morerecursion', MoreCommentJsonTemplate)
|
||||
api('morechildren', MoreCommentJsonTemplate)
|
||||
api('reddit', RedditJsonTemplate)
|
||||
|
||||
@@ -78,6 +78,7 @@ def load_controllers():
|
||||
from api import ApiminimalController
|
||||
from api_docs import ApidocsController
|
||||
from apiv1 import APIv1Controller
|
||||
from multi import MultiApiController
|
||||
from oauth2 import OAuth2FrontendController
|
||||
from oauth2 import OAuth2AccessController
|
||||
from redirect import RedirectController
|
||||
|
||||
@@ -66,6 +66,9 @@ section_info = {
|
||||
'subreddits': {
|
||||
'title': _('subreddits'),
|
||||
},
|
||||
'multis': {
|
||||
'title': _('multis'),
|
||||
},
|
||||
'users': {
|
||||
'title': _('users'),
|
||||
},
|
||||
@@ -172,6 +175,7 @@ class ApidocsController(RedditController):
|
||||
from r2.controllers.captcha import CaptchaController
|
||||
from r2.controllers.front import FrontController
|
||||
from r2.controllers.wiki import WikiApiController
|
||||
from r2.controllers.multi import MultiApiController
|
||||
from r2.controllers import listingcontroller
|
||||
|
||||
api_controllers = [
|
||||
@@ -179,6 +183,7 @@ class ApidocsController(RedditController):
|
||||
(ApiController, '/api'),
|
||||
(ApiminimalController, '/api'),
|
||||
(WikiApiController, '/api/wiki'),
|
||||
(MultiApiController, '/api/multi'),
|
||||
(CaptchaController, ''),
|
||||
(FrontController, '')
|
||||
]
|
||||
|
||||
154
r2/r2/controllers/multi.py
Normal file
154
r2/r2/controllers/multi.py
Normal file
@@ -0,0 +1,154 @@
|
||||
# The contents of this file are subject to the Common Public Attribution
|
||||
# License Version 1.0. (the "License"); you may not use this file except in
|
||||
# compliance with the License. You may obtain a copy of the License at
|
||||
# http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
|
||||
# License Version 1.1, but Sections 14 and 15 have been added to cover use of
|
||||
# software over a computer network and provide for limited attribution for the
|
||||
# Original Developer. In addition, Exhibit A has been modified to be consistent
|
||||
# with Exhibit B.
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
||||
# the specific language governing rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is reddit.
|
||||
#
|
||||
# The Original Developer is the Initial Developer. The Initial Developer of
|
||||
# the Original Code is reddit Inc.
|
||||
#
|
||||
# All portions of the code written by reddit are Copyright (c) 2006-2012 reddit
|
||||
# Inc. All Rights Reserved.
|
||||
###############################################################################
|
||||
|
||||
from pylons import c, request
|
||||
|
||||
from r2.config.extensions import set_extension
|
||||
from r2.controllers.api_docs import api_doc, api_section
|
||||
from r2.controllers.reddit_base import RedditController
|
||||
from r2.controllers.oauth2 import (
|
||||
OAuth2ResourceController,
|
||||
require_oauth2_scope,
|
||||
)
|
||||
from r2.models.subreddit import (
|
||||
Subreddit,
|
||||
LabeledMulti,
|
||||
TooManySubredditsException,
|
||||
)
|
||||
from r2.lib.db import tdb_cassandra
|
||||
from r2.lib.wrapped import Wrapped
|
||||
from r2.lib.validator import (
|
||||
validate,
|
||||
VUser,
|
||||
VModhash,
|
||||
VSRByName,
|
||||
VJSON,
|
||||
VMultiPath,
|
||||
VMultiByPath,
|
||||
)
|
||||
from r2.lib.pages.things import wrap_things
|
||||
from r2.lib.errors import errors, reddit_http_error, RedditError
|
||||
from r2.lib.base import abort
|
||||
|
||||
|
||||
class MultiApiController(RedditController, OAuth2ResourceController):
|
||||
def pre(self):
|
||||
set_extension(request.environ, "json")
|
||||
self.check_for_bearer_token()
|
||||
RedditController.pre(self)
|
||||
|
||||
def on_validation_error(self, error):
|
||||
abort(reddit_http_error(
|
||||
code=error.code,
|
||||
error_name=error.name,
|
||||
explanation=error.message,
|
||||
fields=error.fields,
|
||||
))
|
||||
|
||||
@require_oauth2_scope("read")
|
||||
@api_doc(api_section.multis)
|
||||
@validate(VUser())
|
||||
def GET_my_multis(self):
|
||||
"""Fetch a list of multis belonging to the current user."""
|
||||
multis = LabeledMulti.by_owner(c.user)
|
||||
wrapped = wrap_things(*multis)
|
||||
resp = [w.render() for w in wrapped]
|
||||
return self.api_wrapper(resp)
|
||||
|
||||
@require_oauth2_scope("read")
|
||||
@api_doc(
|
||||
api_section.multis,
|
||||
uri="/api/multi/{multipath}",
|
||||
)
|
||||
@validate(multi=VMultiByPath("path", require_view=True))
|
||||
def GET_multi(self, multi):
|
||||
"""Fetch a multi's data and subreddit list by name."""
|
||||
resp = wrap_things(multi)[0].render()
|
||||
return self.api_wrapper(resp)
|
||||
|
||||
@require_oauth2_scope("subscribe")
|
||||
@api_doc(api_section.multis, extends=GET_multi)
|
||||
@validate(
|
||||
VUser(),
|
||||
VModhash(),
|
||||
info=VMultiPath("path"),
|
||||
data=VJSON("model"),
|
||||
)
|
||||
def PUT_multi(self, info, data):
|
||||
"""Create or update a multi."""
|
||||
if info['username'] != c.user.name:
|
||||
raise RedditError('BAD_MULTI_NAME', code=400, fields="path")
|
||||
|
||||
try:
|
||||
multi = LabeledMulti._byID(info['path'])
|
||||
except tdb_cassandra.NotFound:
|
||||
multi = LabeledMulti.create(info['path'], c.user)
|
||||
|
||||
if 'visibility' in data:
|
||||
if data['visibility'] not in ('private', 'public'):
|
||||
raise RedditError('INVALID_OPTION', code=400, fields="data")
|
||||
multi.visibility = data['visibility']
|
||||
multi._commit()
|
||||
|
||||
return self.GET_multi(path=info['path'])
|
||||
|
||||
@require_oauth2_scope("subscribe")
|
||||
@api_doc(api_section.multis, extends=GET_multi)
|
||||
@validate(
|
||||
VUser(),
|
||||
VModhash(),
|
||||
multi=VMultiByPath("path", require_edit=True),
|
||||
)
|
||||
def DELETE_multi(self, multi):
|
||||
"""Delete a multi."""
|
||||
multi.delete()
|
||||
|
||||
@require_oauth2_scope("subscribe")
|
||||
@api_doc(
|
||||
api_section.multis,
|
||||
uri="/api/multi/{multipath}/r/{srname}",
|
||||
)
|
||||
@validate(
|
||||
VUser(),
|
||||
VModhash(),
|
||||
multi=VMultiByPath("path", require_edit=True),
|
||||
sr=VSRByName('sr_name'),
|
||||
)
|
||||
def PUT_multi_subreddit(self, multi, sr):
|
||||
"""Add a subreddit to a multi."""
|
||||
|
||||
try:
|
||||
multi.add_srs({sr._id: {}})
|
||||
except TooManySubredditsException as e:
|
||||
raise RedditError('MULTI_TOO_MANY_SUBREDDITS', code=409)
|
||||
|
||||
@require_oauth2_scope("subscribe")
|
||||
@api_doc(api_section.multis, extends=PUT_multi_subreddit)
|
||||
@validate(
|
||||
VUser(),
|
||||
VModhash(),
|
||||
multi=VMultiByPath("path", require_edit=True),
|
||||
sr=VSRByName('sr_name'),
|
||||
)
|
||||
def DELETE_multi_subreddit(self, multi, sr):
|
||||
"""Remove a subreddit from a multi."""
|
||||
multi.del_srs(sr._id)
|
||||
@@ -121,6 +121,11 @@ error_list = dict((
|
||||
('BAD_JSONP_CALLBACK', _('that jsonp callback contains invalid characters')),
|
||||
('INVALID_PERMISSION_TYPE', _("permissions don't apply to that type of user")),
|
||||
('INVALID_PERMISSIONS', _('invalid permissions string')),
|
||||
('BAD_MULTI_NAME', _('that name isn\'t going to work')),
|
||||
('MULTI_NOT_FOUND', _('that multireddit doesn\'t exist')),
|
||||
('MULTI_CANNOT_EDIT', _('you can\'t change that multireddit')),
|
||||
('MULTI_TOO_MANY_SUBREDDITS', _('no more space for subreddits in that multireddit')),
|
||||
('BAD_JSON', _('unable to parse JSON data')),
|
||||
))
|
||||
|
||||
errors = Storage([(e, e) for e in error_list.keys()])
|
||||
|
||||
@@ -250,6 +250,24 @@ class SubredditJsonTemplate(ThingJsonTemplate):
|
||||
else:
|
||||
return ThingJsonTemplate.thing_attr(self, thing, attr)
|
||||
|
||||
class LabeledMultiJsonTemplate(ThingJsonTemplate):
|
||||
_data_attrs_ = ThingJsonTemplate.data_attrs(
|
||||
path="path",
|
||||
name="name",
|
||||
subreddits="srs",
|
||||
visibility="visibility",
|
||||
)
|
||||
del _data_attrs_["id"]
|
||||
|
||||
def kind(self, wrapped):
|
||||
return "LabeledMulti"
|
||||
|
||||
def thing_attr(self, thing, attr):
|
||||
if attr == "srs":
|
||||
return [{"name": sr.name} for sr in thing.srs]
|
||||
else:
|
||||
return ThingJsonTemplate.thing_attr(self, thing, attr)
|
||||
|
||||
class IdentityJsonTemplate(ThingJsonTemplate):
|
||||
_data_attrs_ = ThingJsonTemplate.data_attrs(name = "name",
|
||||
link_karma = "safe_karma",
|
||||
|
||||
@@ -38,6 +38,7 @@ from r2.lib.permissions import ModeratorPermissionSet
|
||||
from r2.models import *
|
||||
from r2.lib.authorize import Address, CreditCard
|
||||
from r2.lib.utils import constant_time_compare
|
||||
from r2.lib.require import require, require_split, RequirementException
|
||||
|
||||
from r2.lib.errors import errors, RedditError, UserRequiredException
|
||||
from r2.lib.errors import VerifiedUserRequiredException
|
||||
@@ -2165,3 +2166,71 @@ class VPermissions(Validator):
|
||||
self.set_error(errors.INVALID_PERMISSIONS, field=self.param[1])
|
||||
return (None, None)
|
||||
return type, perm_set
|
||||
|
||||
|
||||
class VJSON(VRequired):
|
||||
def __init__(self, item, *args, **kwargs):
|
||||
VRequired.__init__(self, item, errors.BAD_JSON, *args, **kwargs)
|
||||
|
||||
def run(self, json_str):
|
||||
if not json_str:
|
||||
return self.error()
|
||||
else:
|
||||
try:
|
||||
return json.loads(json_str)
|
||||
except ValueError:
|
||||
return self.error()
|
||||
|
||||
def param_docs(self):
|
||||
return {
|
||||
self.param: "JSON data",
|
||||
}
|
||||
|
||||
|
||||
class VMultiPath(Validator):
|
||||
@classmethod
|
||||
def normalize(self, path):
|
||||
if path[0] != '/':
|
||||
path = '/' + path
|
||||
path = path.lower().rstrip('/')
|
||||
return path
|
||||
|
||||
def run(self, path):
|
||||
try:
|
||||
require(path)
|
||||
path = self.normalize(path)
|
||||
require(path.startswith('/user/'))
|
||||
user, username, m, name = require_split(path, 5, sep='/')[1:]
|
||||
require(m == 'm')
|
||||
username = chkuser(username)
|
||||
require(username)
|
||||
require(subreddit_rx.match(name))
|
||||
return {'path': path, 'username': username, 'name': name}
|
||||
except RequirementException:
|
||||
self.set_error('BAD_MULTI_NAME', code=400)
|
||||
|
||||
def param_docs(self):
|
||||
return {
|
||||
self.param: "multireddit url path",
|
||||
}
|
||||
|
||||
|
||||
class VMultiByPath(Validator):
|
||||
def __init__(self, param, require_view=True, require_edit=False):
|
||||
Validator.__init__(self, param)
|
||||
self.require_view = require_view
|
||||
self.require_edit = require_edit
|
||||
|
||||
def run(self, path):
|
||||
path = VMultiPath.normalize(path)
|
||||
multi = LabeledMulti._byID(path)
|
||||
if not multi or (self.require_view and not multi.can_view(c.user)):
|
||||
return self.set_error('MULTI_NOT_FOUND', code=404)
|
||||
if self.require_edit and not multi.can_edit(c.user):
|
||||
return self.set_error('MULTI_CANNOT_EDIT', code=403)
|
||||
return multi
|
||||
|
||||
def param_docs(self):
|
||||
return {
|
||||
self.param: "multireddit url path",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user