diff --git a/i18n/be/LC_MESSAGES/messages.po b/i18n/be/LC_MESSAGES/messages.po new file mode 100644 index 0000000..8962308 --- /dev/null +++ b/i18n/be/LC_MESSAGES/messages.po @@ -0,0 +1,49 @@ +# Translations template for PROJECT. +# Copyright (C) 2026 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2026. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2026-03-07 17:05+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: be\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"Generated-By: Babel 2.17.0\n" + +#: src/snapshots/templates/article-template.html.j2:66 +msgid "How do I know that I can trust this page?" +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:73 +msgid "" +"This story is a copy of an article from {site_title}. It is delivered to " +"you from a trusted archive to assure its availability over time." +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:76 +msgid "View the article source" +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:143 +msgid "You are leaving this page" +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:163 +msgid "" +"This link will redirect you to an external website. If it’s not available" +" in your region, you may not be able to access it." +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:165 +msgid "Continue" +msgstr "" diff --git a/i18n/en/LC_MESSAGES/messages.po b/i18n/en/LC_MESSAGES/messages.po index 82f1ac0..d3a6f71 100644 --- a/i18n/en/LC_MESSAGES/messages.po +++ b/i18n/en/LC_MESSAGES/messages.po @@ -24,12 +24,12 @@ msgstr "How do I know that I can trust this page?" #: src/snapshots/templates/article-template.html.j2:73 msgid "" -"This story is a copy of an article from %(site_title). It is delivered to " +"This story is a copy of an article from {site_title}. It is delivered to " "you from a trusted archive to assure its availability over time." msgstr "" -"This story is a copy of an article from %(site_title). It is delivered to " +"This story is a copy of an article from {site_title}. It is delivered to " "you from a trusted archive to assure its availability over time." #: src/snapshots/templates/article-template.html.j2:76 diff --git a/i18n/es/LC_MESSAGES/messages.po b/i18n/es/LC_MESSAGES/messages.po new file mode 100644 index 0000000..2157241 --- /dev/null +++ b/i18n/es/LC_MESSAGES/messages.po @@ -0,0 +1,56 @@ +# Translations template for PROJECT. +# Copyright (C) 2026 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2026. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2026-03-07 17:05+0000\n" +"PO-Revision-Date: 2026-03-08 15:54+0000\n" +"Last-Translator: irl \n" +"Language-Team: Spanish \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.17-dev\n" +"Generated-By: Babel 2.17.0\n" + +#: src/snapshots/templates/article-template.html.j2:66 +msgid "How do I know that I can trust this page?" +msgstr "¿Cómo sé que puedo confiar en esta página?" + +#: src/snapshots/templates/article-template.html.j2:73 +msgid "" +"This story is a copy of an article from {site_title}. It is delivered to " +"you from a trusted archive to assure its availability over time." +msgstr "" +"Esta historia es una copia de un artículo de {site_title}. Se la entregamos " +"desde un archivo confiable para garantizar su disponibilidad a lo largo del " +"tiempo." + +#: src/snapshots/templates/article-template.html.j2:76 +msgid "View the article source" +msgstr "Ver el código fuente del artículo" + +#: src/snapshots/templates/article-template.html.j2:143 +msgid "You are leaving this page" +msgstr "Estas abandonando esta pagina" + +#: src/snapshots/templates/article-template.html.j2:163 +msgid "" +"This link will redirect you to an external website. If it’s not available" +" in your region, you may not be able to access it." +msgstr "" +"Este enlace te redirigirá a un sitio web externo. Si no está disponible en " +"tu región, es posible que no lo puedas acceder." + +#: src/snapshots/templates/article-template.html.j2:165 +msgid "Continue" +msgstr "Continuar" diff --git a/i18n/fa/LC_MESSAGES/messages.po b/i18n/fa/LC_MESSAGES/messages.po new file mode 100644 index 0000000..f3c20d0 --- /dev/null +++ b/i18n/fa/LC_MESSAGES/messages.po @@ -0,0 +1,48 @@ +# Translations template for PROJECT. +# Copyright (C) 2026 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2026. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2026-03-07 17:05+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: fa\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"Generated-By: Babel 2.17.0\n" + +#: src/snapshots/templates/article-template.html.j2:66 +msgid "How do I know that I can trust this page?" +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:73 +msgid "" +"This story is a copy of an article from {site_title}. It is delivered to " +"you from a trusted archive to assure its availability over time." +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:76 +msgid "View the article source" +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:143 +msgid "You are leaving this page" +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:163 +msgid "" +"This link will redirect you to an external website. If it’s not available" +" in your region, you may not be able to access it." +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:165 +msgid "Continue" +msgstr "" diff --git a/i18n/ka/LC_MESSAGES/messages.po b/i18n/ka/LC_MESSAGES/messages.po new file mode 100644 index 0000000..f4d8635 --- /dev/null +++ b/i18n/ka/LC_MESSAGES/messages.po @@ -0,0 +1,48 @@ +# Translations template for PROJECT. +# Copyright (C) 2026 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2026. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2026-03-07 17:05+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ka\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"Generated-By: Babel 2.17.0\n" + +#: src/snapshots/templates/article-template.html.j2:66 +msgid "How do I know that I can trust this page?" +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:73 +msgid "" +"This story is a copy of an article from {site_title}. It is delivered to " +"you from a trusted archive to assure its availability over time." +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:76 +msgid "View the article source" +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:143 +msgid "You are leaving this page" +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:163 +msgid "" +"This link will redirect you to an external website. If it’s not available" +" in your region, you may not be able to access it." +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:165 +msgid "Continue" +msgstr "" diff --git a/i18n/messages.pot b/i18n/messages.pot index b23b85c..3356255 100644 --- a/i18n/messages.pot +++ b/i18n/messages.pot @@ -23,8 +23,8 @@ msgstr "" #: src/snapshots/templates/article-template.html.j2:73 msgid "" -"This story is a copy of an article from %(site_title). It is delivered to " +"This story is a copy of an article from {site_title}. It is delivered to " "you from a trusted archive to assure its availability over time." msgstr "" diff --git a/i18n/prs/LC_MESSAGES/messages.po b/i18n/prs/LC_MESSAGES/messages.po new file mode 100644 index 0000000..481721e --- /dev/null +++ b/i18n/prs/LC_MESSAGES/messages.po @@ -0,0 +1,48 @@ +# Translations template for PROJECT. +# Copyright (C) 2026 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2026. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2026-03-07 17:05+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: prs\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"Generated-By: Babel 2.17.0\n" + +#: src/snapshots/templates/article-template.html.j2:66 +msgid "How do I know that I can trust this page?" +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:73 +msgid "" +"This story is a copy of an article from {site_title}. It is delivered to " +"you from a trusted archive to assure its availability over time." +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:76 +msgid "View the article source" +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:143 +msgid "You are leaving this page" +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:163 +msgid "" +"This link will redirect you to an external website. If it’s not available" +" in your region, you may not be able to access it." +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:165 +msgid "Continue" +msgstr "" diff --git a/i18n/ps/LC_MESSAGES/messages.po b/i18n/ps/LC_MESSAGES/messages.po new file mode 100644 index 0000000..2bb963d --- /dev/null +++ b/i18n/ps/LC_MESSAGES/messages.po @@ -0,0 +1,48 @@ +# Translations template for PROJECT. +# Copyright (C) 2026 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2026. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2026-03-07 17:05+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ps\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"Generated-By: Babel 2.17.0\n" + +#: src/snapshots/templates/article-template.html.j2:66 +msgid "How do I know that I can trust this page?" +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:73 +msgid "" +"This story is a copy of an article from {site_title}. It is delivered to " +"you from a trusted archive to assure its availability over time." +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:76 +msgid "View the article source" +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:143 +msgid "You are leaving this page" +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:163 +msgid "" +"This link will redirect you to an external website. If it’s not available" +" in your region, you may not be able to access it." +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:165 +msgid "Continue" +msgstr "" diff --git a/i18n/ru/LC_MESSAGES/messages.po b/i18n/ru/LC_MESSAGES/messages.po new file mode 100644 index 0000000..44b8f91 --- /dev/null +++ b/i18n/ru/LC_MESSAGES/messages.po @@ -0,0 +1,49 @@ +# Translations template for PROJECT. +# Copyright (C) 2026 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2026. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2026-03-07 17:05+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"Generated-By: Babel 2.17.0\n" + +#: src/snapshots/templates/article-template.html.j2:66 +msgid "How do I know that I can trust this page?" +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:73 +msgid "" +"This story is a copy of an article from {site_title}. It is delivered to " +"you from a trusted archive to assure its availability over time." +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:76 +msgid "View the article source" +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:143 +msgid "You are leaving this page" +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:163 +msgid "" +"This link will redirect you to an external website. If it’s not available" +" in your region, you may not be able to access it." +msgstr "" + +#: src/snapshots/templates/article-template.html.j2:165 +msgid "Continue" +msgstr "" diff --git a/src/database.py b/src/database.py new file mode 100644 index 0000000..d344210 --- /dev/null +++ b/src/database.py @@ -0,0 +1,50 @@ +import contextlib +from typing import Annotated, Iterator, Generator + +from fastapi import Depends +from sqlalchemy import ( + MetaData, create_engine, Connection, +) +from sqlalchemy.orm import sessionmaker, Session + +from src.config import settings +from src.constants import DB_NAMING_CONVENTION + +engine = create_engine( + str(settings.DATABASE_URL), + pool_size=settings.DATABASE_POOL_SIZE, + pool_recycle=settings.DATABASE_POOL_TTL, + pool_pre_ping=settings.DATABASE_POOL_PRE_PING, +) +metadata = MetaData(naming_convention=DB_NAMING_CONVENTION) +sm = sessionmaker(autocommit=False, expire_on_commit=False, bind=engine) + + +@contextlib.contextmanager +def get_db_connection() -> Iterator[Connection]: + with engine.connect() as connection: + try: + yield connection + except Exception: + connection.rollback() + raise + + +@contextlib.contextmanager +def get_db_session() -> Iterator[Session]: + session = sm() + try: + yield session + except Exception: + session.rollback() + raise + finally: + session.close() + + +def get_db() -> Generator[Session, None]: + with get_db_session() as session: + yield session + + +DbSession = Annotated[Session, Depends(get_db)] diff --git a/src/models.py b/src/models.py new file mode 100644 index 0000000..614f969 --- /dev/null +++ b/src/models.py @@ -0,0 +1,34 @@ +from datetime import datetime +from typing import Any + +from sqlalchemy import DateTime, JSON, func +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column +from src.database import metadata + + +class CustomBase(DeclarativeBase): + type_annotation_map = { + datetime: DateTime(timezone=True), + dict[str, Any]: JSON, + } + metadata = metadata + +class ActivatedMixin: + active: Mapped[bool] = mapped_column(default=True) + + +class DeletedTimestampMixin: + deleted_at: Mapped[datetime | None] = mapped_column(nullable=True) + + +class DescriptionMixin: + description: Mapped[str] + + +class IdMixin: + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + + +class TimestampMixin: + created_at: Mapped[datetime] = mapped_column(default=func.now()) + updated_at: Mapped[datetime] = mapped_column(default=func.now(), onupdate=func.now()) diff --git a/src/snapshots/models.py b/src/snapshots/models.py new file mode 100644 index 0000000..17b9f01 --- /dev/null +++ b/src/snapshots/models.py @@ -0,0 +1,9 @@ +from sqlalchemy.orm import Mapped + +from src.models import CustomBase, IdMixin + + +class Snapshot(CustomBase, IdMixin): + __tablename__ = "snapshot" + + url: Mapped[str]