# Entity CRUD (Seaography)

Barycenter uses [Seaography](https://www.sea-ql.org/Seaography/) to auto-generate a GraphQL schema from its SeaORM database entities. This schema is served at `POST /admin/graphql` on the admin port and provides full create, read, update, and delete operations for all registered entities without any hand-written resolver code.

## Registered Entities

The following eight entities are registered in the Seaography schema:

| Entity | Description |
|---|---|
| `user` | User accounts with credentials, 2FA settings, and metadata |
| `client` | OAuth 2.0 client registrations with secrets and redirect URIs |
| `session` | Active user sessions with authentication context (AMR, ACR, MFA status) |
| `access_token` | Issued access tokens with subject, scope, and expiration |
| `auth_code` | Authorization codes with PKCE challenge, scope, and nonce |
| `refresh_token` | Refresh tokens with rotation tracking and expiration |
| `property` | Key-value property store (owner, key, value) |
| `job_execution` | Background job execution history and results |

Each entity exposes `findMany` and `findOne` queries, as well as `createOne`, `updateOne`, and `deleteOne` mutations, all auto-generated by Seaography.

## Querying Entities

### List Users

```graphql
{
  user {
    findMany {
      nodes {
        id
        username
        email
        requires2fa
        passkeyEnrolledAt
        createdAt
      }
      paginationInfo {
        pages
        current
        offset
        total
      }
    }
  }
}
```

### Find a Single User

```graphql
{
  user {
    findOne(filter: { username: { eq: "admin" } }) {
      id
      username
      email
      requires2fa
    }
  }
}
```

### List Active Sessions

```graphql
{
  session {
    findMany(
      filter: { expiresAt: { gt: "2026-02-14T00:00:00Z" } }
    ) {
      nodes {
        id
        subject
        amr
        acr
        mfaVerified
        createdAt
        expiresAt
      }
    }
  }
}
```

### List Registered Clients

```graphql
{
  client {
    findMany {
      nodes {
        clientId
        clientName
        redirectUris
        createdAt
      }
    }
  }
}
```

### View Job Execution History

```graphql
{
  jobExecution {
    findMany(
      orderBy: { startedAt: DESC }
    ) {
      nodes {
        id
        jobName
        startedAt
        completedAt
        success
        errorMessage
        recordsProcessed
      }
    }
  }
}
```

## Creating Entities

### Create a New User

```graphql
mutation {
  user {
    createOne(
      data: {
        username: "alice"
        email: "alice@example.com"
        passwordHash: "$argon2id$..."
      }
    ) {
      id
      username
      email
    }
  }
}
```

> **Note**: Password hashes must be pre-computed in argon2 format. For user provisioning, the [user sync CLI](./user-sync.md) or the [public registration endpoint](./public-registration.md) are typically more convenient than direct entity creation.

## Updating Entities

### Update a Client's Redirect URIs

```graphql
mutation {
  client {
    updateOne(
      filter: { clientId: { eq: "my_client_id" } }
      data: {
        redirectUris: "https://app.example.com/callback https://app.example.com/callback2"
      }
    ) {
      clientId
      redirectUris
    }
  }
}
```

## Deleting Entities

### Delete an Expired Access Token

```graphql
mutation {
  accessToken {
    deleteOne(
      filter: { id: { eq: "token_id_here" } }
    ) {
      id
    }
  }
}
```

## Using curl

All queries and mutations can be sent as JSON POST requests:

```bash
# List all users
curl -s -X POST http://localhost:8081/admin/graphql \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ user { findMany { nodes { id username email requires2fa } } } }"
  }' | jq .

# Find a specific client
curl -s -X POST http://localhost:8081/admin/graphql \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ client { findOne(filter: { clientId: { eq: \"my_client_id\" } }) { clientId clientName redirectUris } } }"
  }' | jq .
```

## Filtering and Pagination

Seaography auto-generates filter types for each entity field. Common filter operators include:

| Operator | Description | Example |
|---|---|---|
| `eq` | Equals | `{ username: { eq: "admin" } }` |
| `ne` | Not equals | `{ success: { ne: true } }` |
| `gt` | Greater than | `{ createdAt: { gt: "2026-01-01T00:00:00Z" } }` |
| `lt` | Less than | `{ expiresAt: { lt: "2026-02-14T00:00:00Z" } }` |
| `gte` | Greater than or equal | `{ recordsProcessed: { gte: 10 } }` |
| `lte` | Less than or equal | `{ recordsProcessed: { lte: 100 } }` |
| `contains` | String contains | `{ email: { contains: "@example.com" } }` |

Pagination is available on `findMany` queries using `offset` and `limit` parameters. The response includes a `paginationInfo` object with total count and page information.

## Further Reading

- [Admin GraphQL API](./graphql-api.md) -- overview of the admin API architecture
- [GraphQL Playground](./playground.md) -- interactive schema exploration
- [Job Management](./job-management.md) -- custom job operations at `/admin/jobs`
