Move from AWS to GCP

This commit is contained in:
Matthew Welch 2021-03-06 14:29:44 -08:00
parent dd763deaea
commit bc31348313
13 changed files with 218 additions and 233 deletions

View File

@ -1,8 +0,0 @@
# Elastic Beanstalk Files
.elasticbeanstalk/*
!.elasticbeanstalk/*.cfg.yml
!.elasticbeanstalk/*.global.yml
.idea/
__pycache__/
venv/

7
.gcloudignore Normal file
View File

@ -0,0 +1,7 @@
.idea/
__pycache__/
venv/
.gcloudignore
.git
.gitignore
*.tsv

6
.gitignore vendored
View File

@ -1,9 +1,3 @@
# Elastic Beanstalk Files
.elasticbeanstalk/*
!.elasticbeanstalk/*.cfg.yml
!.elasticbeanstalk/*.global.yml
.env .env
.idea/ .idea/
__pycache__/ __pycache__/

1
app.yaml Normal file
View File

@ -0,0 +1 @@
runtime: python39

View File

@ -1,52 +0,0 @@
from flask import Flask, render_template, redirect, url_for
from botocore.exceptions import ClientError
import database
application = Flask(__name__)
application.config.from_pyfile("config.py")
@application.route("/")
def index():
try:
easy = database.get_random_question_difficulty(1)
medium = database.get_random_question_difficulty(2)
hard = database.get_random_question_difficulty(3)
return render_template(
"index.html",
title="Quiz The Word",
easy=easy,
medium=medium,
hard=hard,
)
except ClientError as e:
return e.response["Error"]["Message"]
except Exception as e:
return str(e)
@application.route("/question/<int:question_index>")
def question(question_index):
try:
item_count = database.get_question_count()
if question_index < item_count:
item = database.get_question(question_index)
return render_template(
"index.html",
title="Bible Trivia",
question=item["Question"],
answer=item["Answer"],
addresses=item["Address"],
category=item["Category"],
difficulty=item["Difficulty"],
)
else:
return "<h1>Question not found.</h1>"
except ClientError as e:
return e.response["Error"]["Message"]
except Exception as e:
return str(e)
if __name__ == "__main__":
application.run()

View File

@ -5,6 +5,12 @@ from dotenv import load_dotenv
load_dotenv(".env") load_dotenv(".env")
SECRET_KEY = environ.get("SECRET_KEY") SECRET_KEY = environ.get("SECRET_KEY")
AWS_ACCESS_KEY_ID = environ.get("AWS_ACCESS_KEY_ID") DB_USER = environ.get("DB_USER")
AWS_SECRET_ACCESS_KEY = environ.get("AWS_SECRET_ACCESS_KEY") DB_PASSWORD = environ.get("DB_PASSWORD")
REGION_NAME = environ.get("REGION_NAME") DB_HOST = environ.get("DB_HOST")
DB_PORT = environ.get("DB_PORT")
DB_NAME = environ.get("DB_NAME")
DB_SOCKET_DIR = environ.get("DB_SOCKET_DIR", "/cloudsql")
CLOUD_SQL_CONNECTION_NAME = environ.get("CLOUD_SQL_CONNECTION_NAME")
# DB_URL = f"postgres+psycopg2://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}?host={DB_SOCKET_DIR}/{CLOUD_SQL_CONNECTION_NAME}"
DB_URL = f"postgres+psycopg2://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}"

View File

@ -1,36 +1,86 @@
from flask import current_app, g from flask import current_app, g
import boto3 import sqlalchemy
from boto3.dynamodb.conditions import Key, Attr from sqlalchemy import Column, JSON, String, Integer, create_engine, ForeignKey, func
import random from sqlalchemy.ext.declarative import declarative_base
from dynamodb_json import json_util from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import sessionmaker, relationship
from config import DB_URL
engine = create_engine(DB_URL)
Session = sessionmaker(bind=engine)
Base = declarative_base()
def get_table(table_name): class AllQuestions(Base):
table = getattr(g, table_name, None) __tablename__ = "all_questions"
if table is None:
dynamo = boto3.resource("dynamodb", aws_access_key_id=current_app.config["AWS_ACCESS_KEY_ID"], question_id = Column(Integer, primary_key=True, nullable=False)
aws_secret_access_key=current_app.config["AWS_SECRET_ACCESS_KEY"], region_name=current_app.config["REGION_NAME"]) question = Column(String)
setattr(g, table_name, dynamo.Table(table_name)) answer = Column(String)
return dynamo.Table(table_name) addresses = Column(String)
return table
def __repr__(self):
return f"<Question: {self.question_id}>"
def get_question_count(): class HiddenAnswer(Base):
table = get_table("BibleQuestions") __tablename__ = "category_hidden_answer"
return table.item_count
question_id = Column(Integer, ForeignKey("all_questions.question_id"), primary_key=True)
difficulty = Column(Integer)
hint = Column(JSON)
all_question_relationship = relationship("AllQuestions", primaryjoin="HiddenAnswer.question_id==AllQuestions.question_id", lazy="joined", uselist=False)
question = association_proxy("all_question_relationship", "question")
answer = association_proxy("all_question_relationship", "answer")
addresses = association_proxy("all_question_relationship", "addresses")
def __repr__(self):
return f"<Question Hidden Answer: {self.question_id}>"
def get_question(id): class MultipleChoice(Base):
table = get_table("BibleQuestions") __tablename__ = "category_multiple_choice"
response = table.get_item(Key={"id": id})
return json_util.loads(response["Item"]) question_id = Column(Integer, ForeignKey("all_questions.question_id"), primary_key=True)
difficulty = Column(Integer)
hint = Column(JSON)
wrong_answers = Column(String)
all_question_relationship = relationship("AllQuestions", primaryjoin="MultipleChoice.question_id==AllQuestions.question_id", lazy="joined", uselist=False)
question = association_proxy("all_question_relationship", "question")
answer = association_proxy("all_question_relationship", "answer")
addresses = association_proxy("all_question_relationship", "addresses")
def __repr__(self):
return f"<Question Multiple Choice: {self.question_id}>"
def get_questions_of_difficulty(difficulty): def get_all_questions():
table = get_table("BibleQuestions") session = Session()
return table.scan(FilterExpression=Attr("Difficulty").eq(difficulty)) return session.query(AllQuestions).all()
def get_random_question_difficulty(difficulty): def get_all_hidden_answer():
questions = get_questions_of_difficulty(difficulty) session = Session()
return questions["Items"][random.randint(0, questions["Count"]-1)] return session.query(HiddenAnswer).all()
def get_all_multiple_choice():
session = Session()
return session.query(MultipleChoice).all()
def get_category_count(category):
session = Session()
return session.query(category).count()
def get_question(category, question_id):
session = Session()
return session.query(category).filter(category.question_id == question_id).one_or_none()
def get_random_question_of_difficulty(category, difficulty):
session = Session()
return session.query(category).filter(category.difficulty == difficulty).order_by(func.random()).first()

23
main.py Normal file
View File

@ -0,0 +1,23 @@
from flask import Flask, render_template
import database
app = Flask(__name__)
app.config.from_pyfile("config.py")
@app.route("/")
def index():
easy = database.get_random_question_of_difficulty(database.HiddenAnswer, 1)
medium = database.get_random_question_of_difficulty(database.HiddenAnswer, 2)
hard = database.get_random_question_of_difficulty(database.HiddenAnswer, 3)
return render_template(
"hidden_answer.html",
title="Home",
easy=easy,
medium=medium,
hard=hard,
)
if __name__ == "__main__":
app.run()

View File

@ -1,16 +1,11 @@
boto3==1.16.25
botocore==1.19.25
click==7.1.2 click==7.1.2
dynamodb-json==1.3
Flask==1.1.2 Flask==1.1.2
itsdangerous==1.1.0 itsdangerous==1.1.0
Jinja2==2.11.2 Jinja2==2.11.3
jmespath==0.10.0
MarkupSafe==1.1.1 MarkupSafe==1.1.1
python-dateutil==2.8.1 pg8000==1.17.0
psycopg2==2.8.6
python-dotenv==0.15.0 python-dotenv==0.15.0
s3transfer==0.3.3 scramp==1.2.0
simplejson==3.17.2 SQLAlchemy==1.3.23
six==1.15.0
urllib3==1.26.2
Werkzeug==1.0.1 Werkzeug==1.0.1

View File

@ -19,3 +19,10 @@
.difficulty { .difficulty {
margin: 10px; margin: 10px;
} }
.vertical-nav {
position: fixed;
right: 0;
writing-mode: vertical-rl;
text-orientation: mixed;
}

25
templates/base.html Normal file
View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html class="h-100" lang="en">
<head>
<meta charset="UTF-8">
<title>Quiz The Word - {{ title }}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/cyborg/bootstrap.min.css">
<link rel="stylesheet" href="/static/style.css">
{% block head %}{% endblock %}
</head>
<body class="d-flex flex-column h-100">
<nav class="nav vertical-nav">
<a class="nav-link" href="#">Hidden Answer</a>
</nav>
{% block body %}{% endblock %}
<footer class="mt-auto py-3">
<div class="container text-center">
<span class="">Created By ItIsGood.com</span>
</div>
</footer>
</body>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"></script>
{% block scripts %}{% endblock %}
</html>

View File

@ -0,0 +1,66 @@
{% extends "base.html" %}
{% block body %}
<main class="flex-shrink-0">
<h1 class="title">Quiz The Word</h1>
<div id="easy-question" class="container-md align-content-center question">
<div class="card question-text">
<p id="easy-question">{{ easy.question }}</p>
<button class="btn btn-secondary show-answer" onclick="showAnswer()">Show Answer</button>
<p id="easy-answer" class="answer">{{ easy.answer }}</p>
<div id="easy-bible-verses">{{ easy.addresses }}</div>
</div>
</div>
<div id="medium-question" class="container-md align-content-center question" style="display: none">
<div class="card question-text">
<p id="medium-question">{{ medium.question }}</p>
<button class="btn btn-secondary show-answer" onclick="showAnswer()">Show Answer</button>
<p id="medium-answer" class="answer">{{ medium.answer }}</p>
<div id="medium-bible-verses">{{ medium.addresses }}</div>
</div>
</div>
<div id="hard-question" class="container-md align-content-center question" style="display: none">
<div class="card question-text">
<p id="hard-question">{{ hard.question }}</p>
<button class="btn btn-secondary show-answer" onclick="showAnswer()">Show Answer</button>
<p id="hard-answer" class="answer">{{ hard.answer }}</p>
<div id="hard-bible-verses">{{ hard.addresses }}</div>
</div>
</div>
<div class="text-center difficulty">
<div class="btn-group btn-group-toggle" data-toggle="buttons" onclick="change_difficulty()">
<label class="btn btn-secondary active">
<input type="radio" name="difficulty" id="easy" autocomplete="off" value="easy" checked>Easy
</label>
<label class="btn btn-secondary">
<input type="radio" name="difficulty" id="medium" autocomplete="off" value="medium">Medium
</label>
<label class="btn btn-secondary">
<input type="radio" name="difficulty" id="hard" autocomplete="off" value="hard">Hard
</label>
</div>
</div>
</main>
{% endblock %}
{% block scripts %}
<script>
function change_difficulty() {
let difficulty = $("input[name='difficulty']:checked").val();
console.log(difficulty);
$(".question").css("display", "none");
if (difficulty === "easy") {
$("#easy-question").css("display", "block");
} else if (difficulty === "medium") {
$("#medium-question").css("display", "block");
} else if (difficulty === "hard") {
$("#hard-question").css("display", "block");
}
}
function showAnswer() {
$(".question:visible").find(".answer").css("display", "block");
$(".show-answer:visible").css("display", "none");
}
</script>
{% endblock %}

View File

@ -1,129 +0,0 @@
<!DOCTYPE html>
<html class="h-100" lang="en">
<head>
<title>{{ title }}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/cyborg/bootstrap.min.css">
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="/static/style.css">
</head>
<body class="d-flex flex-column h-100">
<main class="flex-shrink-0">
<h1 class="title">Quiz The Word</h1>
<div id="easy-question" class="container-md align-content-center question">
<div class="card question-text">
<p id="easy-question">{{ easy["Question"] }}</p>
<button class="btn btn-secondary show-answer" onclick="showAnswer()">Show Answer</button>
<p id="easy-answer" class="answer">{{ easy["Answer"] }}</p>
<div id="easy-bible-verses"></div>
<p>{{ easy["Difficulty"] }}</p>
</div>
</div>
<div id="medium-question" class="container-md align-content-center question" style="display: none">
<div class="card question-text">
<p id="medium-question">{{ medium["Question"] }}</p>
<button class="btn btn-secondary show-answer" onclick="showAnswer()">Show Answer</button>
<p id="medium-answer" class="answer">{{ medium["Answer"] }}</p>
<div id="medium-bible-verses"></div>
<p>{{ medium["Difficulty"] }}</p>
</div>
</div>
<div id="hard-question" class="container-md align-content-center question" style="display: none">
<div class="card question-text">
<p id="hard-question">{{ hard["Question"] }}</p>
<button class="btn btn-secondary show-answer" onclick="showAnswer()">Show Answer</button>
<p id="hard-answer" class="answer">{{ hard["Answer"] }}</p>
<div id="hard-bible-verses"></div>
<p>{{ hard["Difficulty"] }}</p>
</div>
</div>
<div class="text-center difficulty">
<div class="btn-group btn-group-toggle" data-toggle="buttons" onclick="change_difficulty()">
<label class="btn btn-secondary active">
<input type="radio" name="difficulty" id="easy" value="easy" checked>Easy
</label>
<label class="btn btn-secondary">
<input type="radio" name="difficulty" id="medium" value="medium">Medium
</label>
<label class="btn btn-secondary">
<input type="radio" name="difficulty" id="hard" value="hard">Hard
</label>
</div>
</div>
</main>
<footer class="mt-auto py-3">
<div class="container text-center">
<span class="">Created By ItIsGood.com</span>
</div>
</footer>
</body>
<script>
let easy_addresses = {{ easy["Address"]|tojson }};
let medium_addresses = {{ medium["Address"]|tojson }};
let hard_addresses = {{ hard["Address"]|tojson }};
function parse_bible_verses(bible_verses) {
let addresses = [];
for (let address of bible_verses) {
let verses = "";
if (address["Verses"] != null) {
for (let verse of address["Verses"]) {
if (verses != "") {
verses += ", ";
} else {
verses = ":";
}
if (typeof verse != "number") {
verses += `${verse[0]}-${verse[1]}`;
} else {
verses += verse.toString();
}
}
}
if (address["Chapter"] != null) {
addresses.push(`${address["Book"]} ${address["Chapter"]}${verses}`);
} else if (address["Book"] != null) {
addresses.push(`${address["Book"]}`);
}
}
return addresses;
}
function show_bible_verses(element_id, bible_verses) {
let bibleVerses = $(element_id);
if (bible_verses.length > 0) {
for (let address of bible_verses) {
if (address != bible_verses[0]) {
bibleVerses.append(`; ${address}`);
} else {
bibleVerses.append(address);
}
}
}
}
show_bible_verses("#easy-bible-verses", parse_bible_verses(easy_addresses));
show_bible_verses("#medium-bible-verses", parse_bible_verses(medium_addresses));
show_bible_verses("#hard-bible-verses", parse_bible_verses(hard_addresses));
function change_difficulty() {
let difficulty = $("input[name='difficulty']:checked").val();
console.log(difficulty);
$(".question").css("display", "none");
if (difficulty === "easy") {
$("#easy-question").css("display", "block");
} else if (difficulty === "medium") {
$("#medium-question").css("display", "block");
} else if (difficulty === "hard") {
$("#hard-question").css("display", "block");
}
}
function showAnswer() {
$(".question:visible").find(".answer").css("display", "block");
$(".show-answer:visible").css("display", "none");
}
</script>
</html>