Commit 212fa65e by Trevor Austin

Add Week 5 notes and exercises

parent d70119d9
from flask import Flask, request, jsonify
app = Flask(__name__)
verses = [
"Row row row your boat",
"Gently down the stream",
"Merrily merrily merrily merrily",
"Life is but a dream"
]
@app.route('/', methods=['GET', 'POST'])
def hello_world():
if request.method == 'POST':
content = request.get_json()
if content is not None:
newVerse = content.get('verse')
if newVerse is not None:
verses.append(newVerse)
return jsonify(verses)
# curl -d '{"verse":"Row row tow your canoe"}' -H 'Content-Type: application/json' localhost:5000
# Exercise #5: Watch Party
5 points
**DUE: Wednesday, February 17 by 1:50pm**
### Instructions
For this exercise, we will build a group chat web application with asynchronous
Javascript and a REST API written in Python with Flask.
Watch Party lets users start new private chats that work like group texts or
Slack rooms. A user creates a new chat, which is assigned a unique identifier.
They can invite up to 5 other users by sharing a unique link that will
authenticate the bearer to the chat. Users in a chat post messages which appear
in a single conversation thread to all users. Unlike Slack or a group chat,
Watch Party only saves or shows the last 30 messages.
Implement the UI for Watch Party in HTML, CSS, and Javascript, and serve it
using server-side code written in the [latest stable version of Python](https://www.python.org/downloads/release/python-391/)
([3.9.1](https://www.python.org/downloads/release/python-391/))
and [Flask]((https://www.python.org/downloads/release/python-391/)). Be sure to
include important features like:
- Prompt users to enter a username when they create or join a chat.
- Give each chat a unique URL
- As other users type new messages in a chat, Watch Party should asynchronously
fetch them, and those messages should appear automatically without anyone
reloading the page.
- Allow users to be in multiple chats in multiple tabs if they want (hint: scope
storage and session tokens to chat id).
- Redirect users to the home screen if the chat they're trying to join already
has 6 users in it.
- Make sure chat messages support Unicode characters
Watch Party can be visually very simple, but should render responsively on
desktops and on mobile. Make sure the message input for for a chat is always on
the screen regardless of where the user scrolls. Chats are intended to be
ephemeral and are not saved to a database or the filesystem (ie it's ok for them
just to exist in memory on the web server). For the purposes of this Exercise,
you also do not need to worry about garbage collecting chats that are not in use.
Also using Python and Flask, write a REST API for creating and hosting live
Watch Party group chats. It should support the following methods:
- `POST /create`: Allow any user to create a new chat. Return a unique identifier
`chat_id`and a `session_token` to allow the original creator to identify
themselves in subsequent requests. All session tokens should expire within 6
hours.
- `POST /<chat_id>/invite`: Take `chat_id` as a URL param and require that
chat's creator's `session_token` in an authorization header. Generate a
"magic link" that, when visited, loads the Watch Party web application,
prompts the new user for a username and authenticates them by giving them a
`session_token`, and displays the chat's page.
- `GET /<chat_id>`: Require a valid `session_token` in an authorization header.
Return the messages in the chat. Optionally take a parameter that allows it to
return only new messages.
- `POST /<chat_id>`: Require a valid `session_token` in an authorization header.
Post a new message to the chat.
Your web application should use these API methods. Feel free to include any
other methods you think will be useful, either as web controllers or as further
API endpoints. You can use any other libraries or frameworks you find useful, as
well.
Remember to include in your submission any classmates you collaborated with and
any materials you consulted. Watch Party (though it has somewhat different
requirements) is inspired by [yap.chat](https://yap.chat/).
### Rubric
One point each for:
- Sign up with Magic Links: Generate URLs that contain a chat id and a unique
key, such that visiting that URL lets the user authorize and join the chat.
- Session Tokens: Issue a session token to users that create a chat or enter one
with a magic link. Hold those tokens in memory on the server side. Authenticate
API requests by requiring users to bring a token. Tokens should expire within
6 hours, and only 6 tokens may exist for a given chat.
- Chat Web UI: UI to create new chats. UI to post messages and to asynchronously
(and without user input) fetch and display new messages from the server as
they are posted by interacting with the API. Hold authorization token in local
storage.
- JSON API:
- REST endpoint to create a new chat that returns a session token.
- REST endpoint authenticate to an existing chat and receive a session token.
- REST endpoints that require a session token to post messages to a chat and
get messages from a chat
- Advanced UI Handling: Allow users to be in multiple chats in multiple tabs or
windows. Redirect users to the home screen if they try to join a chat that's
full (even by following a magic link). Support usernames and messages that
contain unicode characters.
import string
import random
from datetime import datetime
from flask import Flask, render_template, request
from functools import wraps
app = Flask(__name__)
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
# sample_chats = {
# 1: {
# "authorized_users": {
# "session_token_0": {"username": "Alice", "expires": "2020-02-15T20:53:15Z"},
# "session_token_1": {"username": "Bob", "expires": "2020-02-15T20:57:22Z"}
# },
# "magic_key": "some_really_long_key_value"
# "messages": [
# {"username": "Alice", "body": "Hi Bob!"},
# {"username": "Bob", "body": "Hi Alice!"},
# {"username": "Alice", "body": "Knock knock"},
# {"username": "Bob", "body": "Who's there?"},
# ]
# }
# }
chats = {}
def newChat(host, session_token):
authorized_users = dict([
(session_token, dict([
("username", host),
("expires", datetime.utcnow() + timedelta(hours=6))
]))
])
magic_key = ''.join(random.choices(string.ascii_lowercase + string.digits, k=10))
return dict([
("authorized_users", authorized_users),
("magic_key", magic_key),
("messages", [])
])
@app.route('/')
def index(chat_id=None):
return app.send_static_file('index.html')
@app.route('/auth')
def auth():
return app.send_static_file('auth.html')
@app.route('/chat/<int:chat_id>')
def chat():
return app.send_static_file('chat.html')
# -------------------------------- API ROUTES ----------------------------------
@app.route('/api/create', methods=['POST'])
def create ():
# TODO: generate a session token for the user who created the chat
# TODO: create a chat data structure in global memory
return {
"chat_id": 1,
"session_token": "some_token_value",
"magic_invite_link": "http://localhost:5000/auth?chat_id=1&magic_key=some_really_long_key_value"
}
@app.route('/api/authenticate', methods=['POST'])
def authenticate():
# TODO: check if the request body contains a chat_id and the correct magic_key for that chat
# TODO: also send a username
return {"session_token": "some_token_value"}
@app.route('/api/messages', methods=['GET', 'POST'])
def messages ():
# TODO: check if the request body contains a chat_id and valid session token for that chat
if request.method == 'POST':
# TODO: add the message
pass
messages = {} # TODO: get the messages for this chat from global memory
return {
"messages": messages,
"magic_invite_link": "http://localhost:5000/chat/1?magic_key=some_really_long_key_value"
}
<html>
<head>
<title>Watch Party</title>
<link rel="stylesheet" type="text/css" href="static/style.css"></link>
<script src="static/script.js"></script>
</head>
<body>
<div class="container">
<div class="auth">
<h3>Enter a username!</h3>
<input id="username"></input>
<button onclick="authenticate()" value="Join Chat"></button>
</div>
</div>
</body>
</html>
<html>
<head>
<title>Watch Party</title>
<link rel="stylesheet" type="text/css" href="static/style.css"></link>
<script src="static/script.js"></script>
</head>
<body>
<div class="container">
<div class="chat">
<div class="messages">
Chats chats chats
</div>
<div class="comment_box">
<form>
<input name="comment" />
<input type="submit" value="Post" />
</form>
</div>
</div>
</div>
<script>
startMessagePolling();
</script>
</body>
</html>
<html>
<head>
<title>Watch Party</title>
<link rel="stylesheet" type="text/css" href="static/style.css"></link>
<script src="static/script.js"></script>
</head>
<body>
<div class="container">
<div class="hero">
<h1>Watch Party</h1>
<button value="Create a Chat!"></button>
</div>
</div>
</body>
</html>
/* For index.html */
// TODO: If a user click to create a chat, create a session token for them
// and save it. Redirect the user to /chat/<chat_id>
function createChat() {
}
/* For auth.html */
// TODO: On page load, pull chat_id and magic_key out of the URL parameters
// Send them to the auth API endpoint to get a session token
// If the user authenticaes successfully, save the session token
// and redirect them to /chat/<chat_id>
function authenticate() {
return;
}
/* For chat.html */
// TODO: Fetch the list of existing chat messages.
// POST to the API when the user posts a new message.
// Automatically poll for new messages on a regular interval.
function postMessage() {
return;
}
function getMessages() {
return;
}
function startMessagePolling() {
return;
}
<!DOCTYPE html>
<html>
<head>
<title>Web Development - Week 3</title>
<meta charset="utf-8">
<style>
@import url(https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz);
@import url(https://fonts.googleapis.com/css?family=Droid+Serif:400,700,400italic);
@import url(https://fonts.googleapis.com/css?family=Ubuntu+Mono:400,700,400italic);
body { font-family: 'Droid Serif'; }
h1, h2, h3 {
font-family: 'Yanone Kaffeesatz';
font-weight: normal;
}
img { width: 100%; }
.remark-code, .remark-inline-code { font-family: 'Ubuntu Mono'; }
.agenda h1 { margin-bottom: 0px; }
.agenda ul { margin-block-start: 0; color: #666 }
</style>
</head>
<body>
<textarea id="source">
</textarea>
<script src="https://remarkjs.com/downloads/remark-latest.min.js">
</script>
<script>
var slideshow = remark.create({sourceUrl: 'notes_week_5.md'});
</script>
</body>
</html>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment