@silexlabs/silex 3.0.1-0 → 3.0.1

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 (250) hide show
  1. package/.env.default +3 -2
  2. package/.silex.js +39 -1
  3. package/README.md +37 -35
  4. package/dist/client/client/api.d.ts +8 -1
  5. package/dist/client/client/assetUrl.d.ts +54 -0
  6. package/dist/client/client/config.d.ts +60 -1
  7. package/dist/client/client/events.d.ts +19 -10
  8. package/dist/client/client/grapesjs/PublicationManager.d.ts +6 -8
  9. package/dist/client/client/grapesjs/PublicationUi.d.ts +8 -10
  10. package/dist/client/client/grapesjs/breadcrumbs.d.ts +1 -0
  11. package/dist/client/client/grapesjs/css-props.d.ts +6 -0
  12. package/dist/client/client/grapesjs/footer.d.ts +2 -0
  13. package/dist/client/client/grapesjs/img.d.ts +6 -0
  14. package/dist/client/client/grapesjs/index.d.ts +6 -2
  15. package/dist/client/client/grapesjs/internal-links.d.ts +1 -1
  16. package/dist/client/client/grapesjs/keymaps.d.ts +123 -0
  17. package/dist/client/client/grapesjs/new-page-dialog.d.ts +1 -1
  18. package/dist/client/client/grapesjs/page-panel.d.ts +7 -1
  19. package/dist/client/client/grapesjs/project-bar.d.ts +20 -0
  20. package/dist/client/client/grapesjs/resize-panel.d.ts +1 -0
  21. package/dist/client/client/grapesjs/rich-text.d.ts +4 -1
  22. package/dist/client/client/grapesjs/semantic.d.ts +1 -1
  23. package/dist/client/client/grapesjs/settings-sections.d.ts +16 -0
  24. package/dist/client/client/grapesjs/settings.d.ts +3 -1
  25. package/dist/client/client/grapesjs/storage.d.ts +1 -0
  26. package/dist/client/client/grapesjs/symbolDialogs.d.ts +1 -1
  27. package/dist/client/client/grapesjs/view-buttons.d.ts +1 -0
  28. package/dist/client/client/index.d.ts +5 -0
  29. package/dist/client/client/publication-transformers.d.ts +42 -0
  30. package/dist/client/client/publish-custom-code-block.d.ts +4 -0
  31. package/dist/client/client/publish-fonts.d.ts +4 -0
  32. package/dist/client/client/utils.d.ts +22 -1
  33. package/dist/client/constants.d.ts +3 -0
  34. package/dist/client/css/admin.css +1107 -411
  35. package/dist/client/css/admin.css.map +1 -0
  36. package/dist/client/grapesjs-filter-styles/dist/index.d.ts +4 -0
  37. package/dist/client/grapesjs-fonts/dist/index.d.ts +4 -0
  38. package/dist/client/grapesjs-fonts/src/commands.d.ts +3 -0
  39. package/dist/client/grapesjs-fonts/src/fonts.d.ts +4 -0
  40. package/dist/client/grapesjs-storage-rate-limit/dist/index.d.ts +4 -0
  41. package/dist/client/grapesjs-ui-suggest-classes/dist/index.d.ts +4 -0
  42. package/dist/client/index.html +3 -2
  43. package/dist/client/js/main.js +305 -170
  44. package/dist/client/js/main.js.LICENSE.txt +21 -1
  45. package/dist/client/page.d.ts +1 -1
  46. package/dist/client/silex-lib/src/ts/client/api.d.ts +90 -0
  47. package/dist/client/silex-lib/src/ts/client/assetUrl.d.ts +54 -0
  48. package/dist/client/silex-lib/src/ts/client/config.d.ts +102 -0
  49. package/dist/client/silex-lib/src/ts/client/events.d.ts +23 -0
  50. package/dist/client/silex-lib/src/ts/client/expose.d.ts +8 -0
  51. package/dist/client/silex-lib/src/ts/client/grapesjs/LoginDialog.d.ts +15 -0
  52. package/dist/client/silex-lib/src/ts/client/grapesjs/PublicationManager.d.ts +73 -0
  53. package/dist/client/silex-lib/src/ts/client/grapesjs/PublicationUi.d.ts +59 -0
  54. package/dist/client/silex-lib/src/ts/client/grapesjs/blocks.d.ts +1 -0
  55. package/dist/client/silex-lib/src/ts/client/grapesjs/breadcrumbs.d.ts +1 -0
  56. package/dist/client/silex-lib/src/ts/client/grapesjs/css-props.d.ts +6 -0
  57. package/dist/client/silex-lib/src/ts/client/grapesjs/footer.d.ts +2 -0
  58. package/dist/client/silex-lib/src/ts/client/grapesjs/img.d.ts +6 -0
  59. package/dist/client/silex-lib/src/ts/client/grapesjs/index.d.ts +9 -0
  60. package/dist/client/silex-lib/src/ts/client/grapesjs/internal-links.d.ts +1 -0
  61. package/dist/client/silex-lib/src/ts/client/grapesjs/keymaps.d.ts +123 -0
  62. package/dist/client/silex-lib/src/ts/client/grapesjs/new-page-dialog.d.ts +2 -0
  63. package/dist/client/silex-lib/src/ts/client/grapesjs/openImport.d.ts +9 -0
  64. package/dist/client/silex-lib/src/ts/client/grapesjs/page-panel.d.ts +9 -0
  65. package/dist/client/silex-lib/src/ts/client/grapesjs/project-bar.d.ts +21 -0
  66. package/dist/client/silex-lib/src/ts/client/grapesjs/resize-panel.d.ts +1 -0
  67. package/dist/client/silex-lib/src/ts/client/grapesjs/rich-text.d.ts +4 -0
  68. package/dist/client/silex-lib/src/ts/client/grapesjs/semantic.d.ts +1 -0
  69. package/dist/client/silex-lib/src/ts/client/grapesjs/settings-sections.d.ts +16 -0
  70. package/dist/client/silex-lib/src/ts/client/grapesjs/settings.d.ts +4 -0
  71. package/dist/client/silex-lib/src/ts/client/grapesjs/storage.d.ts +2 -0
  72. package/dist/client/silex-lib/src/ts/client/grapesjs/symbolDialogs.d.ts +3 -0
  73. package/dist/client/silex-lib/src/ts/client/grapesjs/view-buttons.d.ts +1 -0
  74. package/dist/client/silex-lib/src/ts/client/index.d.ts +10 -0
  75. package/dist/client/silex-lib/src/ts/client/publication-transformers.d.ts +42 -0
  76. package/dist/client/silex-lib/src/ts/client/publish-custom-code-block.d.ts +4 -0
  77. package/dist/client/silex-lib/src/ts/client/publish-fonts.d.ts +4 -0
  78. package/dist/client/silex-lib/src/ts/client/utils.d.ts +22 -0
  79. package/dist/client/silex-lib/src/ts/constants.d.ts +32 -0
  80. package/dist/client/silex-lib/src/ts/page.d.ts +2 -0
  81. package/dist/client/silex-lib/src/ts/types.d.ts +339 -0
  82. package/dist/client/types.d.ts +59 -35
  83. package/dist/plugins/client/client/events.d.ts +19 -10
  84. package/dist/plugins/client/client/events.js +11 -0
  85. package/dist/plugins/client/client/events.js.map +1 -1
  86. package/dist/plugins/client/plugins/client/template.js +119 -71
  87. package/dist/plugins/client/plugins/client/template.js.map +1 -1
  88. package/dist/plugins/client/plugins/client/website-info.d.ts +1 -0
  89. package/dist/plugins/client/plugins/client/website-info.js +72 -0
  90. package/dist/plugins/client/plugins/client/website-info.js.map +1 -0
  91. package/dist/plugins/client/types.d.ts +59 -35
  92. package/dist/plugins/client/types.js +23 -0
  93. package/dist/plugins/client/types.js.map +1 -1
  94. package/dist/plugins/server/constants.d.ts +3 -0
  95. package/dist/plugins/server/constants.js +28 -1
  96. package/dist/plugins/server/constants.js.map +1 -1
  97. package/dist/plugins/server/plugins/server/Cache.js +9 -5
  98. package/dist/plugins/server/plugins/server/Cache.js.map +1 -1
  99. package/dist/plugins/server/plugins/server/DownloadConnector.d.ts +29 -0
  100. package/dist/plugins/server/plugins/server/DownloadConnector.js +127 -0
  101. package/dist/plugins/server/plugins/server/DownloadConnector.js.map +1 -0
  102. package/dist/plugins/server/plugins/server/FtpConnector.d.ts +5 -2
  103. package/dist/plugins/server/plugins/server/FtpConnector.js +38 -23
  104. package/dist/plugins/server/plugins/server/FtpConnector.js.map +1 -1
  105. package/dist/plugins/server/plugins/server/GitlabConnector.d.ts +63 -9
  106. package/dist/plugins/server/plugins/server/GitlabConnector.js +367 -91
  107. package/dist/plugins/server/plugins/server/GitlabConnector.js.map +1 -1
  108. package/dist/plugins/server/plugins/server/GitlabHostingConnector.d.ts +16 -0
  109. package/dist/plugins/server/plugins/server/GitlabHostingConnector.js +209 -0
  110. package/dist/plugins/server/plugins/server/GitlabHostingConnector.js.map +1 -0
  111. package/dist/plugins/server/plugins/server/SslPlugin.js +6 -6
  112. package/dist/plugins/server/plugins/server/SslPlugin.js.map +1 -1
  113. package/dist/plugins/server/plugins/server/StaticPlugin.d.ts +8 -1
  114. package/dist/plugins/server/plugins/server/StaticPlugin.js +5 -31
  115. package/dist/plugins/server/plugins/server/StaticPlugin.js.map +1 -1
  116. package/dist/plugins/server/server/config.js +8 -9
  117. package/dist/plugins/server/server/config.js.map +1 -1
  118. package/dist/plugins/server/server/connectors/FsHosting.d.ts +3 -2
  119. package/dist/plugins/server/server/connectors/FsHosting.js +18 -7
  120. package/dist/plugins/server/server/connectors/FsHosting.js.map +1 -1
  121. package/dist/plugins/server/server/connectors/FsStorage.d.ts +3 -1
  122. package/dist/plugins/server/server/connectors/FsStorage.js +35 -28
  123. package/dist/plugins/server/server/connectors/FsStorage.js.map +1 -1
  124. package/dist/plugins/server/server/connectors/connectors.d.ts +7 -7
  125. package/dist/plugins/server/server/connectors/connectors.js +6 -7
  126. package/dist/plugins/server/server/connectors/connectors.js.map +1 -1
  127. package/dist/plugins/server/server/events.js +16 -0
  128. package/dist/plugins/server/server/events.js.map +1 -1
  129. package/dist/plugins/server/server/jobs.d.ts +6 -0
  130. package/dist/plugins/server/server/jobs.js +12 -8
  131. package/dist/plugins/server/server/jobs.js.map +1 -1
  132. package/dist/plugins/server/server/utils/validation.js +1 -2
  133. package/dist/plugins/server/server/utils/validation.js.map +1 -1
  134. package/dist/plugins/server/types.d.ts +59 -35
  135. package/dist/plugins/server/types.js +24 -1
  136. package/dist/plugins/server/types.js.map +1 -1
  137. package/dist/server/constants.js +27 -1
  138. package/dist/server/plugins/server/Cache.js +9 -5
  139. package/dist/server/server/api/api.js +1 -1
  140. package/dist/server/server/api/connectorApi.js +154 -25
  141. package/dist/server/server/api/publicationApi.js +17 -25
  142. package/dist/server/server/api/websiteApi.js +30 -8
  143. package/dist/server/server/cli.js +9 -6
  144. package/dist/server/server/config.js +8 -9
  145. package/dist/server/server/connectors/FsHosting.js +17 -7
  146. package/dist/server/server/connectors/FsStorage.js +34 -28
  147. package/dist/server/server/connectors/connectors.js +6 -7
  148. package/dist/server/server/express.js +4 -5
  149. package/dist/server/server/index.js +5 -1
  150. package/dist/server/server/jobs.js +11 -8
  151. package/dist/server/server/utils/validation.js +1 -2
  152. package/dist/server/types.js +13 -1
  153. package/package.json +56 -42
  154. package/public/assets/download.png +0 -0
  155. package/public/assets/favicon.ico +0 -0
  156. package/public/assets/favicon.png +0 -0
  157. package/public/assets/ftp.png +0 -0
  158. package/public/assets/gitlab.png +0 -0
  159. package/public/assets/gitlab2.png +0 -0
  160. package/public/assets/laptop.png +0 -0
  161. package/public/assets/logo-silex-small.png +0 -0
  162. package/public/silex.js +5 -23
  163. package/src/css/_variables.scss +23 -20
  164. package/src/css/dialog.scss +27 -1
  165. package/src/css/fonts.scss +31 -0
  166. package/src/css/footer.scss +12 -0
  167. package/src/css/grapesjs-light.scss +180 -0
  168. package/src/css/grapesjs-plugins.scss +227 -0
  169. package/src/css/grapesjs.scss +84 -10
  170. package/src/css/index.scss +12 -1
  171. package/src/css/light.scss +7 -0
  172. package/src/css/project-bar.scss +32 -16
  173. package/src/css/silex.scss +79 -5
  174. package/src/css/website-info.scss +14 -0
  175. package/src/html/index.pug +3 -3
  176. package/src/ts/client/api.ts +38 -14
  177. package/src/ts/client/assetUrl.test.ts +164 -0
  178. package/src/ts/client/assetUrl.ts +254 -0
  179. package/src/ts/client/config.ts +133 -5
  180. package/src/ts/client/events.ts +21 -9
  181. package/src/ts/client/grapesjs/LoginDialog.ts +4 -2
  182. package/src/ts/client/grapesjs/PublicationManager.ts +180 -54
  183. package/src/ts/client/grapesjs/PublicationUi.ts +122 -86
  184. package/src/ts/client/grapesjs/breadcrumbs.ts +79 -0
  185. package/src/ts/client/grapesjs/css-props.ts +910 -0
  186. package/src/ts/client/grapesjs/eleventy.ts +11 -8
  187. package/{dist/plugins/client/page.js → src/ts/client/grapesjs/footer.ts} +24 -13
  188. package/src/ts/client/grapesjs/img.ts +147 -0
  189. package/src/ts/client/grapesjs/index.ts +234 -52
  190. package/src/ts/client/grapesjs/internal-links.test.ts +2 -1
  191. package/src/ts/client/grapesjs/internal-links.ts +14 -7
  192. package/src/ts/client/grapesjs/keymaps.ts +207 -0
  193. package/src/ts/client/grapesjs/new-page-dialog.ts +2 -3
  194. package/src/ts/client/grapesjs/page-panel.ts +92 -7
  195. package/src/ts/client/grapesjs/project-bar.ts +116 -72
  196. package/src/ts/client/grapesjs/resize-panel.ts +20 -0
  197. package/src/ts/client/grapesjs/rich-text.ts +21 -13
  198. package/src/ts/client/grapesjs/semantic.ts +3 -3
  199. package/src/ts/client/grapesjs/settings-sections.ts +117 -0
  200. package/src/ts/client/grapesjs/settings.ts +174 -96
  201. package/src/ts/client/grapesjs/storage.ts +98 -33
  202. package/src/ts/client/grapesjs/symbolDialogs.ts +3 -4
  203. package/src/ts/{plugins/server/AuthPlugin.ts → client/grapesjs/view-buttons.ts} +23 -19
  204. package/src/ts/client/index.ts +35 -16
  205. package/src/ts/client/publication-transformers.test.ts +207 -0
  206. package/src/ts/client/publication-transformers.ts +280 -0
  207. package/{dist/plugins/client/client/utils.js → src/ts/client/publish-custom-code-block.ts} +14 -8
  208. package/src/ts/client/publish-fonts.ts +36 -0
  209. package/src/ts/client/utils.ts +53 -1
  210. package/src/ts/constants.ts +31 -0
  211. package/src/ts/page.ts +2 -2
  212. package/src/ts/plugins/client/template.ts +125 -75
  213. package/src/ts/plugins/client/website-info.ts +84 -0
  214. package/src/ts/plugins/server/Cache.ts +6 -2
  215. package/src/ts/plugins/server/DownloadConnector.ts +146 -0
  216. package/src/ts/plugins/server/FtpConnector.ts +47 -21
  217. package/src/ts/plugins/server/GitlabConnector.ts +423 -108
  218. package/src/ts/plugins/server/GitlabHostingConnector.ts +215 -0
  219. package/src/ts/plugins/server/SslPlugin.ts +5 -5
  220. package/src/ts/plugins/server/StaticPlugin.ts +3 -30
  221. package/src/ts/server/api/connectorApi.ts +165 -28
  222. package/src/ts/server/api/publicationApi.ts +17 -24
  223. package/src/ts/server/api/websiteApi.ts +57 -14
  224. package/src/ts/server/cli.ts +7 -5
  225. package/src/ts/server/config.ts +9 -10
  226. package/src/ts/server/connectors/FsHosting.ts +14 -8
  227. package/src/ts/server/connectors/FsStorage.ts +40 -7
  228. package/src/ts/server/connectors/connectors.ts +8 -6
  229. package/src/ts/server/events.ts +17 -0
  230. package/src/ts/server/express.ts +2 -2
  231. package/src/ts/server/index.ts +6 -0
  232. package/src/ts/server/jobs.ts +14 -2
  233. package/src/ts/server/utils/BackwardCompat.ts +530 -529
  234. package/src/ts/types.ts +78 -34
  235. package/dist/plugins/client/client/utils.d.ts +0 -1
  236. package/dist/plugins/client/client/utils.js.map +0 -1
  237. package/dist/plugins/client/page.d.ts +0 -2
  238. package/dist/plugins/client/page.js.map +0 -1
  239. package/dist/plugins/client/plugins/client/publicationRenderer.d.ts +0 -2
  240. package/dist/plugins/client/plugins/client/publicationRenderer.js +0 -128
  241. package/dist/plugins/client/plugins/client/publicationRenderer.js.map +0 -1
  242. package/dist/plugins/server/plugins/server/AuthPlugin.d.ts +0 -1
  243. package/dist/plugins/server/plugins/server/AuthPlugin.js +0 -38
  244. package/dist/plugins/server/plugins/server/AuthPlugin.js.map +0 -1
  245. package/dist/plugins/server/plugins/server/HooksPlugin.d.ts +0 -7
  246. package/dist/plugins/server/plugins/server/HooksPlugin.js +0 -74
  247. package/dist/plugins/server/plugins/server/HooksPlugin.js.map +0 -1
  248. package/src/example-client-config.js +0 -64
  249. package/src/ts/plugins/client/publicationRenderer.ts +0 -128
  250. package/src/ts/plugins/server/HooksPlugin.ts +0 -76
@@ -15,13 +15,13 @@
15
15
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
16
  */
17
17
 
18
- import { API_CONNECTOR_LOGIN_CALLBACK, API_CONNECTOR_PATH, API_PATH, WEBSITE_DATA_FILE, WEBSITE_META_DATA_FILE } from '../../constants'
18
+ import { API_CONNECTOR_LOGIN_CALLBACK, API_CONNECTOR_PATH, API_PATH, WEBSITE_DATA_FILE } from '../../constants'
19
19
  import { ServerConfig } from '../../server/config'
20
20
  import { ConnectorFile, ConnectorFileContent, StatusCallback, StorageConnector, contentToBuffer, contentToString, toConnectorData } from '../../server/connectors/connectors'
21
- import { ApiError, ConnectorType, ConnectorUser, WebsiteData, WebsiteId, WebsiteMeta, WebsiteMetaFileContent } from '../../types'
21
+ import { ApiError, ConnectorType, ConnectorUser, WebsiteData, WebsiteId, WebsiteMeta, WebsiteMetaFileContent, JobStatus } from '../../types'
22
22
  import fetch from 'node-fetch'
23
23
  import crypto, { createHash } from 'crypto'
24
- import { PassThrough, Readable } from 'stream'
24
+ import { join } from 'path'
25
25
 
26
26
  /**
27
27
  * Gitlab connector
@@ -34,12 +34,18 @@ export interface GitlabOptions {
34
34
  clientSecret: string
35
35
  branch: string
36
36
  assetsFolder: string
37
+ //metaRepo: string
38
+ //metaRepoFile: string
39
+ repoPrefix: string
40
+ scope: string
41
+ domain: string
42
+ timeOut: number
37
43
  }
38
44
 
39
- interface GitlabToken {
40
- state: string
41
- codeVerifier: string
42
- codeChallenge: string
45
+ export interface GitlabToken {
46
+ state?: string
47
+ codeVerifier?: string
48
+ codeChallenge?: string
43
49
  token?: {
44
50
  access_token: string
45
51
  token_type: string
@@ -50,22 +56,22 @@ interface GitlabToken {
50
56
  scope: string
51
57
  }
52
58
  userId?: number
59
+ username?: string
53
60
  }
54
61
 
55
- interface GitlabSession {
56
- gitlab?: GitlabToken
57
- }
62
+ export type GitlabSession = Record<string, GitlabToken>
58
63
 
59
64
  interface GitlabAction {
60
- action: 'create' | 'delete' | 'move' | 'update'
61
- file_path: string
65
+ action: 'create' | 'delete' | 'move' | 'update' | 'cherry-pick'
66
+ file_path?: string
62
67
  content?: string
68
+ commit_id?: string
63
69
  }
64
70
 
65
71
  interface GitlabWriteFile {
66
72
  branch: string
67
73
  commit_message: string
68
- id: string
74
+ id?: string
69
75
  actions?: GitlabAction[]
70
76
  content?: string
71
77
  file_path?: string
@@ -90,98 +96,242 @@ interface GitlabCreateBranch {
90
96
  ref: string
91
97
  }
92
98
 
93
- async function wait(ms: number) {
94
- return new Promise(resolve => setTimeout(resolve, ms))
99
+ interface GitlabGetTags {
100
+ per_page?: number
101
+ }
102
+
103
+ interface GitlabCreateTag {
104
+ tag_name: string
105
+ ref: string
106
+ message: string
107
+ }
108
+
109
+ interface GitlabFetchCommits {
110
+ ref_name: string
111
+ since: string
112
+ }
113
+
114
+ interface MetaRepoFileContent {
115
+ websites: {
116
+ [websiteId: string]: {
117
+ meta: WebsiteMetaFileContent,
118
+ createdAt: string,
119
+ updatedAt: string,
120
+ }
121
+ }
95
122
  }
96
123
 
124
+ const svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
125
+ <svg
126
+ xmlns:svg="http://www.w3.org/2000/svg"
127
+ xmlns="http://www.w3.org/2000/svg"
128
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
129
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
130
+ width="1000"
131
+ height="963.197"
132
+ viewBox="0 0 1000 963.197"
133
+ version="1.1"
134
+ id="svg85">
135
+ <sodipodi:namedview
136
+ id="namedview87"
137
+ pagecolor="#ffffff"
138
+ bordercolor="#666666"
139
+ borderopacity="1.0"
140
+ inkscape:pageshadow="2"
141
+ inkscape:pageopacity="0.0"
142
+ inkscape:pagecheckerboard="0"
143
+ showgrid="false"
144
+ inkscape:zoom="1"
145
+ inkscape:cx="991.5"
146
+ inkscape:cy="964.5"
147
+ inkscape:window-width="1126"
148
+ inkscape:window-height="895"
149
+ inkscape:window-x="774"
150
+ inkscape:window-y="12"
151
+ inkscape:window-maximized="0"
152
+ inkscape:current-layer="svg85" />
153
+ <defs
154
+ id="defs74">
155
+ <style
156
+ id="style72">.cls-1{fill:#e24329;}.cls-2{fill:#fc6d26;}.cls-3{fill:#fca326;}</style>
157
+ </defs>
158
+ <g
159
+ id="LOGO"
160
+ transform="matrix(5.2068817,0,0,5.2068817,-489.30756,-507.76085)">
161
+ <path
162
+ class="cls-1"
163
+ 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"
164
+ id="path76" />
165
+ <path
166
+ class="cls-2"
167
+ 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"
168
+ id="path78" />
169
+ <path
170
+ class="cls-3"
171
+ 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"
172
+ id="path80" />
173
+ <path
174
+ class="cls-2"
175
+ 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"
176
+ id="path82" />
177
+ </g>
178
+ </svg>`
179
+ const encodedSvg = encodeURIComponent(svg)
180
+ const ICON = '/assets/gitlab.png'
181
+
97
182
  export default class GitlabConnector implements StorageConnector {
98
183
  connectorId = 'gitlab'
99
184
  connectorType = ConnectorType.STORAGE
100
185
  displayName = 'GitLab'
101
- icon = 'http://gitlab.lcqb.upmc.fr/assets/favicon-075eba76312e8421991a0c1f89a89ee81678bcde72319dd3e8047e2a47cd3a42.ico'
186
+ icon = ICON
102
187
  disableLogout = false
103
- color = '#ffffff'
104
- background = '#FC6D26'
188
+ color = '#2B1B63'
189
+ background = 'rgba(252, 109, 38, 0.2)'
105
190
  options: GitlabOptions
106
191
 
107
192
  constructor(private config: ServerConfig, opts: Partial<GitlabOptions>) {
108
193
  this.options = {
109
194
  branch: 'main',
110
- assetsFolder: '',
195
+ assetsFolder: 'assets',
196
+ //metaRepo: 'silex-meta',
197
+ //metaRepoFile: 'websites.json',
198
+ repoPrefix: 'silex_',
199
+ scope: 'api', // 'api+read_api+read_user+read_repository+write_repository+email+sudo+profile+openid'
111
200
  ...opts,
112
201
  } as GitlabOptions
113
202
  if(!this.options.clientId) throw new Error('Missing Gitlab client ID')
114
203
  if(!this.options.clientSecret) throw new Error('Missing Gitlab client secret')
204
+ if(!this.options.domain) throw new Error('Missing Gitlab domain')
205
+ if(!this.options.timeOut) this.options.timeOut = 15000 /* default value */
115
206
  }
116
207
 
117
208
  // **
118
209
  // Convenience methods for the Gitlab API
119
210
  private getAssetPath(path: string): string {
120
- return `${this.options.assetsFolder}${path}`
211
+ return encodeURIComponent(join(this.options.assetsFolder, path))
121
212
  }
122
213
 
123
- private async createFile(session: GitlabSession, websiteId: WebsiteId, path: string, content: string, isBase64 = false): Promise<void> {
214
+ isUsingOfficialInstance(): boolean {
215
+ const gitlabDomainRegexp = /(^|\b)(gitlab\.com)($|\b)/
216
+ return gitlabDomainRegexp.test(this.options.domain)
217
+ }
218
+
219
+ async createFile(session: GitlabSession, websiteId: WebsiteId, path: string, content: string, isBase64 = false): Promise<void> {
124
220
  // Remove leading slash
125
221
  const safePath = path.replace(/^\//, '')
222
+ const encodePath = decodeURIComponent(path)
126
223
  return this.callApi(session, `api/v4/projects/${websiteId}/repository/files/${safePath}`, 'POST', {
127
224
  id: websiteId,
128
225
  branch: this.options.branch,
129
226
  content,
130
- commit_message: `Create file ${path} from Silex`,
227
+ commit_message: `Create file ${encodePath} from Silex`,
131
228
  encoding: isBase64 ? 'base64' : undefined,
132
229
  })
133
230
  }
134
231
 
135
- private async updateFile(session: GitlabSession, websiteId: WebsiteId, path: string, content: string, isBase64 = false): Promise<void> {
232
+ async updateFile(session: GitlabSession, websiteId: WebsiteId, path: string, content: string, isBase64 = false): Promise<void> {
136
233
  // Remove leading slash
137
234
  const safePath = path.replace(/^\//, '')
235
+ const encodePath = decodeURIComponent(path)
138
236
  return this.callApi(session, `api/v4/projects/${websiteId}/repository/files/${safePath}`, 'PUT', {
139
237
  id: websiteId,
140
238
  branch: this.options.branch,
141
239
  content: await contentToString(content),
142
- commit_message: `Update website asset ${path} from Silex`,
240
+ commit_message: `Update website asset ${encodePath} from Silex`,
143
241
  encoding: isBase64 ? 'base64' : undefined,
144
242
  })
145
243
  }
146
244
 
147
- private async callApi(session: GitlabSession, path: string, method: 'POST' | 'GET' | 'PUT' | 'DELETE' = 'GET', body: GitlabWriteFile | GitlabGetToken | GitlabWebsiteName | GitlabCreateBranch | null = null, params: any = {}): Promise<any> {
148
- const token = session?.gitlab?.token
149
- const tokenParam = token ? `access_token=${token.access_token}&` : ''
150
- const paramsStr = Object.entries(params).map(([k, v]) => `${k}=${encodeURIComponent((v as any).toString())}`).join('&')
151
- const url = `https://gitlab.com/${path}?${tokenParam}${paramsStr}`
245
+ async readFile(session: GitlabSession, websiteId: string, fileName: string): Promise<Buffer> {
246
+ const safePath = fileName.replace(/^\//, '')
247
+ // Call the API
248
+ const url = `${this.options.domain}/api/v4/projects/${websiteId}/repository/files/${safePath}?ref=${this.options.branch}&access_token=${this.getSessionToken(session).token?.access_token}`
152
249
  const response = await fetch(url, {
153
- method,
250
+ method: 'GET',
154
251
  headers: {
155
252
  'Content-Type': 'application/json',
156
253
  },
157
- body: body ? JSON.stringify(body) : undefined
158
254
  })
159
- let json: { message: string, error: string } | any
160
- // Handle the case when the server returns an non-JSON response (e.g. 400 Bad Request)
161
- const text = await response.text()
255
+ const json = await response.json()
256
+ if(!response.ok) throw new ApiError(`Error reading file "${fileName}" from Gitlab: ${json?.message ?? json?.error ?? response.statusText}`, response.status)
257
+ // From base64 string to buffer
258
+ const buf = Buffer.from(json.content, 'base64')
259
+ // Return the image bytes
260
+ return buf
261
+ }
262
+
263
+ /*
264
+ * Get the meta repo path for the current user
265
+ * The meta repo contains a JSON file which contains the list of websites
266
+ */
267
+ //private getMetaRepoPath(session: GitlabSession): string {
268
+ // if(!this.getSessionToken(session).username) throw new ApiError('Missing Gitlab user ID. User not logged in?', 401)
269
+ // return encodeURIComponent(`${this.getSessionToken(session).username}/${this.options.metaRepo}`)
270
+ //}
271
+
272
+ ///**
273
+ // * Initialize the storage with a meta repo
274
+ // */
275
+ //private async initStorage(session: GitlabSession): Promise<void> {
276
+ // // Create the meta repo
277
+ // try {
278
+ // const project = await this.callApi(session, 'api/v4/projects/', 'POST', {
279
+ // name: this.options.metaRepo,
280
+ // }) as any
281
+ // return this.createFile(session, this.getMetaRepoPath(session), this.options.metaRepoFile, JSON.stringify({
282
+ // websites: {}
283
+ // } as MetaRepoFileContent))
284
+ // } catch (e) {
285
+ // console.error('Could not init storage', e.statusCode, e.httpStatusCode, e)
286
+ // throw e
287
+ // }
288
+ //}
289
+
290
+ /**
291
+ * Call the Gitlab API with the user's token and handle errors
292
+ */
293
+ async callApi(session: GitlabSession, path: string, method: 'POST' | 'GET' | 'PUT' | 'DELETE' = 'GET', body: GitlabWriteFile | GitlabGetToken | GitlabWebsiteName | GitlabCreateBranch | GitlabGetTags | GitlabCreateTag | GitlabFetchCommits | null = null, params: any = {}): Promise<any> {
294
+ const token = this.getSessionToken(session).token
295
+ const tokenParam = token ? `access_token=${token.access_token}&` : ''
296
+ const paramsStr = Object.entries(params).map(([k, v]) => `${k}=${encodeURIComponent((v as any).toString())}`).join('&')
297
+ const url = `${this.options.domain}/${path}?${tokenParam}${paramsStr}`
298
+ const headers = {
299
+ 'Content-Type': 'application/json',
300
+ }
301
+ if (method === 'GET' && body) {
302
+ console.error('Gitlab API error (4) - GET request with body', {url, method, body, params})
303
+ }
304
+ // With or without body
305
+ let response
162
306
  try {
163
- json = JSON.parse(text)
307
+ response = await fetch(url, body && method !== 'GET' ? {
308
+ method,
309
+ headers,
310
+ body: body ? JSON.stringify(body) : undefined
311
+ } : {
312
+ method,
313
+ headers,
314
+ })
164
315
  } catch (e) {
165
- if(!response.ok) {
166
- // A real error
167
- throw e
168
- } else {
169
- // Useless error linked to the fact that the response is not JSON
170
- //console.error('Gitlab API error - could not parse response', response.status, response.statusText, {url, method, body, params, text})
171
- return text
172
- }
316
+ console.error('Gitlab API error (0)', e)
317
+ throw new ApiError(`Gitlab API error (0): ${e.message} ${e.code} ${e.name} ${e.type}`, 500)
173
318
  }
174
- if(!response.ok) {
175
- if (response.status === 401 && session?.gitlab?.token?.refresh_token) {
319
+ let json: { message: string, error: string } | any
320
+ // Handle the case when the server returns a non-JSON response (e.g. 400 Bad Request)
321
+ const text = await response.text()
322
+ if (!response.ok) {
323
+ if (text.includes('A file with this name doesn\'t exist')) {
324
+ throw new ApiError('Gitlab API error (5): Not Found', 404)
325
+ } else if (response.status === 401 && this.getSessionToken(session).token?.refresh_token) {
176
326
  // Refresh the token
177
- const token = session?.gitlab?.token
327
+ const token = this.getSessionToken(session).token
178
328
  const body = {
179
329
  grant_type: 'refresh_token',
180
- refresh_token: token.refresh_token,
330
+ refresh_token: token?.refresh_token,
181
331
  client_id: this.options.clientId,
182
332
  client_secret: this.options.clientSecret,
183
333
  }
184
- const response = await fetch('https://gitlab.com/oauth/token', {
334
+ const response = await fetch(this.options.domain + '/oauth/token', {
185
335
  method: 'POST',
186
336
  headers: {
187
337
  'Content-Type': 'application/json',
@@ -190,18 +340,38 @@ export default class GitlabConnector implements StorageConnector {
190
340
  })
191
341
  const refreshJson = await response.json()
192
342
  if (response.ok) {
193
- session.gitlab.token = {
194
- ...token,
195
- ...refreshJson,
196
- }
343
+ this.setSessionToken(session, {
344
+ token: {
345
+ ...token,
346
+ ...refreshJson,
347
+ },
348
+ } as GitlabToken)
197
349
  return await this.callApi(session, path, method, body as any, params)
198
350
  } else {
199
- //console.error('Gitlab API error - could not refresh token', response.status, response.statusText, { url, method, body, params, refreshJson })
200
- throw new ApiError(`Gitlab API error: ${refreshJson?.message ?? response.statusText}`, response.status)
351
+ const message = typeof refreshJson?.message === 'object' ? Object.entries(refreshJson.message).map(entry => entry.join(' ')).join(' ') : refreshJson?.message ?? refreshJson?.error ?? response.statusText
352
+ console.error('Gitlab API error (2) - could not refresh token', response.status, response.statusText, {message}, 'refresh_token:', token?.refresh_token)
353
+ // Workaround for when the token is invalid
354
+ // It happens often which is not normal (refresh token should last 6 months)
355
+ this.logout(session)
356
+ // Notify the user
357
+ throw new ApiError(`Gitlab API error (2): ${message}`, response.status)
201
358
  }
202
359
  } else {
203
- //console.error('Gitlab API error', response.status, response.statusText, { url, method, body, params, json })
204
- throw new ApiError(`Gitlab API error: ${json?.message ?? json?.error ?? response.statusText}`, response.status)
360
+ const message = typeof json?.message === 'object' ? Object.entries(json.message).map(entry => entry.join(' ')).join(' ') : json?.message ?? json?.error ?? response.statusText
361
+ console.error('Gitlab API error (1)', response.status, response.statusText, {url, method, body, params, text, message})
362
+ throw new ApiError(`Gitlab API error (1): ${message}`, response.status)
363
+ }
364
+ }
365
+ try {
366
+ json = JSON.parse(text)
367
+ } catch (e) {
368
+ if(!response.ok) {
369
+ // A real error
370
+ throw e
371
+ } else {
372
+ // Useless error linked to the fact that the response is not JSON
373
+ console.error('Gitlab API error (3) - could not parse response', response.status, response.statusText, {url, method, body, params, text})
374
+ return text
205
375
  }
206
376
  }
207
377
  return json
@@ -245,14 +415,23 @@ export default class GitlabConnector implements StorageConnector {
245
415
  const codeChallenge = await this.generateCodeChallenge(codeVerifier)
246
416
 
247
417
  // Store the code verifier and code challenge in the session
248
- session.gitlab = {
249
- ...session.gitlab,
418
+ this.setSessionToken(session, {
419
+ ...this.getSessionToken(session),
250
420
  state,
251
421
  codeVerifier,
252
422
  codeChallenge,
253
- }
254
- const scope = 'api+read_api+read_user+read_repository+write_repository+email+sudo+profile+openid'
255
- return `https://gitlab.com/oauth/authorize?client_id=${this.options.clientId}&redirect_uri=${redirect_uri}&response_type=code&state=${session.gitlab.state}&scope=${scope}&code_challenge=${codeChallenge}&code_challenge_method=S256`
423
+ })
424
+ return `${this.options.domain}/oauth/authorize?client_id=${this.options.clientId}&redirect_uri=${redirect_uri}&response_type=code&state=${this.getSessionToken(session).state}&scope=${this.options.scope}&code_challenge=${codeChallenge}&code_challenge_method=S256`
425
+ }
426
+
427
+ getSessionToken(session: GitlabSession | undefined): GitlabToken {
428
+ return (session ?? {})[this.connectorId] ?? {}
429
+ }
430
+ setSessionToken(session: GitlabSession, token: GitlabToken): void {
431
+ session[this.connectorId] = token
432
+ }
433
+ resetSessionToken(session: GitlabSession): void {
434
+ delete session[this.connectorId]
256
435
  }
257
436
 
258
437
  getOptions(formData: object): object {
@@ -268,7 +447,7 @@ export default class GitlabConnector implements StorageConnector {
268
447
  }
269
448
 
270
449
  async isLoggedIn(session: GitlabSession): Promise<boolean> {
271
- return !!session?.gitlab?.token
450
+ return !!this.getSessionToken(session).token
272
451
  }
273
452
 
274
453
  /**
@@ -277,11 +456,21 @@ export default class GitlabConnector implements StorageConnector {
277
456
  * OAuth2 Step #2 from https://docs.gitlab.com/ee/api/oauth2.html#authorization-code-with-proof-key-for-code-exchange-pkce
278
457
  */
279
458
  async setToken(session: GitlabSession, loginResult: any): Promise<void> {
280
- if(!loginResult.state || loginResult.state !== session.gitlab?.state) throw new ApiError('Invalid state', 401)
281
- if(!session.gitlab?.codeVerifier) throw new ApiError('Missing code verifier', 401)
282
- if(!session.gitlab?.codeChallenge) throw new ApiError('Missing code challenge', 401)
459
+ const sessionToken = this.getSessionToken(session)
460
+ if(!loginResult.state || loginResult.state !== sessionToken?.state) {
461
+ this.logout(session)
462
+ throw new ApiError('Invalid state', 401)
463
+ }
464
+ if(!sessionToken?.codeVerifier) {
465
+ this.logout(session)
466
+ throw new ApiError('Missing code verifier', 401)
467
+ }
468
+ if(!sessionToken?.codeChallenge) {
469
+ this.logout(session)
470
+ throw new ApiError('Missing code challenge', 401)
471
+ }
283
472
 
284
- const token = await fetch('https://gitlab.com/oauth/token', {
473
+ const response = await fetch(this.options.domain + '/oauth/token', {
285
474
  method: 'POST',
286
475
  headers: {
287
476
  'Content-Type': 'application/json',
@@ -292,20 +481,31 @@ export default class GitlabConnector implements StorageConnector {
292
481
  code: loginResult.code,
293
482
  grant_type: 'authorization_code',
294
483
  redirect_uri: this.getRedirect(),
295
- code_verifier: session.gitlab.codeVerifier,
484
+ code_verifier: sessionToken.codeVerifier,
296
485
  }),
297
486
  })
298
- .then((response) => response.json())
299
487
 
300
- session.gitlab = { token, state: session.gitlab.state, codeVerifier: session.gitlab.codeVerifier, codeChallenge: session.gitlab.codeChallenge }
488
+ const token = await response.json()
489
+
490
+ // Store the token in the session
491
+ this.setSessionToken(session, {
492
+ token,
493
+ ...this.getSessionToken(session),
494
+ })
301
495
 
302
496
  // We need to get the user ID for listWebsites
303
497
  const user = await this.callApi(session, 'api/v4/user') as any
304
- session.gitlab.userId = user.id
498
+
499
+ // Store the user details in the session
500
+ this.setSessionToken(session, {
501
+ ...this.getSessionToken(session),
502
+ userId: user.id,
503
+ username: user.username,
504
+ })
305
505
  }
306
506
 
307
507
  async logout(session: GitlabSession): Promise<void> {
308
- delete session.gitlab
508
+ this.resetSessionToken(session)
309
509
  }
310
510
 
311
511
  async getUser(session: GitlabSession): Promise<ConnectorUser> {
@@ -313,19 +513,49 @@ export default class GitlabConnector implements StorageConnector {
313
513
  return {
314
514
  name: user.name,
315
515
  email: user.email,
516
+ picture: user.avatar_url,
316
517
  storage: await toConnectorData(session, this as StorageConnector),
317
518
  }
318
519
  }
319
520
 
320
521
  async listWebsites(session: GitlabSession): Promise<WebsiteMeta[]> {
321
- const projects = await this.callApi(session, `api/v4/users/${session.gitlab?.userId}/projects`) as any[]
322
- return projects.map(p => ({
323
- websiteId: p.id,
324
- name: p.name,
325
- createdAt: p.created_at,
326
- updatedAt: p.last_activity_at,
327
- connectorUserSettings: {},
328
- }))
522
+ //try {
523
+ // const result = await this.callApi(session, `api/v4/projects/${this.getMetaRepoPath(session)}/repository/files/${this.options.metaRepoFile}`, 'GET', null, {
524
+ // ref: this.options.branch,
525
+ // })
526
+ // const { content } = result
527
+ // const contentDecoded = Buffer.from(content, 'base64').toString('utf8')
528
+ // const websites = (JSON.parse(contentDecoded) as MetaRepoFileContent).websites
529
+ // return Object.entries(websites).map(([websiteId, {meta, createdAt, updatedAt}]) => ({
530
+ // websiteId,
531
+ // createdAt: new Date(createdAt),
532
+ // updatedAt: new Date(updatedAt),
533
+ // ...meta,
534
+ // }))
535
+ //} catch (e) {
536
+ // console.error('Could not list websites', e.statusCode, e.httpStatusCode, e.code)
537
+ // if (e.statusCode === 404 || e.httpStatusCode === 404) {
538
+ // await this.initStorage(session)
539
+ // return []
540
+ // } else {
541
+ // throw e
542
+ // }
543
+ //}
544
+ const userId = this.getSessionToken(session).userId
545
+ if(!userId) {
546
+ this.logout(session)
547
+ throw new ApiError('Missing Gitlab user ID. User not logged in?', 401)
548
+ }
549
+ const projects = await this.callApi(session, `api/v4/users/${userId}/projects`) as any[]
550
+ return projects
551
+ .filter(p => p.name.startsWith(this.options.repoPrefix))
552
+ .map(p => ({
553
+ websiteId: p.id,
554
+ name: p.name.replace(this.options.repoPrefix, ''),
555
+ createdAt: p.created_at,
556
+ updatedAt: p.last_activity_at,
557
+ connectorUserSettings: {},
558
+ }))
329
559
  }
330
560
 
331
561
  async readWebsite(session: GitlabSession, websiteId: string): Promise<WebsiteData> {
@@ -340,10 +570,10 @@ export default class GitlabConnector implements StorageConnector {
340
570
 
341
571
  async createWebsite(session: GitlabSession, websiteMeta: WebsiteMetaFileContent): Promise<WebsiteId> {
342
572
  const project = await this.callApi(session, 'api/v4/projects/', 'POST', {
343
- name: websiteMeta.name,
573
+ name: this.options.repoPrefix + websiteMeta.name,
344
574
  }) as any
345
575
  await this.createFile(session, project.id, WEBSITE_DATA_FILE, JSON.stringify({} as WebsiteData))
346
- await this.createFile(session, project.id, WEBSITE_META_DATA_FILE, JSON.stringify(websiteMeta))
576
+ //await this.createFile(session, project.id, WEBSITE_META_DATA_FILE, JSON.stringify(websiteMeta))
347
577
  //await this.updateWebsite(session, project.id, {} as WebsiteData)
348
578
  //await this.setWebsiteMeta(session, project.id, websiteMeta)
349
579
  return project.id
@@ -354,20 +584,85 @@ export default class GitlabConnector implements StorageConnector {
354
584
  branch: this.options.branch,
355
585
  commit_message: 'Update website data from Silex',
356
586
  content: JSON.stringify(websiteData),
357
- file_path: WEBSITE_DATA_FILE,
358
587
  id: websiteId,
359
588
  })
360
589
  }
361
590
 
362
591
  async deleteWebsite(session: GitlabSession, websiteId: WebsiteId): Promise<void> {
592
+ // Delete repo
363
593
  await this.callApi(session, `api/v4/projects/${websiteId}`, 'DELETE')
594
+ //// Load the meta repo data
595
+ //const file = await this.callApi(session, `api/v4/projects/${this.getMetaRepoPath(session)}/repository/files/${this.options.metaRepoFile}`, 'GET', null, {
596
+ // ref: this.options.branch,
597
+ //})
598
+ //const metaRepo = JSON.parse(Buffer.from(file.content, 'base64').toString('utf8')) as MetaRepoFileContent
599
+ //const data = metaRepo.websites[websiteId]
600
+ //if(!data) throw new ApiError(`Website ${websiteId} not found`, 404)
601
+ //// Update or create the website meta data
602
+ //delete metaRepo.websites[websiteId]
603
+ //// Save the meta repo data
604
+ //const project = await this.callApi(session, `api/v4/projects/${this.getMetaRepoPath(session)}/repository/files/${this.options.metaRepoFile}`, 'PUT', {
605
+ // branch: this.options.branch,
606
+ // commit_message: `Delete meta data of ${data.meta.name} (${websiteId}) from Silex`,
607
+ // content: JSON.stringify(metaRepo),
608
+ // file_path: this.options.metaRepoFile,
609
+ //})
610
+ }
611
+
612
+ async duplicateWebsite(session: GitlabSession, websiteId: string): Promise<void> {
613
+ // Get the repo meta data
614
+ const meta = await this.getWebsiteMeta(session, websiteId)
615
+ // List all the repository files
616
+ const blobs = await this.callApi(session, `api/v4/projects/${websiteId}/repository/tree`, 'GET', null, {
617
+ recursive: true,
618
+ })
619
+ const files = blobs
620
+ .filter(item => item.type === 'blob')
621
+ .map(item => item.path)
622
+ // Create a new repo
623
+ const newId = await this.createWebsite(session, {
624
+ ...meta,
625
+ name: meta.name + ' Copy ' + new Date().toISOString().replace(/T.*/, '') + ' ' + Math.random().toString(36).substring(2, 4),
626
+ })
627
+ // Upload all files
628
+ for(const file of files) {
629
+ const path = encodeURIComponent(file)
630
+ const content = await this.readFile(session, websiteId, path)
631
+ // From buffer to string
632
+ const contentStr = content.toString('base64')
633
+ switch(file) {
634
+ case WEBSITE_DATA_FILE:
635
+ await this.updateFile(session, newId, path, contentStr, true)
636
+ break
637
+ default:
638
+ await this.createFile(session, newId, path, contentStr, true)
639
+ }
640
+ }
364
641
  }
365
642
 
366
643
  async getWebsiteMeta(session: GitlabSession, websiteId: WebsiteId): Promise<WebsiteMeta> {
367
- const project = await this.callApi(session, `api/v4/projects/${websiteId}/repository/files/${WEBSITE_META_DATA_FILE}`) as any
644
+ //const file = await this.callApi(session, `api/v4/projects/${this.getMetaRepoPath(session)}/repository/files/${this.options.metaRepoFile}`, 'GET', null, {
645
+ // ref: this.options.branch,
646
+ //})
647
+ //const metaRepo = JSON.parse(Buffer.from(file.content, 'base64').toString('utf8')) as MetaRepoFileContent
648
+ //if(!metaRepo.websites[websiteId]) throw new ApiError(`Website ${websiteId} not found`, 404)
649
+ //return {
650
+ // websiteId,
651
+ // createdAt: new Date(metaRepo.websites[websiteId].createdAt),
652
+ // updatedAt: new Date(metaRepo.websites[websiteId].updatedAt),
653
+ // ...metaRepo.websites[websiteId].meta,
654
+ //}
655
+ // const response = await this.callApi(session, `api/v4/projects/${websiteId}/repository/files/${WEBSITE_META_DATA_FILE}`, 'GET', null, {
656
+ // ref: this.options.branch,
657
+ // }) as any
658
+ // Base64 to string to JSON
659
+ // const contentDecoded = Buffer.from(response.content, 'base64').toString('utf8')
660
+ // const websiteMeta = JSON.parse(contentDecoded) as WebsiteMetaFileContent
661
+ const project = await this.callApi(session, `api/v4/projects/${websiteId}`)
368
662
  return {
369
- websiteId: project.id,
370
- name: project.name,
663
+ websiteId,
664
+ name: project.name.replace(this.options.repoPrefix, ''),
665
+ imageUrl: project.avatar_url,
371
666
  createdAt: project.created_at,
372
667
  updatedAt: project.last_activity_at,
373
668
  connectorUserSettings: {},
@@ -375,49 +670,69 @@ export default class GitlabConnector implements StorageConnector {
375
670
  }
376
671
 
377
672
  async setWebsiteMeta(session: GitlabSession, websiteId: WebsiteId, websiteMeta: WebsiteMetaFileContent): Promise<void> {
378
- const project = await this.callApi(session, `api/v4/projects/${websiteId}/repository/files/${WEBSITE_META_DATA_FILE}`, 'PUT', {
379
- branch: this.options.branch,
380
- commit_message: 'Update website meta data from Silex',
381
- content: JSON.stringify(websiteMeta),
382
- file_path: WEBSITE_META_DATA_FILE,
383
- id: websiteId,
384
- })
673
+ //// Load the meta repo data
674
+ //const file = await this.callApi(session, `api/v4/projects/${this.getMetaRepoPath(session)}/repository/files/${this.options.metaRepoFile}`, 'GET', null, {
675
+ // ref: this.options.branch,
676
+ //})
677
+ //const metaRepo = JSON.parse(Buffer.from(file.content, 'base64').toString('utf8')) as MetaRepoFileContent
678
+ //// Update or create the website meta data
679
+ //metaRepo.websites[websiteId] = {
680
+ // updatedAt: new Date().toISOString(),
681
+ // createdAt: metaRepo.websites[websiteId]?.createdAt ?? new Date().toISOString(),
682
+ // meta: websiteMeta,
683
+ //}
684
+ //// Save the meta repo data
685
+ //const project = await this.callApi(session, `api/v4/projects/${this.getMetaRepoPath(session)}/repository/files/${this.options.metaRepoFile}`, 'PUT', {
686
+ // branch: this.options.branch,
687
+ // commit_message: `Update website meta data of ${websiteMeta.name} (${websiteId}) from Silex`,
688
+ // content: JSON.stringify(metaRepo),
689
+ // file_path: this.options.metaRepoFile,
690
+ //})
691
+
692
+ // Rename the repo if needed
693
+ const oldMeta = await this.getWebsiteMeta(session, websiteId)
694
+ if(websiteMeta.name !== oldMeta.name) {
695
+ await this.callApi(session, `api/v4/projects/${websiteId}`, 'PUT', {
696
+ name: this.options.repoPrefix + websiteMeta.name,
697
+ })
698
+ }
699
+ //// Update the metadata file
700
+ //await this.callApi(session, `api/v4/projects/${websiteId}/repository/files/${WEBSITE_META_DATA_FILE}`, 'PUT', {
701
+ // branch: this.options.branch,
702
+ // commit_message: 'Update website meta data from Silex',
703
+ // content: JSON.stringify(websiteMeta),
704
+ // file_path: WEBSITE_META_DATA_FILE,
705
+ // id: websiteId,
706
+ //})
385
707
  }
386
708
 
387
709
  async writeAssets(session: GitlabSession, websiteId: string, files: ConnectorFile[], status?: StatusCallback | undefined): Promise<void> {
710
+ status && await status({ message: 'in progress...', status: JobStatus.IN_PROGRESS })
388
711
  // For each file
389
712
  for (const file of files) {
390
713
  // Convert to base64
391
714
  const content = (await contentToBuffer(file.content)).toString('base64')
715
+ const path = this.getAssetPath(file.path)
716
+
392
717
  try {
393
- await this.updateFile(session, websiteId, this.getAssetPath(file.path), content, true)
718
+ await this.updateFile(session, websiteId, path, content, true)
394
719
  } catch (e) {
395
720
  // If the file does not exist, create it
396
- if (e.statusCode === 404 || e.message.endsWith('A file with this name doesn\'t exist')) {
397
- await this.createFile(session, websiteId, file.path, content, true)
721
+ if (e.statusCode === 404 || e.httpStatusCode === 404 || e.message.endsWith('A file with this name doesn\'t exist')) {
722
+ await this.createFile(session, websiteId, path, content, true)
398
723
  } else {
724
+ status && await status({ message: 'Error', status: JobStatus.ERROR })
399
725
  throw e
400
726
  }
401
727
  }
402
728
  }
729
+ status && await status({ message: 'Successfull', status: JobStatus.SUCCESS })
403
730
  }
404
731
 
405
732
  async readAsset(session: GitlabSession, websiteId: string, fileName: string): Promise<ConnectorFileContent> {
406
733
  // Remove leading slash
407
734
  const finalPath = this.getAssetPath(fileName)
408
- // Call the API
409
- const url = `https://gitlab.com/api/v4/projects/${websiteId}/repository/files/${finalPath}?ref=${this.options.branch}&access_token=${session.gitlab?.token?.access_token}`
410
- const response = await fetch(url, {
411
- method: 'GET',
412
- headers: {
413
- 'Content-Type': 'application/json',
414
- },
415
- })
416
- const json = await response.json()
417
- if(!response.ok) throw new ApiError(`Gitlab API error: ${json?.message ?? json?.error ?? response.statusText}`, response.status)
418
- // From base64 string to buffer
419
- const buf = Buffer.from(json.content, 'base64')
420
- return buf
735
+ return this.readFile(session, websiteId, finalPath)
421
736
  }
422
737
 
423
738
  async deleteAssets(session: GitlabSession, websiteId: string, fileNames: string[]): Promise<void> {