storybooker 0.19.3 → 0.22.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/README.md +40 -18
  2. package/dist/adapters/_internal/queue.d.mts +127 -0
  3. package/dist/aws-dynamodb.d.mts +22 -0
  4. package/dist/aws-dynamodb.mjs +118 -0
  5. package/dist/aws-dynamodb.mjs.map +1 -0
  6. package/dist/aws-s3.d.mts +20 -0
  7. package/dist/aws-s3.mjs +96 -0
  8. package/dist/aws-s3.mjs.map +1 -0
  9. package/dist/azure-blob-storage.d.mts +20 -0
  10. package/dist/azure-blob-storage.mjs +126 -0
  11. package/dist/azure-blob-storage.mjs.map +1 -0
  12. package/dist/azure-cosmos-db.d.mts +23 -0
  13. package/dist/azure-cosmos-db.mjs +87 -0
  14. package/dist/azure-cosmos-db.mjs.map +1 -0
  15. package/dist/azure-data-tables.d.mts +23 -0
  16. package/dist/azure-data-tables.mjs +127 -0
  17. package/dist/azure-data-tables.mjs.map +1 -0
  18. package/dist/azure-easy-auth.d.mts +50 -0
  19. package/dist/azure-easy-auth.mjs +88 -0
  20. package/dist/azure-easy-auth.mjs.map +1 -0
  21. package/dist/azure-functions.d.mts +62 -0
  22. package/dist/azure-functions.mjs +147 -0
  23. package/dist/azure-functions.mjs.map +1 -0
  24. package/dist/fs.d.mts +37 -0
  25. package/dist/fs.mjs +240 -0
  26. package/dist/fs.mjs.map +1 -0
  27. package/dist/gcp-big-table.d.mts +23 -0
  28. package/dist/gcp-big-table.mjs +92 -0
  29. package/dist/gcp-big-table.mjs.map +1 -0
  30. package/dist/gcp-firestore.d.mts +22 -0
  31. package/dist/gcp-firestore.mjs +87 -0
  32. package/dist/gcp-firestore.mjs.map +1 -0
  33. package/dist/gcp-storage.d.mts +20 -0
  34. package/dist/gcp-storage.mjs +96 -0
  35. package/dist/gcp-storage.mjs.map +1 -0
  36. package/dist/handlers/handle-process-zip.mjs +90 -0
  37. package/dist/handlers/handle-process-zip.mjs.map +1 -0
  38. package/dist/handlers/handle-purge.d.mts +12 -0
  39. package/dist/handlers/handle-purge.mjs +36 -0
  40. package/dist/handlers/handle-purge.mjs.map +1 -0
  41. package/dist/handlers/handle-serve-storybook.mjs +94 -0
  42. package/dist/handlers/handle-serve-storybook.mjs.map +1 -0
  43. package/dist/index.d.mts +28 -0
  44. package/dist/index.mjs +62 -0
  45. package/dist/index.mjs.map +1 -0
  46. package/dist/models/builds-model.mjs +248 -0
  47. package/dist/models/builds-model.mjs.map +1 -0
  48. package/dist/models/builds-schema.d.mts +171 -0
  49. package/dist/models/builds-schema.mjs +67 -0
  50. package/dist/models/builds-schema.mjs.map +1 -0
  51. package/dist/models/projects-model.mjs +122 -0
  52. package/dist/models/projects-model.mjs.map +1 -0
  53. package/dist/models/projects-schema.d.mts +70 -0
  54. package/dist/models/projects-schema.mjs +37 -0
  55. package/dist/models/projects-schema.mjs.map +1 -0
  56. package/dist/models/tags-model.mjs +110 -0
  57. package/dist/models/tags-model.mjs.map +1 -0
  58. package/dist/models/tags-schema.d.mts +76 -0
  59. package/dist/models/tags-schema.mjs +34 -0
  60. package/dist/models/tags-schema.mjs.map +1 -0
  61. package/dist/models/~model.mjs +43 -0
  62. package/dist/models/~model.mjs.map +1 -0
  63. package/dist/models/~shared-schema.d.mts +1 -0
  64. package/dist/models/~shared-schema.mjs +20 -0
  65. package/dist/models/~shared-schema.mjs.map +1 -0
  66. package/dist/mysql.d.mts +39 -0
  67. package/dist/mysql.mjs +151 -0
  68. package/dist/mysql.mjs.map +1 -0
  69. package/dist/redis.d.mts +33 -0
  70. package/dist/redis.mjs +118 -0
  71. package/dist/redis.mjs.map +1 -0
  72. package/dist/routers/account-router.mjs +91 -0
  73. package/dist/routers/account-router.mjs.map +1 -0
  74. package/dist/routers/builds-router.mjs +347 -0
  75. package/dist/routers/builds-router.mjs.map +1 -0
  76. package/dist/routers/projects-router.mjs +236 -0
  77. package/dist/routers/projects-router.mjs.map +1 -0
  78. package/dist/routers/root-router.mjs +108 -0
  79. package/dist/routers/root-router.mjs.map +1 -0
  80. package/dist/routers/tags-router.mjs +269 -0
  81. package/dist/routers/tags-router.mjs.map +1 -0
  82. package/dist/routers/tasks-router.mjs +71 -0
  83. package/dist/routers/tasks-router.mjs.map +1 -0
  84. package/dist/urls.d.mts +47 -0
  85. package/dist/urls.mjs +208 -0
  86. package/dist/urls.mjs.map +1 -0
  87. package/dist/utils/adapter-utils.d.mts +14 -0
  88. package/dist/utils/adapter-utils.mjs +14 -0
  89. package/dist/utils/adapter-utils.mjs.map +1 -0
  90. package/dist/utils/auth.mjs +25 -0
  91. package/dist/utils/auth.mjs.map +1 -0
  92. package/dist/utils/error.d.mts +21 -0
  93. package/dist/utils/error.mjs +109 -0
  94. package/dist/utils/error.mjs.map +1 -0
  95. package/dist/utils/file-utils.mjs +16 -0
  96. package/dist/utils/file-utils.mjs.map +1 -0
  97. package/dist/utils/openapi-utils.mjs +45 -0
  98. package/dist/utils/openapi-utils.mjs.map +1 -0
  99. package/dist/utils/request.mjs +35 -0
  100. package/dist/utils/request.mjs.map +1 -0
  101. package/dist/utils/response.mjs +24 -0
  102. package/dist/utils/response.mjs.map +1 -0
  103. package/dist/utils/store.mjs +54 -0
  104. package/dist/utils/store.mjs.map +1 -0
  105. package/dist/utils/ui-utils.mjs +38 -0
  106. package/dist/utils/ui-utils.mjs.map +1 -0
  107. package/dist/utils/url-utils.d.mts +10 -0
  108. package/dist/utils/url-utils.mjs +54 -0
  109. package/dist/utils/url-utils.mjs.map +1 -0
  110. package/dist/~internal/adapter/auth.d.mts +123 -0
  111. package/dist/~internal/adapter/auth.mjs +20 -0
  112. package/dist/~internal/adapter/auth.mjs.map +1 -0
  113. package/dist/~internal/adapter/database.d.mts +240 -0
  114. package/dist/~internal/adapter/database.mjs +63 -0
  115. package/dist/~internal/adapter/database.mjs.map +1 -0
  116. package/dist/~internal/adapter/logger.d.mts +34 -0
  117. package/dist/~internal/adapter/logger.mjs +13 -0
  118. package/dist/~internal/adapter/logger.mjs.map +1 -0
  119. package/dist/~internal/adapter/storage.d.mts +208 -0
  120. package/dist/~internal/adapter/storage.mjs +63 -0
  121. package/dist/~internal/adapter/storage.mjs.map +1 -0
  122. package/dist/~internal/adapter/ui.d.mts +109 -0
  123. package/dist/~internal/adapter/ui.mjs +1 -0
  124. package/dist/~internal/adapter.d.mts +8 -0
  125. package/dist/~internal/adapter.mjs +6 -0
  126. package/dist/~internal/constants.d.mts +24 -0
  127. package/dist/~internal/constants.mjs +32 -0
  128. package/dist/~internal/constants.mjs.map +1 -0
  129. package/dist/~internal/mimes.d.mts +449 -0
  130. package/dist/~internal/mimes.mjs +454 -0
  131. package/dist/~internal/mimes.mjs.map +1 -0
  132. package/dist/~internal/router.d.mts +1651 -0
  133. package/dist/~internal/router.mjs +39 -0
  134. package/dist/~internal/router.mjs.map +1 -0
  135. package/dist/~internal/types.d.mts +77 -0
  136. package/dist/~internal/types.mjs +1 -0
  137. package/dist/~internal/utils.d.mts +4 -0
  138. package/dist/~internal/utils.mjs +5 -0
  139. package/openapi.json +3162 -0
  140. package/package.json +148 -27
  141. package/src/adapters/_internal/auth.ts +135 -0
  142. package/src/adapters/_internal/database.ts +241 -0
  143. package/src/adapters/_internal/index.ts +8 -0
  144. package/src/adapters/_internal/logger.ts +41 -0
  145. package/src/adapters/_internal/queue.ts +151 -0
  146. package/src/adapters/_internal/storage.ts +197 -0
  147. package/src/adapters/_internal/ui.ts +103 -0
  148. package/src/adapters/aws-dynamodb.ts +201 -0
  149. package/src/adapters/aws-s3.ts +160 -0
  150. package/src/adapters/azure-blob-storage.ts +223 -0
  151. package/src/adapters/azure-cosmos-db.ts +158 -0
  152. package/src/adapters/azure-data-tables.ts +223 -0
  153. package/src/adapters/azure-easy-auth.ts +174 -0
  154. package/src/adapters/azure-functions.ts +242 -0
  155. package/src/adapters/fs.ts +398 -0
  156. package/src/adapters/gcp-big-table.ts +157 -0
  157. package/src/adapters/gcp-firestore.ts +146 -0
  158. package/src/adapters/gcp-storage.ts +141 -0
  159. package/src/adapters/mysql.ts +296 -0
  160. package/src/adapters/redis.ts +242 -0
  161. package/src/handlers/handle-process-zip.ts +117 -0
  162. package/src/handlers/handle-purge.ts +65 -0
  163. package/src/handlers/handle-serve-storybook.ts +101 -0
  164. package/src/index.ts +81 -16
  165. package/src/mocks/mock-auth-service.ts +51 -0
  166. package/src/mocks/mock-store.ts +26 -0
  167. package/src/models/builds-model.ts +373 -0
  168. package/src/models/builds-schema.ts +84 -0
  169. package/src/models/projects-model.ts +177 -0
  170. package/src/models/projects-schema.ts +69 -0
  171. package/src/models/tags-model.ts +138 -0
  172. package/src/models/tags-schema.ts +45 -0
  173. package/src/models/~model.ts +79 -0
  174. package/src/models/~shared-schema.ts +14 -0
  175. package/src/routers/_app-router.ts +57 -0
  176. package/src/routers/account-router.ts +136 -0
  177. package/src/routers/builds-router.ts +464 -0
  178. package/src/routers/projects-router.ts +309 -0
  179. package/src/routers/root-router.ts +127 -0
  180. package/src/routers/tags-router.ts +339 -0
  181. package/src/routers/tasks-router.ts +75 -0
  182. package/src/types.ts +107 -0
  183. package/src/urls.ts +327 -0
  184. package/src/utils/adapter-utils.ts +26 -0
  185. package/src/utils/auth.test.ts +71 -0
  186. package/src/utils/auth.ts +39 -0
  187. package/src/utils/constants.ts +31 -0
  188. package/src/utils/date-utils.ts +10 -0
  189. package/src/utils/error.test.ts +86 -0
  190. package/src/utils/error.ts +140 -0
  191. package/src/utils/file-utils.test.ts +65 -0
  192. package/src/utils/file-utils.ts +43 -0
  193. package/src/utils/index.ts +3 -0
  194. package/src/utils/mime-utils.ts +457 -0
  195. package/src/utils/openapi-utils.ts +49 -0
  196. package/src/utils/request.ts +97 -0
  197. package/src/utils/response.ts +20 -0
  198. package/src/utils/store.ts +85 -0
  199. package/src/utils/story-utils.ts +42 -0
  200. package/src/utils/text-utils.ts +10 -0
  201. package/src/utils/ui-utils.ts +57 -0
  202. package/src/utils/url-utils.ts +113 -0
  203. package/dist/index.js +0 -554
  204. package/src/commands/create.ts +0 -263
  205. package/src/commands/purge.ts +0 -70
  206. package/src/commands/test.ts +0 -42
  207. package/src/service-schema.d.ts +0 -2023
  208. package/src/utils/auth-utils.ts +0 -31
  209. package/src/utils/pkg-utils.ts +0 -37
  210. package/src/utils/sb-build.ts +0 -55
  211. package/src/utils/sb-test.ts +0 -115
  212. package/src/utils/schema-utils.ts +0 -123
  213. package/src/utils/stream-utils.ts +0 -72
  214. package/src/utils/types.ts +0 -4
  215. package/src/utils/zip.ts +0 -77
@@ -0,0 +1,138 @@
1
+ import { HTTPException } from "hono/http-exception";
2
+ import type { StoryBookerPermissionAction } from "../adapters/_internal/auth.ts";
3
+ import { generateDatabaseCollectionId } from "../utils/adapter-utils.ts";
4
+ import { checkAuthorisation } from "../utils/auth.ts";
5
+ import { BuildsModel } from "./builds-model.ts";
6
+ import { ProjectsModel } from "./projects-model.ts";
7
+ import { TagSchema, type TagCreateType, type TagType, type TagUpdateType } from "./tags-schema.ts";
8
+ import { Model, type BaseModel, type ListOptions } from "./~model.ts";
9
+
10
+ export class TagsModel extends Model<TagType> {
11
+ constructor(projectId: string) {
12
+ super(projectId, generateDatabaseCollectionId(projectId, "Tags"));
13
+ }
14
+
15
+ async list(options: ListOptions<TagType> = {}): Promise<TagType[]> {
16
+ this.log("List tags...");
17
+
18
+ const items = await this.database.listDocuments(this.collectionId, options, this.dbOptions);
19
+
20
+ return TagSchema.array().parse(items);
21
+ }
22
+
23
+ async create(data: TagCreateType, withBuild = false): Promise<TagType> {
24
+ this.log("Create tag '%s'...", data.value);
25
+ const id = TagsModel.createId(data.value);
26
+
27
+ try {
28
+ if (await this.has(id)) {
29
+ throw new HTTPException(409, {
30
+ message: `Tag '${id}' already exists.`,
31
+ });
32
+ }
33
+
34
+ const now = new Date().toISOString();
35
+ const tag: TagType = {
36
+ ...data,
37
+ buildsCount: withBuild ? 1 : 0,
38
+ createdAt: now,
39
+ id: id,
40
+ updatedAt: now,
41
+ };
42
+ await this.database.createDocument(this.collectionId, tag, this.dbOptions);
43
+
44
+ return tag;
45
+ } catch (error) {
46
+ throw new HTTPException(500, {
47
+ cause: error,
48
+ message: `Failed to create tag '${id}'.`,
49
+ });
50
+ }
51
+ }
52
+
53
+ async get(id: string): Promise<TagType> {
54
+ this.log("Get tag '%s'...", id);
55
+
56
+ const item = await this.database.getDocument(this.collectionId, id, this.dbOptions);
57
+
58
+ return TagSchema.parse(item);
59
+ }
60
+
61
+ async has(id: string): Promise<boolean> {
62
+ this.log("Check tag '%s'...", id);
63
+ try {
64
+ return await this.database.hasDocument(this.collectionId, id, this.dbOptions);
65
+ } catch {
66
+ return false;
67
+ }
68
+ }
69
+
70
+ async update(id: string, data: TagUpdateType): Promise<void> {
71
+ this.log("Update tag '%s'...", id);
72
+
73
+ await this.database.updateDocument(
74
+ this.collectionId,
75
+ id,
76
+ { ...data, updatedAt: new Date().toISOString() },
77
+ this.dbOptions,
78
+ );
79
+ }
80
+
81
+ async delete(id: string): Promise<void> {
82
+ this.log("Delete tag '%s'...", id);
83
+
84
+ const { gitHubDefaultBranch } = await new ProjectsModel().get(this.projectId);
85
+ if (id === TagsModel.createId(gitHubDefaultBranch)) {
86
+ const message = `Cannot delete the tag associated with default branch (${gitHubDefaultBranch}) of the project '${this.projectId}'.`;
87
+ this.error(message);
88
+ throw new Error(message);
89
+ }
90
+
91
+ await this.database.deleteDocument(this.collectionId, id, this.dbOptions);
92
+
93
+ try {
94
+ this.debug("Delete builds associated with tag '%s'...", id);
95
+ await new BuildsModel(this.projectId).deleteByTag(id, false);
96
+ } catch (error) {
97
+ this.error("Error deleting builds associated with tag:", error);
98
+ }
99
+ }
100
+
101
+ checkAuth = (action: StoryBookerPermissionAction): boolean => {
102
+ return checkAuthorisation({
103
+ action,
104
+ projectId: this.projectId,
105
+ resource: "tag",
106
+ });
107
+ };
108
+
109
+ id: BaseModel<TagType>["id"] = (id: string) => {
110
+ return {
111
+ checkAuth: (action) =>
112
+ checkAuthorisation({
113
+ action,
114
+ projectId: this.projectId,
115
+ resource: "tag",
116
+ }),
117
+ delete: this.delete.bind(this, id),
118
+ get: this.get.bind(this, id),
119
+ has: this.has.bind(this, id),
120
+ id,
121
+ update: this.update.bind(this, id),
122
+ };
123
+ };
124
+
125
+ static createId(value: string): string {
126
+ return value.trim().toLowerCase().replace(/\W+/, "-");
127
+ }
128
+
129
+ static guessType(id: string): TagType["type"] {
130
+ if (/^\d+$/.test(id)) {
131
+ return "pr";
132
+ }
133
+ if (/^\w+-\d+$/.test(id)) {
134
+ return "jira";
135
+ }
136
+ return "branch";
137
+ }
138
+ }
@@ -0,0 +1,45 @@
1
+ import { z } from "@hono/zod-openapi";
2
+ import { TagTypes } from "../utils/constants.ts";
3
+ import { BuildIdSchema, TagIdSchema } from "./~shared-schema.ts";
4
+
5
+ export { TagIdSchema, TagTypes };
6
+
7
+ export type TagVariant = (typeof TagTypes)[number];
8
+
9
+ export type TagType = z.infer<typeof TagSchema>;
10
+ export const TagSchema = z
11
+ .object({
12
+ buildsCount: z.number().default(0),
13
+ createdAt: z.iso.datetime().default(new Date().toISOString()),
14
+ id: TagIdSchema,
15
+ latestBuildId: z.union([BuildIdSchema.optional(), z.literal("")]),
16
+ type: z.enum(TagTypes),
17
+ updatedAt: z.iso.datetime().default(new Date().toISOString()),
18
+ value: z.string().meta({ description: "The value of the tag." }),
19
+ })
20
+ .meta({ id: "Tag", title: "StoryBooker Tag" });
21
+
22
+ export type TagCreateType = z.infer<typeof TagCreateSchema>;
23
+ export const TagCreateSchema = TagSchema.omit({
24
+ buildsCount: true,
25
+ createdAt: true,
26
+ id: true,
27
+
28
+ updatedAt: true,
29
+ });
30
+
31
+ export type TagUpdateType = z.infer<typeof TagUpdateSchema>;
32
+ export const TagUpdateSchema = TagSchema.omit({
33
+ createdAt: true,
34
+ id: true,
35
+ updatedAt: true,
36
+ }).partial();
37
+
38
+ export type TagsListResultType = z.infer<typeof TagsListResultSchema>;
39
+ export const TagsListResultSchema = z.object({
40
+ tags: TagSchema.array(),
41
+ });
42
+ export type TagsGetResultType = z.infer<typeof TagsGetResultSchema>;
43
+ export const TagsGetResultSchema = z.object({
44
+ tag: TagSchema,
45
+ });
@@ -0,0 +1,79 @@
1
+ import type {
2
+ DatabaseAdapter,
3
+ DatabaseAdapterOptions,
4
+ LoggerAdapter,
5
+ StorageAdapter,
6
+ StorageAdapterOptions,
7
+ StoryBookerDatabaseDocument,
8
+ StoryBookerPermissionAction,
9
+ } from "../adapters/_internal/index.ts";
10
+ import { parseErrorMessage } from "../utils/error.ts";
11
+ import { getStore } from "../utils/store.ts";
12
+
13
+ export interface ListOptions<Item extends Record<string, unknown>> {
14
+ limit?: number;
15
+ filter?: string | ((item: Item) => boolean);
16
+ select?: string[];
17
+ sort?: "latest" | ((item1: Item, item2: Item) => number);
18
+ }
19
+
20
+ export abstract class Model<Data extends StoryBookerDatabaseDocument> implements BaseModel<Data> {
21
+ projectId: string;
22
+ collectionId: string;
23
+ database: DatabaseAdapter<Data>;
24
+ storage: StorageAdapter;
25
+ logger: LoggerAdapter;
26
+ dbOptions: DatabaseAdapterOptions;
27
+ storageOptions: StorageAdapterOptions;
28
+
29
+ constructor(projectId: string | null, collectionId: string) {
30
+ const { abortSignal, database, storage, logger } = getStore();
31
+ this.projectId = projectId ?? "";
32
+ this.collectionId = collectionId;
33
+ this.database = database as unknown as DatabaseAdapter<Data>;
34
+ this.storage = storage;
35
+ this.logger = logger;
36
+ this.dbOptions = { abortSignal, logger };
37
+ this.storageOptions = { abortSignal, logger };
38
+ }
39
+
40
+ log(message: string, ...args: unknown[]): void {
41
+ this.logger.log(`[Model:${this.projectId || "All"}] ${message}`, ...args);
42
+ }
43
+ debug(message: string, ...args: unknown[]): void {
44
+ this.logger.debug?.(`[Model:${this.projectId || "All"}] ${message}`, ...args);
45
+ }
46
+ error(error: unknown, ...args: unknown[]): void {
47
+ const { errorMessage } = parseErrorMessage(error);
48
+ this.logger.error(`[Model:${this.projectId || "All"}] Error:`, errorMessage, ...args);
49
+ }
50
+
51
+ abstract list(options?: ListOptions<Data>): Promise<Data[]>;
52
+ abstract create(data: unknown): Promise<Data>;
53
+ abstract get(id: string): Promise<Data>;
54
+ abstract has(id: string): Promise<boolean>;
55
+ abstract update(id: string, data: unknown): Promise<void>;
56
+ abstract delete(id: string): Promise<void>;
57
+ abstract id: (id: string) => BaseIdModel<Data>;
58
+ abstract checkAuth(action: StoryBookerPermissionAction): boolean;
59
+ }
60
+
61
+ export interface BaseModel<Data extends StoryBookerDatabaseDocument> {
62
+ list(options?: ListOptions<Data>): Promise<Data[]>;
63
+ create(data: unknown): Promise<Data>;
64
+ get(id: string): Promise<Data>;
65
+ has(id: string): Promise<boolean>;
66
+ update(id: string, data: unknown): Promise<void>;
67
+ delete(id: string): Promise<void>;
68
+ checkAuth(action: StoryBookerPermissionAction): boolean;
69
+ id: (id: string) => BaseIdModel<Data>;
70
+ }
71
+
72
+ export interface BaseIdModel<Data extends StoryBookerDatabaseDocument> {
73
+ id: string;
74
+ get(): Promise<Data>;
75
+ has(): Promise<boolean>;
76
+ update(data: unknown): Promise<void>;
77
+ delete(): Promise<void>;
78
+ checkAuth(action: StoryBookerPermissionAction): boolean;
79
+ }
@@ -0,0 +1,14 @@
1
+ import { z } from "@hono/zod-openapi";
2
+ import { PATTERNS } from "../utils/constants.ts";
3
+
4
+ export const TagIdSchema = z.string().meta({ description: "The ID of the tag.", id: "TagID" });
5
+
6
+ export const BuildIdSchema = z
7
+ .string()
8
+ .check(z.minLength(7))
9
+ .meta({ description: "The ID of the build.", id: "BuildID" });
10
+
11
+ export const ProjectIdSchema = z
12
+ .string()
13
+ .refine((val) => new RegExp(PATTERNS.projectId.pattern).test(val), PATTERNS.projectId.message)
14
+ .meta({ description: "The ID of the project.", id: "ProjectID" });
@@ -0,0 +1,57 @@
1
+ import { swaggerUI } from "@hono/swagger-ui";
2
+ import { OpenAPIHono, type OpenAPIObjectConfigure } from "@hono/zod-openapi";
3
+ import type { Context, Env } from "hono";
4
+ import { SERVICE_NAME } from "../utils/constants.ts";
5
+ import { getStore } from "../utils/store.ts";
6
+ import { createUIAdapterOptions } from "../utils/ui-utils.ts";
7
+ import { accountRouter } from "./account-router.ts";
8
+ import { buildsRouter } from "./builds-router.ts";
9
+ import { projectsRouter } from "./projects-router.ts";
10
+ import { rootRouter } from "./root-router.ts";
11
+ import { tagsRouter } from "./tags-router.ts";
12
+ import { tasksRouter } from "./tasks-router.ts";
13
+
14
+ type OpenAPIObjectConfig<Configure extends OpenAPIObjectConfigure<Env, "">> = Configure extends (
15
+ context: Context,
16
+ ) => infer Config
17
+ ? Config
18
+ : Configure;
19
+ /**
20
+ * @private
21
+ */
22
+ export const openapiConfig: OpenAPIObjectConfig<OpenAPIObjectConfigure<Env, "">> = {
23
+ openapi: "3.1.0",
24
+ info: { title: SERVICE_NAME, version: "" },
25
+ };
26
+
27
+ /**
28
+ * @private
29
+ */
30
+ export type AppRouter = typeof appRouter;
31
+
32
+ /**
33
+ * @private
34
+ */
35
+ export const appRouter = new OpenAPIHono({ strict: false })
36
+ .doc31("/openapi.json", openapiConfig)
37
+ .get("/openapi.yaml", async (ctx) => {
38
+ const spec = (appRouter as OpenAPIHono).getOpenAPI31Document(openapiConfig);
39
+ const dumpYaml = await import("js-yaml").then((mod) => mod.dump);
40
+ const content: string = dumpYaml(spec, { forceQuotes: true });
41
+ return ctx.body(content, 200, { "Content-Type": "application/yaml" });
42
+ })
43
+ .get("/openapi", swaggerUI({ url: "/openapi.json" }))
44
+ .route("/", rootRouter)
45
+ .route("/", projectsRouter)
46
+ .route("/", buildsRouter)
47
+ .route("/", tagsRouter)
48
+ .route("/tasks", tasksRouter)
49
+ .route("/account", accountRouter)
50
+ .get("/:filepath{.+}", async (context) => {
51
+ const { ui } = getStore();
52
+ if (!ui?.handleUnhandledRoute) {
53
+ return context.notFound();
54
+ }
55
+
56
+ return await ui.handleUnhandledRoute(context.req.param("filepath"), createUIAdapterOptions());
57
+ });
@@ -0,0 +1,136 @@
1
+ import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
2
+ import { HTTPException } from "hono/http-exception";
3
+ import { urlBuilder } from "../urls.ts";
4
+ import { QUERY_PARAMS } from "../utils/constants.ts";
5
+ import {
6
+ openapiCommonErrorResponses,
7
+ openapiResponseRedirect,
8
+ openapiResponsesHtml,
9
+ } from "../utils/openapi-utils.ts";
10
+ import { getStore } from "../utils/store.ts";
11
+ import { createUIResultResponse } from "../utils/ui-utils.ts";
12
+
13
+ const accountTag = "Account";
14
+
15
+ /**
16
+ * @private
17
+ */
18
+ export const accountRouter = new OpenAPIHono()
19
+ .openapi(
20
+ createRoute({
21
+ summary: "Account page",
22
+ method: "get",
23
+ path: "/",
24
+ tags: [accountTag],
25
+ responses: {
26
+ 200: {
27
+ description: "Render account page",
28
+ content: openapiResponsesHtml,
29
+ },
30
+ ...openapiCommonErrorResponses,
31
+ },
32
+ }),
33
+ async (context) => {
34
+ const { abortSignal, auth, logger, request, user, ui } = getStore();
35
+
36
+ if (!auth) {
37
+ throw new HTTPException(500, { message: "Auth is not setup" });
38
+ }
39
+
40
+ if (!user) {
41
+ const { pathname } = new URL(urlBuilder.account());
42
+ return context.redirect(urlBuilder.login(pathname), 302);
43
+ }
44
+
45
+ if (!ui?.renderAccountsPage) {
46
+ throw new HTTPException(405, { message: "UI is not available for this route." });
47
+ }
48
+
49
+ const children = await auth.renderAccountDetails?.(user, {
50
+ abortSignal,
51
+ logger,
52
+ request,
53
+ });
54
+
55
+ return createUIResultResponse(context, ui.renderAccountsPage, { children });
56
+ },
57
+ )
58
+ .openapi(
59
+ createRoute({
60
+ summary: "Login to account",
61
+ method: "get",
62
+ path: "/login",
63
+ tags: [accountTag],
64
+ request: {
65
+ query: z
66
+ .object({ [QUERY_PARAMS.redirect]: z.string() })
67
+ .partial()
68
+ .loose(),
69
+ },
70
+ responses: {
71
+ 302: openapiResponseRedirect("Login successful"),
72
+ ...openapiCommonErrorResponses,
73
+ },
74
+ }),
75
+ async (context) => {
76
+ const { abortSignal, auth, logger, request } = getStore();
77
+
78
+ if (!auth) {
79
+ throw new HTTPException(500, { message: "Auth is not setup" });
80
+ }
81
+
82
+ const response = await auth.login({ abortSignal, logger, request });
83
+
84
+ if (response.status >= 400) {
85
+ return response;
86
+ }
87
+
88
+ const { redirect = "" } = context.req.valid("query");
89
+ const location = new URL(redirect, urlBuilder.homepage());
90
+ for (const [key, value] of response.headers) {
91
+ context.res.headers.set(key, value);
92
+ }
93
+
94
+ return context.redirect(location.toString());
95
+ },
96
+ )
97
+ .openapi(
98
+ createRoute({
99
+ summary: "Logout from account",
100
+ method: "get",
101
+ path: "/logout",
102
+ tags: [accountTag],
103
+ responses: {
104
+ 302: openapiResponseRedirect("Logout successful"),
105
+ ...openapiCommonErrorResponses,
106
+ },
107
+ }),
108
+ async (context) => {
109
+ const { abortSignal, auth, logger, request, user } = getStore();
110
+ if (!auth) {
111
+ throw new HTTPException(500, { message: "Auth is not setup" });
112
+ }
113
+
114
+ if (!user) {
115
+ throw new HTTPException(401, { message: "User is not authenticated" });
116
+ }
117
+
118
+ const response = await auth.logout(user, {
119
+ abortSignal,
120
+ logger,
121
+ request,
122
+ });
123
+ if (response.status >= 400) {
124
+ return response;
125
+ }
126
+
127
+ const responseHeaders = new Headers(response.headers);
128
+ const responseLocation = responseHeaders.get("location");
129
+ responseHeaders.delete("location");
130
+ for (const [key, value] of responseHeaders) {
131
+ context.res.headers.set(key, value);
132
+ }
133
+
134
+ return context.redirect(responseLocation ?? urlBuilder.homepage());
135
+ },
136
+ );