attachment
Gitea Actions Demo / build_and_push (push) Successful in 27m21s Details

This commit is contained in:
artem 2026-01-16 18:09:54 +03:00
parent 9dfdb6ec29
commit ee7739b875
8 changed files with 54 additions and 21 deletions

View File

@ -1,4 +1,3 @@
import flask_login import flask_login
from flask import Response, redirect, render_template, request, url_for from flask import Response, redirect, render_template, request, url_for
from flask_admin import Admin, AdminIndexView, expose from flask_admin import Admin, AdminIndexView, expose
@ -40,14 +39,14 @@ async def get_file(raw_path: str):
cache_ctrl = "public, max-age=864000" # 10 дней cache_ctrl = "public, max-age=864000" # 10 дней
last_mod = attach[0].created_at.strftime("%a, %d %b %Y %H:%M:%S GMT") last_mod = attach[0].created_at.strftime("%a, %d %b %Y %H:%M:%S GMT")
return Response( return Response(
body, body,
mimetype=attach[0].content_type, mimetype=attach[0].content_type,
headers={ headers={
"Cache-Control": cache_ctrl, "Cache-Control": cache_ctrl,
"Last-Modified": last_mod, "Last-Modified": last_mod,
}, },
) )
@login_manager.user_loader @login_manager.user_loader

View File

@ -12,7 +12,7 @@ app.config["SQLALCHEMY_DATABASE_URI"] = (
app.config["SQLALCHEMY_ENGINE_OPTIONS"] = { app.config["SQLALCHEMY_ENGINE_OPTIONS"] = {
"pool_recycle": 600, "pool_recycle": 600,
"pool_pre_ping": True, "pool_pre_ping": True,
"pool_size": 5, "pool_size": 30,
} }
app.config["SECRET_KEY"] = cnf.admin_secret_key app.config["SECRET_KEY"] = cnf.admin_secret_key
app.config["SQLALCHEMY_ECHO"] = True app.config["SQLALCHEMY_ECHO"] = True

View File

@ -23,7 +23,7 @@ class AttachmentView(ModelView):
@staticmethod @staticmethod
def _list_thumbnail(view, _context, model, _name): def _list_thumbnail(view, _context, model, _name):
data = "" data = ""
data += f'<img src="/admin/attachment/{model.path}.original.jpg" width="100" />' data += f'<img src="/attachments{model.path}.original.jpg" width="100" />'
return Markup(data) return Markup(data)

View File

@ -24,7 +24,7 @@ class AsyncDB(AbstractDB):
# self.engine.execution_options(stream_results=True) # self.engine.execution_options(stream_results=True)
if self.engine is None: if self.engine is None:
raise ConnectError raise ConnectError
session = asyncio.async_sessionmaker(self.engine, expire_on_commit=False) session = asyncio.async_sessionmaker(self.engine, expire_on_commit=True)
if session is None: if session is None:
raise ConnectError raise ConnectError
self.async_session = session self.async_session = session
@ -35,13 +35,10 @@ class AsyncDB(AbstractDB):
@property @property
def session(self): def session(self):
return asyncio.async_sessionmaker(self.engine, expire_on_commit=False) return asyncio.async_sessionmaker(self.engine, expire_on_commit=True)
def new_session(self):
return asyncio.async_sessionmaker(self.engine, expire_on_commit=False)()
def session_master(self): def session_master(self):
return self.new_session() return self.async_session()
def session_slave(self): def session_slave(self):
return self.new_session() return self.async_session()

View File

@ -1,6 +1,7 @@
from server.infra.web.attachments import AtachmentController
from server.infra.web.description import DescriptionController from server.infra.web.description import DescriptionController
from server.infra.web.recognizer import BreedsController from server.infra.web.recognizer import BreedsController
from server.infra.web.seo import SeoController from server.infra.web.seo import SeoController
from server.infra.web.vote import VoteController from server.infra.web.vote import VoteController
__all__ = ("DescriptionController", "SeoController", "BreedsController", "VoteController") __all__ = ("DescriptionController", "SeoController", "BreedsController", "VoteController", "AtachmentController")

View File

@ -0,0 +1,35 @@
import inject
from litestar import Controller, get
from litestar.exceptions import HTTPException
from litestar.response import Response
from server.modules.attachments import AtachmentService
class AtachmentController(Controller):
@get("/attachments/{raw_path:path}", media_type="image/jpeg")
async def get_file(self, raw_path: str) -> Response:
attach_service: AtachmentService = inject.instance(AtachmentService)
attach_path = attach_service.path_from_url(raw_path)
# Query within session scope
attach = await attach_service.get_info_bypath(session=None, path=[attach_path])
if not attach:
raise HTTPException(status_code=404, detail="Attachment not found")
# Get file data (assuming async)
body = await attach_service.get_data(attach_path)
# Extract metadata within session scope
content_type = attach[0].content_type
last_mod = attach[0].created_at.strftime("%a, %d %b %Y %H:%M:%S GMT")
return Response(
content=body,
media_type=content_type,
headers={
"Cache-Control": "public, max-age=864000",
"Last-Modified": last_mod,
},
)

View File

@ -12,7 +12,7 @@ from litestar.template.config import TemplateConfig
from server.config import get_app_config from server.config import get_app_config
from server.infra.db import AsyncDB from server.infra.db import AsyncDB
from server.infra.web import BreedsController, DescriptionController, SeoController, VoteController from server.infra.web import AtachmentController, BreedsController, DescriptionController, SeoController, VoteController
from server.modules.attachments import AtachmentService, DBAttachmentRepository, S3StorageDriver from server.modules.attachments import AtachmentService, DBAttachmentRepository, S3StorageDriver
from server.modules.descriptions import CharactersService, PGCharactersRepository from server.modules.descriptions import CharactersService, PGCharactersRepository
from server.modules.rate import PGVoteRepository, VotesService from server.modules.rate import PGVoteRepository, VotesService
@ -45,6 +45,7 @@ app = Litestar(
DescriptionController, DescriptionController,
SeoController, SeoController,
VoteController, VoteController,
AtachmentController,
create_static_files_router(path="/static", directories=["server/static"]), create_static_files_router(path="/static", directories=["server/static"]),
], ],
template_config=TemplateConfig( template_config=TemplateConfig(

View File

@ -237,7 +237,7 @@ class AtachmentService:
if ".original" not in url: if ".original" not in url:
raise ValueError(f"wrong url: {url}") raise ValueError(f"wrong url: {url}")
parts = url.split(".original")[0] parts = url.split(".original")[0]
return f"/{parts}" return parts
def url(self, attachment_id: str, content_type: str | None = None) -> str: def url(self, attachment_id: str, content_type: str | None = None) -> str:
return f"{self._cnf.app_public_url}/api/v0/attachment/{attachment_id}.original.{self.extension(content_type)}" return f"{self._cnf.app_public_url}/api/v0/attachment/{attachment_id}.original.{self.extension(content_type)}"