diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3f2c87f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM python:3 + +MAINTAINER tiyn tiyn@mail-mk.eu + +COPY src /blog + +WORKDIR /blog + +RUN pip3 install -r requirements.txt + +VOLUME /blog/templates/entry + +EXPOSE 5000 + +ENTRYPOINT [ "python3" ] + +CMD [ "app.py" ] diff --git a/README.md b/README.md new file mode 100644 index 0000000..6949af2 --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# Container Critique + +This is a blog based on Pythons Flask framework. +The blog is intended to be used to review and critique things. + +## Features/To-Dos + +- [ ] Plain text support for blog entries + - [ ] HTML files (.html) + - [ ] Markdown Files (.md) +- [ ] Infinite-scroll blog page +- [ ] Archive page + - [ ] Months as headings + - [ ] Links to scrolling blog page + - [ ] Links to standalone article +- [ ] Standalone article page + - [ ] Links to scrolling blog page +- [ ] RSS feed +- [ ] Navigation + - [ ] Header + - [ ] Footer +- [ ] Switchable CSS + - [ ] CSS dark-theme + - [ ] CSS light-theme +- [ ] Config file +- [ ] Docker installation +- [ ] Logo + +## Usage + +## Deployment + +### PIP/Python + +- `git clone https://github.com/tiyn/container-critique` +- `cd container-critique/src` +- edit the `config.py` file according to your needs +- `pip3install -r requirements.txt` - install depenencies +- run `python app.py` +- blog is available on port 5000 + +### Docker + +Make sure you copy an example `config.py` and edit it before running the container. +The `config.py` can be found in the `src` folder. + +#### Volumes + +Set the following volumes with the -v tag. + +| Volume-Name | Container mount | Description | +| ------------- | --------------------------- | ------------------------------------------------------------ | +| `config-file` | `/blog/src/config.py` | Config file | +| `entries` | `/blog/src/templates/entry` | Directory for blog entries | +| `css` | `/blog/src/static/css` | (optional) Directory for css files | +| `html` | `/blog/src/templates` | (optional) Directory for templates (entry-volume not needed) | + +#### Ports + +Set the following ports with the -p tag. + +| Container-Port | Recommended outside port | Protocol | Description | +| -------------- | ------------------------ | -------- | ----------- | +| `5000` | `80` | TCP | HTTP port | + +#### Example run-command + +An example run command is shown in `rebuild.sh`. diff --git a/beaker_blog_alt.png b/beaker_blog_alt.png new file mode 100644 index 0000000..5ad0e0f Binary files /dev/null and b/beaker_blog_alt.png differ diff --git a/rebuild.sh b/rebuild.sh new file mode 100755 index 0000000..816ac62 --- /dev/null +++ b/rebuild.sh @@ -0,0 +1,9 @@ +#!/bin/sh +docker stop container-critique +docker rm container-critique +docker build . -t tiyn/container-critique +docker run --name container-critique \ + --restart unless-stopped \ + -p "5000:5000" \ + -e FLASK_ENV=development \ + -d tiyn/container-critique diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/src/app.py b/src/app.py new file mode 100644 index 0000000..0c10f93 --- /dev/null +++ b/src/app.py @@ -0,0 +1,90 @@ +from flask import Flask, flash, make_response, render_template, request, redirect, abort, url_for +from flask_login import current_user, login_user, LoginManager + +import content as con_gen +import config + +from flask_wtf import FlaskForm +from wtforms import StringField, PasswordField, SubmitField, BooleanField +from wtforms.validators import DataRequired + +class LoginForm(FlaskForm): + username = StringField('Username', validators=[DataRequired()]) + password = PasswordField('Password', validators=[DataRequired()]) + remember_me = BooleanField('Remember Me') + submit = SubmitField('Sign In') + + +app = Flask(__name__) +login = LoginManager(app) + +TITLE = config.TITLE +STYLE = config.STYLE +DESCRIPTION = config.DESCRIPTION +WEBSITE = config.WEBSITE + + +class Config(object): + SECRET_KEY = '123534' + +app.config.from_object(Config) + + +@app.errorhandler(404) +def page_not_found(e): + return render_template('error.html', title=TITLE, errorcode='404', style=STYLE), 404 + + +@app.route('/') +@app.route('/index.html') +def index(): + content = con_gen.gen_index_string() + return render_template('index.html', title=TITLE, content_string=content, style=STYLE) + + +@app.route('/archive') +@app.route('/archive.html') +def blog_archive(): + content = con_gen.gen_arch_string() + return render_template('archive.html', title=TITLE, content_string=content, style=STYLE) + + +@app.route('/entry/') +def entry(path): + content = con_gen.gen_stand_string(path) + if content != '': + return render_template('standalone.html', title=TITLE, content_string=content, style=STYLE) + abort(404) + + +@app.route('/feed.xml') +@app.route('/rss.xml') +def feed(): + content = con_gen.get_rss_string() + rss_xml = render_template('rss.xml', content_string=content, title=TITLE, + description=DESCRIPTION, website=WEBSITE) + response = make_response(rss_xml) + response.headers['Content-Type'] = 'application/rss+xml' + return response + +@login.user_loader +def load_user(id): + return "" + +@app.route('/login', methods=['GET', 'POST']) +def login(): + if current_user.is_authenticated: + return redirect(url_for('index')) + form = LoginForm() + if form.validate_on_submit(): + user = User.query.filter_by(username=form.username.data).first() + if user is None or not user.check_password(form.password.data): + flash('Invalid username or password') + return redirect(url_for('login')) + login_user(user, remember=form.remember_me.data) + return redirect(url_for('index')) + return render_template('login.html', title='Sign In', form=form) + + +if __name__ == '__main__': + app.run(host='0.0.0.0') diff --git a/src/config.py b/src/config.py new file mode 100644 index 0000000..bb76e62 --- /dev/null +++ b/src/config.py @@ -0,0 +1,11 @@ +# Name/title of your blog +TITLE = 'Container Critique' + +# Description for RSS of your blog +DESCRIPTION = 'This is your personal Container Critique.' + +# URL for your website: e.g. https://domain.tld +WEBSITE = 'localhost:5000' + +# Theme for the blog: dark, light +STYLE = 'dark' diff --git a/src/content.py b/src/content.py new file mode 100644 index 0000000..45e2615 --- /dev/null +++ b/src/content.py @@ -0,0 +1,45 @@ +ENTRY_DIR = 'templates/entry' + +def gen_arch_string(): + """ + Creates and returns a archive string of every file in ENTRY_DIR. + + Returns: + string: html-formatted archive-string + """ + return "" + + +def gen_index_string(): + """ + Create and returns a string including every file in the ENTRY_DIR as an index. + + Returns: + string: html-formatted index string + """ + return "" + + +def gen_stand_string(path_ex): + """ + Creates a html-string for a file. + If the file is markdown it will convert it. + This functions ensures upscaling for future formats. + + Parameters: + path_ex: path to a file. + + Returns: + string: html-formatted string string equivalent to the file + """ + return "" + + +def get_rss_string(): + """ + Create a rss-string of the blog and return it. + + Returns: + string: rss-string of everything that is in the ENTRY_DIR. + """ + return "" diff --git a/src/requirements.txt b/src/requirements.txt new file mode 100644 index 0000000..6b32b3a --- /dev/null +++ b/src/requirements.txt @@ -0,0 +1,4 @@ +Flask==2.1.2 +flask_login==0.6.2 +flask_wtf==1.0.1 +WTForms==3.0.1 diff --git a/src/static/css/dark.css b/src/static/css/dark.css new file mode 100644 index 0000000..75a2a05 --- /dev/null +++ b/src/static/css/dark.css @@ -0,0 +1,83 @@ +@import 'style.css'; + +:root { + --bg0: rgb(29,32,33); + --color0: rgb(220,120,0); + --error: rgb(255,0,0); + --footerbg0: rgb(29,32,33); + --link0: rgb(220, 120, 0); + --link1: rgb(255,255,255); + --menulink0: rgb(220, 120, 0); + --menulink1: rgb(255,255,255); + --menubg0: rgb(29,32,33); + --text0: rgb(235,219,178); + --text1: rgb(220, 120, 0); +} + +a { + color: var(--link0); + transition: var(--transtime); +} + +a:hover { + color: var(--link1); +} + +body { + background: var(--bg0); +} + +footer { + background: var(--footerbg0); + color: var(--text0); +} + +span { + color: var(--text1); +} + +.container { + color: var(--text0); +} + +.container h1, +.container h2 { + color: var(--text1); +} + +.container .flash { + background-color: var(--error); +} + +.hide-menu:hover, +.main-menu a:hover, +.show-menu:hover { + color: var(--menulink1); +} + +.main-menu a { + color: var(--menulink0); +} + +.main-menu-dropdown { + background: var(--menubg0); + color: var(--menulink0); +} + +@media screen and (max-width:800px) { + + .main-menu { + background: var(--menubg0); + } +} + +.entry { + background: var(--bg0); + border-left: 10px solid var(--color0); + color: var(--text0); +} + +.entry h1, +.entry h2 { + color: var(--text1); +} diff --git a/src/static/css/light.css b/src/static/css/light.css new file mode 100644 index 0000000..ce6379d --- /dev/null +++ b/src/static/css/light.css @@ -0,0 +1,83 @@ +@import 'style.css'; + +:root { + --bg0: rgb(255,255,255); + --color0: rgb(0,0,120); + --error: rgb(255,0,0); + --footerbg0: rgb(192,192,192); + --link0: rgb(0,0,120); + --link1: rgb(255,255,255); + --menulink0: rgb(0,0,120); + --menulink1: rgb(255,255,255); + --menubg0: rgb(192,192,192); + --text0: rgb(0,0,0); + --text1: rgb(0,0,120); +} + +a { + color: var(--link0); + transition: var(--transtime); +} + +a:hover { + color: var(--link1); +} + +body { + background: var(--bg0); +} + +footer { + background: var(--footerbg0); + color: var(--text0); +} + +span { + color: var(--text1); +} + +.container { + color: var(--text0); +} + +.container h1, +.container h2 { + color: var(--text1); +} + +.container .flash { + background-color: var(--error); +} + +.hide-menu:hover, +.main-menu a:hover, +.show-menu:hover { + color: var(--menulink1); +} + +.main-menu a { + color: var(--menulink0); +} + +.main-menu-dropdown { + background: var(--menubg0); + color: var(--menulink0); +} + +@media screen and (max-width:800px) { + + .main-menu { + background: var(--menubg0); + } +} + +.entry { + background: var(--bg0); + border-left: 10px solid var(--color0); + color: var(--text0); +} + +.entry h1, +.entry h2 { + color: var(--text1); +} diff --git a/src/static/css/style.css b/src/static/css/style.css new file mode 100644 index 0000000..1d1a16d --- /dev/null +++ b/src/static/css/style.css @@ -0,0 +1,169 @@ +:root { + --error: rgb(255,0,0); + --transtime: 0.7s; +} + +* { + margin: 0; + padding: 0; +} + +a { + text-decoration: none; + transition: var(--transtime); +} + +a:hover { + cursor: pointer; +} + +body { + margin: 0; +} + +body, +html { + font-family: sans-serif; + height: 100%; + max-width: 100%; + overflow-x: hidden; +} + +footer { + height: 100px; + padding-top: 20px; +} + +footer .center { + text-align: center; +} + +.container { + min-height: 100%; + padding-bottom: 50px; + padding-left: 10%; + padding-right: 10%; + padding-top: 5%; +} + +.container .flash { + padding: 10px; + width: 400px; +} + +.hide-menu, +.show-menu { + cursor: pointer; + display: none; + font-size: 30px; + transition: var(--transtime); +} + +.important { + font-size: xx-large; + padding-left: 25vw; + padding-right: 25vw; + padding-top: 30vh; + text-align: left; +} + +.important span { + font-weight: bold; +} + +.logo { + height: 80px; + padding-top: 10px; +} + +.main-menu-dropdown span { + float: left; + font-family: monospace; + font-size: 30px; + font-weight: bold; + line-height: 100px; + padding: 0 10px; + text-decoration: none; + text-transform: uppercase; + transition: 0.7s; +} + +.main-menu { + float: right; + font-family: monospace; + font-size: 30px; + font-weight: bold; + line-height: 100px; +} + +.main-menu a { + padding: 0 10px; + text-decoration: none; + text-transform: uppercase; + transition: 0.7s; +} + +.main-menu-dropdown { + height: 100px; + padding: 0 20px; +} + +.show-menu { + float: right; + line-height: 100px; +} + +#main-menu-check { + position: absolute; + visibility: hidden; + z-index: -1111; +} + +@media screen and (max-width:800px) { + .hide-menu { + position: absolute; + right: 40px; + top: 40px; + } + + .hide-menu, + .show-menu { + display: block; + } + + .main-menu { + height: 100vh; + line-height: normal; + padding: 80px 0; + position: fixed; + right: -100%; + text-align: center; + top: 0; + transition: var(--transtime); + width: 100%; + } + .main-menu a { + display: block; + padding: 20px; + } + + #main-menu-check:checked ~ .main-menu { + right: 0; + } +} + +.entry { + border-radius: 0 10px 30px 0; + margin-bottom: 20px; + padding: 10px; +} + +.entry h1, +.entry h2 { + margin: 5px auto 2px auto; +} + +.entry ul { + padding-left: 20; +} + diff --git a/src/static/images/logo.png b/src/static/images/logo.png new file mode 100644 index 0000000..e69de29 diff --git a/src/templates/archive.html b/src/templates/archive.html new file mode 100644 index 0000000..4b9de72 --- /dev/null +++ b/src/templates/archive.html @@ -0,0 +1,11 @@ +{% extends 'template.html' %} +{% block content %} +
+
+

Archive


+ {% autoescape off %} + {{ content_string }} + {% endautoescape %} +
+
+{% endblock %} diff --git a/src/templates/error.html b/src/templates/error.html new file mode 100644 index 0000000..84571ce --- /dev/null +++ b/src/templates/error.html @@ -0,0 +1,9 @@ +{% extends "template.html" %} +{% block content %} +
+
+ Error
+ {{ errorcode }} +
+
+{% endblock %} diff --git a/src/templates/index.html b/src/templates/index.html new file mode 100644 index 0000000..db050ab --- /dev/null +++ b/src/templates/index.html @@ -0,0 +1,11 @@ +{% extends "template.html" %} +{% block content %} +
+
+

Index


+ {% autoescape off %} + {{ content_string }} + {% endautoescape %} +
+
+{% endblock %} diff --git a/src/templates/rss.xml b/src/templates/rss.xml new file mode 100644 index 0000000..9f8b6b1 --- /dev/null +++ b/src/templates/rss.xml @@ -0,0 +1,16 @@ + + + + + {{ title }} + {{ description }} +en-us +{{ website }}/feed.xml + + +{% autoescape off %} +{{ content_string }} +{% endautoescape %} + + + diff --git a/src/templates/standalone.html b/src/templates/standalone.html new file mode 100644 index 0000000..f80f5f8 --- /dev/null +++ b/src/templates/standalone.html @@ -0,0 +1,10 @@ +{% extends 'template.html' %} +{% block content %} +
+
+ {% autoescape off %} + {{ content_string }} + {% endautoescape %} +
+
+{% endblock %} diff --git a/src/templates/template.html b/src/templates/template.html new file mode 100644 index 0000000..c7cae8e --- /dev/null +++ b/src/templates/template.html @@ -0,0 +1,32 @@ + + + {{ title }} + + + + + + + + + + {% block content %} + {% endblock %} + + + +