Răsfoiți Sursa

API doc generation! (#280)

Co-authored-by: trgwii <trgwii@hotmail.com>
Pouria Ezzati 6 ani în urmă
părinte
comite
2c261ad4af
6 a modificat fișierele cu 1288 adăugiri și 97 ștergeri
  1. 3 1
      .gitignore
  2. 529 0
      docs/api/api.ts
  3. 48 0
      docs/api/generate.ts
  4. 697 90
      package-lock.json
  5. 3 1
      package.json
  6. 8 5
      tsconfig.json

+ 3 - 1
.gitignore

@@ -8,4 +8,6 @@ server/config.js
 server/old.config.js
 production-server
 .idea/
-dump.rdb
+dump.rdb
+docs/api/*.js
+docs/api/static

+ 529 - 0
docs/api/api.ts

@@ -0,0 +1,529 @@
+import * as p from '../../package.json';
+
+export default {
+	openapi: '3.0.0',
+	info: {
+		title: "Kutt.it",
+		description: "API referrence for [http://kutt.it](http://kutt.it).\n",
+		version: p.version
+	},
+	servers: [{
+		url: "https://kutt.it/api/v2"
+	}],
+	tags: [{
+		name: "health"
+	}, {
+		name: "links"
+	}, {
+		name: "domains"
+	}, {
+		name: "users"
+	}],
+	paths: {
+		'/health': {
+			get: {
+				tags: ["health"],
+				summary: "API health",
+				responses: {
+					200: {
+						description: "Health",
+						content: {
+							'text/html': {
+								example: "OK"
+							}
+						}
+					}
+				}
+			}
+		},
+		'/links': {
+			get: {
+				tags: ["links"],
+				description: "Get list of links",
+				parameters: [{
+					name: "limit",
+					in: "query",
+					description: "Limit",
+					required: false,
+					style: "form",
+					explode: true,
+					schema: {
+						type: "number",
+						example: 10
+					}
+				}, {
+					name: "skip",
+					in: "query",
+					description: "Skip",
+					required: false,
+					style: "form",
+					explode: true,
+					schema: {
+						type: "number",
+						example: 0
+					}
+				}, {
+					name: "all",
+					in: "query",
+					description: "All links (ADMIN only)",
+					required: false,
+					style: "form",
+					explode: true,
+					schema: {
+						type: "boolean",
+						example: false
+					}
+				}],
+				responses: {
+					200: {
+						description: "List of links",
+						content: {
+							'application/json': {
+								schema: {
+									$ref: "#/components/schemas/inline_response_200"
+								}
+							}
+						}
+					}
+				},
+				security: [{
+					APIKeyAuth: []
+				}]
+			},
+			post: {
+				tags: ["links"],
+				description: "Create a short link",
+				requestBody: {
+					content: {
+						'application/json': {
+							schema: {
+								$ref: "#/components/schemas/body"
+							}
+						}
+					}
+				},
+				responses: {
+					200: {
+						description: "Craeted link",
+						content: {
+							'application/json': {
+								schema: {
+									$ref: "#/components/schemas/Link"
+								}
+							}
+						}
+					}
+				},
+				security: [{
+					APIKeyAuth: []
+				}]
+			}
+		},
+		'/links/{id}': {
+			delete: {
+				tags: ["links"],
+				description: "Delete a link",
+				parameters: [{
+					name: "id",
+					in: "path",
+					required: true,
+					style: "simple",
+					explode: false,
+					schema: {
+						type: "string",
+						format: "uuid"
+					}
+				}],
+				responses: {
+					200: {
+						description: "Deleted link successfully",
+						content: {
+							'application/json': {
+								schema: {
+									$ref: "#/components/schemas/inline_response_200_1"
+								}
+							}
+						}
+					}
+				},
+				security: [{
+					APIKeyAuth: []
+				}]
+			}
+		},
+		'/links/{id}/stats': {
+			get: {
+				tags: ["links"],
+				description: "Get link stats",
+				parameters: [{
+					name: "id",
+					in: "path",
+					required: true,
+					style: "simple",
+					explode: false,
+					schema: {
+						type: "string",
+						format: "uuid"
+					}
+				}],
+				responses: {
+					200: {
+						description: "Link stats",
+						content: {
+							'application/json': {
+								schema: {
+									$ref: "#/components/schemas/Stats"
+								}
+							}
+						}
+					}
+				},
+				security: [{
+					APIKeyAuth: []
+				}]
+			}
+		},
+		'/domains': {
+			post: {
+				tags: ["domains"],
+				description: "Create a domain",
+				requestBody: {
+					content: {
+						'application/json': {
+							schema: {
+								$ref: "#/components/schemas/body_1"
+							}
+						}
+					}
+				},
+				responses: {
+					200: {
+						description: "Created domain",
+						content: {
+							'application/json': {
+								schema: {
+									$ref: "#/components/schemas/Domain"
+								}
+							}
+						}
+					}
+				},
+				security: [{
+					APIKeyAuth: []
+				}]
+			}
+		},
+		'/domains/{id}': {
+			delete: {
+				tags: ["domains"],
+				description: "Delete a domain",
+				parameters: [{
+					name: "id",
+					in: "path",
+					required: true,
+					style: "simple",
+					explode: false,
+					schema: {
+						type: "string",
+						format: "uuid"
+					}
+				}],
+				responses: {
+					200: {
+						description: "Deleted domain successfully",
+						content: {
+							'application/json': {
+								schema: {
+									$ref: "#/components/schemas/inline_response_200_1"
+								}
+							}
+						}
+					}
+				},
+				security: [{
+					APIKeyAuth: []
+				}]
+			}
+		},
+		'/users': {
+			get: {
+				tags: ["users"],
+				description: "Get user info",
+				responses: {
+					200: {
+						description: "User info",
+						content: {
+							'application/json': {
+								schema: {
+									$ref: "#/components/schemas/User"
+								}
+							}
+						}
+					}
+				},
+				security: [{
+					APIKeyAuth: []
+				}]
+			}
+		}
+	},
+	components: {
+		schemas: {
+			Link: {
+				type: "object",
+				properties: {
+					address: {
+						type: "string"
+					},
+					banned: {
+						type: "boolean",
+						default: false
+					},
+					created_at: {
+						type: "string",
+						format: "date-time"
+					},
+					id: {
+						type: "string",
+						format: "uuid"
+					},
+					link: {
+						type: "string"
+					},
+					password: {
+						type: "boolean",
+						default: false
+					},
+					target: {
+						type: "string"
+					},
+					updated_at: {
+						type: "string",
+						format: "date-time"
+					},
+					visit_count: {
+						type: "number"
+					}
+				}
+			},
+			Domain: {
+				type: "object",
+				properties: {
+					address: {
+						type: "string"
+					},
+					banned: {
+						type: "boolean",
+						default: false
+					},
+					created_at: {
+						type: "string",
+						format: "date-time"
+					},
+					id: {
+						type: "string",
+						format: "uuid"
+					},
+					homepage: {
+						type: "string"
+					},
+					updated_at: {
+						type: "string",
+						format: "date-time"
+					}
+				}
+			},
+			User: {
+				type: "object",
+				properties: {
+					apikey: {
+						type: "string"
+					},
+					email: {
+						type: "string"
+					},
+					domains: {
+						type: "array",
+						items: {
+							$ref: "#/components/schemas/Domain"
+						}
+					}
+				}
+			},
+			StatsItem: {
+				type: "object",
+				properties: {
+					stats: {
+						$ref: "#/components/schemas/StatsItem_stats"
+					},
+					views: {
+						type: "array",
+						items: {
+							type: "number"
+						}
+					}
+				}
+			},
+			Stats: {
+				type: "object",
+				properties: {
+					allTime: {
+						$ref: "#/components/schemas/StatsItem"
+					},
+					lastDay: {
+						$ref: "#/components/schemas/StatsItem"
+					},
+					lastMonth: {
+						$ref: "#/components/schemas/StatsItem"
+					},
+					lastWeek: {
+						$ref: "#/components/schemas/StatsItem"
+					},
+					updatedAt: {
+						type: "string"
+					},
+					address: {
+						type: "string"
+					},
+					banned: {
+						type: "boolean",
+						default: false
+					},
+					created_at: {
+						type: "string",
+						format: "date-time"
+					},
+					id: {
+						type: "string",
+						format: "uuid"
+					},
+					link: {
+						type: "string"
+					},
+					password: {
+						type: "boolean",
+						default: false
+					},
+					target: {
+						type: "string"
+					},
+					updated_at: {
+						type: "string",
+						format: "date-time"
+					},
+					visit_count: {
+						type: "number"
+					}
+				}
+			},
+			inline_response_200: {
+				properties: {
+					limit: {
+						type: "number",
+						default: 10
+					},
+					skip: {
+						type: "number",
+						default: 0
+					},
+					total: {
+						type: "number",
+						default: 0
+					},
+					data: {
+						type: "array",
+						items: {
+							$ref: "#/components/schemas/Link"
+						}
+					}
+				}
+			},
+			body: {
+				required: ["target"],
+				properties: {
+					target: {
+						type: "string"
+					},
+					password: {
+						type: "string"
+					},
+					customurl: {
+						type: "string"
+					},
+					reuse: {
+						type: "boolean",
+						default: false
+					},
+					domain: {
+						type: "string"
+					}
+				}
+			},
+			inline_response_200_1: {
+				properties: {
+					message: {
+						type: "string"
+					}
+				}
+			},
+			body_1: {
+				required: ["address"],
+				properties: {
+					address: {
+						type: "string"
+					},
+					homepage: {
+						type: "string"
+					}
+				}
+			},
+			StatsItem_stats_browser: {
+				type: "object",
+				properties: {
+					name: {
+						type: "string"
+					},
+					value: {
+						type: "number"
+					}
+				}
+			},
+			StatsItem_stats: {
+				type: "object",
+				properties: {
+					browser: {
+						type: "array",
+						items: {
+							$ref: "#/components/schemas/StatsItem_stats_browser"
+						}
+					},
+					os: {
+						type: "array",
+						items: {
+							$ref: "#/components/schemas/StatsItem_stats_browser"
+						}
+					},
+					country: {
+						type: "array",
+						items: {
+							$ref: "#/components/schemas/StatsItem_stats_browser"
+						}
+					},
+					referrer: {
+						type: "array",
+						items: {
+							$ref: "#/components/schemas/StatsItem_stats_browser"
+						}
+					}
+				}
+			}
+		},
+		securitySchemes: {
+			APIKeyAuth: {
+				type: "apiKey",
+				name: "X-API-KEY",
+				in: "header"
+			}
+		}
+	}
+};

+ 48 - 0
docs/api/generate.ts

@@ -0,0 +1,48 @@
+import { join, dirname } from 'path';
+
+import { promises as fs } from 'fs';
+
+import api from './api';
+
+const Template = (output, { api, title, redoc }) =>
+	fs.writeFile(output,
+`<DOCTYPE html>
+<html>
+	<head>
+		<meta charset="UTF-8" />
+		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+		<meta http-equiv="X-UA-Compatible" content="ie=edge" />
+		<title>${title}</title>
+	</head>
+	<body>
+		<redoc spec-url="${api}" />
+		<script src="${redoc}"></script>
+	</body>
+</html>
+`);
+
+const Api = output =>
+	fs.writeFile(output, JSON.stringify(api));
+
+const Redoc = output =>
+	fs.copyFile(join(
+		dirname(require.resolve('redoc')), 
+		'redoc.standalone.js'),
+		output);
+
+export default (async () => {
+	const out = join(__dirname, 'static');
+	const apiFile = 'api.json';
+	const redocFile = 'redoc.js';
+	await fs.mkdir(out, { recursive: true });
+	return Promise.all([
+		Api(join(out, apiFile)),
+		Redoc(join(out, redocFile)),
+		Template(join(out, 'index.html'), {
+			api: apiFile,
+			title: api.info.title,
+			redoc: redocFile
+		}),
+
+	]);
+})();

Fișier diff suprimat deoarece este prea mare
+ 697 - 90
package-lock.json


+ 3 - 1
package.json

@@ -12,7 +12,8 @@
     "start": "NODE_ENV=production node production-server/server.js",
     "migrate": "knex migrate:up --env production",
     "lint": "eslint server/ --ext .js,.ts --fix",
-    "lint:nofix": "eslint server/ --ext .js,.ts"
+    "lint:nofix": "eslint server/ --ext .js,.ts",
+    "docs:build": "cd docs/api && tsc generate.ts --resolveJsonModule && node generate && cd ../.."
   },
   "husky": {
     "hooks": {
@@ -147,6 +148,7 @@
     "nock": "^9.3.3",
     "nodemon": "^1.19.4",
     "prettier": "^1.19.1",
+    "redoc": "^2.0.0-rc.20",
     "rimraf": "^3.0.0",
     "sinon": "^6.0.0",
     "typescript": "^3.7.5"

+ 8 - 5
tsconfig.json

@@ -1,5 +1,5 @@
 {
-  "compilerOptions": {
+	"compilerOptions": {
 		"target": "es2018",
 		"module": "commonjs",
 		"sourceMap": true,
@@ -8,9 +8,12 @@
 		"resolveJsonModule": true,
 		"esModuleInterop": true,
 		"noEmit": false,
-    "emitDecoratorMetadata": true,
-    "experimentalDecorators": true,		
+		"emitDecoratorMetadata": true,
+		"experimentalDecorators": true,
 		"strict": false
-  },
-	"include": ["global.d.ts", "server"]
+	},
+	"include": [
+		"global.d.ts",
+		"server"
+	]
 }

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff