# Gate GQL Skill

This service is a project-scoped, dynamically loaded GraphQL storage engine.  
You can upload SDL through management endpoints, then immediately read/write data via project GraphQL endpoints (hot reload, no restart required).

## Service Endpoints

- Health check: `GET /health`
- Skill document: `GET /` (returns this file)
- Production base URL: `https://gql.ctrl1.com`

## Core Conventions

- Project GraphQL endpoint: `/:projectId/graphql`
- `projectId` normalization rule: `-` is automatically converted to `_`
  - Example: `foo-bar` -> `foo_bar`
- SDL file path: `schemas/<normalizedProjectId>.graphql`
- Database name: `p_<normalizedProjectId>`

## Management APIs (Schema)

### 1) Get project schema status

- `GET /graphql/projects/:projectId/schema/status`

Response example:

```json
{
  "ok": true,
  "projectId": "abc",
  "normalizedProjectId": "abc",
  "schemaVersion": "1745310000000",
  "sourcePath": "D:\\Ctrl-Gate\\gate-gql\\schemas\\abc.graphql",
  "cacheHit": true,
  "staleFallback": false
}
```

### 2) Get current SDL

- `GET /graphql/projects/:projectId/schema/sdl`

### 3) Upload new SDL (recommended)

- `POST /graphql/projects/:projectId/schema/sdl`
- `Content-Type: application/json`
- Body:

```json
{
  "sdl": "type ScrapedProduct { url: String! @unique @index title: String! price: Float }"
}
```

Behavior:

- Server validates SDL syntax and mapping rules first
- If valid, writes file to `schemas/<normalizedProjectId>.graphql`
- Invalidates schema cache for this project
- The next `/:projectId/graphql` request will use the new version (hot reload)

### 4) Manually invalidate schema cache

- `POST /graphql/projects/:projectId/schema/reload`

## GraphQL Data API

- `ALL /:projectId/graphql`
- Use `POST` in practice

### Example for project `abc`

Endpoint:

- `https://gql.ctrl1.com/abc/graphql`

Create data:

```bash
curl -X POST "https://gql.ctrl1.com/abc/graphql" ^
  -H "content-type: application/json" ^
  -d "{\"query\":\"mutation { scrapedProductCreate(record:{ url:\\\"https://a.com/p1\\\", title:\\\"测试商品\\\", price: 12.5 }) { record { _id url title price } } }\"}"
```

Query data:

```bash
curl -X POST "https://gql.ctrl1.com/abc/graphql" ^
  -H "content-type: application/json" ^
  -d "{\"query\":\"query { scrapedProductMany { _id url title price } }\"}"
```

Upsert (update if exists, create if not):

```bash
curl -X POST "https://gql.ctrl1.com/abc/graphql" ^
  -H "content-type: application/json" ^
  -d "{\"query\":\"mutation { scrapedProductCreateOrUpdate(record:{ url:\\\"https://a.com/p1\\\", title:\\\"Updated title\\\", price: 66.6 }) { _id url title price } }\"}"
```

## SDL Authoring Guidelines

- Type name: PascalCase (e.g. `ScrapedProduct`)
- Field name: camelCase (e.g. `createdAt`)
- Recommended: include at least one `@unique` field (used by `CreateOrUpdate`)
- Supported directives:
  - `@unique`
  - `@index`
  - `@hidden`
  - `@textSearch`
  - `@collection(name: String!)`

Minimal SDL example:

```graphql
directive @unique on FIELD_DEFINITION
directive @index on FIELD_DEFINITION
directive @hidden on FIELD_DEFINITION
directive @textSearch on FIELD_DEFINITION
directive @collection(name: String!) on OBJECT

type ScrapedProduct @collection(name: "scraped_products") {
  url: String! @unique @index
  title: String! @textSearch
  price: Float
  rawJson: String @hidden
}
```

## Common Troubleshooting

- `Schema Initialization Failed`
  - Usually caused by SDL syntax errors or mapping rule conflicts (e.g. invalid type name, duplicate collection mapping)
- `Command find requires authentication`
  - Mongo user does not have permission on the project database (e.g. `p_abc`)
- `SDL file not found`
  - The target file `schemas/<normalizedProjectId>.graphql` does not exist
