@silexlabs/silex 3.0.0-alpha.100

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 (282) hide show
  1. package/.env.default +19 -0
  2. package/.silex.js +37 -0
  3. package/LICENSE +661 -0
  4. package/README.md +96 -0
  5. package/dist/client/client/api.d.ts +85 -0
  6. package/dist/client/client/assetUrl.d.ts +51 -0
  7. package/dist/client/client/config.d.ts +58 -0
  8. package/dist/client/client/events.d.ts +14 -0
  9. package/dist/client/client/expose.d.ts +8 -0
  10. package/dist/client/client/grapesjs/LoginDialog.d.ts +15 -0
  11. package/dist/client/client/grapesjs/PublicationManager.d.ts +72 -0
  12. package/dist/client/client/grapesjs/PublicationUi.d.ts +62 -0
  13. package/dist/client/client/grapesjs/blocks.d.ts +1 -0
  14. package/dist/client/client/grapesjs/breadcrumbs.d.ts +1 -0
  15. package/dist/client/client/grapesjs/footer.d.ts +2 -0
  16. package/dist/client/client/grapesjs/index.d.ts +5 -0
  17. package/dist/client/client/grapesjs/internal-links.d.ts +1 -0
  18. package/dist/client/client/grapesjs/new-page-dialog.d.ts +2 -0
  19. package/dist/client/client/grapesjs/openImport.d.ts +9 -0
  20. package/dist/client/client/grapesjs/page-panel.d.ts +3 -0
  21. package/dist/client/client/grapesjs/project-bar.d.ts +1 -0
  22. package/dist/client/client/grapesjs/rich-text.d.ts +1 -0
  23. package/dist/client/client/grapesjs/semantic.d.ts +1 -0
  24. package/dist/client/client/grapesjs/settings.d.ts +2 -0
  25. package/dist/client/client/grapesjs/storage.d.ts +1 -0
  26. package/dist/client/client/grapesjs/symbolDialogs.d.ts +3 -0
  27. package/dist/client/client/index.d.ts +5 -0
  28. package/dist/client/client/publication-transformers.d.ts +41 -0
  29. package/dist/client/client/utils.d.ts +2 -0
  30. package/dist/client/constants.d.ts +29 -0
  31. package/dist/client/css/admin.css +4491 -0
  32. package/dist/client/index.html +9 -0
  33. package/dist/client/js/main.js +321 -0
  34. package/dist/client/js/main.js.LICENSE.txt +24 -0
  35. package/dist/client/page.d.ts +2 -0
  36. package/dist/client/types.d.ts +334 -0
  37. package/dist/plugins/client/client/api.d.ts +85 -0
  38. package/dist/plugins/client/client/api.js +114 -0
  39. package/dist/plugins/client/client/api.js.map +1 -0
  40. package/dist/plugins/client/client/assetUrl.d.ts +51 -0
  41. package/dist/plugins/client/client/assetUrl.js +224 -0
  42. package/dist/plugins/client/client/assetUrl.js.map +1 -0
  43. package/dist/plugins/client/client/config.d.ts +58 -0
  44. package/dist/plugins/client/client/config.js +90 -0
  45. package/dist/plugins/client/client/config.js.map +1 -0
  46. package/dist/plugins/client/client/events.d.ts +14 -0
  47. package/dist/plugins/client/client/events.js +34 -0
  48. package/dist/plugins/client/client/events.js.map +1 -0
  49. package/dist/plugins/client/client/grapesjs/LoginDialog.d.ts +15 -0
  50. package/dist/plugins/client/client/grapesjs/LoginDialog.js +195 -0
  51. package/dist/plugins/client/client/grapesjs/LoginDialog.js.map +1 -0
  52. package/dist/plugins/client/client/grapesjs/PublicationManager.d.ts +72 -0
  53. package/dist/plugins/client/client/grapesjs/PublicationManager.js +356 -0
  54. package/dist/plugins/client/client/grapesjs/PublicationManager.js.map +1 -0
  55. package/dist/plugins/client/client/grapesjs/PublicationUi.d.ts +62 -0
  56. package/dist/plugins/client/client/grapesjs/PublicationUi.js +336 -0
  57. package/dist/plugins/client/client/grapesjs/PublicationUi.js.map +1 -0
  58. package/dist/plugins/client/client/grapesjs/blocks.d.ts +1 -0
  59. package/dist/plugins/client/client/grapesjs/blocks.js +37 -0
  60. package/dist/plugins/client/client/grapesjs/blocks.js.map +1 -0
  61. package/dist/plugins/client/client/grapesjs/breadcrumbs.d.ts +1 -0
  62. package/dist/plugins/client/client/grapesjs/breadcrumbs.js +75 -0
  63. package/dist/plugins/client/client/grapesjs/breadcrumbs.js.map +1 -0
  64. package/dist/plugins/client/client/grapesjs/footer.d.ts +2 -0
  65. package/dist/plugins/client/client/grapesjs/footer.js +28 -0
  66. package/dist/plugins/client/client/grapesjs/footer.js.map +1 -0
  67. package/dist/plugins/client/client/grapesjs/index.d.ts +5 -0
  68. package/dist/plugins/client/client/grapesjs/index.js +313 -0
  69. package/dist/plugins/client/client/grapesjs/index.js.map +1 -0
  70. package/dist/plugins/client/client/grapesjs/internal-links.d.ts +1 -0
  71. package/dist/plugins/client/client/grapesjs/internal-links.js +219 -0
  72. package/dist/plugins/client/client/grapesjs/internal-links.js.map +1 -0
  73. package/dist/plugins/client/client/grapesjs/new-page-dialog.d.ts +2 -0
  74. package/dist/plugins/client/client/grapesjs/new-page-dialog.js +83 -0
  75. package/dist/plugins/client/client/grapesjs/new-page-dialog.js.map +1 -0
  76. package/dist/plugins/client/client/grapesjs/openImport.d.ts +9 -0
  77. package/dist/plugins/client/client/grapesjs/openImport.js +74 -0
  78. package/dist/plugins/client/client/grapesjs/openImport.js.map +1 -0
  79. package/dist/plugins/client/client/grapesjs/page-panel.d.ts +3 -0
  80. package/dist/plugins/client/client/grapesjs/page-panel.js +113 -0
  81. package/dist/plugins/client/client/grapesjs/page-panel.js.map +1 -0
  82. package/dist/plugins/client/client/grapesjs/project-bar.d.ts +1 -0
  83. package/dist/plugins/client/client/grapesjs/project-bar.js +104 -0
  84. package/dist/plugins/client/client/grapesjs/project-bar.js.map +1 -0
  85. package/dist/plugins/client/client/grapesjs/rich-text.d.ts +1 -0
  86. package/dist/plugins/client/client/grapesjs/rich-text.js +31 -0
  87. package/dist/plugins/client/client/grapesjs/rich-text.js.map +1 -0
  88. package/dist/plugins/client/client/grapesjs/semantic.d.ts +1 -0
  89. package/dist/plugins/client/client/grapesjs/semantic.js +110 -0
  90. package/dist/plugins/client/client/grapesjs/semantic.js.map +1 -0
  91. package/dist/plugins/client/client/grapesjs/settings.d.ts +2 -0
  92. package/dist/plugins/client/client/grapesjs/settings.js +198 -0
  93. package/dist/plugins/client/client/grapesjs/settings.js.map +1 -0
  94. package/dist/plugins/client/client/grapesjs/storage.d.ts +1 -0
  95. package/dist/plugins/client/client/grapesjs/storage.js +126 -0
  96. package/dist/plugins/client/client/grapesjs/storage.js.map +1 -0
  97. package/dist/plugins/client/client/grapesjs/symbolDialogs.d.ts +3 -0
  98. package/dist/plugins/client/client/grapesjs/symbolDialogs.js +36 -0
  99. package/dist/plugins/client/client/grapesjs/symbolDialogs.js.map +1 -0
  100. package/dist/plugins/client/client/publication-transformers.d.ts +41 -0
  101. package/dist/plugins/client/client/publication-transformers.js +238 -0
  102. package/dist/plugins/client/client/publication-transformers.js.map +1 -0
  103. package/dist/plugins/client/client/utils.d.ts +2 -0
  104. package/dist/plugins/client/client/utils.js +25 -0
  105. package/dist/plugins/client/client/utils.js.map +1 -0
  106. package/dist/plugins/client/constants.d.ts +29 -0
  107. package/dist/plugins/client/constants.js +46 -0
  108. package/dist/plugins/client/constants.js.map +1 -0
  109. package/dist/plugins/client/page.d.ts +2 -0
  110. package/dist/plugins/client/page.js +31 -0
  111. package/dist/plugins/client/page.js.map +1 -0
  112. package/dist/plugins/client/plugins/client/11ty.d.ts +17 -0
  113. package/dist/plugins/client/plugins/client/11ty.js +86 -0
  114. package/dist/plugins/client/plugins/client/11ty.js.map +1 -0
  115. package/dist/plugins/client/plugins/client/publicationRenderer.d.ts +2 -0
  116. package/dist/plugins/client/plugins/client/publicationRenderer.js +170 -0
  117. package/dist/plugins/client/plugins/client/publicationRenderer.js.map +1 -0
  118. package/dist/plugins/client/plugins/client/template.d.ts +2 -0
  119. package/dist/plugins/client/plugins/client/template.js +183 -0
  120. package/dist/plugins/client/plugins/client/template.js.map +1 -0
  121. package/dist/plugins/client/plugins/client/website-info.d.ts +1 -0
  122. package/dist/plugins/client/plugins/client/website-info.js +72 -0
  123. package/dist/plugins/client/plugins/client/website-info.js.map +1 -0
  124. package/dist/plugins/client/types.d.ts +334 -0
  125. package/dist/plugins/client/types.js +74 -0
  126. package/dist/plugins/client/types.js.map +1 -0
  127. package/dist/plugins/server/constants.d.ts +29 -0
  128. package/dist/plugins/server/constants.js +49 -0
  129. package/dist/plugins/server/constants.js.map +1 -0
  130. package/dist/plugins/server/plugins/server/AuthPlugin.d.ts +1 -0
  131. package/dist/plugins/server/plugins/server/AuthPlugin.js +40 -0
  132. package/dist/plugins/server/plugins/server/AuthPlugin.js.map +1 -0
  133. package/dist/plugins/server/plugins/server/Cache.d.ts +2 -0
  134. package/dist/plugins/server/plugins/server/Cache.js +32 -0
  135. package/dist/plugins/server/plugins/server/Cache.js.map +1 -0
  136. package/dist/plugins/server/plugins/server/DownloadConnector.d.ts +29 -0
  137. package/dist/plugins/server/plugins/server/DownloadConnector.js +129 -0
  138. package/dist/plugins/server/plugins/server/DownloadConnector.js.map +1 -0
  139. package/dist/plugins/server/plugins/server/FtpConnector.d.ts +86 -0
  140. package/dist/plugins/server/plugins/server/FtpConnector.js +526 -0
  141. package/dist/plugins/server/plugins/server/FtpConnector.js.map +1 -0
  142. package/dist/plugins/server/plugins/server/GitlabConnector.d.ts +87 -0
  143. package/dist/plugins/server/plugins/server/GitlabConnector.js +564 -0
  144. package/dist/plugins/server/plugins/server/GitlabConnector.js.map +1 -0
  145. package/dist/plugins/server/plugins/server/PublicationOptimizer.d.ts +0 -0
  146. package/dist/plugins/server/plugins/server/PublicationOptimizer.js +14 -0
  147. package/dist/plugins/server/plugins/server/PublicationOptimizer.js.map +1 -0
  148. package/dist/plugins/server/plugins/server/SslPlugin.d.ts +1 -0
  149. package/dist/plugins/server/plugins/server/SslPlugin.js +86 -0
  150. package/dist/plugins/server/plugins/server/SslPlugin.js.map +1 -0
  151. package/dist/plugins/server/plugins/server/StaticPlugin.d.ts +1 -0
  152. package/dist/plugins/server/plugins/server/StaticPlugin.js +78 -0
  153. package/dist/plugins/server/plugins/server/StaticPlugin.js.map +1 -0
  154. package/dist/plugins/server/server/config.d.ts +59 -0
  155. package/dist/plugins/server/server/config.js +181 -0
  156. package/dist/plugins/server/server/config.js.map +1 -0
  157. package/dist/plugins/server/server/connectors/FsHosting.d.ts +13 -0
  158. package/dist/plugins/server/server/connectors/FsHosting.js +54 -0
  159. package/dist/plugins/server/server/connectors/FsHosting.js.map +1 -0
  160. package/dist/plugins/server/server/connectors/FsStorage.d.ts +43 -0
  161. package/dist/plugins/server/server/connectors/FsStorage.js +257 -0
  162. package/dist/plugins/server/server/connectors/FsStorage.js.map +1 -0
  163. package/dist/plugins/server/server/connectors/connectors.d.ts +87 -0
  164. package/dist/plugins/server/server/connectors/connectors.js +127 -0
  165. package/dist/plugins/server/server/connectors/connectors.js.map +1 -0
  166. package/dist/plugins/server/server/events.d.ts +26 -0
  167. package/dist/plugins/server/server/events.js +31 -0
  168. package/dist/plugins/server/server/events.js.map +1 -0
  169. package/dist/plugins/server/server/jobs.d.ts +12 -0
  170. package/dist/plugins/server/server/jobs.js +77 -0
  171. package/dist/plugins/server/server/jobs.js.map +1 -0
  172. package/dist/plugins/server/server/utils/validation.d.ts +7 -0
  173. package/dist/plugins/server/server/utils/validation.js +38 -0
  174. package/dist/plugins/server/server/utils/validation.js.map +1 -0
  175. package/dist/plugins/server/types.d.ts +334 -0
  176. package/dist/plugins/server/types.js +78 -0
  177. package/dist/plugins/server/types.js.map +1 -0
  178. package/dist/server/constants.js +29 -0
  179. package/dist/server/plugins/server/Cache.js +15 -0
  180. package/dist/server/server/api/api.js +20 -0
  181. package/dist/server/server/api/connectorApi.js +216 -0
  182. package/dist/server/server/api/publicationApi.js +108 -0
  183. package/dist/server/server/api/websiteApi.js +296 -0
  184. package/dist/server/server/cli.js +68 -0
  185. package/dist/server/server/config.js +120 -0
  186. package/dist/server/server/connectors/FsHosting.js +35 -0
  187. package/dist/server/server/connectors/FsStorage.js +225 -0
  188. package/dist/server/server/connectors/connectors.js +89 -0
  189. package/dist/server/server/events.js +14 -0
  190. package/dist/server/server/expose.js +27 -0
  191. package/dist/server/server/express.js +47 -0
  192. package/dist/server/server/index.js +90 -0
  193. package/dist/server/server/jobs.js +48 -0
  194. package/dist/server/server/utils/validation.js +15 -0
  195. package/dist/server/types.js +34 -0
  196. package/package.json +209 -0
  197. package/public/assets/container.png +0 -0
  198. package/public/assets/favicon.png +0 -0
  199. package/public/assets/logo-silex-small.png +0 -0
  200. package/public/robots.txt +2 -0
  201. package/public/silex.js +24 -0
  202. package/src/css/_mixins.scss +8 -0
  203. package/src/css/_variables.scss +33 -0
  204. package/src/css/dialog.scss +41 -0
  205. package/src/css/footer.scss +12 -0
  206. package/src/css/grapesjs-light.scss +180 -0
  207. package/src/css/grapesjs.scss +152 -0
  208. package/src/css/index.scss +32 -0
  209. package/src/css/light.scss +7 -0
  210. package/src/css/loader.scss +12 -0
  211. package/src/css/page-panel.scss +65 -0
  212. package/src/css/project-bar.scss +102 -0
  213. package/src/css/silex.scss +189 -0
  214. package/src/css/website-info.scss +14 -0
  215. package/src/html/debug.pug +8 -0
  216. package/src/html/index.pug +39 -0
  217. package/src/html/templates/includes.pug +1 -0
  218. package/src/html/templates/workspace.pug +2 -0
  219. package/src/ts/client/api.ts +144 -0
  220. package/src/ts/client/assetUrl.test.ts +125 -0
  221. package/src/ts/client/assetUrl.ts +235 -0
  222. package/src/ts/client/config.ts +105 -0
  223. package/src/ts/client/events.ts +34 -0
  224. package/src/ts/client/expose.ts +30 -0
  225. package/src/ts/client/grapesjs/LoginDialog.ts +204 -0
  226. package/src/ts/client/grapesjs/PublicationManager.ts +370 -0
  227. package/src/ts/client/grapesjs/PublicationUi.ts +342 -0
  228. package/src/ts/client/grapesjs/blocks.ts +37 -0
  229. package/src/ts/client/grapesjs/breadcrumbs.ts +79 -0
  230. package/src/ts/client/grapesjs/eleventy.ts +47 -0
  231. package/src/ts/client/grapesjs/footer.ts +27 -0
  232. package/src/ts/client/grapesjs/index.ts +335 -0
  233. package/src/ts/client/grapesjs/internal-links.test.ts +105 -0
  234. package/src/ts/client/grapesjs/internal-links.ts +225 -0
  235. package/src/ts/client/grapesjs/new-page-dialog.ts +90 -0
  236. package/src/ts/client/grapesjs/openImport.ts +81 -0
  237. package/src/ts/client/grapesjs/page-panel.ts +118 -0
  238. package/src/ts/client/grapesjs/project-bar.ts +107 -0
  239. package/src/ts/client/grapesjs/rich-text.ts +33 -0
  240. package/src/ts/client/grapesjs/semantic.ts +114 -0
  241. package/src/ts/client/grapesjs/settings.ts +208 -0
  242. package/src/ts/client/grapesjs/storage.ts +121 -0
  243. package/src/ts/client/grapesjs/symbolDialogs.ts +38 -0
  244. package/src/ts/client/index.ts +98 -0
  245. package/src/ts/client/publication-transformers.test.ts +199 -0
  246. package/src/ts/client/publication-transformers.ts +263 -0
  247. package/src/ts/client/utils.ts +28 -0
  248. package/src/ts/constants.ts +52 -0
  249. package/src/ts/externs.ts +18 -0
  250. package/src/ts/page.ts +31 -0
  251. package/src/ts/plugins/client/11ty.ts +104 -0
  252. package/src/ts/plugins/client/publicationRenderer.ts +174 -0
  253. package/src/ts/plugins/client/template.ts +192 -0
  254. package/src/ts/plugins/client/website-info.ts +84 -0
  255. package/src/ts/plugins/server/AuthPlugin.ts +38 -0
  256. package/src/ts/plugins/server/Cache.ts +28 -0
  257. package/src/ts/plugins/server/DownloadConnector.ts +148 -0
  258. package/src/ts/plugins/server/FtpConnector.test.ts +82 -0
  259. package/src/ts/plugins/server/FtpConnector.ts +609 -0
  260. package/src/ts/plugins/server/GitlabConnector.ts +644 -0
  261. package/src/ts/plugins/server/PublicationOptimizer.ts +14 -0
  262. package/src/ts/plugins/server/SslPlugin.ts +84 -0
  263. package/src/ts/plugins/server/StaticPlugin.ts +79 -0
  264. package/src/ts/server/api/api.ts +19 -0
  265. package/src/ts/server/api/connectorApi.ts +345 -0
  266. package/src/ts/server/api/publicationApi.ts +159 -0
  267. package/src/ts/server/api/websiteApi.ts +432 -0
  268. package/src/ts/server/cli.ts +65 -0
  269. package/src/ts/server/config.ts +189 -0
  270. package/src/ts/server/connectors/FsHosting.ts +60 -0
  271. package/src/ts/server/connectors/FsStorage.test.ts +51 -0
  272. package/src/ts/server/connectors/FsStorage.ts +261 -0
  273. package/src/ts/server/connectors/connectors.ts +203 -0
  274. package/src/ts/server/events.ts +37 -0
  275. package/src/ts/server/expose.ts +19 -0
  276. package/src/ts/server/express.ts +70 -0
  277. package/src/ts/server/index.ts +100 -0
  278. package/src/ts/server/jobs.test.ts +75 -0
  279. package/src/ts/server/jobs.ts +87 -0
  280. package/src/ts/server/utils/BackwardCompat.ts +552 -0
  281. package/src/ts/server/utils/validation.ts +34 -0
  282. package/src/ts/types.ts +339 -0
@@ -0,0 +1,644 @@
1
+ /*
2
+ * Silex website builder, free/libre no-code tool for makers.
3
+ * Copyright (c) 2023 lexoyo and Silex Labs foundation
4
+ *
5
+ * This program is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU Affero General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU Affero General Public License
15
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ import { API_CONNECTOR_LOGIN_CALLBACK, API_CONNECTOR_PATH, API_PATH, WEBSITE_DATA_FILE } from '../../constants'
19
+ import { ServerConfig } from '../../server/config'
20
+ import { ConnectorFile, ConnectorFileContent, StatusCallback, StorageConnector, contentToBuffer, contentToString, toConnectorData } from '../../server/connectors/connectors'
21
+ import { ApiError, ConnectorType, ConnectorUser, WebsiteData, WebsiteId, WebsiteMeta, WebsiteMetaFileContent } from '../../types'
22
+ import fetch from 'node-fetch'
23
+ import crypto, { createHash } from 'crypto'
24
+
25
+ /**
26
+ * Gitlab connector
27
+ * @fileoverview Gitlab connector for Silex, connect to the user's Gitlab account to store websites
28
+ * @see https://docs.gitlab.com/ee/api/oauth2.html
29
+ */
30
+
31
+ export interface GitlabOptions {
32
+ clientId: string
33
+ clientSecret: string
34
+ branch: string
35
+ assetsFolder: string
36
+ //metaRepo: string
37
+ //metaRepoFile: string
38
+ repoPrefix: string
39
+ scope: string
40
+ }
41
+
42
+ interface GitlabToken {
43
+ state: string
44
+ codeVerifier: string
45
+ codeChallenge: string
46
+ token?: {
47
+ access_token: string
48
+ token_type: string
49
+ expires_in: number
50
+ refresh_token: string
51
+ created_at: number
52
+ id_token: string
53
+ scope: string
54
+ }
55
+ userId?: number
56
+ username?: string
57
+ }
58
+
59
+ interface GitlabSession {
60
+ gitlab?: GitlabToken
61
+ }
62
+
63
+ interface GitlabAction {
64
+ action: 'create' | 'delete' | 'move' | 'update'
65
+ file_path: string
66
+ content?: string
67
+ }
68
+
69
+ interface GitlabWriteFile {
70
+ branch: string
71
+ commit_message: string
72
+ id?: string
73
+ actions?: GitlabAction[]
74
+ content?: string
75
+ file_path?: string
76
+ encoding?: 'base64' | 'text'
77
+ }
78
+
79
+ interface GitlabGetToken {
80
+ grant_type: 'authorization_code'
81
+ client_id: string
82
+ client_secret: string
83
+ code: string
84
+ redirect_uri: string
85
+ code_verifier: string
86
+ }
87
+
88
+ interface GitlabWebsiteName {
89
+ name: string
90
+ }
91
+
92
+ interface GitlabCreateBranch {
93
+ branch: string
94
+ ref: string
95
+ }
96
+
97
+ interface MetaRepoFileContent {
98
+ websites: {
99
+ [websiteId: string]: {
100
+ meta: WebsiteMetaFileContent,
101
+ createdAt: string,
102
+ updatedAt: string,
103
+ }
104
+ }
105
+ }
106
+
107
+ async function wait(ms: number) {
108
+ return new Promise(resolve => setTimeout(resolve, ms))
109
+ }
110
+
111
+ const svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
112
+ <svg
113
+ xmlns:svg="http://www.w3.org/2000/svg"
114
+ xmlns="http://www.w3.org/2000/svg"
115
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
116
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
117
+ width="1000"
118
+ height="963.197"
119
+ viewBox="0 0 1000 963.197"
120
+ version="1.1"
121
+ id="svg85">
122
+ <sodipodi:namedview
123
+ id="namedview87"
124
+ pagecolor="#ffffff"
125
+ bordercolor="#666666"
126
+ borderopacity="1.0"
127
+ inkscape:pageshadow="2"
128
+ inkscape:pageopacity="0.0"
129
+ inkscape:pagecheckerboard="0"
130
+ showgrid="false"
131
+ inkscape:zoom="1"
132
+ inkscape:cx="991.5"
133
+ inkscape:cy="964.5"
134
+ inkscape:window-width="1126"
135
+ inkscape:window-height="895"
136
+ inkscape:window-x="774"
137
+ inkscape:window-y="12"
138
+ inkscape:window-maximized="0"
139
+ inkscape:current-layer="svg85" />
140
+ <defs
141
+ id="defs74">
142
+ <style
143
+ id="style72">.cls-1{fill:#e24329;}.cls-2{fill:#fc6d26;}.cls-3{fill:#fca326;}</style>
144
+ </defs>
145
+ <g
146
+ id="LOGO"
147
+ transform="matrix(5.2068817,0,0,5.2068817,-489.30756,-507.76085)">
148
+ <path
149
+ class="cls-1"
150
+ d="m 282.83,170.73 -0.27,-0.69 -26.14,-68.22 a 6.81,6.81 0 0 0 -2.69,-3.24 7,7 0 0 0 -8,0.43 7,7 0 0 0 -2.32,3.52 l -17.65,54 h -71.47 l -17.65,-54 a 6.86,6.86 0 0 0 -2.32,-3.53 7,7 0 0 0 -8,-0.43 6.87,6.87 0 0 0 -2.69,3.24 L 97.44,170 l -0.26,0.69 a 48.54,48.54 0 0 0 16.1,56.1 l 0.09,0.07 0.24,0.17 39.82,29.82 19.7,14.91 12,9.06 a 8.07,8.07 0 0 0 9.76,0 l 12,-9.06 19.7,-14.91 40.06,-30 0.1,-0.08 a 48.56,48.56 0 0 0 16.08,-56.04 z"
151
+ id="path76" />
152
+ <path
153
+ class="cls-2"
154
+ d="m 282.83,170.73 -0.27,-0.69 a 88.3,88.3 0 0 0 -35.15,15.8 L 190,229.25 c 19.55,14.79 36.57,27.64 36.57,27.64 l 40.06,-30 0.1,-0.08 a 48.56,48.56 0 0 0 16.1,-56.08 z"
155
+ id="path78" />
156
+ <path
157
+ class="cls-3"
158
+ d="m 153.43,256.89 19.7,14.91 12,9.06 a 8.07,8.07 0 0 0 9.76,0 l 12,-9.06 19.7,-14.91 c 0,0 -17.04,-12.89 -36.59,-27.64 -19.55,14.75 -36.57,27.64 -36.57,27.64 z"
159
+ id="path80" />
160
+ <path
161
+ class="cls-2"
162
+ d="M 132.58,185.84 A 88.19,88.19 0 0 0 97.44,170 l -0.26,0.69 a 48.54,48.54 0 0 0 16.1,56.1 l 0.09,0.07 0.24,0.17 39.82,29.82 c 0,0 17,-12.85 36.57,-27.64 z"
163
+ id="path82" />
164
+ </g>
165
+ </svg>`
166
+ const encodedSvg = encodeURIComponent(svg)
167
+ const ICON = `data:image/svg+xml,${encodedSvg}`
168
+
169
+ export default class GitlabConnector implements StorageConnector {
170
+ connectorId = 'gitlab'
171
+ connectorType = ConnectorType.STORAGE
172
+ displayName = 'GitLab'
173
+ icon = ICON
174
+ disableLogout = false
175
+ color = '#2B1B63'
176
+ background = 'rgba(252, 109, 38, 0.2)'
177
+ options: GitlabOptions
178
+
179
+ constructor(private config: ServerConfig, opts: Partial<GitlabOptions>) {
180
+ this.options = {
181
+ branch: 'main',
182
+ assetsFolder: 'assets',
183
+ //metaRepo: 'silex-meta',
184
+ //metaRepoFile: 'websites.json',
185
+ repoPrefix: 'silex_',
186
+ scope: 'api', // 'api+read_api+read_user+read_repository+write_repository+email+sudo+profile+openid'
187
+ ...opts,
188
+ } as GitlabOptions
189
+ if(!this.options.clientId) throw new Error('Missing Gitlab client ID')
190
+ if(!this.options.clientSecret) throw new Error('Missing Gitlab client secret')
191
+ }
192
+
193
+ // **
194
+ // Convenience methods for the Gitlab API
195
+ private getAssetPath(path: string): string {
196
+ return encodeURIComponent(`${this.options.assetsFolder}${path}`)
197
+ }
198
+
199
+ private async createFile(session: GitlabSession, websiteId: WebsiteId, path: string, content: string, isBase64 = false): Promise<void> {
200
+ // Remove leading slash
201
+ const safePath = path.replace(/^\//, '')
202
+ return this.callApi(session, `api/v4/projects/${websiteId}/repository/files/${safePath}`, 'POST', {
203
+ id: websiteId,
204
+ branch: this.options.branch,
205
+ content,
206
+ commit_message: `Create file ${path} from Silex`,
207
+ encoding: isBase64 ? 'base64' : undefined,
208
+ })
209
+ }
210
+
211
+ private async updateFile(session: GitlabSession, websiteId: WebsiteId, path: string, content: string, isBase64 = false): Promise<void> {
212
+ // Remove leading slash
213
+ const safePath = path.replace(/^\//, '')
214
+ return this.callApi(session, `api/v4/projects/${websiteId}/repository/files/${safePath}`, 'PUT', {
215
+ id: websiteId,
216
+ branch: this.options.branch,
217
+ content: await contentToString(content),
218
+ commit_message: `Update website asset ${path} from Silex`,
219
+ encoding: isBase64 ? 'base64' : undefined,
220
+ })
221
+ }
222
+
223
+ /*
224
+ * Get the meta repo path for the current user
225
+ * The meta repo contains a JSON file which contains the list of websites
226
+ */
227
+ //private getMetaRepoPath(session: GitlabSession): string {
228
+ // if(!session.gitlab?.username) throw new ApiError('Missing Gitlab user ID. User not logged in?', 401)
229
+ // return encodeURIComponent(`${session.gitlab.username}/${this.options.metaRepo}`)
230
+ //}
231
+
232
+ ///**
233
+ // * Initialize the storage with a meta repo
234
+ // */
235
+ //private async initStorage(session: GitlabSession): Promise<void> {
236
+ // // Create the meta repo
237
+ // try {
238
+ // const project = await this.callApi(session, 'api/v4/projects/', 'POST', {
239
+ // name: this.options.metaRepo,
240
+ // }) as any
241
+ // return this.createFile(session, this.getMetaRepoPath(session), this.options.metaRepoFile, JSON.stringify({
242
+ // websites: {}
243
+ // } as MetaRepoFileContent))
244
+ // } catch (e) {
245
+ // console.error('Could not init storage', e.statusCode, e.httpStatusCode, e)
246
+ // throw e
247
+ // }
248
+ //}
249
+
250
+ /**
251
+ * Call the Gitlab API with the user's token and handle errors
252
+ */
253
+ private async callApi(session: GitlabSession, path: string, method: 'POST' | 'GET' | 'PUT' | 'DELETE' = 'GET', body: GitlabWriteFile | GitlabGetToken | GitlabWebsiteName | GitlabCreateBranch | null = null, params: any = {}): Promise<any> {
254
+ const token = session?.gitlab?.token
255
+ const tokenParam = token ? `access_token=${token.access_token}&` : ''
256
+ const paramsStr = Object.entries(params).map(([k, v]) => `${k}=${encodeURIComponent((v as any).toString())}`).join('&')
257
+ const url = `https://gitlab.com/${path}?${tokenParam}${paramsStr}`
258
+ const headers = {
259
+ 'Content-Type': 'application/json',
260
+ }
261
+ if(method === 'GET' && body) {
262
+ console.error('Gitlab API error (4) - GET request with body', {url, method, body, params})
263
+ }
264
+ // With or without body
265
+ const response = await fetch(url, body && method !== 'GET' ? {
266
+ method,
267
+ headers,
268
+ body: body ? JSON.stringify(body) : undefined
269
+ } : {
270
+ method,
271
+ headers,
272
+ })
273
+ let json: { message: string, error: string } | any
274
+ // Handle the case when the server returns an non-JSON response (e.g. 400 Bad Request)
275
+ const text = await response.text()
276
+ try {
277
+ json = JSON.parse(text)
278
+ } catch (e) {
279
+ if(!response.ok) {
280
+ // A real error
281
+ throw e
282
+ } else {
283
+ // Useless error linked to the fact that the response is not JSON
284
+ console.error('Gitlab API error (3) - could not parse response', response.status, response.statusText, {url, method, body, params, text})
285
+ return text
286
+ }
287
+ }
288
+ if(!response.ok) {
289
+ if (response.status === 401 && session?.gitlab?.token?.refresh_token) {
290
+ // Refresh the token
291
+ const token = session?.gitlab?.token
292
+ const body = {
293
+ grant_type: 'refresh_token',
294
+ refresh_token: token.refresh_token,
295
+ client_id: this.options.clientId,
296
+ client_secret: this.options.clientSecret,
297
+ }
298
+ const response = await fetch('https://gitlab.com/oauth/token', {
299
+ method: 'POST',
300
+ headers: {
301
+ 'Content-Type': 'application/json',
302
+ },
303
+ body: JSON.stringify(body)
304
+ })
305
+ const refreshJson = await response.json()
306
+ if (response.ok) {
307
+ session.gitlab.token = {
308
+ ...token,
309
+ ...refreshJson,
310
+ }
311
+ return await this.callApi(session, path, method, body as any, params)
312
+ } else {
313
+ const message = typeof refreshJson?.message === 'object' ? Object.entries(refreshJson.message).map(entry => entry.join(' ')).join(' ') : refreshJson?.message ?? refreshJson?.error ?? response.statusText
314
+ console.error('Gitlab API error (2) - could not refresh token', response.status, response.statusText, {message}, 'refresh_token:', token.refresh_token)
315
+ // Workaround for when the token is invalid
316
+ // It happens often which is not normal (refresh token should last 6 months)
317
+ this.logout(session)
318
+ // Notify the user
319
+ throw new ApiError(`Gitlab API error (2): ${message}`, response.status)
320
+ }
321
+ } else {
322
+ const message = typeof json?.message === 'object' ? Object.entries(json.message).map(entry => entry.join(' ')).join(' ') : json?.message ?? json?.error ?? response.statusText
323
+ console.error('Gitlab API error (1)', response.status, response.statusText, {message})
324
+ throw new ApiError(`Gitlab API error (1): ${message}`, response.status)
325
+ }
326
+ }
327
+ return json
328
+ }
329
+
330
+ private generateCodeVerifier() {
331
+ return crypto.randomBytes(64).toString('base64')
332
+ .replace(/\+/g, '-')
333
+ .replace(/\//g, '_')
334
+ .replace(/=/g, '')
335
+ .substr(0, 128)
336
+ }
337
+
338
+ private async generateCodeChallenge(verifier) {
339
+ const hashed = createHash('sha256').update(verifier).digest()
340
+ let base64Url = hashed.toString('base64')
341
+ // Replace '+' with '-', '/' with '_', and remove '='
342
+ base64Url = base64Url.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
343
+ return base64Url
344
+ }
345
+
346
+
347
+ private getRedirect() {
348
+ const params = `connectorId=${this.connectorId}&type=${this.connectorType}`
349
+ return `${this.config.url}${API_PATH}${API_CONNECTOR_PATH}${API_CONNECTOR_LOGIN_CALLBACK}?${params}`
350
+ }
351
+
352
+ /**
353
+ * Get the OAuth URL to redirect the user to
354
+ * The URL should look like
355
+ * https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=STATE&scope=REQUESTED_SCOPES&code_challenge=CODE_CHALLENGE&code_challenge_method=S256
356
+ * OAuth2 Step #1 from https://docs.gitlab.com/ee/api/oauth2.html#authorization-code-with-proof-key-for-code-exchange-pkce
357
+ */
358
+ async getOAuthUrl(session: GitlabSession): Promise<string> {
359
+ const redirect_uri = encodeURIComponent(this.getRedirect())
360
+
361
+ const state = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
362
+ const codeVerifier = this.generateCodeVerifier()
363
+
364
+ // Create the code challenge
365
+ const codeChallenge = await this.generateCodeChallenge(codeVerifier)
366
+
367
+ // Store the code verifier and code challenge in the session
368
+ session.gitlab = {
369
+ ...session.gitlab,
370
+ state,
371
+ codeVerifier,
372
+ codeChallenge,
373
+ }
374
+ return `https://gitlab.com/oauth/authorize?client_id=${this.options.clientId}&redirect_uri=${redirect_uri}&response_type=code&state=${session.gitlab.state}&scope=${this.options.scope}&code_challenge=${codeChallenge}&code_challenge_method=S256`
375
+ }
376
+
377
+ getOptions(formData: object): object {
378
+ return {} // FIXME: store branch
379
+ }
380
+
381
+ async getLoginForm(session: GitlabSession, redirectTo: string): Promise<null> {
382
+ return null
383
+ }
384
+
385
+ async getSettingsForm(session: GitlabSession, redirectTo: string): Promise<null> {
386
+ return null
387
+ }
388
+
389
+ async isLoggedIn(session: GitlabSession): Promise<boolean> {
390
+ return !!session?.gitlab?.token
391
+ }
392
+
393
+ /**
394
+ * Get the token from return code
395
+ * Set the token in the session
396
+ * OAuth2 Step #2 from https://docs.gitlab.com/ee/api/oauth2.html#authorization-code-with-proof-key-for-code-exchange-pkce
397
+ */
398
+ async setToken(session: GitlabSession, loginResult: any): Promise<void> {
399
+ if(!loginResult.state || loginResult.state !== session.gitlab?.state) throw new ApiError('Invalid state', 401)
400
+ if(!session.gitlab?.codeVerifier) throw new ApiError('Missing code verifier', 401)
401
+ if(!session.gitlab?.codeChallenge) throw new ApiError('Missing code challenge', 401)
402
+
403
+ const response = await fetch('https://gitlab.com/oauth/token', {
404
+ method: 'POST',
405
+ headers: {
406
+ 'Content-Type': 'application/json',
407
+ },
408
+ body: JSON.stringify({
409
+ client_id: this.options.clientId,
410
+ client_secret: this.options.clientSecret,
411
+ code: loginResult.code,
412
+ grant_type: 'authorization_code',
413
+ redirect_uri: this.getRedirect(),
414
+ code_verifier: session.gitlab.codeVerifier,
415
+ }),
416
+ })
417
+
418
+ const token = await response.json()
419
+
420
+ // Store the token in the session
421
+ session.gitlab = { token, state: session.gitlab.state, codeVerifier: session.gitlab.codeVerifier, codeChallenge: session.gitlab.codeChallenge }
422
+
423
+ // We need to get the user ID for listWebsites
424
+ const user = await this.callApi(session, 'api/v4/user') as any
425
+ session.gitlab.userId = user.id
426
+ session.gitlab.username = user.username
427
+ }
428
+
429
+ async logout(session: GitlabSession): Promise<void> {
430
+ delete session.gitlab
431
+ }
432
+
433
+ async getUser(session: GitlabSession): Promise<ConnectorUser> {
434
+ const user = await this.callApi(session, 'api/v4/user') as any
435
+ return {
436
+ name: user.name,
437
+ email: user.email,
438
+ picture: user.avatar_url,
439
+ storage: await toConnectorData(session, this as StorageConnector),
440
+ }
441
+ }
442
+
443
+ async listWebsites(session: GitlabSession): Promise<WebsiteMeta[]> {
444
+ //try {
445
+ // const result = await this.callApi(session, `api/v4/projects/${this.getMetaRepoPath(session)}/repository/files/${this.options.metaRepoFile}`, 'GET', null, {
446
+ // ref: this.options.branch,
447
+ // })
448
+ // const { content } = result
449
+ // const contentDecoded = Buffer.from(content, 'base64').toString('utf8')
450
+ // const websites = (JSON.parse(contentDecoded) as MetaRepoFileContent).websites
451
+ // return Object.entries(websites).map(([websiteId, {meta, createdAt, updatedAt}]) => ({
452
+ // websiteId,
453
+ // createdAt: new Date(createdAt),
454
+ // updatedAt: new Date(updatedAt),
455
+ // ...meta,
456
+ // }))
457
+ //} catch (e) {
458
+ // console.error('Could not list websites', e.statusCode, e.httpStatusCode, e.code)
459
+ // if (e.statusCode === 404 || e.httpStatusCode === 404) {
460
+ // await this.initStorage(session)
461
+ // return []
462
+ // } else {
463
+ // throw e
464
+ // }
465
+ //}
466
+ const projects = await this.callApi(session, `api/v4/users/${session.gitlab?.userId}/projects`) as any[]
467
+ return projects
468
+ .filter(p => p.name.startsWith(this.options.repoPrefix))
469
+ .map(p => ({
470
+ websiteId: p.id,
471
+ name: p.name.replace(this.options.repoPrefix, ''),
472
+ createdAt: p.created_at,
473
+ updatedAt: p.last_activity_at,
474
+ connectorUserSettings: {},
475
+ }))
476
+ }
477
+
478
+ async readWebsite(session: GitlabSession, websiteId: string): Promise<WebsiteData> {
479
+ const result = await this.callApi(session, `api/v4/projects/${websiteId}/repository/files/${WEBSITE_DATA_FILE}`, 'GET', null, {
480
+ ref: this.options.branch,
481
+ }) as any
482
+ const { content } = result
483
+ const contentDecoded = Buffer.from(content, 'base64').toString('utf8')
484
+ const websiteData = JSON.parse(contentDecoded) as WebsiteData
485
+ return websiteData
486
+ }
487
+
488
+ async createWebsite(session: GitlabSession, websiteMeta: WebsiteMetaFileContent): Promise<WebsiteId> {
489
+ const project = await this.callApi(session, 'api/v4/projects/', 'POST', {
490
+ name: this.options.repoPrefix + websiteMeta.name,
491
+ }) as any
492
+ await this.createFile(session, project.id, WEBSITE_DATA_FILE, JSON.stringify({} as WebsiteData))
493
+ //await this.createFile(session, project.id, WEBSITE_META_DATA_FILE, JSON.stringify(websiteMeta))
494
+ //await this.updateWebsite(session, project.id, {} as WebsiteData)
495
+ //await this.setWebsiteMeta(session, project.id, websiteMeta)
496
+ return project.id
497
+ }
498
+
499
+ async updateWebsite(session: GitlabSession, websiteId: WebsiteId, websiteData: WebsiteData): Promise<void> {
500
+ const project = await this.callApi(session, `api/v4/projects/${websiteId}/repository/files/${WEBSITE_DATA_FILE}`, 'PUT', {
501
+ branch: this.options.branch,
502
+ commit_message: 'Update website data from Silex',
503
+ content: JSON.stringify(websiteData),
504
+ file_path: WEBSITE_DATA_FILE,
505
+ id: websiteId,
506
+ })
507
+ }
508
+
509
+ async deleteWebsite(session: GitlabSession, websiteId: WebsiteId): Promise<void> {
510
+ // Delete repo
511
+ await this.callApi(session, `api/v4/projects/${websiteId}`, 'DELETE')
512
+ //// Load the meta repo data
513
+ //const file = await this.callApi(session, `api/v4/projects/${this.getMetaRepoPath(session)}/repository/files/${this.options.metaRepoFile}`, 'GET', null, {
514
+ // ref: this.options.branch,
515
+ //})
516
+ //const metaRepo = JSON.parse(Buffer.from(file.content, 'base64').toString('utf8')) as MetaRepoFileContent
517
+ //const data = metaRepo.websites[websiteId]
518
+ //if(!data) throw new ApiError(`Website ${websiteId} not found`, 404)
519
+ //// Update or create the website meta data
520
+ //delete metaRepo.websites[websiteId]
521
+ //// Save the meta repo data
522
+ //const project = await this.callApi(session, `api/v4/projects/${this.getMetaRepoPath(session)}/repository/files/${this.options.metaRepoFile}`, 'PUT', {
523
+ // branch: this.options.branch,
524
+ // commit_message: `Delete meta data of ${data.meta.name} (${websiteId}) from Silex`,
525
+ // content: JSON.stringify(metaRepo),
526
+ // file_path: this.options.metaRepoFile,
527
+ //})
528
+ }
529
+
530
+ async getWebsiteMeta(session: GitlabSession, websiteId: WebsiteId): Promise<WebsiteMeta> {
531
+ //const file = await this.callApi(session, `api/v4/projects/${this.getMetaRepoPath(session)}/repository/files/${this.options.metaRepoFile}`, 'GET', null, {
532
+ // ref: this.options.branch,
533
+ //})
534
+ //const metaRepo = JSON.parse(Buffer.from(file.content, 'base64').toString('utf8')) as MetaRepoFileContent
535
+ //if(!metaRepo.websites[websiteId]) throw new ApiError(`Website ${websiteId} not found`, 404)
536
+ //return {
537
+ // websiteId,
538
+ // createdAt: new Date(metaRepo.websites[websiteId].createdAt),
539
+ // updatedAt: new Date(metaRepo.websites[websiteId].updatedAt),
540
+ // ...metaRepo.websites[websiteId].meta,
541
+ //}
542
+ // const response = await this.callApi(session, `api/v4/projects/${websiteId}/repository/files/${WEBSITE_META_DATA_FILE}`, 'GET', null, {
543
+ // ref: this.options.branch,
544
+ // }) as any
545
+ // Base64 to string to JSON
546
+ // const contentDecoded = Buffer.from(response.content, 'base64').toString('utf8')
547
+ // const websiteMeta = JSON.parse(contentDecoded) as WebsiteMetaFileContent
548
+ const project = await this.callApi(session, `api/v4/projects/${websiteId}`)
549
+ return {
550
+ websiteId,
551
+ name: project.name.replace(this.options.repoPrefix, ''),
552
+ imageUrl: project.avatar_url,
553
+ createdAt: project.created_at,
554
+ updatedAt: project.last_activity_at,
555
+ connectorUserSettings: {},
556
+ }
557
+ }
558
+
559
+ async setWebsiteMeta(session: GitlabSession, websiteId: WebsiteId, websiteMeta: WebsiteMetaFileContent): Promise<void> {
560
+ //// Load the meta repo data
561
+ //const file = await this.callApi(session, `api/v4/projects/${this.getMetaRepoPath(session)}/repository/files/${this.options.metaRepoFile}`, 'GET', null, {
562
+ // ref: this.options.branch,
563
+ //})
564
+ //const metaRepo = JSON.parse(Buffer.from(file.content, 'base64').toString('utf8')) as MetaRepoFileContent
565
+ //// Update or create the website meta data
566
+ //metaRepo.websites[websiteId] = {
567
+ // updatedAt: new Date().toISOString(),
568
+ // createdAt: metaRepo.websites[websiteId]?.createdAt ?? new Date().toISOString(),
569
+ // meta: websiteMeta,
570
+ //}
571
+ //// Save the meta repo data
572
+ //const project = await this.callApi(session, `api/v4/projects/${this.getMetaRepoPath(session)}/repository/files/${this.options.metaRepoFile}`, 'PUT', {
573
+ // branch: this.options.branch,
574
+ // commit_message: `Update website meta data of ${websiteMeta.name} (${websiteId}) from Silex`,
575
+ // content: JSON.stringify(metaRepo),
576
+ // file_path: this.options.metaRepoFile,
577
+ //})
578
+
579
+ // Rename the repo if needed
580
+ const oldMeta = await this.getWebsiteMeta(session, websiteId)
581
+ if(websiteMeta.name !== oldMeta.name) {
582
+ await this.callApi(session, `api/v4/projects/${websiteId}`, 'PUT', {
583
+ name: this.options.repoPrefix + websiteMeta.name,
584
+ })
585
+ }
586
+ //// Update the metadata file
587
+ //await this.callApi(session, `api/v4/projects/${websiteId}/repository/files/${WEBSITE_META_DATA_FILE}`, 'PUT', {
588
+ // branch: this.options.branch,
589
+ // commit_message: 'Update website meta data from Silex',
590
+ // content: JSON.stringify(websiteMeta),
591
+ // file_path: WEBSITE_META_DATA_FILE,
592
+ // id: websiteId,
593
+ //})
594
+ }
595
+
596
+ async writeAssets(session: GitlabSession, websiteId: string, files: ConnectorFile[], status?: StatusCallback | undefined): Promise<void> {
597
+ // For each file
598
+ for (const file of files) {
599
+ // Convert to base64
600
+ const content = (await contentToBuffer(file.content)).toString('base64')
601
+ try {
602
+ await this.updateFile(session, websiteId, this.getAssetPath(file.path), content, true)
603
+ } catch (e) {
604
+ // If the file does not exist, create it
605
+ if (e.statusCode === 404 || e.httpStatusCode === 404 || e.message.endsWith('A file with this name doesn\'t exist')) {
606
+ await this.createFile(session, websiteId, this.getAssetPath(file.path), content, true)
607
+ } else {
608
+ throw e
609
+ }
610
+ }
611
+ }
612
+ }
613
+
614
+ async readAsset(session: GitlabSession, websiteId: string, fileName: string): Promise<ConnectorFileContent> {
615
+ // Remove leading slash
616
+ const finalPath = this.getAssetPath(fileName)
617
+ // Call the API
618
+ const url = `https://gitlab.com/api/v4/projects/${websiteId}/repository/files/${finalPath}?ref=${this.options.branch}&access_token=${session.gitlab?.token?.access_token}`
619
+ const response = await fetch(url, {
620
+ method: 'GET',
621
+ headers: {
622
+ 'Content-Type': 'application/json',
623
+ },
624
+ })
625
+ const json = await response.json()
626
+ if(!response.ok) throw new ApiError(`Gitlab API error: ${json?.message ?? json?.error ?? response.statusText}`, response.status)
627
+ // From base64 string to buffer
628
+ const buf = Buffer.from(json.content, 'base64')
629
+ // Return the image bytes
630
+ return buf
631
+ }
632
+
633
+ async deleteAssets(session: GitlabSession, websiteId: string, fileNames: string[]): Promise<void> {
634
+ return this.callApi(session, `api/v4/projects/${websiteId}/repository/commits`, 'POST', {
635
+ id: websiteId,
636
+ branch: this.options.branch,
637
+ commit_message: `Delete assets from Silex: ${fileNames.join(', ')}`,
638
+ actions: fileNames.map(f => ({
639
+ action: 'delete',
640
+ file_path: this.getAssetPath(f),
641
+ })),
642
+ })
643
+ }
644
+ }
@@ -0,0 +1,14 @@
1
+
2
+ //import { minify } from 'html-minifier'
3
+ //const optim = files.map(file => ({
4
+ // ...file,
5
+ // html: minify(file.html, {
6
+ // continueOnParseError: true,
7
+ // collapseInlineTagWhitespace: true,
8
+ // collapseWhitespace: true,
9
+ // minifyCSS: true,
10
+ // minifyJS: true,
11
+ // removeScriptTypeAttributes: true,
12
+ // removeStyleLinkTypeAttributes: true,
13
+ // }).trim(),
14
+ //}))