ai fix-02
This commit is contained in:
@@ -15,6 +15,7 @@ ENV NODE_ENV=production
|
|||||||
|
|
||||||
COPY --from=dependencies /app/node_modules ./node_modules
|
COPY --from=dependencies /app/node_modules ./node_modules
|
||||||
COPY .idea/SKILL.md ./.idea/SKILL.md
|
COPY .idea/SKILL.md ./.idea/SKILL.md
|
||||||
|
COPY public ./public
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
COPY scripts ./scripts
|
COPY scripts ./scripts
|
||||||
|
|
||||||
|
|||||||
141
public/SKILL.md
Normal file
141
public/SKILL.md
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
# Dune API Skill For OpenClaw Agents
|
||||||
|
|
||||||
|
You are working with the Dune API, a Node.js Express service that imports Dune: Awakening data from Questlog into MongoDB and exposes it through REST endpoints for OpenClaw.
|
||||||
|
|
||||||
|
## Primary Goal
|
||||||
|
|
||||||
|
Help OpenClaw retrieve complete Dune: Awakening records with the least guesswork. Prefer the detailed singular datasets for client-facing lookups and search.
|
||||||
|
|
||||||
|
## Public Base URL
|
||||||
|
|
||||||
|
Use this production API base URL:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://dune.api.coppnic.cc
|
||||||
|
```
|
||||||
|
|
||||||
|
Swagger/OpenAPI is available here:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://dune.api.coppnic.cc/openapi.json
|
||||||
|
https://ui.dune.api.coppnic.cc/docs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Endpoints For OpenClaw
|
||||||
|
|
||||||
|
Prefer singular datasets because they contain full Questlog single-record payloads in `raw`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /api/item
|
||||||
|
GET /api/item/{id}
|
||||||
|
GET /api/skill
|
||||||
|
GET /api/skill/{id}
|
||||||
|
GET /api/recipe
|
||||||
|
GET /api/recipe/{id}
|
||||||
|
GET /api/placeable
|
||||||
|
GET /api/placeable/{id}
|
||||||
|
GET /api/npc
|
||||||
|
GET /api/npc/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful examples:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /api/item/LongRifle_Unique_Poison_03?language=en
|
||||||
|
GET /api/item/Bloodsack_02?language=en
|
||||||
|
GET /api/skill/skills_ability_poisonmine?language=en
|
||||||
|
GET /api/recipe/Bloodsack_2_Recipe?language=en
|
||||||
|
GET /api/placeable/Atre_Banner_Placeable?language=en
|
||||||
|
GET /api/npc/bs43q?language=en
|
||||||
|
```
|
||||||
|
|
||||||
|
For text search:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /api/search?q=rifle&datasets=item&language=en
|
||||||
|
GET /api/search?q=poison&datasets=item,skill,recipe&language=en
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported languages are `en` and `de`.
|
||||||
|
|
||||||
|
## Dataset Rules
|
||||||
|
|
||||||
|
Detailed singular datasets:
|
||||||
|
|
||||||
|
| Dataset | Mongo collection | Questlog detail method |
|
||||||
|
| ----------- | ---------------- | ----------------------- |
|
||||||
|
| `item` | `item` | `database.getItem` |
|
||||||
|
| `skill` | `skill` | `database.getSkill` |
|
||||||
|
| `recipe` | `recipe` | `database.getRecipe` |
|
||||||
|
| `placeable` | `placeable` | `database.getPlaceable` |
|
||||||
|
| `npc` | `npc` | `database.getNpc` |
|
||||||
|
|
||||||
|
Summary plural datasets:
|
||||||
|
|
||||||
|
| Dataset | Mongo collection | Questlog page method |
|
||||||
|
| ------------ | ---------------- | ------------------------ |
|
||||||
|
| `items` | `items` | `database.getItems` |
|
||||||
|
| `skills` | `skills` | `database.getSkills` |
|
||||||
|
| `recipes` | `recipes` | `database.getRecipes` |
|
||||||
|
| `placeables` | `placeables` | `database.getPlaceables` |
|
||||||
|
| `npcs` | `npcs` | `database.getNpcs` |
|
||||||
|
|
||||||
|
Use plural datasets only when a compact Questlog page-summary record is enough. If a plural lookup includes an id, for example `/api/items/LongRifle_Unique_Poison_03`, the API checks the matching singular collection first and falls back to the plural summary collection.
|
||||||
|
|
||||||
|
## Response Shape
|
||||||
|
|
||||||
|
Stored documents have normalized metadata and the original Questlog payload:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dataset": "item",
|
||||||
|
"language": "en",
|
||||||
|
"source": "questlog.gg",
|
||||||
|
"sourceMethod": "database.getItem",
|
||||||
|
"sourceId": "LongRifle_Unique_Poison_03",
|
||||||
|
"name": "Assassin's Rifle",
|
||||||
|
"raw": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For OpenClaw, inspect `raw` for domain-specific fields:
|
||||||
|
|
||||||
|
- Items: `raw.stats`, `raw.mainCategory`, `raw.subCategory`, recipe relationships, wearable/equip fields.
|
||||||
|
- Skills: `raw.levels`, `raw.connections`, grid position, bonuses.
|
||||||
|
- Recipes: `raw.recipeInputItems`, `raw.recipeOutputItems`, crafting requirements, crafting stations.
|
||||||
|
- Placeables: production types, power/water fields, craftable recipe relationships.
|
||||||
|
- NPCs: `raw.description`, `raw.npcTags`, category metadata.
|
||||||
|
|
||||||
|
## Repository Map
|
||||||
|
|
||||||
|
Look here when changing behavior:
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/datasets.js Dataset keys, collections, Questlog methods, API allowlists.
|
||||||
|
src/importer/questlogClient.js Questlog URL building and TRPC response extraction.
|
||||||
|
src/importer/importer.js Import orchestration, detail fetches, Mongo upserts.
|
||||||
|
src/db/indexes.js Mongo indexes for summary and singular collections.
|
||||||
|
src/routes/api.js REST API routes, search, dataset lookup behavior.
|
||||||
|
src/swagger/openapi.js OpenAPI/Swagger documentation.
|
||||||
|
src/app.js Express middleware and public top-level routes.
|
||||||
|
scripts/import.js CLI import entry point.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Useful endpoint checks:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /health
|
||||||
|
GET /api/datasets
|
||||||
|
GET /api/item/LongRifle_Unique_Poison_03?language=en
|
||||||
|
GET /api/search?q=poison&datasets=item,skill,recipe&language=en
|
||||||
|
GET /SKILL.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important Constraints
|
||||||
|
|
||||||
|
- Do not expose secrets in public documentation or API responses.
|
||||||
|
- Keep `.env` values private; production configuration comes from environment variables.
|
||||||
|
- Keep the singular datasets first-class for OpenClaw.
|
||||||
|
- Keep changes small and consistent with the existing CommonJS/Express style.
|
||||||
39
src/app.js
39
src/app.js
@@ -8,12 +8,41 @@ const { pingMongo } = require("./db/client");
|
|||||||
const { router: apiRouter } = require("./routes/api");
|
const { router: apiRouter } = require("./routes/api");
|
||||||
const { openApiDocument } = require("./swagger/openapi");
|
const { openApiDocument } = require("./swagger/openapi");
|
||||||
|
|
||||||
|
const publicDir = path.join(__dirname, "..", "public");
|
||||||
const skillFilePaths = [
|
const skillFilePaths = [
|
||||||
|
path.join(publicDir, "SKILL.md"),
|
||||||
path.join(__dirname, "SKILL.md"),
|
path.join(__dirname, "SKILL.md"),
|
||||||
path.join(__dirname, "..", ".idea", "SKILL.md"),
|
path.join(__dirname, "..", ".idea", "SKILL.md"),
|
||||||
path.join(__dirname, "..", ".ai", "SKILL.md"),
|
path.join(__dirname, "..", ".ai", "SKILL.md"),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const fallbackSkillMarkdown = `# Dune API Skill For OpenClaw Agents
|
||||||
|
|
||||||
|
Use https://dune.api.coppnic.cc as the API base URL.
|
||||||
|
|
||||||
|
Prefer detailed singular datasets for OpenClaw:
|
||||||
|
|
||||||
|
- GET /api/item/{id}
|
||||||
|
- GET /api/skill/{id}
|
||||||
|
- GET /api/recipe/{id}
|
||||||
|
- GET /api/placeable/{id}
|
||||||
|
- GET /api/npc/{id}
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- GET /api/item/LongRifle_Unique_Poison_03?language=en
|
||||||
|
- GET /api/skill/skills_ability_poisonmine?language=en
|
||||||
|
- GET /api/recipe/Bloodsack_2_Recipe?language=en
|
||||||
|
- GET /api/placeable/Atre_Banner_Placeable?language=en
|
||||||
|
- GET /api/npc/bs43q?language=en
|
||||||
|
|
||||||
|
Search example:
|
||||||
|
|
||||||
|
- GET /api/search?q=poison&datasets=item,skill,recipe&language=en
|
||||||
|
|
||||||
|
Supported languages: en, de.
|
||||||
|
`;
|
||||||
|
|
||||||
function getSkillFilePath() {
|
function getSkillFilePath() {
|
||||||
return skillFilePaths.find((filePath) => fs.existsSync(filePath));
|
return skillFilePaths.find((filePath) => fs.existsSync(filePath));
|
||||||
}
|
}
|
||||||
@@ -24,6 +53,7 @@ function createApp() {
|
|||||||
app.set("trust proxy", true);
|
app.set("trust proxy", true);
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(express.json({ limit: "2mb" }));
|
app.use(express.json({ limit: "2mb" }));
|
||||||
|
app.use(express.static(publicDir));
|
||||||
app.use((request, response, next) => {
|
app.use((request, response, next) => {
|
||||||
const forwardedProto = request.get("x-forwarded-proto");
|
const forwardedProto = request.get("x-forwarded-proto");
|
||||||
const isSecure = request.secure || forwardedProto === "https";
|
const isSecure = request.secure || forwardedProto === "https";
|
||||||
@@ -64,13 +94,14 @@ function createApp() {
|
|||||||
|
|
||||||
app.get(["/SKILL.md", "/skill.md"], (request, response) => {
|
app.get(["/SKILL.md", "/skill.md"], (request, response) => {
|
||||||
const skillFilePath = getSkillFilePath();
|
const skillFilePath = getSkillFilePath();
|
||||||
if (!skillFilePath) {
|
response.type("text/markdown");
|
||||||
response.status(404).json({ error: "SKILL.md not found" });
|
|
||||||
|
if (skillFilePath) {
|
||||||
|
response.send(fs.readFileSync(skillFilePath, "utf8"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
response.type("text/markdown");
|
response.send(fallbackSkillMarkdown);
|
||||||
response.sendFile(skillFilePath);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use("/docs", swaggerUi.serve, swaggerUi.setup(openApiDocument));
|
app.use("/docs", swaggerUi.serve, swaggerUi.setup(openApiDocument));
|
||||||
|
|||||||
Reference in New Issue
Block a user