Metadata-Version: 2.4
Name: vanty-content
Version: 0.3.1
Summary: Vanty App: headless content & blog (pages, collections, tags, authors, sitemap).
Project-URL: Homepage, https://github.com/advantch/vanty-content
Project-URL: Repository, https://github.com/advantch/vanty-content
Project-URL: Issues, https://github.com/advantch/vanty-content/issues
Author-email: Themba <themba@advantch.com>
License-Expression: MIT
License-File: LICENSE
Keywords: blocknote,blog,cms,content,fastapi,tortoise
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.12
Requires-Dist: fastapi>=0.135.1
Requires-Dist: itsdangerous>=2.2.0
Requires-Dist: pydantic-settings>=2.13.1
Requires-Dist: python-slugify>=8.0.4
Requires-Dist: taskiq>=0.11
Requires-Dist: tortoise-orm>=1.1.6
Requires-Dist: vanty-core>=0.3.0
Description-Content-Type: text/markdown

# Vanty Content

[![PyPI](https://img.shields.io/pypi/v/vanty-content)](https://pypi.org/project/vanty-content/)
[![Python](https://img.shields.io/pypi/pyversions/vanty-content)](https://pypi.org/project/vanty-content/)

Headless content & blog toolkit for FastAPI with Tortoise ORM. Pages with
BlockNote bodies, collections, tags, authors, content calendars, public
blog endpoints, signed preview tokens, and an XML sitemap — in a single
`pip install`.

## Installation

```bash
pip install vanty-content
# or
uv pip install vanty-content
```

## Quick start

```python
from contextlib import asynccontextmanager

from fastapi import FastAPI

from vanty_content import ContentSettings, mount_content_router

settings = ContentSettings(
    database_url="sqlite://./vanty-content.db",
    base_url="https://example.com",
    preview_secret="change-me",
)

app = FastAPI()
kit = mount_content_router(app, settings)
app.include_router(kit.admin_router)


@asynccontextmanager
async def lifespan(_: FastAPI):
    await kit.init_orm(generate_schemas=True)
    try:
        yield
    finally:
        await kit.close_orm()


app.router.lifespan_context = lifespan
```

The package depends on `vanty-core` for events and the
`OrganizationScopedModel` base, and integrates with `vanty-auth` only via
the `AuthContext` resolver — never via direct ORM imports.

For documentation, see your hosted Docs site (Fumadocs / external) — this
package no longer ships any docs surface itself.

## BlockNote round-trip

Page bodies are opaque BlockNote JSON: a `list[dict]` of block nodes. The
package never parses inner blocks; bodies round-trip exactly between
write and read.

```python
from uuid import uuid4

from vanty_content import ContentApp, ContentSettings
from vanty_content.schemas.page import PageCreate

org_id = uuid4()
kit = ContentApp(ContentSettings(database_url="sqlite://:memory:"))
await kit.init_orm(generate_schemas=True)

body = [
    {"type": "paragraph", "content": [{"type": "text", "text": "Hello"}]},
    {
        "type": "heading",
        "props": {"level": 2},
        "content": [{"type": "text", "text": "World"}],
    },
]

page = await kit.page_service.create(
    org_id, PageCreate(title="Hello, world", slug="hello-world", body=body)
)
await kit.page_service.publish(org_id, page.id)

# GET /content/blog/hello-world returns identical body bytes.
fresh = await kit.page_service.get_by_slug(org_id, "hello-world", published_only=True)
assert fresh.body == body
```

## Public endpoints

Mounted under `prefix="/content"` by default:

| Method | Path | Description |
| ------ | ---- | ----------- |
| `GET`  | `/content/blog`                  | Paginated published posts. Filters: `collection`, `tag`, `page`, `size`. |
| `GET`  | `/content/blog/{slug}`           | Single published post by slug. |
| `GET`  | `/content/preview/{page_id}`     | Preview a draft page using `?token=`. |
| `GET`  | `/content/sitemap.xml`           | XML sitemap of all published pages for the resolved organization. |

The organization is resolved from the `X-Organization-ID` header, the
current `vanty-auth` `AuthContext`, or `app.state.content_organization_id_override`
(used in tests).

## Admin endpoints

`ContentApp.admin_router` is mounted by the host with
`require_superuser` enforced. Routes live under `/admin/content/`:
`pages`, `collections`, `tags`, `authors`, and
`projects/{id}/calendar`.

## Events

All events are typed dataclasses on the shared `vanty-core` event bus.

```python
from vanty_core.events import on
from vanty_content.events import PagePublished


@on(PagePublished)
async def push_to_cdn(event: PagePublished) -> None:
    ...
```

| Event name                              | Dataclass               |
| --------------------------------------- | ----------------------- |
| `vanty_content.page.created`            | `PageCreated`           |
| `vanty_content.page.updated`            | `PageUpdated`           |
| `vanty_content.page.published`          | `PagePublished`         |
| `vanty_content.page.unpublished`        | `PageUnpublished`       |
| `vanty_content.page.scheduled`          | `PageScheduled`         |
| `vanty_content.page.deleted`            | `PageDeleted`           |
| `vanty_content.collection.created`      | `CollectionCreated`     |
| `vanty_content.author.created`          | `AuthorCreated`         |
| `vanty_content.calendar.item_scheduled` | `CalendarItemScheduled` |

## Publish to GitHub

This package lives inside the Vanty monorepo at
`vanty-all/vanty-content/`. To publish a standalone, history-preserving
GitHub repo using `git filter-repo`:

```bash
make publish-github REPO=git@github.com:advantch/vanty-content.git
```

The Makefile clones the monorepo to a scratch directory, filters to the
`vanty-content/` subdirectory, points `origin` at the requested repo,
and prints the final `git push` command — it never pushes for you.

## License

MIT — Themba <themba@advantch.com>
