Skip to content
This repository has been archived by the owner on May 1, 2023. It is now read-only.

Commit

Permalink
feat: bootstrapped basic web rendering using jinja2 (#102)
Browse files Browse the repository at this point in the history
* feat: bootstrapped basic web rendering using jinja2

* Formatting and remove unused import

* chore: removed reference to Form component

* chore: ignore unreferenced objects since import is required

* chore: api is now versioned and front end has been extracted to its own module

* feat: i18n for en & fr added

* chore: renamed pydantic organisation model to OrganisationFilter

* chore: formatting

* feat: add test

* chore: formatting isnt my friend

* chore: remove unused import after refactor

* chore: another test

* chore: remove unused imports from test

* chore: test removal of db initialize

* chore: remove import of disabled feature

* chore: remove broken test and database reset debug code

* chore: remove unused import

* chore: cleaned up and new db session code

* chore: less ugly page
  • Loading branch information
mohdnr committed Sep 2, 2021
1 parent 6a81cb7 commit a135c28
Show file tree
Hide file tree
Showing 24 changed files with 420 additions and 66 deletions.
2 changes: 2 additions & 0 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ services:
AWS_LOCALSTACK: "True"
AXE_CORE_URLS_TOPIC: "arn:aws:sns:us-east-1:000000000000:axe-core-urls-topic"

ports:
- "8080:8080"
db:
image: postgres:11.2
volumes:
Expand Down
47 changes: 0 additions & 47 deletions api/api_gateway/api.py

This file was deleted.

Empty file added api/api_gateway/v1/__init__.py
Empty file.
76 changes: 76 additions & 0 deletions api/api_gateway/v1/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from os import environ
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from fastapi.responses import RedirectResponse
from database.db import db_session
from logger import log

from models.Organisation import Organisation
from schemas.Organization import OrganizationCreate

# from crawler.crawler import crawl
# import uuid
from pydantic import BaseModel

app = FastAPI()


# Dependency
def get_db():
db = db_session()
try:
yield db
finally:
db.close()


@app.get("/api/v1/version")
def version():
return {"version": environ.get("GIT_SHA", "unknown")}


def get_db_version(session):

query = "SELECT version_num FROM alembic_version"
full_name = session.execute(query).fetchone()[0]
return full_name


@app.get("/api/v1/healthcheck")
def healthcheck(session: Session = Depends(get_db)):
try:
full_name = get_db_version(session)
db_status = {"able_to_connect": True, "db_version": full_name}
except SQLAlchemyError as err:
log.error(err)
db_status = {"able_to_connect": False}

return {"database": db_status}


# TODO Require auth and redirect to home
# TODO Push errors to cloudwatch metric and response when debug enabled
@app.post("/api/v1/organisation", response_class=RedirectResponse)
def create_organisation(
organisation: OrganizationCreate, session: Session = Depends(get_db)
):

try:
new_organisation = Organisation(name=organisation.name)
session.add(new_organisation)
session.commit()
return RedirectResponse("/dashboard")
except Exception as e:
log.error(e)
raise HTTPException(status_code=500, detail=str(e))


class CrawlUrl(BaseModel):
url: str


# @app.post("/crawl")
# def crawl_endpoint(crawl_url: CrawlUrl):
# log.info(f"Crawling {crawl_url}")
# crawl(uuid.uuid4(), crawl_url.url)
49 changes: 49 additions & 0 deletions api/front_end/templates/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="{{ lang }}">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%3E%3Ctext%20x='0'%20y='14'%3E🍁%3C/text%3E%3C/svg%3E" type="image/svg+xml" />
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.1/css/all.css">
<title>{{ webpage_title }}</title>
</head>
<body class="bg-gray-100 font-sans leading-normal tracking-normal h-full">
<div class="min-h-screen flex flex-col">
<div class="flex-grow">
<nav class="bg-white w-full z-10 top-0 shadow" id="header">
<div class="w-full container mx-auto flex flex-wrap items-center mt-0 pt-3 pb-3 md:pb-0">
<div class="pl-2 pb-4 w-1/2 md:pl-0">
<a class="text-gray-900 text-base no-underline font-bold xl:text-xl hover:no-underline" href="/{{ lang }}" aria-label="{{ goc }}">
<img class="h-6 inline-block pr-10" src="{{ goc_banner }}" alt="{{ goc }}" />
</a>
<span class="leading-none lg:ml-4 mr-5 lg:pl-5 font-semibold inline-block top-0 text-brand pb-0">{{ webpage_title }}</span>
<span class="bg-blue-200 py-1 px-2 rounded-lg text-small">{{ alpha }}</span>
</div>
<div class="pl-2 pb-4 w-1/2 md:pl-0 text-right">
<a class="text-base no-underline hover:no-underline" href="/{{other_lang}}" aria-label="{{ goc }}">
{{ other_language }}
</a>
</div>
</div>
</nav>
<div class="container w-full mx-auto h-full">
<div class="w-full px-4 mb-16 text-gray-800 leading-normal md:px-0 md:mt-8">
<div class="flex flex-col flex-1 max-h-full pl-2 pr-2 rounded-md xl:pr-4">
<main class="flex-1 pt-2">
{% block body %}{% endblock %}
</main>
</div>
</div>
<div class="footer">
{% block footer %}
<br>
<br>
{% endblock %}
</div>
</div>
</div>
</div>

</body>
</html>
40 changes: 40 additions & 0 deletions api/front_end/templates/dashboard.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{% extends "base.html" %}
{% block body %}
<center><h1>{{ organisations_locale }}</h1></center>
<div class="flex flex-col">
<div class="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div class="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
<div class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Name
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">Configure</span>
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
{% for organisation in organisations %}
<tr>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">
{{ organisation.name }}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a href="{{lang}}/organisation/{{ organisation.id }}/edit" class="text-indigo-600 hover:text-indigo-900">Edit</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>


{% endblock %}
7 changes: 7 additions & 0 deletions api/front_end/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block content %}
<div>{{ welcome }}</div>
{% block footer %}
{{super()}}
{% endblock %}
{% endblock %}
104 changes: 104 additions & 0 deletions api/front_end/view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
from fastapi import Depends, FastAPI, Request, HTTPException
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from babel.plural import PluralRule
from database.db import db_session
from logger import log
from sqlalchemy.orm import Session

from models.Organisation import Organisation

import glob
import json
import os

app = FastAPI()


# Dependency
def get_db():
db = db_session()
try:
yield db
finally:
db.close()


templates = Jinja2Templates(directory="front_end/templates")
default_fallback = "en"
languages = {}


def generate_languages(locale_files):
language_list = glob.glob(locale_files)
for lang in language_list:
filename = lang.split(os.path.sep)
lang_code = filename[1].split(".")[0]

with open(lang, "r", encoding="utf8") as file:
languages[lang_code] = json.load(file)


generate_languages("i18n/*.json")


# custom filters for Jinja2
def plural_formatting(key_value, input, locale):
plural_rule = PluralRule({"one": "n in 0..1"})
key = ""
for i in languages[locale]:
if key_value == languages[locale][i]:
key = i
break

if not key:
return key_value

plural_key = f"{key}_plural"

if plural_rule(input) != "one" and plural_key in languages[locale]:
key = plural_key

return languages[locale][key]


# assign filter to Jinja2
templates.env.filters["plural_formatting"] = plural_formatting


@app.get("/", response_class=HTMLResponse)
async def force_lang():
return RedirectResponse("/en")


@app.get("/{locale}", response_class=HTMLResponse)
async def home(request: Request, locale: str):
try:
if locale not in languages:
locale = default_fallback

result = {"request": request}
result.update(languages[locale])
return templates.TemplateResponse("index.html", result)
except Exception as e:
log.error(e)
raise HTTPException(status_code=500, detail=str(e))


# TODO Require auth & limit to users current organisation
# TODO Push errors to cloudwatch metric and response when debug enabled
# TODO Enable detailed error messages via debug flag
@app.get("/{locale}/dashboard", response_class=HTMLResponse)
async def dashboard(request: Request, locale: str, session: Session = Depends(get_db)):
try:
if locale not in languages:
locale = default_fallback

organisation_list = session.query(Organisation).all()
result = {"request": request}
result.update(languages[locale])
result.update({"organisations": organisation_list})
except Exception as e:
log.error(e)
raise HTTPException(status_code=500, detail=str(e))
return templates.TemplateResponse("dashboard.html", result)
11 changes: 11 additions & 0 deletions api/i18n/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"alpha": "Alpha",
"goc": "Government of Canada",
"goc_banner": "https://ssl-templates.services.gc.ca/app/cls/wet/gcintranet/v4_0_20/assets/sig-blk-en.svg",
"lang": "en",
"organisations_locale" : "Organisations",
"other_lang": "fr",
"other_language": "Français",
"webpage_title": "Scan websites",
"welcome": "Welcome to Scan websites"
}
11 changes: 11 additions & 0 deletions api/i18n/fr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"alpha": "Alpha",
"goc": "Gouvernement du Canada",
"goc_banner": "https://ssl-templates.services.gc.ca/app/cls/wet/gcintranet/v4_0_20/assets/sig-blk-fr.svg",
"lang": "fr",
"organisations_locale" : "Organisations",
"other_lang": "en",
"other_language": "English",
"webpage_title": "Analyser les sites web",
"welcome": "Bienvenue sur les sites Web de Scan"
}
Loading

0 comments on commit a135c28

Please sign in to comment.