def subscribed(request, user_id): target_profile = get_user(user_id).profile is_subscribed = bool(request.user.is_authenticated and request.user.profile.subscriptions.filter( profile_id=target_profile.profile_id)) if request.method == "GET": return api.succeed({"subscribed": is_subscribed}) elif request.method == "PATCH": if not request.user.is_authenticated: return api.error("Not logged in.", status=401) data = json.loads(request.body) subscribed = data["subscribed"] if type(subscribed) is not bool: return api.error("Data for key 'subscribed' must be a boolean.") if not is_subscribed and data["subscribed"]: request.user.profile.subscriptions.add(target_profile) elif is_subscribed and not data["subscribed"]: request.user.profile.subscriptions.remove(target_profile) return api.succeed({"subscribed": data["subscribed"]})
def subscribed(request, id): try: target_profile = Profile.objects.get(profile_id=id) except Profile.DoesNotExist: # If id doesn't match, we try username. If username doesn't, we throw an error caught by standardAPIErrors target_profile = User.objects.select_related('profile').get(username=id).profile is_subscribed = bool(request.user.is_authenticated and request.user.profile.subscriptions.filter(profile_id=target_profile.profile_id)) if request.method == "GET": return api.succeed({ "subscribed": is_subscribed }) elif request.method == "PATCH": if not request.user.is_authenticated: return api.error("Not logged in.", status=401) data = json.loads(request.body) subscribed = data["subscribed"] if type(subscribed) is not bool: return api.error("Data for key 'subscribed' must be a boolean.") if not is_subscribed and data["subscribed"]: request.user.profile.subscriptions.add(target_profile) elif is_subscribed and not data["subscribed"]: request.user.profile.subscriptions.remove(target_profile) return api.succeed({ "subscribed": data["subscribed"] })
def user(request, user_id): requested_user = get_user(user_id) if request.method == "GET": user_data = { "username": requested_user.username, "id": requested_user.profile.profile_id, "displayName": requested_user.profile.display_name, "bio": requested_user.profile.bio, "programs": list( Program.objects.filter(user=requested_user).values_list( "program_id", flat=True)), "joined": requested_user.date_joined.replace(microsecond=0).isoformat() + "Z" } return api.succeed(user_data) elif request.method == "PATCH": data = json.loads(request.body) if request.user != requested_user: return api.error("Not authorized.", status=401) if "displayName" in data: if len(data["displayName"]) > 45: return api.error( "displayName length exceeds maximum characters.") requested_user.profile.display_name = data["displayName"] if "bio" in data: if len(data["bio"]) > 500: return api.error("bio length exceeds maximum characters.") requested_user.profile.bio = data["bio"] if "username" in data: if not check_username(data["username"], requested_user.username): return api.error("Invalid username.") requested_user.username = data["username"] requested_user.save() return api.succeed() elif request.method == "DELETE": if request.user != requested_user: return api.error("Not authorized.", status=401) requested_user.delete() return api.succeed()
def user(request, id): try: requested_user = Profile.objects.select_related('user').get(profile_id=id).user except Profile.DoesNotExist: #If id doesn't match, we try username. If username doesn't, we throw an error caught by standardAPIErrors requested_user = User.objects.select_related('profile').get(username=id) if request.method == "GET": user_data = { "username": requested_user.username, "id": requested_user.profile.profile_id, "displayName": requested_user.profile.display_name, "bio": requested_user.profile.bio, "programs": list(Program.objects.filter(user=requested_user).values_list("program_id", flat=True)), "joined": requested_user.date_joined.replace(microsecond=0).isoformat() + "Z" } return api.succeed(user_data) elif request.method == "PATCH": data = json.loads(request.body) if request.user != requested_user: return api.error("Not authorized.", status=401) if "displayName" in data: if len(data["displayName"]) > 45: return api.error("displayName length exceeds maximum characters.") else: requested_user.profile.display_name = data["displayName"] if "bio" in data: if len(data["bio"]) > 500: return api.error("bio length exceeds maximum characters.") else: requested_user.profile.bio = data["bio"] if "username" in data: if (not check_username(data["username"], requested_user.username)): return api.error("Invalid username.") else: requested_user.username = data["username"] requested_user.save() return api.succeed() elif (request.method == "DELETE"): if request.user != requested_user: return api.error("Not authorized.", status=401) requested_user.delete() return api.succeed()
def program(request, program_id): requested_program = Program.objects.get(program_id=program_id) if (request.method == "GET"): return api.succeed(requested_program.to_dict()) elif (request.method == "PATCH"): data = json.loads(request.body) return_data = {} if request.user != requested_program.user: return api.error("Not authorized.", status=401) valid_props = ["html", "js", "css", "title"] if "title" in data and len(data["title"]) > 45: return api.error("Title length exceeds maximum characters.", status=400) if "publishedMessage" in data and len(data["publishedMessage"]) > 250: return api.error("Publish message can't exceed 250 characters") for prop in valid_props: if prop in data: setattr(requested_program, prop, data[prop]) if "publishedMessage" in data: requested_program.published_message = data["publishedMessage"]; requested_program.last_published = datetime.datetime.now() return_data["lastPublished"] = requested_program.last_published.replace(microsecond=0).isoformat() + "Z" # Create notification for subscribers subscribers = requested_program.user.profile.profile_set.all() for subscriber in subscribers: Notif.objects.create( target_user = subscriber.user, link = "/program/" + requested_program.program_id, description = "<strong>{0}</strong> just published a new program, <strong>{1}</strong>".format( escape(request.user.profile.display_name), escape(requested_program.title)), source_program = requested_program ) requested_program.save() return api.succeed(return_data) elif (request.method == "DELETE"): if request.user != requested_program.user: return api.error("Not authorized.", status=401) requested_program.delete() return api.succeed()
def program_list(request, user_id, sort): if request.method == "POST": # It seems intutive that POSTing here would make a new program. However, that is not the case return api.error("Make a new program by posting to /api/program/new") requested_user = get_user(user_id, and_profile=False) offset = get_as_int(request.GET, "offset", 0) limit = get_as_int(request.GET, "limit", 20) if (limit > 20 or limit <= 0): limit = 20 try: programs = get_programs(sort, Q(user=requested_user), offset=offset, limit=limit, published_only=False) except ValueError as err: return api.error(str(err)) program_dicts = [p.to_dict(include_code=False) for p in programs] return api.succeed({"sort": sort, "programs": program_dicts})
def forks(request, program_id): if (request.method == "POST"): if (not request.user.is_authenticated): return api.error("Not logged in.", status=401) parent_program = Program.objects.get(program_id=program_id) data = json.loads(request.body) if (len(data["title"]) > 45): return api.error("Title length exceeds maximum characters.") program = Program.objects.create( user = request.user, parent = parent_program, title = data["title"], html = data["html"], js = data["js"], css = data["css"], ) if (parent_program.user != program.user): Notif.objects.create( target_user = parent_program.user, link = "/program/" + program.program_id, description = "<strong>{0}</strong> created a fork of your program, <strong>{1}</strong>".format( escape(request.user.profile.display_name), escape(parent_program.title)), ) response = api.succeed({ "id": program.program_id }, status=201) response["Location"] = "/program/" + program.program_id return response
def new_user(request): data = json.loads(request.body) username = data['username'] if "email" in data: email = data['email'] else: email = '' password = data['password'] display_name = data['displayName'] if (not check_username(username, "")): return api.error("Invalid username") if (password == ""): return api.error("Password cannot be blank") if (display_name == "" or len(display_name) > 45): return api.error("Invalid display name") if (not re.match(r"^([\w.+-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+)?$", email)): return api.error("Invalid email") user = User.objects.create_user( username, email, password, ) user.profile.display_name = display_name user.save() auth.login(request, user) return api.succeed({ "id": user.profile.profile_id, "username": user.username }, status=200)
def login(request): data = json.loads(request.body) user = auth.authenticate(username=data["username"], password=data["password"]) if user is not None: auth.login(request, user) return api.succeed({"username": user.username}) else: # At this point, error should be handled by Javascript return api.error("Username or password incorrect.", status=401)
def comment(request, *args): if (len(args) == 1): comment_id = args[0] requested_comment = Comment.objects.get(comment_id=comment_id) elif (len(args) == 2): program_id = args[0] comment_id = args[1] requested_comment = Comment.objects.get(program_id=program_id, comment_id=comment_id) if (request.method == "GET"): return api.succeed(requested_comment.to_dict()) # Comment editing! elif (request.method == "PATCH"): data = json.loads(request.body) if request.user != requested_comment.user: return api.error("Not authorized.", status=401) requested_comment.content = data["content"] requested_comment.edited = datetime.datetime.now() requested_comment.save() return api.succeed() elif (request.method == "DELETE"): if request.user != requested_comment.user: return api.error("Not authorized.", status=401) parent = requested_comment.parent if (parent is not None): parent.reply_count -= 1 parent.save() requested_comment.delete() return api.succeed()
def program_vote(request, program_id): vote_type = parse_qs(request.META["QUERY_STRING"])["type"][0] if vote_type not in vote_types: return api.error("Invalid vote type.") try: voted_program = Program.objects.get(program_id=program_id) orig_votes = getattr(voted_program, vote_type + "_votes") # Look for a vote of the same type cast by the user before orig_vote = Vote.objects.get(voted_object_id=program_id, vote_type=vote_type, user_id=request.user.id) # If the vote already exists and: if request.method == "DELETE": setattr(voted_program, vote_type + "_votes", orig_votes - 1) voted_program.save() orig_vote.delete() return api.succeed() # If the specific vote already exists, and you're posting it again elif request.method == "POST": # 403 Forbidden, is used here beacuse there is no authentication that would allow the request return api.error("Already voted.", status=403) except Vote.DoesNotExist: # If the vote doesn't already exist if request.method == "POST": Vote.objects.create(user_id=request.user.id, vote_type=vote_type, voted_object_id=program_id) setattr(voted_program, vote_type + "_votes", orig_votes + 1) voted_program.save() # Return with 201, created return api.succeed({}, status=201) elif request.method == "DELETE": return api.error("Vote not found.", 404)
def program_list(request, sort): offset = get_as_int(request.GET, "offset", 0) limit = get_as_int(request.GET, "limit", 20) if (limit > 20 or limit <= 0): limit = 20 try: programs = get_programs(sort, offset=offset, limit=limit) except ValueError as err: return api.error(str(err)) program_dicts = [p.to_dict(include_code=False) for p in programs] return api.succeed({"sort": sort, "programs": program_dicts})
def new_program(request): data = json.loads(request.body) if (len(data["title"]) > 45): return api.error("Title length exceeds maximum characters.") program = Program.objects.create( user = request.user, title = data["title"], html = data["html"], js = data["js"], css = data["css"], ) response = api.succeed({ "id": program.program_id }, status=201) response["Location"] = "/program/" + program.program_id return response
def comment_comments(request, *args): if (len(args) == 1): comment_id = args[0] comments = Comment.objects.select_related("user__profile").filter( parent__comment_id=comment_id).order_by("created") elif (len(args) == 2): program_id = args[0] comment_id = args[1] comments = Comment.objects.select_related("user__profile").filter( program_id=program_id, parent__comment_id=comment_id).order_by("created") comments = list(comments) return api.succeed({"comments": [c.to_dict() for c in comments]})
def notif(request, notif_id): notif = Notif.objects.get(notif_id=notif_id) if (request.method == "PATCH"): data = json.loads(request.body) read = data["isRead"] #true or false if request.user != notif.target_user: return api.error("Not authorized", status=401) if (read != True and read != False): return api.error("Invalid type for key \"isRead\"") notif.is_read = read notif.save() return api.succeed()
def forgot_password(request): username = json.loads(request.body)["username"] try: user = User.objects.get(username=username) except User.DoesNotExist: # Other DoesNotExist errors 404, but we need this one to 400 return api.error("No user found with matching email and username.", status=400) email = user.email timezone = int(json.loads(request.body)["timezone"]) # Not perfect, but checks for a reasonable time value. if (timezone > 840 or timezone < -840): timezone = 0 # Creates a time string in the form HH:MM AM|PM req_time = time.strftime("%I:%M %p", time.localtime(time.time() - (timezone) * 60)) token = token_generator.make_token(user) link = "{protocol}://{domain}/user/reset-password?{query_params}" link = link.format(protocol="https" if request.is_secure() else "http", domain=Site.objects.get_current().domain, query_params=urlencode({ "token": token, "user_id": user.profile.profile_id })) message = render_to_string("account/forgotPasswordEmail.html", { "link": link, "time": req_time, }) if (email != ""): send_mail(subject="OurJSEditor Reset Password Request", from_email="*****@*****.**", recipient_list=[email], message="Here's your password reset link: " + link, html_message=message) return api.succeed({"user": {"id": user.profile.profile_id}})
def program_list(request, sort): if (not sort): sort = "new" # Default sort. sort is actually passed in as None, so we can't use an argument default if (sort not in key_func_mapping): return api.error("Invalid sort type: \"{}\"".format(sort)) key_func = key_func_mapping[sort] if (type(key_func) is unicode): key_func = lambda program: getattr(program, key_func_mapping[sort]) programs = sorted(Program.objects.all(), reverse=True, key=key_func)[:20] program_dicts = [] for program in programs: program = program.to_dict() del(program["css"]) del(program["html"]) del(program["js"]) program_dicts.append(program) return api.succeed({"sort": sort, "programs": program_dicts})
def collaborators(request, program_id): requested_program = Program.objects.get(program_id=program_id) # Any collaborator can add or remove other collaborators if not requested_program.can_user_edit(request.user): return api.error("Not authorized.", status=401) # Payload: # {user:{id:""}} # {user:{username:""}} # TODO: Allow getting a user by username *or* id? requested_user_identifier = json.loads(request.body)["user"] try: user = Profile.objects.get(profile_id=requested_user_identifier["id"]).user except KeyError: user = User.objects.get(username=requested_user_identifier["username"]) if request.method == "POST": # Check if collaborators is already a collab # or is the author if requested_program.can_user_edit(user): return api.error("That user can already edit this program.") # Send a notification to the program owner, if it wasn't the author who did the adding if request.user != requested_program.user: Notif.objects.create( target_user=requested_program.user, link="/program/" + requested_program.program_id, description="<strong>{0}</strong> added <strong>{1}</strong> to the list of collaborators on your program, <strong>{2}</strong>.".format( escape(request.user.profile.display_name), escape(user.profile.display_name), escape(requested_program.title)), ) # Send a notification to the user who has been added # if (request.user != user): Notif.objects.create( target_user=user, link="/program/" + requested_program.program_id, description="<strong>{0}</strong> added you as a collaborator on the program, <strong>{1}</strong>.".format( escape(request.user.profile.display_name), escape(requested_program.title)), ) requested_program.collaborators.add(user) return api.succeed({"username": user.username, "id": user.profile.profile_id}) elif request.method == "DELETE": if requested_program.collaborators.filter(id=user.id).exists(): # Send a notification to the program owner, if it wasn't the author who did the removing if request.user != requested_program.user: Notif.objects.create( target_user=requested_program.user, # Program author link="/program/" + requested_program.program_id, description="<strong>{0}</strong> removed <strong>{1}</strong> from the list of collaborators on your program, <strong>{2}</strong>.".format( escape(request.user.profile.display_name), escape(user.profile.display_name), escape(requested_program.title)), ) # Send a notification to the user who was removed, unless they removed themselves if request.user != user: Notif.objects.create( target_user=user, link="/program/" + requested_program.program_id, description="<strong>{0}</strong> removed you from the list of collaborators on the program, <strong>{1}</strong>.".format( escape(request.user.profile.display_name), escape(requested_program.title)), ) # Other, 3rd-party, collaborators don't get notifications requested_program.collaborators.remove(user) return api.succeed() return api.error("User isn't a collaborator on this program.")
def username_valid(request, username): return api.succeed({ "usernameValid": check_username(username, "") })
def program_comments(request, program_id): comments = list( Comment.objects.select_related("user__profile").filter( program_id=program_id, depth=0).order_by("-created")) return api.succeed({"comments": [c.to_dict() for c in comments]})
def new_comment(request, program_id): data = json.loads(request.body) # A JSON of `null` gets parsed into a Python of `None` # If parent isn't passed, KeyError, caught by standardAPIErrors parent_comment = data["parent"] if (parent_comment is None): depth = 0 else: try: parent_comment = Comment.objects.get(comment_id=parent_comment, program_id=program_id) except Comment.DoesNotExist: return api.error("Invalid comment parent") depth = parent_comment.depth + 1 if (depth > 1): return api.error("Comments have gone too deep!") if (parent_comment is not None): parent_comment.reply_count += 1 parent_comment.save() program = Program.objects.get(program_id=program_id) comment = Comment.objects.create( user=request.user, program=program, parent=parent_comment, depth=depth, content=data["content"], original_content=data["content"], ) link = "/program/{0}#comment-{1}".format(comment.program.program_id, comment.comment_id) if (depth == 0): if (program.user != comment.user): Notif.objects.create( target_user=program.user, link=link, description= "<strong>{0}</strong> left a comment on your program, <strong>{1}</strong>" .format(escape(request.user.profile.display_name), escape(program.title)), source_comment=comment) else: #Create a list of everyone in the comment thread and spam them all to_notify = set( Comment.objects.filter(parent=parent_comment).exclude( user=parent_comment.user ) #Not the thread starter, they get a different message .exclude(user=comment.user) #Not the user who posted this comment .values_list("user", flat=True)) for user in to_notify: Notif.objects.create( target_user_id=user, link=link, description= "<strong>{0}</strong> commented on a thread on <strong>{1}</strong>" .format(escape(request.user.profile.display_name), escape(program.title)), source_comment=comment) #Notify the original comment creator seperately Notif.objects.create( target_user=parent_comment.user, link=link, description= "<strong>{0}</strong> replied to your comment on <strong>{1}</strong>" .format(escape(request.user.profile.display_name), escape(program.title)), source_comment=comment) response = api.succeed({"id": comment.comment_id}, status=201) response["Location"] = link return response
def program(request, program_id): requested_program = Program.objects.get(program_id=program_id) if (request.method == "GET"): return api.succeed(requested_program.to_dict()) elif (request.method == "PATCH"): data = json.loads(request.body) return_data = {} if request.user != requested_program.user: return api.error("Not authorized.", status=401) if "title" in data and len(data["title"]) > 45: return api.error("Title length exceeds maximum characters.", status=400) if "publishedMessage" in data and len(data["publishedMessage"]) > 250: return api.error("Publish message can't exceed 250 characters") if "publishedMessage" in data: # Should it be possible to publish without an image or update the image without publishing try: image = base64toImageFile(data["imageData"], "{}.png".format(requested_program.program_id)) except TypeError as err: if (err.args[0] == "Image isn't a PNG"): return api.error("Image must be a PNG") raise except: return api.error("Invalid Image") requested_program.image = image requested_program.published_message = data["publishedMessage"] requested_program.last_published = datetime.datetime.now() return_data["lastPublished"] = requested_program.last_published.replace(microsecond=0).isoformat() + "Z" # Create notification for subscribers subscribers = requested_program.user.profile.profile_set.all() for subscriber in subscribers: Notif.objects.create( target_user = subscriber.user, link = "/program/" + requested_program.program_id, description = "<strong>{0}</strong> just published a new program, <strong>{1}</strong>".format( escape(request.user.profile.display_name), escape(requested_program.title)), source_program = requested_program ) valid_props = ["html", "js", "css", "title"] for prop in valid_props: if prop in data: setattr(requested_program, prop, data[prop]) requested_program.save() return api.succeed(return_data) elif (request.method == "DELETE"): if (request.user != requested_program.user): return api.error("Not authorized.", status=401) if (requested_program.image.name != "program/nophoto.png"): requested_program.image.delete() requested_program.delete() return api.succeed()
def notif_list(request): notifs = Notif.objects.filter( target_user=request.user).order_by("-created") notifs = map(lambda n: n.to_dict(), notifs) return api.succeed({"notifs": notifs})
def notif_count(request): return api.succeed({ "notifCount": Notif.objects.filter(target_user=request.user, is_read=False).count() })
def notif_list(request): notifs = Notif.objects.filter( target_user=request.user).order_by("-created") notifs = [n.to_dict() for n in notifs] return api.succeed({"notifs": notifs})