Fix up a few more things, add a Dockerfile and a local docker-compose deployment
parent
14fcfff656
commit
9f38029f62
|
@ -0,0 +1,13 @@
|
||||||
|
FROM python:3
|
||||||
|
|
||||||
|
RUN useradd --system --gid root --uid 1001 uwsgi
|
||||||
|
RUN pip install uwsgi psycopg2_binary
|
||||||
|
|
||||||
|
RUN mkdir -p /app
|
||||||
|
COPY . /app
|
||||||
|
WORKDIR /app
|
||||||
|
RUN pip install .
|
||||||
|
|
||||||
|
EXPOSE 3031
|
||||||
|
CMD ["uwsgi", "--master", "--workers", "4", "--socket", "0.0.0.0:3031", "--protocol", "uwsgi", \
|
||||||
|
"--plugin", "python3", "--uid", "uwsgi", "--wsgi", "libertywiki.wsgi"]
|
|
@ -0,0 +1,21 @@
|
||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
wiki:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
environment:
|
||||||
|
- SQLALCHEMY_DATABASE_URI=postgresql://wiki:wiki@postgres/wiki
|
||||||
|
- SECRET_KEY=super-secret-key-replace-me
|
||||||
|
- SECURITY_PASSWORD_SALT=password-salt-replace-me
|
||||||
|
postgres:
|
||||||
|
image: postgres:14
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=wiki
|
||||||
|
- POSTGRES_PASSWORD=wiki
|
||||||
|
- POSTGRES_DB=wiki
|
||||||
|
nginx:
|
||||||
|
image: nginx
|
||||||
|
volumes:
|
||||||
|
- "./nginx.conf:/etc/nginx/conf.d/default.conf:ro"
|
||||||
|
ports:
|
||||||
|
- "8000:80"
|
|
@ -4,7 +4,7 @@ from flask import current_app, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask_login.config import EXEMPT_METHODS
|
from flask_login.config import EXEMPT_METHODS
|
||||||
|
|
||||||
from libertywiki.utils import AccessType, WikiMode
|
from libertywiki.utils import WikiMode, needs_authentication
|
||||||
|
|
||||||
|
|
||||||
def check_access(access_type):
|
def check_access(access_type):
|
||||||
|
@ -21,12 +21,10 @@ def check_access(access_type):
|
||||||
if request.method in EXEMPT_METHODS or \
|
if request.method in EXEMPT_METHODS or \
|
||||||
current_app.config.get('LOGIN_DISABLED'):
|
current_app.config.get('LOGIN_DISABLED'):
|
||||||
pass
|
pass
|
||||||
elif (wiki_mode == WikiMode.PRIVATE
|
elif needs_authentication(wiki_mode, access_type) \
|
||||||
or (wiki_mode == WikiMode.PUBLIC and access_type == AccessType.READ)) \
|
|
||||||
and not current_user.is_authenticated:
|
and not current_user.is_authenticated:
|
||||||
return current_app.login_manager.unauthorized()
|
return current_app.login_manager.unauthorized()
|
||||||
try:
|
try:
|
||||||
# current_app.ensure_sync available in Flask >= 2.0
|
|
||||||
return current_app.ensure_sync(func)(*args, **kwargs)
|
return current_app.ensure_sync(func)(*args, **kwargs)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
{% import 'admin/layout.html' as layout with context -%}
|
||||||
|
{% import 'admin/static.html' as admin_static with context %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{% block title %}{% if admin_view.category %}{{ admin_view.category }} - {% endif %}{{ admin_view.name }} - {{ admin_view.admin.name }}{% endblock %}</title>
|
||||||
|
{% block head_meta %}
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="author" content="">
|
||||||
|
{% endblock %}
|
||||||
|
{% block head_css %}
|
||||||
|
<link href="{{ admin_static.url(filename='bootstrap/bootstrap4/swatch/{swatch}/bootstrap.min.css'.format(swatch=config.get('FLASK_ADMIN_SWATCH', 'default')), v='4.2.1') }}"
|
||||||
|
rel="stylesheet">
|
||||||
|
{% if config.get('FLASK_ADMIN_SWATCH', 'default') == 'default' %}
|
||||||
|
<link href="{{ admin_static.url(filename='bootstrap/bootstrap4/css/bootstrap.min.css', v='4.2.1') }}" rel="stylesheet">
|
||||||
|
{% endif %}
|
||||||
|
<link href="{{ admin_static.url(filename='admin/css/bootstrap4/admin.css', v='1.1.1') }}" rel="stylesheet">
|
||||||
|
<link href="{{ admin_static.url(filename='bootstrap/bootstrap4/css/font-awesome.min.css', v='4.7.0') }}" rel="stylesheet">
|
||||||
|
{% if admin_view.extra_css %}
|
||||||
|
{% for css_url in admin_view.extra_css %}
|
||||||
|
<link href="{{ css_url }}" rel="stylesheet">
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
<style>
|
||||||
|
.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
{% block head %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block head_tail %}
|
||||||
|
{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% block page_body %}
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-2" role="navigation">
|
||||||
|
<div class="container{% if config.get('FLASK_ADMIN_FLUID_LAYOUT', False) %}-fluid{% endif %}">
|
||||||
|
<!-- Brand and toggle get grouped for better mobile display -->
|
||||||
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#admin-navbar-collapse"
|
||||||
|
aria-controls="admin-navbar-collapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<!-- navbar content -->
|
||||||
|
<div class="collapse navbar-collapse" id="admin-navbar-collapse">
|
||||||
|
{% block brand %}
|
||||||
|
<a class="navbar-brand" href="{{ admin_view.admin.url }}">{{ admin_view.admin.name }}</a>
|
||||||
|
{% endblock %}
|
||||||
|
{% block main_menu %}
|
||||||
|
<ul class="navbar-nav mr-auto">
|
||||||
|
{{ layout.menu() }}
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block menu_links %}
|
||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
{{ layout.menu_links() }}
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
||||||
|
{% block access_control %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container{% if config.get('FLASK_ADMIN_FLUID_LAYOUT', False) %}-fluid{% endif %}">
|
||||||
|
{% block messages %}
|
||||||
|
{{ layout.messages() }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{# store the jinja2 context for form_rules rendering logic #}
|
||||||
|
{% set render_ctx = h.resolve_ctx() %}
|
||||||
|
|
||||||
|
{% block body %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block tail_js %}
|
||||||
|
<script src="{{ admin_static.url(filename='vendor/jquery.min.js', v='2.1.4') }}" type="text/javascript"></script>
|
||||||
|
<script src="{{ admin_static.url(filename='bootstrap/bootstrap4/js/popper.min.js') }}" type="text/javascript"></script>
|
||||||
|
<script src="{{ admin_static.url(filename='bootstrap/bootstrap4/js/bootstrap.min.js', v='4.2.1') }}"
|
||||||
|
type="text/javascript"></script>
|
||||||
|
<script src="{{ admin_static.url(filename='vendor/moment.min.js', v='2.9.0') }}" type="text/javascript"></script>
|
||||||
|
<script src="{{ admin_static.url(filename='vendor/bootstrap4/util.js', v='4.3.1') }}" type="text/javascript"></script>
|
||||||
|
<script src="{{ admin_static.url(filename='vendor/bootstrap4/dropdown.js', v='4.3.1') }}" type="text/javascript"></script>
|
||||||
|
<script src="{{ admin_static.url(filename='vendor/select2/select2.min.js', v='4.2.1') }}"
|
||||||
|
type="text/javascript"></script>
|
||||||
|
<script src="{{ admin_static.url(filename='vendor/multi-level-dropdowns-bootstrap/bootstrap4-dropdown-ml-hack.js') }}" type="text/javascript"></script>
|
||||||
|
<script src="{{ admin_static.url(filename='admin/js/helpers.js', v='1.0.0') }}" type="text/javascript"></script>
|
||||||
|
{% if admin_view.extra_js %}
|
||||||
|
{% for js_url in admin_view.extra_js %}
|
||||||
|
<script src="{{ js_url }}" type="text/javascript"></script>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block tail %}
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
|
@ -4,19 +4,8 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-10 col-sm-offset-1">
|
<div class="col-sm-10 col-sm-offset-1">
|
||||||
<h1>Flask-Admin example</h1>
|
<h1>LibertyWiki Administration</h1>
|
||||||
<p class="lead">
|
|
||||||
Authentication
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
This example shows how you can use <a href="https://pythonhosted.org/Flask-Security/index.html" target="_blank">Flask-Security</a> for authentication.
|
|
||||||
</p>
|
|
||||||
{% if not current_user.is_authenticated %}
|
{% if not current_user.is_authenticated %}
|
||||||
<p>You can register as a regular user, or log in as a superuser with the following credentials:
|
|
||||||
<ul>
|
|
||||||
<li>email: <b>admin</b></li>
|
|
||||||
<li>password: <b>admin</b></li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
<p>
|
||||||
<a class="btn btn-primary" href="{{ url_for('security.login') }}">login</a> <a class="btn btn-default" href="{{ url_for('security.register') }}">register</a>
|
<a class="btn btn-primary" href="{{ url_for('security.login') }}">login</a> <a class="btn btn-default" href="{{ url_for('security.register') }}">register</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
<div class="navbar-text btn-group pull-right">
|
<div class="navbar-text btn-group pull-right">
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
|
||||||
<i class="glyphicon glyphicon-user"></i>
|
<i class="glyphicon glyphicon-user"></i>
|
||||||
{% if current_user.first_name -%}
|
{% if current_user.name -%}
|
||||||
{{ current_user.first_name }}
|
{{ current_user.name }}
|
||||||
{% else -%}
|
{% else -%}
|
||||||
{{ current_user.email }}
|
{{ current_user.email }}
|
||||||
{%- endif %}<span class="caret"></span></a>
|
{%- endif %}<span class="caret"></span></a>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Wiki</title>
|
<title>Wiki</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.0/font/bootstrap-icons.css">
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
body {
|
body {
|
||||||
min-height: 75rem;
|
min-height: 75rem;
|
||||||
|
@ -18,8 +19,32 @@
|
||||||
<a class="navbar-brand d-flex" href="/">Wiki</a>
|
<a class="navbar-brand d-flex" href="/">Wiki</a>
|
||||||
<form class="d-flex">
|
<form class="d-flex">
|
||||||
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
|
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
|
||||||
<button class="btn btn-outline-success" type="submit">Search</button>
|
<button class="btn btn-outline-light" type="submit">Search</button>
|
||||||
</form>
|
</form>
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="bi-person-fill"></i>
|
||||||
|
{% if current_user.name -%}
|
||||||
|
{{ current_user.name }}
|
||||||
|
{% else -%}
|
||||||
|
{{ current_user.email }}
|
||||||
|
{%- endif %}
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
|
||||||
|
{% if current_user.has_role('admin') %}
|
||||||
|
<li><a class="dropdown-item" href="{{ url_for('admin.index') }}">Admin</a></li>
|
||||||
|
{% endif %}
|
||||||
|
<li><a class="dropdown-item" href="{{ url_for('security.logout') }}">Logout</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" aria-current="page" href="{{ url_for('security.login') }}">Log in</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<article class="{{page.slug}}">
|
<article class="{{page.slug}}">
|
||||||
|
<h1 class="display-6">{{ page.title }}</h1>
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<span class="nav-link active" aria-current="page">Article</span>
|
<span class="nav-link active" aria-current="page">Article</span>
|
||||||
|
@ -9,8 +10,7 @@
|
||||||
<a class="nav-link" href="/{{page.slug}}/edit">Edit</a>
|
<a class="nav-link" href="/{{page.slug}}/edit">Edit</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h1 class="display-6 border-bottom">{{ page.title }}</h1>
|
<div class="row mt-2">
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
<div class="col">
|
||||||
{{ page.body | rst2html | safe }}
|
{{ page.body | rst2html | safe }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from flask_bcrypt import Bcrypt
|
|
||||||
|
|
||||||
|
|
||||||
class AccessType(Enum):
|
class AccessType(Enum):
|
||||||
READ = 1
|
READ = 1
|
||||||
|
@ -14,7 +12,10 @@ class WikiMode(Enum):
|
||||||
PRIVATE = 3
|
PRIVATE = 3
|
||||||
|
|
||||||
|
|
||||||
bcrypt = Bcrypt()
|
def needs_authentication(wiki_mode, access_type):
|
||||||
|
"""Determine if the access type needs authentication based on the wiki mode"""
|
||||||
|
return wiki_mode == WikiMode.PRIVATE \
|
||||||
|
or (wiki_mode == WikiMode.PUBLIC and access_type == AccessType.WRITE)
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['AccessType', 'WikiMode', 'bcrypt']
|
__all__ = ['AccessType', 'WikiMode']
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
from libertywiki.app import get_app
|
||||||
|
|
||||||
|
|
||||||
|
application = get_app()
|
|
@ -0,0 +1,10 @@
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
include uwsgi_params;
|
||||||
|
uwsgi_pass wiki:3031;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue