From d20079f3ed8f261bcda0f5379f4c9e23ee941527 Mon Sep 17 00:00:00 2001
From: retoor <retoor@molodetz.nl>
Date: Fri, 24 Jan 2025 14:00:10 +0100
Subject: [PATCH] Complete system.

---
 .gitignore                        |   3 +
 Dockerfile                        |   4 +-
 pyproject.toml                    |   4 +-
 src/snek/app.py                   |  17 ++-
 src/snek/form/register.py         |   2 +-
 src/snek/mapper/__init__.py       |  12 ++
 src/snek/mapper/user.py           |   6 +
 src/snek/model/__init__.py        |  12 ++
 src/snek/model/user.py            |   4 +-
 src/snek/service/__init__.py      |  12 ++
 src/snek/service/user.py          |  16 +++
 src/snek/static/generic-form.js   |   4 +
 src/snek/static/html-frame.js     |  11 +-
 src/snek/static/markdown-frame.js |  39 +++++
 src/snek/static/style.css         |  20 +++
 src/snek/system/api.py            |   0
 src/snek/system/form.py           | 177 +++++++----------------
 src/snek/system/http.py           |  78 ++++++----
 src/snek/system/mapper.py         |  64 +++++++++
 src/snek/system/markdown.py       |  43 ++++++
 src/snek/system/middleware.py     |  16 ++-
 src/snek/system/model.py          | 231 +++++++++++++++++++-----------
 src/snek/system/security.py       |  20 +++
 src/snek/system/service.py        |  40 ++++++
 src/snek/system/view.py           |  38 +++++
 src/snek/templates/about.html     |   7 +
 src/snek/templates/about.md       |  15 ++
 src/snek/templates/base.html      |   3 +-
 src/snek/templates/index.html     |   3 +-
 src/snek/templates/login.html     |   2 +-
 src/snek/templates/register.html  |   2 +-
 src/snek/view/about.py            |  14 ++
 src/snek/view/base.py             |  31 ----
 src/snek/view/index.py            |   2 +-
 src/snek/view/login.py            |   2 +-
 src/snek/view/login_form.py       |   2 +-
 src/snek/view/register.py         |   2 +-
 src/snek/view/register_form.py    |   2 +-
 src/snek/view/{view.py => web.py} |   2 +-
 39 files changed, 663 insertions(+), 299 deletions(-)
 create mode 100644 src/snek/mapper/__init__.py
 create mode 100644 src/snek/mapper/user.py
 create mode 100644 src/snek/service/__init__.py
 create mode 100644 src/snek/service/user.py
 create mode 100644 src/snek/static/markdown-frame.js
 create mode 100644 src/snek/static/style.css
 create mode 100644 src/snek/system/api.py
 create mode 100644 src/snek/system/mapper.py
 create mode 100644 src/snek/system/markdown.py
 create mode 100644 src/snek/system/security.py
 create mode 100644 src/snek/system/service.py
 create mode 100644 src/snek/system/view.py
 create mode 100644 src/snek/templates/about.html
 create mode 100644 src/snek/templates/about.md
 create mode 100644 src/snek/view/about.py
 delete mode 100644 src/snek/view/base.py
 rename src/snek/view/{view.py => web.py} (73%)

diff --git a/.gitignore b/.gitignore
index 3747073..ece77be 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,8 @@
 .vscode
 .history
+.resources
+.backup*
+docs
 *.db*
 *.png
 # ---> Python
diff --git a/Dockerfile b/Dockerfile
index 47c0ece..9af8e87 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
 FROM surnet/alpine-wkhtmltopdf:3.21.2-0.12.6-full as wkhtmltopdf 
-FROM python:3.10-alpine
+FROM python:3.12.8-alpine3.21
 WORKDIR /code
 ENV FLASK_APP=app.py
 ENV FLASK_RUN_HOST=0.0.0.0
@@ -37,5 +37,5 @@ RUN pip install --upgrade pip
 RUN pip install -e .
 EXPOSE 8081
 
-python -m snek.app
+CMD ["python","-m","snek.app"]
 #CMD ["gunicorn", "-w", "10", "-k", "aiohttp.worker.GunicornWebWorker", "snek.gunicorn:app","--bind","0.0.0.0:8081"]
diff --git a/pyproject.toml b/pyproject.toml
index d98557e..cc36846 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,6 +20,8 @@ dependencies = [
     "beautifulsoup4",
     "gunicorn",
     "imgkit",
-    "wkhtmltopdf"
+    "wkhtmltopdf",
+    "jinja-markdown2",
+    "mistune"
 ]
 
diff --git a/src/snek/app.py b/src/snek/app.py
index 0e2ed63..deac5d3 100644
--- a/src/snek/app.py
+++ b/src/snek/app.py
@@ -3,14 +3,16 @@ import pathlib
 from aiohttp import web
 from app.app import Application as BaseApplication
 
+from jinja_markdown2 import MarkdownExtension
 from snek.system import http
 from snek.system.middleware import cors_middleware
+from snek.view.about import AboutHTMLView, AboutMDView
 from snek.view.index import IndexView
 from snek.view.login import LoginView
 from snek.view.login_form import LoginFormView
 from snek.view.register import RegisterView
 from snek.view.register_form import RegisterFormView
-from snek.view.view import WebView
+from snek.view.web import WebView
 
 
 class Application(BaseApplication):
@@ -24,6 +26,7 @@ class Application(BaseApplication):
         super().__init__(
             middlewares=middlewares, template_path=self.template_path, *args, **kwargs
         )
+        self.jinja2_env.add_extension(MarkdownExtension)
         self.setup_router()
 
     def setup_router(self):
@@ -34,12 +37,14 @@ class Application(BaseApplication):
             name="static",
             show_index=True,
         )
-        self.router.add_view("/web", WebView)
-        self.router.add_view("/login", LoginView)
-        self.router.add_view("/login-form", LoginFormView)
-        self.router.add_view("/register", RegisterView)
+        self.router.add_view("/about.html", AboutHTMLView)
+        self.router.add_view("/about.md", AboutMDView)
+        self.router.add_view("/web.html", WebView)
+        self.router.add_view("/login.html", LoginView)
+        self.router.add_view("/login-form.json", LoginFormView)
+        self.router.add_view("/register.html", RegisterView)
         
-        self.router.add_view("/register-form", RegisterFormView)
+        self.router.add_view("/register-form.json", RegisterFormView)
         self.router.add_get("/http-get", self.handle_http_get)
         self.router.add_get("/http-photo", self.handle_http_photo)
 
diff --git a/src/snek/form/register.py b/src/snek/form/register.py
index 60399fb..7dff3e4 100644
--- a/src/snek/form/register.py
+++ b/src/snek/form/register.py
@@ -15,7 +15,7 @@ class RegisterForm(Form):
     )
     email = FormInputElement(
         name="email",
-        required=True,
+        required=False,
         regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",
         place_holder="Email address",
         type="email"
diff --git a/src/snek/mapper/__init__.py b/src/snek/mapper/__init__.py
new file mode 100644
index 0000000..dc9e047
--- /dev/null
+++ b/src/snek/mapper/__init__.py
@@ -0,0 +1,12 @@
+import functools 
+from snek.mapper.user import UserMapper
+
+@functools.cache 
+def get_mappers(app=None):
+    return dict(
+        user=UserMapper(app=app)
+
+    )
+
+def get_mapper(name, app=None):
+    return get_mappers(app=app)[name]
\ No newline at end of file
diff --git a/src/snek/mapper/user.py b/src/snek/mapper/user.py
new file mode 100644
index 0000000..5b8671e
--- /dev/null
+++ b/src/snek/mapper/user.py
@@ -0,0 +1,6 @@
+from snek.system.mapper import BaseMapper
+from snek.model.user import UserModel
+
+class UserMapper(BaseMapper):
+    table_name = "user"
+    model: UserModel 
\ No newline at end of file
diff --git a/src/snek/model/__init__.py b/src/snek/model/__init__.py
index e69de29..52af21a 100644
--- a/src/snek/model/__init__.py
+++ b/src/snek/model/__init__.py
@@ -0,0 +1,12 @@
+from snek.model.user import UserModel 
+import functools 
+
+@functools.cache
+def get_models():
+    return dict(
+        user=UserModel
+
+    )
+
+def get_model(name):
+    return get_models()[name]
diff --git a/src/snek/model/user.py b/src/snek/model/user.py
index 44553f8..254b6c9 100644
--- a/src/snek/model/user.py
+++ b/src/snek/model/user.py
@@ -1,6 +1,6 @@
 from snek.system.model import BaseModel,ModelField
 
-class User(BaseModel):
+class UserModel(BaseModel):
     
     username = ModelField(
         name="username", 
@@ -11,7 +11,7 @@ class User(BaseModel):
     )
     email = ModelField(
         name="email",
-        required=True,
+        required=False,
         regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
     )
     password = ModelField(name="password",required=True,regex=r"^[a-zA-Z0-9_.+-]{6,}")
diff --git a/src/snek/service/__init__.py b/src/snek/service/__init__.py
new file mode 100644
index 0000000..4038f70
--- /dev/null
+++ b/src/snek/service/__init__.py
@@ -0,0 +1,12 @@
+from snek.service.user import UserService 
+import functools 
+
+@functools.cache
+def get_services(app):
+
+    return dict(
+        user = UserService(app=app)
+
+    )
+def get_service(name, app=None):
+    return get_services(app=app)[name]
\ No newline at end of file
diff --git a/src/snek/service/user.py b/src/snek/service/user.py
new file mode 100644
index 0000000..cde4b8c
--- /dev/null
+++ b/src/snek/service/user.py
@@ -0,0 +1,16 @@
+from snek.system.service import BaseService 
+from snek.system import security 
+
+class UserService:
+    mapper_name = "user"
+
+    async def create_user(self, username, password):
+        if await self.exists(username=username):
+            raise Exception("User already exists.")
+        model = await self.new()
+        model.username = username
+        model.password = await security.hash(password)
+        if await self.save(model):
+            return model 
+        raise Exception(f"Failed to create user: {model.errors}.")
+        
\ No newline at end of file
diff --git a/src/snek/static/generic-form.js b/src/snek/static/generic-form.js
index 11fea47..58a67e2 100644
--- a/src/snek/static/generic-form.js
+++ b/src/snek/static/generic-form.js
@@ -224,7 +224,11 @@ class GenericForm extends HTMLElement {
 
       }
       @media (max-width: 500px) {
+        width:100%;
+        height:100%;
           form {
+              height:100%;
+              width: 100%;
               width: 80%;
           }
       }`
diff --git a/src/snek/static/html-frame.js b/src/snek/static/html-frame.js
index 22581ce..0d5d4c9 100644
--- a/src/snek/static/html-frame.js
+++ b/src/snek/static/html-frame.js
@@ -26,7 +26,16 @@ class HTMLFrame extends HTMLElement {
           throw new Error(`Error: ${response.status} ${response.statusText}`);
         }
         const html = await response.text();
-        this.container.innerHTML = html;
+        if(url.endsWith(".md")){
+          const parent = this
+          const markdownElement = document.createElement('div')
+          markdownElement.innerHTML = html
+          document.body.appendChild(markdownElement)
+          //parent.parentElement.appendChild(markdownElement)
+          
+        }else{
+          this.container.innerHTML = html;
+        }
         
     } catch (error) {
         this.container.textContent = `Error: ${error.message}`;
diff --git a/src/snek/static/markdown-frame.js b/src/snek/static/markdown-frame.js
new file mode 100644
index 0000000..e2b7a77
--- /dev/null
+++ b/src/snek/static/markdown-frame.js
@@ -0,0 +1,39 @@
+
+
+
+class HTMLFrame extends HTMLElement {
+    constructor() {
+      super();
+      this.attachShadow({ mode: 'open' });
+      this.container = document.createElement('div');
+      this.shadowRoot.appendChild(this.container);
+    }
+
+    connectedCallback() {
+        this.container.classList.add("html_frame")
+      const url = this.getAttribute('url');
+      if (url) {
+        const fullUrl = url.startsWith("/") ? window.location.origin + url : new URL(window.location.origin + "/http-get")
+        if(!url.startsWith("/"))
+            fullUrl.searchParams.set('url', url)   
+        this.loadAndRender(fullUrl.toString());
+      } else {
+        this.container.textContent = "No source URL!";
+      }
+    }
+
+    async loadAndRender(url) {
+      try {
+        const response = await fetch(url);
+        if (!response.ok) {
+          throw new Error(`Error: ${response.status} ${response.statusText}`);
+        }
+        const html = await response.text();
+        this.container.innerHTML = html;
+        
+    } catch (error) {
+        this.container.textContent = `Error: ${error.message}`;
+      }
+    }
+  }
+  customElements.define('markdown-frame', HTMLFrame);
\ No newline at end of file
diff --git a/src/snek/static/style.css b/src/snek/static/style.css
new file mode 100644
index 0000000..990fcf9
--- /dev/null
+++ b/src/snek/static/style.css
@@ -0,0 +1,20 @@
+h1 {
+    font-size: 2em;
+    color: #f05a28;
+    margin-bottom: 20px;
+}
+
+h2 {
+    font-size: 1.4em;
+    color: #f05a28;
+    margin-bottom: 20px;
+}
+body {
+    background-color: #000;
+    color: #efefef;
+
+}
+div {
+    text-align: left;
+
+}
\ No newline at end of file
diff --git a/src/snek/system/api.py b/src/snek/system/api.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/snek/system/form.py b/src/snek/system/form.py
index 68f7c0f..f9ebebb 100644
--- a/src/snek/system/form.py
+++ b/src/snek/system/form.py
@@ -1,171 +1,96 @@
-from snek.system import model 
+# Written by retoor@molodetz.nl
+
+# This code defines a framework for handling HTML elements as Python objects, including specific classes for HTML, form input, and form button elements. It offers methods to convert these elements to JSON, manipulate them, and validate form data.
+
+# This code uses the `snek.system.model` library for managing model fields.
+
+# MIT License
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from snek.system import model
 
 class HTMLElement(model.ModelField):
-    def __init__(self,id:str=None, tag:str="div", name:str=None,html:str=None, class_name:str=None, text:str=None, *args, **kwargs):
-        """
-        Create a new HTMLElement.
-        
-        :param id: The id of the element
-        :param tag: The tag of the element
-        :param name: The name of the element, used to generate a class name if not provided
-        :param html: The inner html of the element
-        :param class_name: The class name of the element
-        :param text: The text of the element
-        """
+    def __init__(self, id=None, tag="div", name=None, html=None, class_name=None, text=None, *args, **kwargs):
         self.tag = tag
         self.text = text
-        self.id = id 
+        self.id = id
         self.class_name = class_name or name
-        self.html = html 
-        super().__init__(name=name,*args, **kwargs)
+        self.html = html
+        super().__init__(name=name, *args, **kwargs)
 
     def to_json(self):
-        """
-        Return a json representation of the element.
-        
-        This will return a dict with the following keys:
-        
-        - text: The text of the element
-        - id: The id of the element
-        - html: The inner html of the element
-        - class_name: The class name of the element
-        - tag: The tag of the element
-        
-        :return: A json representation of the element
-        :rtype: dict
-        """
         result = super().to_json()
-        result['text'] = self.text 
-        result['id'] = self.id 
-        result['html'] = self.html 
+        result['text'] = self.text
+        result['id'] = self.id
+        result['html'] = self.html
         result['class_name'] = self.class_name
         result['tag'] = self.tag
-        return result 
+        return result
 
 class FormElement(HTMLElement):
     pass
-           
+
 class FormInputElement(FormElement):
-
-    def __init__(self,type="text",place_holder=None, *args, **kwargs):
-        """
-        Initialize a FormInputElement with specified attributes.
-
-        :param type: The type of the input element (default is "text").
-        :param place_holder: The placeholder text for the input element.
-        :param args: Additional positional arguments.
-        :param kwargs: Additional keyword arguments.
-        """
-
+    def __init__(self, type="text", place_holder=None, *args, **kwargs):
         super().__init__(tag="input", *args, **kwargs)
-        self.place_holder = place_holder 
+        self.place_holder = place_holder
         self.type = type
-        
 
     def to_json(self):
-        """
-        Return a json representation of the element.
-
-        This will return a dict with the following keys:
-
-        - place_holder: The placeholder text for the input element
-        - type: The type of the input element
-
-        :return: A json representation of the element
-        :rtype: dict
-        """
         data = super().to_json()
         data["place_holder"] = self.place_holder
         data["type"] = self.type
-        return data 
-    
-class FormButtonElement(FormElement):
-    # Just use the label text property to assign a button label.
-    def __init__(self, tag="button", *args, **kwargs):
-        """
-        Initialize a FormButtonElement with specified attributes.
+        return data
 
-        :param tag: The tag of the button element (default is "button").
-        :param args: Additional positional arguments.
-        :param kwargs: Additional keyword arguments.
-        """
+class FormButtonElement(FormElement):
+    def __init__(self, tag="button", *args, **kwargs):
         super().__init__(tag=tag, *args, **kwargs)
 
-
 class Form(model.BaseModel):
-    
     @property
     def html_elements(self):
-        """
-        Return a list of all :class:`HTMLElement` objects in the form.
-
-        This is a convenience property that filters the :attr:`fields` list to only
-        include elements that are instances of :class:`HTMLElement`.
-
-        :return: A list of :class:`HTMLElement` objects
-        :rtype: list
-        """
         json_elements = super().to_json()
-        return [element for element in self.fields if isinstance(element,HTMLElement)]
+        return [element for element in self.fields if isinstance(element, HTMLElement)]
+
     def set_user_data(self, data):
-        """
-        Set user data for the form by updating the fields with the provided data.
-
-        This method extracts the 'fields' key from the provided data dictionary
-        and passes it to the parent class's `set_user_data` method to update the
-        form fields accordingly.
-
-        :param data: A dictionary containing the form data, expected to have a 
-                    'fields' key with the data to update the form fields.
-        """
-
         return super().set_user_data(data.get('fields'))
 
     def to_json(self, encode=False):
-        """
-        Return a JSON representation of the form, including field values and metadata.
-
-        This method returns a dictionary with the following keys:
-
-        - ``fields``: A dictionary of field names to their current values.
-        - ``is_valid``: A boolean indicating whether the form is valid.
-        - ``errors``: A dictionary of field names to lists of error strings.
-
-        If the ``encode`` argument is ``True``, the dictionary will be JSON-encoded
-        before being returned. Otherwise, the dictionary is returned directly.
-
-        :param encode: If ``True``, JSON-encode the returned dictionary.
-        :type encode: bool
-        :return: A JSON representation of the form.
-        :rtype: dict
-        """
         elements = super().to_json()
         html_elements = {}
         for element in elements.keys():
-            print("DDD!",element,flush=True)
-            field = getattr(self,element)
-            if isinstance(field,HTMLElement):
-                print("QQQQ!",element,flush=True)
+            field = getattr(self, element)
+            if isinstance(field, HTMLElement):
                 try:
                     html_elements[element] = elements[element]
                 except KeyError:
-                    pass 
+                    pass
+        return dict(fields=html_elements, is_valid=self.is_valid, errors=self.errors)
 
-        return dict(fields=html_elements,is_valid=self.is_valid,errors=self.errors)
     @property
     def errors(self):
-        """
-        Return a list of all error strings from all fields in the form.
-
-        The list will be empty if all fields are valid.
-
-        :return: A list of error strings.
-        :rtype: list
-        """
         result = []
         for field in self.html_elements:
-            result += field.errors 
-        return result 
+            result += field.errors
+        return result
+
     @property
     def is_valid(self):
-        return all(element.is_valid for element in self.html_elements)
+        return all(element.is_valid for element in self.html_elements)
\ No newline at end of file
diff --git a/src/snek/system/http.py b/src/snek/system/http.py
index 0b16bee..b5e8b4f 100644
--- a/src/snek/system/http.py
+++ b/src/snek/system/http.py
@@ -1,77 +1,99 @@
-from aiohttp import web 
-import aiohttp 
+# Written by retoor@molodetz.nl
+
+# This script enables downloading, processing, and caching web content, including taking website screenshots and repairing links in HTML content.
+
+# Imports used: aiohttp, aiohttp.web for creating web servers and handling async requests; app.cache for caching utilities; BeautifulSoup from bs4 for HTML parsing; imgkit for creating screenshots.
+
+# The MIT License (MIT)
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+
+from aiohttp import web
+import aiohttp
 from app.cache import time_cache_async
 from bs4 import BeautifulSoup
 from urllib.parse import urljoin
-import pathlib 
-import uuid 
-import imgkit 
+import pathlib
+import uuid
+import imgkit
 import asyncio
 import zlib
-import io 
+import io
 
 async def crc32(data):
     try:
         data = data.encode()
     except:
-        pass 
-    result = "crc32" + str(zlib.crc32(data))
-    return result 
+        pass
+    return "crc32" + str(zlib.crc32(data))
 
-async def get_file(name,suffix=".cache"):
+async def get_file(name, suffix=".cache"):
     name = await crc32(name)
     path = pathlib.Path(".").joinpath("cache")
     if not path.exists():
-        path.mkdir(parents=True,exist_ok=True)
-    path = path.joinpath(name + suffix)
-    return path
-
-
+        path.mkdir(parents=True, exist_ok=True)
+    return path.joinpath(name + suffix)
 
 async def public_touch(name=None):
-    path = pathlib.Path(".").joinpath(str(uuid.uuid4())+name)
+    path = pathlib.Path(".").joinpath(str(uuid.uuid4()) + name)
     path.open("wb").close()
-    return path 
+    return path
 
 async def create_site_photo(url):
     loop = asyncio.get_event_loop()
     if not url.startswith("https"):
         url = "https://" + url
-    output_path = await get_file("site-screenshot-" + url,".png")
+    output_path = await get_file("site-screenshot-" + url, ".png")
     
     if output_path.exists():
         return output_path
     output_path.touch()
+
     def make_photo():
         imgkit.from_url(url, output_path.absolute())
-        return output_path 
+        return output_path
 
-    return await loop.run_in_executor(None,make_photo)
+    return await loop.run_in_executor(None, make_photo)
 
 async def repair_links(base_url, html_content):
     soup = BeautifulSoup(html_content, "html.parser")
     for tag in soup.find_all(['a', 'img', 'link']):
-        if tag.has_attr('href') and not tag['href'].startswith("http"):  # For <a> and <link> tags
+        if tag.has_attr('href') and not tag['href'].startswith("http"):
             tag['href'] = urljoin(base_url, tag['href'])
-        if tag.has_attr('src') and not tag['src'].startswith("http"):  # For <img> tags
+        if tag.has_attr('src') and not tag['src'].startswith("http"):
             tag['src'] = urljoin(base_url, tag['src'])
-            print("Fixed: ",tag['src'])
     return soup.prettify()
 
 async def is_html_content(content: bytes):
     try:
         content = content.decode(errors='ignore')
     except:
-        pass 
-    marks = ['<html','<img','<p','<span','<div']
+        pass
+    marks = ['<html', '<img', '<p', '<span', '<div']
     try:
         content = content.lower()
         for mark in marks:
             if mark in content:
-                return True 
+                return True
     except Exception as ex:
         print(ex)
-    return False 
+    return False
 
 @time_cache_async(120)
 async def get(url):
@@ -79,5 +101,5 @@ async def get(url):
         response = await session.get(url)
         content = await response.text()
         if await is_html_content(content):
-            content = (await repair_links(url,content)).encode()
+            content = (await repair_links(url, content)).encode()
         return content
\ No newline at end of file
diff --git a/src/snek/system/mapper.py b/src/snek/system/mapper.py
new file mode 100644
index 0000000..f4beb2e
--- /dev/null
+++ b/src/snek/system/mapper.py
@@ -0,0 +1,64 @@
+
+DEFAULT_LIMIT = 30
+from snek.system.model import BaseModel
+from snek.app import Application 
+import types
+
+class Mapper:
+
+    model_class:BaseModel = None 
+    default_limit:int = DEFAULT_LIMIT
+    table_name:str = None 
+
+    def __init__(self, app:Application, table_name:str, model_class:BaseModel):
+        self.app = app 
+        
+        if not self.model_class:
+            raise ValueError("Mapper configuration error: model_class is not set.")
+        self.model_class = model_class 
+        
+        self.table_name = table_name
+        if not self.table_name:
+            raise ValueError("Mapper configuration error: table_name is not set.")
+        self.default_limit = self.__class__.default_limit 
+    
+    @property
+    def db(self): 
+        return self.app.db
+
+    async def new(self):
+        return self.model_class(mapper=self)
+
+    @property
+    def table(self):
+        return self.db[self.table_name]
+
+    async def get(self, uid:str=None, **kwargs) -> types.Optional[BaseModel]
+        if uid:
+            kwargs['uid'] = uid 
+        model = self.new()
+        record = self.table.find_one(**kwargs)
+        return self.model_class.from_record(mapper=self,record=record)
+
+    async def exists(self, **kwargs):
+        return self.table.exists(**kwargs)
+
+    async def count(self, **kwargs) -> int:
+        return self.table.count(**kwargs)
+
+    async def save(self, model:BaseModel) -> bool:
+        record = model.record
+        if not record.get('uid'):
+            raise Exception(f"Attempt to save without uid: {record}.")
+        return self.table.upsert(record,['uid'])
+
+    async def find(self, **kwargs) -> types.List[BaseModel]:
+        if not kwargs.get("_limit"):
+            kwargs["_limit"] = self.default_limit
+        for record in self.table.find(**kwargs):
+            yield self.model_class.from_record(mapper=self,record=record)
+    
+    async def delete(self, kwargs=None)-> int: 
+        if not kwargs or not isinstance(kwargs, dict):
+            raise Exception("Can't execute delete with no filter.")
+        return self.table.delete(**kwargs)
diff --git a/src/snek/system/markdown.py b/src/snek/system/markdown.py
new file mode 100644
index 0000000..bded949
--- /dev/null
+++ b/src/snek/system/markdown.py
@@ -0,0 +1,43 @@
+
+# Original source: https://brandonjay.dev/posts/2021/render-markdown-html-in-python-with-jinja2
+
+from mistune import escape
+from mistune import Markdown
+from mistune import HTMLRenderer
+from pygments import highlight
+from pygments.lexers import get_lexer_by_name
+from pygments.formatters import html
+from pygments.styles import get_style_by_name
+
+
+class MarkdownRenderer(HTMLRenderer):
+    def __init__(self, app, template):
+         self.template = template
+         
+         self.app = app
+         self.env = self.app.jinja2_env
+         formatter = html.HtmlFormatter()
+         self.env.globals['highlight_styles'] = formatter.get_style_defs()
+    def _escape(self,str):
+        return str ##escape(str)
+    def block_code(self, code, lang=None,info=None):
+        if not lang:
+            lang = info
+        if not lang:
+            return f"<div>{code}</div>"
+            #return '\n<pre><code>%s</code></pre>\n' % escape(code)
+        lexer = get_lexer_by_name(lang, stripall=True)
+        formatter = html.HtmlFormatter(lineseparator="<br>")
+        print(code, lang,info, flush=True)
+        return highlight(code, lexer, formatter)
+    def render(self):
+        markdown_string = self.app.template_path.joinpath(self.template).read_text()
+        renderer = MarkdownRenderer(self.app,self.template)
+        markdown = Markdown(renderer=renderer)
+        return markdown(markdown_string)
+
+
+async def render_markdown(app, markdown_string):
+     renderer = MarkdownRenderer(app,None)
+     markdown = Markdown(renderer=renderer)
+     return markdown(markdown_string)
\ No newline at end of file
diff --git a/src/snek/system/middleware.py b/src/snek/system/middleware.py
index 6b801ab..7fe457f 100644
--- a/src/snek/system/middleware.py
+++ b/src/snek/system/middleware.py
@@ -1,4 +1,12 @@
-from aiohttp import web 
+# Written by retoor@molodetz.nl
+
+# This code provides middleware functions for an aiohttp server to manage and modify CORS (Cross-Origin Resource Sharing) headers.
+
+# Imports from 'aiohttp' library are used to create middleware; they are not part of Python's standard library.
+
+# MIT License: This code is distributed under the MIT License.
+
+from aiohttp import web
 
 @web.middleware
 async def no_cors_middleware(request, handler):
@@ -7,16 +15,15 @@ async def no_cors_middleware(request, handler):
     return response
 
 @web.middleware
-async def cors_allow_middleware(request ,handler):
+async def cors_allow_middleware(request, handler):
     response = await handler(request)
     response.headers["Access-Control-Allow-Origin"] = "*"
     response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS, PUT, DELETE"
     response.headers["Access-Control-Allow-Headers"] = "*"
-    return response 
+    return response
 
 @web.middleware
 async def cors_middleware(request, handler):
-    # Handle preflight (OPTIONS) requests
     if request.method == "OPTIONS":
         response = web.Response()
         response.headers["Access-Control-Allow-Origin"] = "*"
@@ -24,7 +31,6 @@ async def cors_middleware(request, handler):
         response.headers["Access-Control-Allow-Headers"] = "*"
         return response
 
-    # Handle actual requests
     response = await handler(request)
     response.headers["Access-Control-Allow-Origin"] = "*"
     response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
diff --git a/src/snek/system/model.py b/src/snek/system/model.py
index a699a9e..eb490b3 100644
--- a/src/snek/system/model.py
+++ b/src/snek/system/model.py
@@ -1,36 +1,67 @@
+# Written by retoor@molodetz.nl
+
+# The script defines a flexible validation and field management system for models, with capabilities for setting attributes, validation, error handling, and JSON conversion. It includes classes for managing various field types with specific properties such as UUID, timestamps for creation and updates, and custom validation rules.
+
+# This script utilizes external Python libraries such as 're' for regex operations, 'uuid' for generating unique identifiers, and 'json' for data interchange. The 'datetime' and 'timezone' modules from the Python standard library are used for date and time operations. 'OrderedDict' from 'collections' provides enhanced dictionary capabilities, and 'copy' allows deep copying of objects.
+
+# MIT License
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+
 import re
 import uuid
-import json 
-from datetime import datetime , timezone 
+import json
+from datetime import datetime, timezone
 from collections import OrderedDict
-import copy 
+import copy
 
 TIMESTAMP_REGEX = r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}\+\d{2}:\d{2}$"
 
+
 def now():
     return str(datetime.now(timezone.utc))
 
+
 def add_attrs(**kwargs):
     def decorator(func):
         for key, value in kwargs.items():
             setattr(func, key, value)
-
         return func
     return decorator
 
-def validate_attrs(required=False,min_length=None,max_length=None,regex=None,**kwargs):
-        def decorator(func):
-            return add_attrs(required=required,min_length=min_length,max_length=max_length,regex=regex,**kwargs)(func)
+
+def validate_attrs(required=False, min_length=None, max_length=None, regex=None, **kwargs):
+    def decorator(func):
+        return add_attrs(required=required, min_length=min_length, max_length=max_length, regex=regex, **kwargs)(func)
+
 
 class Validator:
     _index = 0
+
     @property
     def value(self):
-        return self._value 
+        return self._value
 
-    @value.setter 
-    def value(self,val):
-        self._value = json.loads(json.dumps(val,default=str))
+    @value.setter
+    def value(self, val):
+        self._value = json.loads(json.dumps(val, default=str))
 
     @property
     def initial_value(self):
@@ -39,48 +70,49 @@ class Validator:
     def custom_validation(self):
         return True
 
-    def __init__(self,required=False,min_num=None,max_num=None,min_length=None,max_length=None,regex=None,value=None,kind=None,help_text=None,**kwargs):
+    def __init__(self, required=False, min_num=None, max_num=None, min_length=None, max_length=None, regex=None, value=None, kind=None, help_text=None, **kwargs):
         self.index = Validator._index
         Validator._index += 1
-        self.required = required 
-        self.min_num = min_num 
+        self.required = required
+        self.min_num = min_num
         self.max_num = max_num
-        self.min_length = min_length 
-        self.max_length = max_length 
-        self.regex = regex 
-        self._value = None 
+        self.min_length = min_length
+        self.max_length = max_length
+        self.regex = regex
+        self._value = None
         self.value = value
-        print("xxxx", value,flush=True) 
-        
+        print("xxxx", value, flush=True)
+
         self.kind = kind
-        self.help_text = help_text 
+        self.help_text = help_text
         self.__dict__.update(kwargs)
-    @property 
+
+    @property
     def errors(self):
         error_list = []
         if self.value is None and self.required:
             error_list.append("Field is required.")
-            return error_list 
-        
-        if self.value is None:
-            return error_list 
+            return error_list
 
-        if self.kind == float or self.kind == int:
+        if self.value is None:
+            return error_list
+
+        if self.kind in [int, float]:
             if self.min_num is not None and self.value < self.min_num:
-                error_list.append("Field should be minimal {}.".format(self.min_num))
+                error_list.append(f"Field should be minimal {self.min_num}.")
             if self.max_num is not None and self.value > self.max_num:
-                error_list.append("Field should be maximal {}.".format(self.max_num))
+                error_list.append(f"Field should be maximal {self.max_num}.")
         if self.min_length is not None and len(self.value) < self.min_length:
-            error_list.append("Field should be minimal {} characters long.".format(self.min_length))
+            error_list.append(f"Field should be minimal {self.min_length} characters long.")
         if self.max_length is not None and len(self.value) > self.max_length:
-            error_list.append("Field should be maximal {} characters long.".format(self.max_length))
-        print(self.regex, self.value,flush=True)
-        if not self.regex is None and not self.value is None and not re.match(self.regex, self.value):
-            error_list.append("Invalid value.".format(self.regex))
-        if not self.kind is None and type(self.value) != self.kind:
-            error_list.append("Invalid kind. It is supposed to be {}.".format(self.kind))
-        return error_list 
-        
+            error_list.append(f"Field should be maximal {self.max_length} characters long.")
+        print(self.regex, self.value, flush=True)
+        if self.regex and self.value and not re.match(self.regex, self.value):
+            error_list.append("Invalid value.")
+        if self.kind and not isinstance(self.value, self.kind):
+            error_list.append(f"Invalid kind. It is supposed to be {self.kind}.")
+        return error_list
+
     def validate(self):
         if self.errors:
             raise ValueError("\n", self.errors)
@@ -94,8 +126,6 @@ class Validator:
         except ValueError:
             return False
 
-    
-
     def to_json(self):
         return {
             "required": self.required,
@@ -109,25 +139,27 @@ class Validator:
             "help_text": self.help_text,
             "errors": self.errors,
             "is_valid": self.is_valid,
-            "index":self.index
+            "index": self.index
         }
 
+
 class ModelField(Validator):
 
     index = 1
-    def __init__(self,name=None,save=True, *args, **kwargs):
-        self.name = name 
+
+    def __init__(self, name=None, save=True, *args, **kwargs):
+        self.name = name
         self.save = save
         super().__init__(*args, **kwargs)
 
     def to_json(self):
         result = super().to_json()
         result['name'] = self.name
-        return result 
+        return result
 
 
 class CreatedField(ModelField):
-    
+
     @property
     def initial_value(self):
         return now()
@@ -136,67 +168,99 @@ class CreatedField(ModelField):
         if not self.value:
             self.value = now()
 
+
 class UpdatedField(ModelField):
 
     def update(self):
         self.value = now()
 
+
 class DeletedField(ModelField):
 
     def update(self):
         self.value = now()
 
+
 class UUIDField(ModelField):
-    
-    @property 
+
+    @property
     def initial_value(self):
         return str(uuid.uuid4())
 
 
 class BaseModel:
-    
-    uid = UUIDField(name="uid",required=True)
-    created_at = CreatedField(name="created_at",required=True, regex=TIMESTAMP_REGEX, place_holder="Created at")
-    updated_at = UpdatedField(name="updated_at",regex=TIMESTAMP_REGEX,place_holder="Updated at")
-    deleted_at = DeletedField(name="deleted_at",regex=TIMESTAMP_REGEX, place_holder="Deleted at")
 
-   
+    uid = UUIDField(name="uid", required=True)
+    created_at = CreatedField(name="created_at", required=True, regex=TIMESTAMP_REGEX, place_holder="Created at")
+    updated_at = UpdatedField(name="updated_at", regex=TIMESTAMP_REGEX, place_holder="Updated at")
+    deleted_at = DeletedField(name="deleted_at", regex=TIMESTAMP_REGEX, place_holder="Deleted at")
+
+    @classmethod 
+    def from_record(cls, record, mapper):
+        model = cls.__new__()
+        model.mapper = mapper 
+        model.record = record
+        return model
+
+    @property 
+    def mapper(self):
+        return self._mapper 
+
+    @mapper.setter  
+    def mapper(self, value):
+        self._mapper = value  
+
+    @property 
+    def record(self):
+        return {field.name: field.value for field in self.fields}
+    
+    @record.setter 
+    def record(self, value):
+        for key, value in self._record.items():
+            field = self.fields.get(key)
+            if not field:
+                continue
+            field.value = value
+        return self
+
     def __init__(self, *args, **kwargs):
         print(self.__dict__)
         print(dir(self.__class__))
+        self._mapper = None
         self.fields = {}
         for key in dir(self.__class__):
-            obj = getattr(self.__class__,key)
+            obj = getattr(self.__class__, key)
 
-            if isinstance(obj,Validator):
+            if isinstance(obj, Validator):
                 self.__dict__[key] = copy.deepcopy(obj)
                 print("JAAA")
-                self.__dict__[key].value = kwargs.pop(key,self.__dict__[key].initial_value)
+                self.__dict__[key].value = kwargs.pop(key, self.__dict__[key].initial_value)
                 self.fields[key] = self.__dict__[key]
 
     def __setitem__(self, key, value):
         obj = self.__dict__.get(key)
-        if isinstance(obj,Validator):
-            obj.value = value 
+        if isinstance(obj, Validator):
+            obj.value = value
 
     def __getattr__(self, key):
         obj = self.__dict__.get(key)
-        if isinstance(obj,Validator):
+        if isinstance(obj, Validator):
             print("HPAPP")
-            return obj.value 
+            return obj.value
         return obj
 
     def set_user_data(self, data):
         for key, value in data.items():
             field = self.fields.get(key)
             if not field:
-                continue 
+                continue
             if value.get('name'):
                 value = value.get('value')
             field.value = value
-           
 
-    @property 
+    
+
+    @property
     def is_valid(self):
         for field in self.fields.values():
             if not field.is_valid:
@@ -205,46 +269,44 @@ class BaseModel:
 
     def __getitem__(self, key):
         obj = self.__dict__.get(key)
-        if isinstance(obj,Validator):
-            return obj.value 
+        if isinstance(obj, Validator):
+            return obj.value
 
     def __setattr__(self, key, value):
-        obj = getattr(self,key)
-        if isinstance(obj,Validator):
+        obj = getattr(self, key)
+        if isinstance(obj, Validator):
             obj.value = value
         else:
-            self.__dict__[key] = value #setattr(self,key,value)
-    #def __getattr__(self, key):
-    #    obj = self.__dict__.get(key)
-    #    if isinstance(obj,Validator):
-    #        return obj.value
-    @property 
+            self.__dict__[key] = value
+
+    @property
     def record(self):
         obj = self.to_json()
         record = {}
-        for key,value in obj.items():
-            if getattr(self,key).save:
+        for key, value in obj.items():
+            if getattr(self, key).save:
                 record[key] = value.get('value')
         return record
 
-    def to_json(self,encode=False):
+    def to_json(self, encode=False):
         model_data = OrderedDict({
             "uid": self.uid.value,
             "created_at": self.created_at.value,
             "updated_at": self.updated_at.value,
             "deleted_at": self.deleted_at.value
         })
-        
-        for key,value in self.__dict__.items(): 
+
+        for key, value in self.__dict__.items():
             if key == "record":
                 continue
             value = self.__dict__[key]
-            if hasattr(value,"value"):
+            if hasattr(value, "value"):
                 model_data[key] = value.to_json()
         if encode:
-            return json.dumps(model_data,indent=2)
+            return json.dumps(model_data, indent=2)
         return model_data
 
+
 class FormElement(ModelField):
 
     def __init__(self, place_holder=None, *args, **kwargs):
@@ -252,15 +314,14 @@ class FormElement(ModelField):
         self.place_holder = place_holder
 
 
-
 class FormElement(ModelField):
 
-    def __init__(self,place_holder=None, *args, **kwargs): 
-        self.place_holder = place_holder 
+    def __init__(self, place_holder=None, *args, **kwargs):
+        self.place_holder = place_holder
         super().__init__(*args, **kwargs)
 
     def to_json(self):
         data = super().to_json()
-        data["name"] = self.name 
+        data["name"] = self.name
         data["place_holder"] = self.place_holder
-        return data 
+        return data
\ No newline at end of file
diff --git a/src/snek/system/security.py b/src/snek/system/security.py
new file mode 100644
index 0000000..b319f54
--- /dev/null
+++ b/src/snek/system/security.py
@@ -0,0 +1,20 @@
+import hashlib 
+
+DEFAULT_SALT = b"snekker-de-snek-"
+
+async def hash(data,salt=DEFAULT_SALT):
+    try:
+        data = data.encode(errors="ignore")
+    except AttributeError:
+        pass 
+    try:
+        salt = salt.encode(errors="ignore")
+    except AttributeError:
+        pass
+    salted = salt + data
+
+    obj = hashlib.sha256(salted)
+    return obj.hexdigest()
+
+async def verify(string:str, hashed:str):
+    return await hash(string) == hashed 
diff --git a/src/snek/system/service.py b/src/snek/system/service.py
new file mode 100644
index 0000000..5a8b553
--- /dev/null
+++ b/src/snek/system/service.py
@@ -0,0 +1,40 @@
+
+
+
+from snek.mapper import get_mapper
+from snek.system.mapper import BaseMapper 
+from snek.model.user import UserModel
+
+class BaseService:
+
+    mapper_name:BaseMapper = None
+
+    def __init__(self, app):
+        self.app = app 
+        if self.mapper_name:
+            self.mapper = get_mapper(self.mapper_name, app=self.app)
+        else:
+            self.mapper = None 
+
+    async def exists(self, **kwargs):
+        return self.mapper.exists(**kwargs)
+    
+    async def count(self, **kwargs):
+        return self.mapper.count(**kwargs)
+
+    async def new(self, **kwargs):
+        return await self.mapper.new()
+
+    async def get(self, **kwargs):
+        return await self.mapper.get(**kwargs)
+    
+    async def save(self, model:UserModel):
+        if model.is_valid:
+            return self.mapper.save(model) and True 
+        return False 
+    
+    async def find(self, **kwargs):
+        return await self.mapper.find(**kwargs)
+    
+    async def delete(self, **kwargs):
+        return await self.mapper.delete(**kwargs)
\ No newline at end of file
diff --git a/src/snek/system/view.py b/src/snek/system/view.py
new file mode 100644
index 0000000..458aa20
--- /dev/null
+++ b/src/snek/system/view.py
@@ -0,0 +1,38 @@
+from aiohttp import web
+
+from snek.system.markdown import render_markdown 
+
+class BaseView(web.View):
+    
+    @property 
+    def app(self):
+        return self.request.app
+    
+    @property
+    def db(self):
+        return self.app.db
+
+    async def json_response(self, data):
+        return web.json_response(data)
+
+    async def render_template(self, template_name, context=None):
+        if template_name.endswith(".md"):
+            response = await self.request.app.render_template(template_name,self.request,context)
+            body = await render_markdown(self.app, response.body.decode())
+            return web.Response(body=body,content_type="text/html")
+        return await self.request.app.render_template(template_name, self.request,context)
+    
+class BaseFormView(BaseView):
+
+    form = None 
+
+    async def get(self):
+        form = self.form()
+        return await self.json_response(form.to_json())
+       
+    async def post(self):
+        form = self.form()
+        post = await self.request.json()
+        form.set_user_data(post['form'])
+        return await self.json_response(form.to_json())  
+
diff --git a/src/snek/templates/about.html b/src/snek/templates/about.html
new file mode 100644
index 0000000..0f1b8a9
--- /dev/null
+++ b/src/snek/templates/about.html
@@ -0,0 +1,7 @@
+{% extends "base.html" %}
+
+{% block main %}
+
+<html-frame url="/about.md"></html-frame>
+<fancy-button text="Back" url="/web.html"></fancy-button>
+{% endblock %}
\ No newline at end of file
diff --git a/src/snek/templates/about.md b/src/snek/templates/about.md
new file mode 100644
index 0000000..134fbc9
--- /dev/null
+++ b/src/snek/templates/about.md
@@ -0,0 +1,15 @@
+# Snek
+
+## What is a snek?
+A snek is a danger noodle.
+
+## Design choices
+I made several design choices: 
+- Implemented **the worst 3rd party markdown to html renderer ever**. See this nice *bullet list*.
+ - Only password requirement is  thats it requires six characters. Users are responsibly for their own security. Snek is not so arrogant to determine if a password is strong enough. It's up to what user prefers. Snek does not have a forgot-my-password service tho.
+ - Email is not required for registration. Email is (maybe) used in future for resetting password.
+ - Database is SQLite by default. Postgres is also possible. In that case you have to change `db_path` prefix to `postgres:///` and add a postgres docker container to the docker setup.
+ - Homebrew made ORM framework based on dataset.
+ - Homebrew made Form framework based on the homebrew made ORM. Most forms are ModelForms but always require an service to be saved for sake of consistency and structure.
+ - !DRY for HMTL/jinja2 templates. For templates Snek does prefer to repeat itself to implement exceptions for a page easier. For Snek it's preffered do a few updates instead of maintaining a complex generic system that requires maintenance regarding templates.
+ - No existing chat backend like `inspircd` (Popular decent IRC server written in the language of angels) because I prefer to know what is exactly going on above performance and concurrency limit. Also, this approach reduces as networking layer / gateway layer.
diff --git a/src/snek/templates/base.html b/src/snek/templates/base.html
index 5b1bdf2..9f57467 100644
--- a/src/snek/templates/base.html
+++ b/src/snek/templates/base.html
@@ -4,9 +4,8 @@
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>{% block title %}{% endblock %}</title>
+  <link rel="stylesheet" href="/style.css">
    <script src="/fancy-button.js"></script>
-    <link rel="stylesheet" href="/style.css">
-    <link rel="stylesheet" href="/generic-form.css">
     <script src="/html-frame.js"></script>
     <script src="/generic-form.js"></script>
     <link rel="stylesheet" href="/html-frame.css"></script>
diff --git a/src/snek/templates/index.html b/src/snek/templates/index.html
index 1ee1f77..c2200fb 100644
--- a/src/snek/templates/index.html
+++ b/src/snek/templates/index.html
@@ -14,7 +14,8 @@
     <fancy-button url="/login" text="Login"></fancy-button>
     <span style="padding:10px;">Or</span>
     <fancy-button url="/register" text="Register"></fancy-button>
-
+    <a href="/about.html">Design choices</a>
+    <a href="/web.html">See web Application so far</a>
   </div>
 </body>
 </html>
diff --git a/src/snek/templates/login.html b/src/snek/templates/login.html
index c09ec70..c70d429 100644
--- a/src/snek/templates/login.html
+++ b/src/snek/templates/login.html
@@ -1,5 +1,5 @@
 {% extends "base.html" %}
 
 {% block main %}
-  <generic-form url="/login-form"></generic-form>
+  <generic-form url="/login-form.json"></generic-form>
 {% endblock %}
diff --git a/src/snek/templates/register.html b/src/snek/templates/register.html
index 61da961..f0a82e0 100644
--- a/src/snek/templates/register.html
+++ b/src/snek/templates/register.html
@@ -1,5 +1,5 @@
 {% extends "base.html" %}
 
 {% block main %}
-  <generic-form url="/register-form"></generic-form>
+  <generic-form url="/register-form.json"></generic-form>
 {% endblock %}
\ No newline at end of file
diff --git a/src/snek/view/about.py b/src/snek/view/about.py
new file mode 100644
index 0000000..593d5a9
--- /dev/null
+++ b/src/snek/view/about.py
@@ -0,0 +1,14 @@
+
+
+from snek.system.view import BaseView
+
+
+class AboutHTMLView(BaseView):
+
+    async def get(self):
+        return await self.render_template("about.html")
+    
+class AboutMDView(BaseView):
+
+    async def get(self):
+        return await self.render_template("about.md")
\ No newline at end of file
diff --git a/src/snek/view/base.py b/src/snek/view/base.py
deleted file mode 100644
index d962ee5..0000000
--- a/src/snek/view/base.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from aiohttp import web 
-
-class BaseView(web.View):
-    
-    @property 
-    def app(self):
-        return self.request.app
-    
-    @property
-    def db(self):
-        return self.app.db
-
-    def json_response(self, data):
-        return web.json_response(data)
-
-    def render_template(self, template_name, context=None):
-        return self.request.app.render_template(template_name, self.request,context)
-    
-class BaseFormView(BaseView):
-
-    form = None 
-
-    async def get(self):
-        form = self.form()
-        return self.json_response(form.to_json())
-       
-    async def post(self):
-        form = self.form()
-        post = await self.request.json()
-        form.set_user_data(post['form'])
-        return self.json_response(form.to_json())  
\ No newline at end of file
diff --git a/src/snek/view/index.py b/src/snek/view/index.py
index a5d8b92..c7861fa 100644
--- a/src/snek/view/index.py
+++ b/src/snek/view/index.py
@@ -1,4 +1,4 @@
-from snek.view.base import BaseView
+from snek.system.view import BaseView
 
 class IndexView(BaseView):
 
diff --git a/src/snek/view/login.py b/src/snek/view/login.py
index 3a3beaf..ffedc79 100644
--- a/src/snek/view/login.py
+++ b/src/snek/view/login.py
@@ -1,5 +1,5 @@
 from snek.form.register import RegisterForm
-from snek.view.base import BaseView 
+from snek.system.view import BaseView
 
 class LoginView(BaseView):
 
diff --git a/src/snek/view/login_form.py b/src/snek/view/login_form.py
index 26527da..e9b6eac 100644
--- a/src/snek/view/login_form.py
+++ b/src/snek/view/login_form.py
@@ -1,4 +1,4 @@
-from snek.view.base import BaseFormView
+from snek.system.view import BaseFormView
 from snek.form.login import LoginForm
 
 class LoginFormView(BaseFormView):
diff --git a/src/snek/view/register.py b/src/snek/view/register.py
index 095b7a3..e3b3038 100644
--- a/src/snek/view/register.py
+++ b/src/snek/view/register.py
@@ -1,4 +1,4 @@
-from snek.view.base import BaseView 
+from snek.system.view import BaseView
 
 class RegisterView(BaseView):
 
diff --git a/src/snek/view/register_form.py b/src/snek/view/register_form.py
index 0ae7630..8099b01 100644
--- a/src/snek/view/register_form.py
+++ b/src/snek/view/register_form.py
@@ -1,5 +1,5 @@
 from snek.form.register import RegisterForm
-from snek.view.base import BaseFormView 
+from snek.system.view import BaseFormView
 
 class RegisterFormView(BaseFormView):
     form = RegisterForm
\ No newline at end of file
diff --git a/src/snek/view/view.py b/src/snek/view/web.py
similarity index 73%
rename from src/snek/view/view.py
rename to src/snek/view/web.py
index ea642a3..b06563a 100644
--- a/src/snek/view/view.py
+++ b/src/snek/view/web.py
@@ -1,4 +1,4 @@
-from snek.view.base import BaseView 
+from snek.system.view import BaseView
 
 class WebView(BaseView):