feat: initial public release

ConsentOS — a privacy-first cookie consent management platform.

Self-hosted, source-available alternative to OneTrust, Cookiebot, and
CookieYes. Full standards coverage (IAB TCF v2.2, GPP v1, Google
Consent Mode v2, GPC, Shopify Customer Privacy API), multi-tenant
architecture with role-based access, configuration cascade
(system → org → group → site → region), dark-pattern detection in
the scanner, and a tamper-evident consent record audit trail.

This is the initial public release. Prior development history is
retained internally.

See README.md for the feature list, architecture overview, and
quick-start instructions. Licensed under the Elastic Licence 2.0 —
self-host freely; do not resell as a managed service.
This commit is contained in:
James Cottrill
2026-04-13 14:20:15 +00:00
commit fbf26453f2
341 changed files with 62807 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
import uuid
from sqlalchemy import ForeignKey, String, UniqueConstraint
from sqlalchemy.dialects.postgresql import ARRAY, UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from src.models.base import Base, SoftDeleteMixin, TimestampMixin, UUIDPrimaryKeyMixin
class Site(UUIDPrimaryKeyMixin, TimestampMixin, SoftDeleteMixin, Base):
"""A domain being managed for cookie consent, belongs to an organisation."""
__tablename__ = "sites"
__table_args__ = (UniqueConstraint("organisation_id", "domain", name="uq_sites_org_domain"),)
organisation_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("organisations.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
domain: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
display_name: Mapped[str] = mapped_column(String(255), nullable=False)
is_active: Mapped[bool] = mapped_column(default=True, nullable=False)
additional_domains: Mapped[list[str] | None] = mapped_column(
ARRAY(String(255)), nullable=True, server_default=None
)
site_group_id: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True),
ForeignKey("site_groups.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
# Relationships
organisation: Mapped["Organisation"] = relationship(back_populates="sites") # noqa: F821
site_group: Mapped["SiteGroup | None"] = relationship(back_populates="sites") # noqa: F821
config: Mapped["SiteConfig | None"] = relationship( # noqa: F821
back_populates="site", uselist=False
)
cookies: Mapped[list["Cookie"]] = relationship(back_populates="site") # noqa: F821
cookie_allow_list: Mapped[list["CookieAllowListEntry"]] = relationship( # noqa: F821
back_populates="site"
)
scan_jobs: Mapped[list["ScanJob"]] = relationship(back_populates="site") # noqa: F821
translations: Mapped[list["Translation"]] = relationship( # noqa: F821
back_populates="site"
)