diff --git a/.gitignore b/.gitignore
index 1ddb7da..460600a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ __pycache__
.ycm_extra_conf.py
postgres/
.envrc
+.mypy_cache/
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..c83e35d
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "material-design-icons"]
+ path = material-design-icons
+ url = https://github.com/google/material-design-icons
diff --git a/Makefile b/Makefile
index 1b2beff..7e4a8d4 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,7 @@
-.PHONY: check install
+.PHONY: setup check install
+
+setup:
+ ! [ -d wikilain/static/mdi ] && ln -s ../../material-design-icons/iconfont/ wikilain/static/mdi || true
check:
@mypy . || true
diff --git a/README.md b/README.md
index 6b8085e..ec30867 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,7 @@ mistune
mistune_contrib
flask-sqlalchemy
flask-migrate
+pygments
```
```
diff --git a/material-design-icons b/material-design-icons
new file mode 160000
index 0000000..224895a
--- /dev/null
+++ b/material-design-icons
@@ -0,0 +1 @@
+Subproject commit 224895a86501195e7a7ff3dde18e39f00b8e3d5a
diff --git a/setup.py b/setup.py
index 9c15bf9..45e7f2a 100644
--- a/setup.py
+++ b/setup.py
@@ -15,7 +15,8 @@ setup(name='wikilain',
"mistune",
"mistune_contrib",
"flask-sqlalchemy",
- "flask-migrate"
+ "flask-migrate",
+ "pygments"
],
include_package_data=True,
entry_points={
diff --git a/wikilain/__init__.py b/wikilain/__init__.py
index b276726..0c8e17b 100644
--- a/wikilain/__init__.py
+++ b/wikilain/__init__.py
@@ -1,15 +1,29 @@
-from flask import Flask
-from flask_sqlalchemy import SQLAlchemy
-from flask_migrate import Migrate
+from flask import Flask, render_template, g
+
+from .models import *
+from .markdown import *
+from .adapters.session_simple import session_provider
app = Flask(__name__)
-app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:////tmp/test.db'
+app.config["SECRET_KEY"] = "changeme"
+app.config["SQLALCHEMY_DATABASE_URI"] = 'postgresql://@/wikilain_dev'
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
-from .models import db, migrate, User
db.init_app(app)
migrate.init_app(app, db)
+app.register_blueprint(session_provider, url_prefix="/session")
+
+
@app.route("/")
def main_page():
- return "Hello and also world"
+ print(g.wl_user)
+ return render_template('index.html')
+
+
+@app.route("/test")
+def test_article():
+ return render_template(
+ 'article.html', article_title="Sample Text",
+ article_content=md_render("# sample\n\nmeme and also\n\n\n---\nmeme $2+2$"))
+
diff --git a/wikilain/adapters/__init__.py b/wikilain/adapters/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/wikilain/adapters/session_simple.py b/wikilain/adapters/session_simple.py
new file mode 100644
index 0000000..bf6661d
--- /dev/null
+++ b/wikilain/adapters/session_simple.py
@@ -0,0 +1,49 @@
+from flask import Blueprint, session, request, render_template, redirect, g, flash
+import secrets
+
+from ..models import User, db
+
+__all__ = ["session_provider"]
+
+session_provider = Blueprint("session_simple", __name__, template_folder='templates')
+
+@session_provider.before_app_request
+def get_session_user():
+ if session.get("username", None) is not None:
+ g.wl_user = User.query.filter_by(username=session["username"]).first()
+ else:
+ g.wl_user = None
+
+
+@session_provider.route("/login", methods=["GET", "POST"])
+def login():
+ old_csrf = session.get("_csrf", None)
+ csrf = secrets.token_urlsafe(32)
+ session["_csrf"] = csrf
+
+ if g.wl_user is not None:
+ return redirect("/", code=302)
+
+ if request.method == 'GET':
+ return render_template("login_form.html", csrf=csrf)
+ else:
+ if not request.form["username"]:
+ flash("Must provide username", "error")
+ return render_template("login_form.html", csrf=csrf)
+ if old_csrf != request.form["_csrf"]:
+ flash("Invalid request", "error")
+ return render_template("login_form.html", csrf=csrf)
+ user = User.query.filter_by(username=request.form["username"]).first()
+ if not user:
+ db.session.add(User(username=request.form["username"], email=secrets.token_hex(8) + "@example.com"))
+ db.session.commit()
+ session["username"] = request.form["username"]
+ del session["_csrf"]
+ return redirect("/", code=302)
+
+
+@session_provider.route("/logout", methods=["POST"])
+def logout():
+ del session["username"]
+ del session["_csrf"]
+ return redirect("/", code=302)
diff --git a/wikilain/adapters/templates/login_form.html b/wikilain/adapters/templates/login_form.html
new file mode 100644
index 0000000..a1a7087
--- /dev/null
+++ b/wikilain/adapters/templates/login_form.html
@@ -0,0 +1,14 @@
+{% extends 'base.html' %}
+
+{% block title %}
+Log In
+{% endblock %}
+
+{% block content %}
+
Log In
+
+{% endblock %}
diff --git a/wikilain/markdown.py b/wikilain/markdown.py
new file mode 100644
index 0000000..fe57848
--- /dev/null
+++ b/wikilain/markdown.py
@@ -0,0 +1,32 @@
+from mistune import Markdown, Renderer, InlineLexer, BlockLexer, BlockGrammar
+from mistune_contrib.highlight import HighlightMixin
+from mistune_contrib.math import MathBlockMixin, MathInlineMixin, MathRendererMixin
+from flask import Markup
+
+
+class WlInlineLexer(InlineLexer, MathInlineMixin):
+ def __init__(self, *args, **kwargs):
+ super(WlInlineLexer, self).__init__(*args, **kwargs)
+ self.enable_math()
+
+
+class WlBlockLexer(BlockLexer, MathBlockMixin):
+ def __init__(self, *args, **kwargs):
+ super(WlBlockLexer, self).__init__(*args, **kwargs)
+ self.enable_math()
+
+
+class WlRenderer(Renderer, MathRendererMixin, HighlightMixin):
+ def __init__(self):
+ super(WlRenderer, self).__init__(escape=True, use_xhtml=True)
+
+
+renderer = WlRenderer()
+inline = WlInlineLexer(renderer)
+block = WlBlockLexer(BlockGrammar())
+markdown = Markdown(renderer=renderer, inline=inline, block=block)
+
+__all__ = ["md_render"]
+
+def md_render(*args, **kwargs):
+ return Markup(markdown(*args, **kwargs))
diff --git a/wikilain/models.py b/wikilain/models.py
index f222048..e350808 100644
--- a/wikilain/models.py
+++ b/wikilain/models.py
@@ -5,9 +5,44 @@ db = SQLAlchemy()
migrate = Migrate(None, db)
class User(db.Model):
+ __tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
- username = db.Column(db.String(80), unique=True, nullable=False)
- email = db.Column(db.String(120), unique=True, nullable=False)
+ username = db.Column(db.String, unique=True, nullable=False)
+ email = db.Column(db.String, unique=True, nullable=False)
+ avatar = db.Column(db.String, nullable=True)
def __repr__(self):
- return '' % self.username
+ return f""
+
+class Section(db.Model):
+ __tablename__ = "sections"
+ id = db.Column(db.Integer, primary_key=True)
+ title = db.Column(db.String, unique=True, nullable=False)
+ articles = db.relationship("Article", backref="section", lazy=True)
+
+ def __repr__(self):
+ return f""
+
+DEFAULT_SECTION = 1
+
+class Article(db.Model):
+ __tablename__ = "articles"
+ id = db.Column(db.Integer, primary_key=True)
+ title = db.Column(db.String, unique=True, nullable=False)
+ section_id = db.Column(db.Integer, db.ForeignKey("sections.id"), nullable=False)
+ revisions = db.relationship("ArticleRevision", backref="article", lazy=True)
+
+ def __repr__(self):
+ return f""
+
+class ArticleRevision(db.Model):
+ __tablename__ = "articlerevisions"
+ id = db.Column(db.Integer, primary_key=True)
+ article_id = db.Column(db.Integer, db.ForeignKey("articles.id"), nullable=False)
+ date = db.Column(db.DateTime, nullable=False)
+ user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
+ user = db.relationship("User", lazy=True)
+ content = db.Column(db.Text, nullable=False)
+
+ def __repr__(self):
+ return f""
diff --git a/wikilain/static/mdi b/wikilain/static/mdi
new file mode 120000
index 0000000..d6ee876
--- /dev/null
+++ b/wikilain/static/mdi
@@ -0,0 +1 @@
+../../material-design-icons/iconfont/
\ No newline at end of file
diff --git a/wikilain/static/style.css b/wikilain/static/style.css
new file mode 100644
index 0000000..599c8da
--- /dev/null
+++ b/wikilain/static/style.css
@@ -0,0 +1,5 @@
+body {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
diff --git a/wikilain/templates/article.html b/wikilain/templates/article.html
new file mode 100644
index 0000000..9da3580
--- /dev/null
+++ b/wikilain/templates/article.html
@@ -0,0 +1,10 @@
+{% extends 'base.html' %}
+
+{% block title %}
+ {{ article_title }}
+{% endblock %}
+
+{% block content %}
+ {{ article_title }}
+ {{ article_content }}
+{% endblock %}
diff --git a/wikilain/templates/base.html b/wikilain/templates/base.html
new file mode 100644
index 0000000..dcc6bec
--- /dev/null
+++ b/wikilain/templates/base.html
@@ -0,0 +1,35 @@
+
+
+
+
+ {% block title %}{% endblock %} - WikiLain
+
+
+
+
+
+ {% with messages = get_flashed_messages(with_categories=true) %}
+ {% if messages %}
+
+ {% for category, message in messages %}
+ {{ message }}
+ {% endfor %}
+
+ {% endif %}
+ {% endwith %}
+
+ {% block content %}{% endblock %}
+
+
+
diff --git a/wikilain/templates/index.html b/wikilain/templates/index.html
new file mode 100644
index 0000000..bf1c158
--- /dev/null
+++ b/wikilain/templates/index.html
@@ -0,0 +1,10 @@
+{% extends 'base.html' %}
+
+{% block title %}
+Main Page
+{% endblock %}
+
+{% block content %}
+ Welcome to WikiLain
+ Sample and also text
+{% endblock %}