Commit d39f4929 by Trevor Austin

Add week 8 notes and labs

parent 1ab3bd4a
from flask import Flask, render_template, request, jsonify
from functools import wraps
app = Flask(__name__)
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
posts = [
{
"id": 0,
"title": "Hello World!",
"body": "This is my first post and I'm so proud of it."
}
]
@app.route('/')
def index():
return app.send_static_file('index.html')
# -------------------------------- API ROUTES ----------------------------------
@app.route('/api/login', methods=['POST'])
def login ():
print(request.data)
body = request.get_json()
print(body)
username = body['username']
password = body['password']
if(username == "trevor" and password == "password"):
return {"session_token": "12345",}
return {}, 403
@app.route('/api/post', methods=['POST'])
def post():
print(request.data)
body = request.get_json()
session_token = body['session_token']
if(session_token != "12345"):
return {}, 403
if 'title' in body and 'body' in body:
new_post = {
"id": len(posts),
"title": body['title'],
"body": body['body']
}
posts.insert(0,new_post)
return jsonify(posts)
<html lang="en" dir="ltr">
<head>
<title>Single-Page Login and Post</title>
<link rel="stylesheet" type="text/css" href="static/style.css">
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/prop-types@15.6/prop-types.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel" src="static/script.js"></script>
</body>
</html>
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
display: true,
}
}
login() {
const username = document.getElementById("username").value;
const password = document.getElementById("password").value;
this.setState({display: false});
fetch("http://127.0.0.1:5000/api/login", {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({username: username, password: password})
}).then((response) => {
if(response.status == 200) {
response.json().then((data) => {
window.localStorage.setItem("journal_session_token", data.session_token);
document.getElementById("compose").setAttribute('style', 'display: block;');
document.getElementById("posts").setAttribute('style', 'display: block;');
});
} else {
console.log(response.status);
this.logout();
}
}).catch((response) =>{
console.log(response);
this.logout();
})
}
logout() {
window.localStorage.removeItem("journal_session_token");
this.setState({display: true});
document.getElementById("compose").setAttribute('style', 'display: none;');
document.getElementById("posts").setAttribute('style', 'display: none;');
}
render() {
if(this.state.display) {
return (
<div>
<h2>Login</h2>
<div className="login_form">
<label htmlFor="username">Username</label>
<input id="username"></input>
<label htmlFor="password">Password</label>
<input id="password" type="password"></input>
<button className="form_button" onClick={() => this.login()}>
Submit
</button>
</div>
</div>
);
}
else {
return (
<div className="logout_button">
<button onClick={() => {this.logout()}}>
Logout
</button>
</div>
);
}
}
}
class Compose extends React.Component {
post() {
const title = document.getElementById("compose_title").value;
const body = document.getElementById("compose_body").value;
const session_token = window.localStorage.getItem("journal_session_token");
fetch("http://127.0.0.1:5000/api/post", {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
session_token: session_token,
title: title,
body: body
})
}).then(() => {
document.getElementById("compose_title").value = "";
document.getElementById("compose_body").value = "";
});
}
render() {
return (
<div className="compose" id="compose">
<h2>Compose</h2>
<div className="post_form">
<label htmlFor="compose_title">Title</label>
<input id="compose_title"></input>
<label htmlFor="compose_body">Post</label>
<textarea id="compose_body"></textarea>
<button className="form_button" onClick={() => this.post()}>
Post
</button>
</div>
</div>
);
}
}
class Posts extends React.Component {
constructor(props) {
super(props);
this.state = {
posts: [],
}
}
refresh(){
const session_token = window.localStorage.getItem("journal_session_token");
fetch("http://127.0.0.1:5000/api/post", {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({session_token: session_token})
})
.then((response) => response.json())
.then((data) => {
this.setState({posts: data});
});
}
render() {
const posts = this.state.posts.map((post) =>
<div key={post.id} id={"post_" + post.id}>
<h3>{post.title}</h3>
<div>{post.body}</div>
</div>
);
return (
<div className="posts" id="posts">
<h2>Posts</h2>
<button onClick={() => this.refresh()}>Refresh</button>
{posts}
</div>
);
}
}
Posts.propTypes = {
posts: window.PropTypes.array
}
class Journal extends React.Component {
loginHandler() {
// TODO: update this call by managing State
}
logoutHandler() {
// TODO: replace this call by managing State
}
render() {
return (
<div className="weblog">
<TitleBar />
<Login
loginHandler={this.loginHandler}
logoutHandler={this.logoutHandler}
/>
<Compose />
<Posts />
</div>
);
}
}
function TitleBar() {
return (
<div className="title_bar">
<h1>Sample Single-Page Journal</h1>
</div>
);
}
// ========================================
ReactDOM.render(
React.createElement(Journal),
document.getElementById('root')
);
body {
margin: 0
}
.weblog {
width: 960px;
margin: auto;
font-family: sans-serif;
}
.title_bar {
background-color: #CCFFEE
}
h1 {
margin: 0;
padding-top: 10px;
padding-bottom: 5px;
}
.login_form, .post_form {
width: 400px;
border: 2px solid #AADDCC;
border-radius: 4px;
display: grid;
grid-template-columns: auto auto auto;
grid-gap: 10px 10px;
padding: 20px 40px 20px 40px;
}
.login_form label {
grid-column: 1 / 2;
text-align: right;
margin-top: auto;
margin-bottom: auto;
}
.login_form input {
grid-column: 2 / 4;
font-size: 24px;
border: 2px solid #AADDCC;
border-radius: 4px;
}
.login_form button {
grid-column: 2;
}
.post_form label {
grid-column: 1 / 2;
margin-top: auto;
margin-bottom: auto;
}
.post_form input {
grid-column: 1 / 4;
font-size: 24px;
border: 2px solid #AADDCC;
border-radius: 4px;
}
.post_form textarea {
height: 150px;
grid-column: 1 / 4;
border: 2px solid #AADDCC;
border-radius: 4px;
}
.post_form button {
grid-column: 1;
}
button.form_button {
width: 100%;
font-size: 18px;
border: 2px solid #AADDCC;
border-radius: 4px;
}
.posts button {
padding: 10px 14px 10px 14px;
margin: 6px 6px 6px 6px;
background-color: white;
border: 2px solid #AADDCC;
border-radius: 4px;
color: #AADDCC;
font-size: 18;
}
.logout_button {
float: right;
}
.logout_button button {
padding: 10px 14px 10px 14px;
margin: 6px 6px 6px 6px;
background-color: white;
border: 2px solid #AADDCC;
border-radius: 4px;
color: #AADDCC;
font-size: 18;
}
-- sudo mysql -u root < 20200224T184500-create_database.sql
create database weblog CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- sudo mysql -u root weblog < 20200224T184700-create_tables.sql
create table posts (
id INT AUTO_INCREMENT PRIMARY KEY,
slug VARCHAR(30) NOT NULL,
title VARCHAR(255) NOT NULL,
body TEXT
);
create table comments (
id INT AUTO_INCREMENT PRIMARY KEY,
post_id INT,
body TEXT,
author VARCHAR(30),
FOREIGN KEY(post_id) REFERENCES posts(id)
);
-- sudo mysql -u root weblog < 20200224T184900-insert_posts_and_comments.sql
-- text from http://shakespeare.mit.edu/tempest/full.html
insert into posts (id, slug, title, body) values (
1,
"act_1_scene_1",
"On a ship at sea: a tempestuous noise",
"Enter a Master and a Boatswain"
);
insert into comments (post_id, author, body) values (
1, "Master", "Boatswain!"
);
insert into comments (post_id, author, body) values (
1, "Boatswain", "Here, master: what cheer?"
);
insert into comments (post_id, author, body) values (
1, "Master", "Good, speak to the mariners nfall to't, yarely,
or we run ourselves aground: bestir, bestir."
);
insert into comments (post_id, author, body) values (
1, "Boatswain", "Heigh, my hearts! cheerly, cheerly, my hearts!
yare, yare! Take in the topsail. Tend to the
master's whistle. Blow, till thou burst thy wind,
if room enough!"
);
insert into posts (id, slug, title, body) values (
2,
"act_1_scene_2",
"The island. Before PROSPERO'S cell.",
"If by your art, my dearest father, you have
Put the wild waters in this roar, allay them.
The sky, it seems, would pour down stinking pitch,
But that the sea, mounting to the welkin's cheek,
Dashes the fire out. O, I have suffered
With those that I saw suffer: a brave vessel,
Who had, no doubt, some noble creature in her,
Dash'd all to pieces. O, the cry did knock
Against my very heart. Poor souls, they perish'd.
Had I been any god of power, I would
Have sunk the sea within the earth or ere
It should the good ship so have swallow'd and
The fraughting souls within her."
);
insert into comments (post_id, author, body) values (
2, "Prospero", "Be collected:
No more amazement: tell your piteous heart
There's no harm done."
);
insert into comments (post_id, author, body) values (
2, "Miranda", "O, woe the day!"
);
insert into posts (id, slug, title, body) values (
3,
"act_2_scene_1",
"Another part of the island.",
"Beseech you, sir, be merry; you have cause,
So have we all, of joy; for our escape
Is much beyond our loss. Our hint of woe
Is common; every day some sailor's wife,
The masters of some merchant and the merchant
Have just our theme of woe; but for the miracle,
I mean our preservation, few in millions
Can speak like us: then wisely, good sir, weigh
Our sorrow with our comfort."
);
insert into comments (post_id, author, body) values (
3, "Alonso", "Prithee, peace."
);
insert into comments (post_id, author, body) values (
3, "Sebastian", "He receives comfort like cold porridge."
);
insert into comments (post_id, author, body) values (
3, "Alonso", "The visitor will not give him o'er so."
);
insert into comments (post_id, author, body) values (
3, "Sebastian", "Look he's winding up the watch of his wit;
by and by it will strike."
);
insert into posts (id, slug, title, body) values (
4,
"act_2_scene_2",
"Another part of the island.",
"All the infections that the sun sucks up
From bogs, fens, flats, on Prosper fall and make him
By inch-meal a disease! His spirits hear me
And yet I needs must curse. But they'll nor pinch,
Fright me with urchin--shows, pitch me i' the mire,
Nor lead me, like a firebrand, in the dark
Out of my way, unless he bid 'em; but
For every trifle are they set upon me;
Sometime like apes that mow and chatter at me
And after bite me, then like hedgehogs which
Lie tumbling in my barefoot way and mount
Their pricks at my footfall; sometime am I
All wound with adders who with cloven tongues
Do hiss me into madness.
Enter TRINCULO
Lo, now, lo!
Here comes a spirit of his, and to torment me
For bringing wood in slowly. I'll fall flat;
Perchance he will not mind me."
);
insert into comments (post_id, author, body) values (
4, "Trinculo", "Here's neither bush nor shrub, to bear off
any weather at all, and another storm brewing;
I hear it sing i' the wind: yond same black
cloud, yond huge one, looks like a foul
bombard that would shed his liquor. If it
should thunder as it did before, I know not
where to hide my head: yond same cloud cannot
choose but fall by pailfuls. What have we
here? a man or a fish? dead or alive? A fish:
he smells like a fish; a very ancient and fish-
like smell; a kind of not of the newest Poor-
John. A strange fish! Were I in England now,
as once I was, and had but this fish painted,
not a holiday fool there but would give a piece
of silver: there would this monster make a
man; any strange beast there makes a man:
when they will not give a doit to relieve a lame
beggar, they will lazy out ten to see a dead
Indian. Legged like a man and his fins like
arms! Warm o' my troth! I do now let loose
my opinion; hold it no longer: this is no fish,
but an islander, that hath lately suffered by a
thunderbolt.
Thunder
Alas, the storm is come again! my best way is to
creep under his gaberdine; there is no other
shelter hereabouts: misery acquaints a man with
strange bed-fellows. I will here shroud till the
dregs of the storm be past.
Enter STEPHANO, singing: a bottle in his hand"
);
class: center, middle
# MPCS 52553-2: Web Development
## Week 8: React Classes and Database Migrations
---
class: agenda
# Database Migrations
- Configuration as Code
- Migrating Safely
- Lab: Posts and Comments
# React Classes
- Thinking in React
- Conditional Rendering
- Lab: Login and Post
- Type Checking
- Lab: Login and Post
---
# Database Migrations
A database is a critical part of most modern web applications, and the way that
database is structured is a critical part of their design. And as database
software has gotten more mature, larger and larger teams have moved away from
having dedicated database administrators (DBAs) and towards putting the database
under the control of web application engineers.
(There are more DBAs then ever [BLS Statistics](https://www.bls.gov/ooh/computer-and-information-technology/database-administrators.htm#tab-1),
but they tend to work on bigger databases, which are of course now bigger then
ever)
---
# Database Migrations
Rather than defining tables or modifying their structure by hand, the best
practice is to write code that performs the transformations and check it into
version control. This code is commonly called a **database migration**.
Writing the transformations out and committing them to version control has a
number of advantages:
- It's easy to discover what the state of the database is or should be
- The application code and database Configuration are all in one place, making
it easier to be sure they stay in sync
- It's easier to restore a damaged database, or make a new copy for staging or
development work, because you have the code that created it
- It's easier to do collaborative review of database changes, through the
regular code review process
https://martinfowler.com/articles/evodb.html
---
# Migrating Safely
Application code that tries to reference tables that aren't there will throw an error.
Database tables that aren't referenced by any application code sit there harmlessly.
So for **creating** a table, you have to make the database changes first, then
the application code changes.
For **dropping** a table, you have to make the application code changes first.
We preface our migrations with an [ISO-8601](https://en.wikipedia.org/wiki/ISO_8601)
date and time so that their lexical order is the same as the order they are
meant to be run in.
---
# Migrating Safely
Sometimes you'll be able to make these changes all at once. You'll schedule a
downtime window, make the changes to the database and the application code
together, restart the servers and be on your way.
But sometimes, especially in web development, you may not be able to schedule
downtime. You may have to carefully sequence your changes so that they can be
deployed without needing to turn the servers off. So to change the way a table
is structured you might need to:
1. Create the new table structure
1. Deploy a change to the application code to write to the new **and** the old
tables
1. Copy over the historical data from the old table to the new one
1. Change the application code to read from the new table and stop writing to
the old one
1. Drop the old table
https://firstround.com/review/shims-jigs-and-other-woodworking-concepts-to-conquer-technical-debt/
---
# Lab: Posts and Comments
In the `/examples` directory we have SQL migrations to create the posts and
comments tables from Exercise 3, and to insert some sample data. Let's say we
want to add a new feature where users (with some `user_id`) can `like` posts and
comments.
--
So we know we'll need a `likes` table. We could let it link to posts and
comments separately with `post_id` and `comment_id` columns. But maybe thinking
about likes makes us realize the posts and comments have lots of behaviors in
common, and should really be modeled as one table.
--
How would we do that migration?
---
# React Classes
https://reactjs.org/docs/lifting-state-up.html
https://reactjs.org/docs/thinking-in-react.html
https://reactjs.org/docs/conditional-rendering.html
---
# Lab: Login and Post
In the `/examples` directory we have a simple single-page application that is
doing a lot of showing and hiding of page elements manually, and has the
application state spread over several components. Let's lift the state up to the
top component and use conditional formatting to let React manage the display for
us.
---
# Type Checking
https://reactjs.org/docs/typechecking-with-proptypes.html
https://www.npmjs.com/package/prop-types
https://flow.org/
https://www.typescriptlang.org/
---
# Lab: Login and Post
The Journal app already has some rudimentary type checking for the Posts
component, let's expand it to check that every Post has an id, a title, and a
post body.
<!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_8.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