diff --git a/.gitignore b/.gitignore index 8cb2598..3747073 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ .vscode .history *.db* - +*.png # ---> Python # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..76436ba --- /dev/null +++ b/Dockerfile @@ -0,0 +1,40 @@ +FROM surnet/alpine-wkhtmltopdf:3.21.2-0.12.6-full as wkhtmltopdf +FROM python:3.10-alpine +WORKDIR /code +ENV FLASK_APP=app.py +ENV FLASK_RUN_HOST=0.0.0.0 +RUN apk add --no-cache gcc musl-dev linux-headers git + +#WKHTMLTOPDFNEEDS +RUN apk add --no-cache \ + libstdc++ \ + libx11 \ + libxrender \ + libxext \ + libssl3 \ + ca-certificates \ + fontconfig \ + freetype \ + ttf-dejavu \ + ttf-droid \ + ttf-freefont \ + ttf-liberation \ + # more fonts + && apk add --no-cache --virtual .build-deps \ + msttcorefonts-installer \ + # Install microsoft fonts + && update-ms-fonts \ + && fc-cache -f \ + # Clean up when done + && rm -rf /tmp/* \ + && apk del .build-deps +COPY --from=wkhtmltopdf /bin/wkhtmltopdf /bin/wkhtmltopdf +COPY --from=wkhtmltopdf /bin/wkhtmltoimage /bin/wkhtmltoimage +COPY setup.cfg setup.cfg +COPY pyproject.toml pyproject.toml +COPY src src +RUN pip install --upgrade pip +RUN pip install -e . +EXPOSE 8081 + +CMD ["gunicorn", "-w", "10", "-k", "aiohttp.worker.GunicornWebWorker", "snek.gunicorn:app","--bind","0.0.0.0:8081"] diff --git a/Makefile b/Makefile index d41b81a..65c58fa 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ PYTHON=./.venv/bin/python PIP=./.venv/bin/pip -APP=./venv/bin/snek.serve +APP=./.venv/bin/snek.serve +GUNICORN=./.venv/bin/gunicorn +GUNICORN_WORKERS = 1 PORT = 8081 @@ -9,5 +11,5 @@ install: $(PIP) install -e . run: - $(APP) --port=$(PORT) + $(GUNICORN) -w $(GUNICORN_WORKERS) -k aiohttp.worker.GunicornWebWorker snek.gunicorn:app --bind 0.0.0.0:$(PORT) --reload diff --git a/cache/crc321300331366.cache b/cache/crc321300331366.cache new file mode 100644 index 0000000..e69de29 diff --git a/cache/crc322507170282.cache b/cache/crc322507170282.cache new file mode 100644 index 0000000..e69de29 diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..9186108 --- /dev/null +++ b/compose.yml @@ -0,0 +1,12 @@ +services: + snek: + build: . + ports: + - "8081:8081" + volumes: + - ./:/code + develop: + watch: + - action: sync + path: . + target: /code \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index bb6480d..ca8353d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,6 +15,10 @@ package_dir = python_requires = >=3.7 install_requires = app @ git+https://retoor.molodetz.nl/retoor/app + beautifulsoup4 + gunicorn + imgkit + wkhtmltopdf [options.packages.find] where = src diff --git a/src/snek/app.py b/src/snek/app.py index feb2fc0..97fbb96 100644 --- a/src/snek/app.py +++ b/src/snek/app.py @@ -1,20 +1,60 @@ from app.app import Application as BaseApplication from snek.forms import RegisterForm from aiohttp import web +import aiohttp +import pathlib +from snek import http +from snek.middleware import cors_allow_middleware,cors_middleware + class Application(BaseApplication): def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + middlewares = [ + cors_middleware, + web.normalize_path_middleware(merge_slashes=True) + ] + self.template_path = pathlib.Path(__file__).parent.joinpath("templates") + super().__init__(middlewares=middlewares, template_path=self.template_path,*args, **kwargs) + self.router.add_static("/",pathlib.Path(__file__).parent.joinpath("static"),name="static",show_index=True) self.router.add_get("/register", self.handle_register) + self.router.add_get("/login", self.handle_login) + self.router.add_get("/test", self.handle_test) self.router.add_post("/register", self.handle_register) + self.router.add_get("/http-get",self.handle_http_get) + self.router.add_get("/http-photo",self.handle_http_photo) + + async def handle_test(self,request): + + return await self.render_template("test.html",request,context={"name":"retoor"}) + + async def handle_http_get(self, request:web.Request): + url = request.query.get("url") + content = await http.get(url) + return web.Response(body=content) + + async def handle_http_photo(self, request): + url = request.query.get("url") + path = await http.create_site_photo(url) + return web.Response(body=path.read_bytes(),headers={ + "Content-Type": "image/png" + }) + + async def handle_login(self, request): + if request.method == "GET": + return await self.render_template("login.html", request) #web.json_response({"form": RegisterForm().to_json()}) + elif request.method == "POST": + return await self.render_template("login.html", request) #web.json_response({"form": RegisterForm().to_json()}) + async def handle_register(self, request): if request.method == "GET": - return web.json_response({"form": RegisterForm().to_json()}) + return await self.render_template("register.html", request) #web.json_response({"form": RegisterForm().to_json()}) elif request.method == "POST": return self.render("register.html") +app = Application() + if __name__ == '__main__': - app = Application() + web.run_app(app,port=8081,host="0.0.0.0") diff --git a/src/snek/gunicorn.py b/src/snek/gunicorn.py new file mode 100644 index 0000000..4055bbd --- /dev/null +++ b/src/snek/gunicorn.py @@ -0,0 +1,3 @@ +from snek.app import app + +application = app diff --git a/src/snek/http.py b/src/snek/http.py new file mode 100644 index 0000000..0b16bee --- /dev/null +++ b/src/snek/http.py @@ -0,0 +1,83 @@ +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 asyncio +import zlib +import io + +async def crc32(data): + try: + data = data.encode() + except: + pass + result = "crc32" + str(zlib.crc32(data)) + return result + +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 + + + +async def public_touch(name=None): + path = pathlib.Path(".").joinpath(str(uuid.uuid4())+name) + path.open("wb").close() + 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") + + if output_path.exists(): + return output_path + output_path.touch() + def make_photo(): + imgkit.from_url(url, output_path.absolute()) + return output_path + + 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 and tags + tag['href'] = urljoin(base_url, tag['href']) + if tag.has_attr('src') and not tag['src'].startswith("http"): # For tags + 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 = [' + + + + + Register + + + +
+

Login

+
+ + + + Not having an account yet? Register here. +
+
+ + diff --git a/src/snek/templates/prachtig_gitter_like.html b/src/snek/templates/prachtig_gitter_like.html new file mode 100644 index 0000000..cd2d863 --- /dev/null +++ b/src/snek/templates/prachtig_gitter_like.html @@ -0,0 +1,51 @@ + + + + + + Dark Themed Chat Application + + + +
+ + +
+
+ +
+
+

General

+
+
+
+ Alice: + Hello, everyone! +
+
+ Bob: + Hi Alice! How are you? +
+
+
+ + +
+
+
+ + + diff --git a/src/snek/templates/register.html b/src/snek/templates/register.html new file mode 100644 index 0000000..da41629 --- /dev/null +++ b/src/snek/templates/register.html @@ -0,0 +1,22 @@ + + + + + + Register + + + +
+

Register

+
+ + + + + + Already have an account? Login here. +
+
+ + diff --git a/src/snek/templates/test.html b/src/snek/templates/test.html new file mode 100644 index 0000000..c23103a --- /dev/null +++ b/src/snek/templates/test.html @@ -0,0 +1,63 @@ + + + + + + Dark Themed Chat Application + + + + + +
+ + +
+
+ +
+
+

General

+
+
+
+
A
+
+
Alice
+
Hello, everyone!
+
10:45 AM
+
+
+ +
+
B
+
+
Bob
+
Hi Alice! How are you?
+
10:46 AM
+
+
+
+
+ + +
+
+
+ + + + diff --git a/src/snek/templates/test2.html b/src/snek/templates/test2.html new file mode 100644 index 0000000..9aad83a --- /dev/null +++ b/src/snek/templates/test2.html @@ -0,0 +1,122 @@ + + + + + + Dynamic Form Component + + + + + + + + + +