@silexlabs/silex 3.0.1-alpha.0 → 3.0.2
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.
- package/.env.default +20 -0
- package/.silex.js +67 -8
- package/LICENSE +661 -203
- package/README.md +37 -35
- package/dist/client/client/api.d.ts +90 -0
- package/dist/client/client/assetUrl.d.ts +54 -0
- package/dist/client/client/config.d.ts +102 -0
- package/dist/client/client/events.d.ts +23 -0
- package/dist/client/client/expose.d.ts +8 -0
- package/dist/client/client/grapesjs/LoginDialog.d.ts +15 -0
- package/dist/client/client/grapesjs/PublicationManager.d.ts +73 -0
- package/dist/client/client/grapesjs/PublicationUi.d.ts +59 -0
- package/dist/client/client/grapesjs/blocks.d.ts +1 -0
- package/dist/client/client/grapesjs/breadcrumbs.d.ts +1 -0
- package/dist/client/client/grapesjs/css-props.d.ts +6 -0
- package/dist/client/client/grapesjs/footer.d.ts +2 -0
- package/dist/client/client/grapesjs/img.d.ts +6 -0
- package/dist/client/client/grapesjs/index.d.ts +9 -0
- package/dist/client/client/grapesjs/internal-links.d.ts +1 -0
- package/dist/client/client/grapesjs/keymaps.d.ts +123 -0
- package/dist/client/client/grapesjs/new-page-dialog.d.ts +2 -0
- package/dist/client/client/grapesjs/openImport.d.ts +9 -0
- package/dist/client/client/grapesjs/page-panel.d.ts +9 -0
- package/dist/client/client/grapesjs/project-bar.d.ts +21 -0
- package/dist/client/client/grapesjs/resize-panel.d.ts +1 -0
- package/dist/client/client/grapesjs/rich-text.d.ts +4 -0
- package/dist/client/client/grapesjs/semantic.d.ts +1 -0
- package/dist/client/client/grapesjs/settings-sections.d.ts +16 -0
- package/dist/client/client/grapesjs/settings.d.ts +4 -0
- package/dist/client/client/grapesjs/storage.d.ts +2 -0
- package/dist/client/client/grapesjs/symbolDialogs.d.ts +3 -0
- package/dist/client/client/grapesjs/view-buttons.d.ts +1 -0
- package/dist/client/client/index.d.ts +10 -0
- package/dist/client/client/publication-transformers.d.ts +42 -0
- package/dist/client/client/publish-custom-code-block.d.ts +4 -0
- package/dist/client/client/publish-fonts.d.ts +4 -0
- package/dist/client/client/utils.d.ts +22 -0
- package/dist/client/constants.d.ts +32 -0
- package/dist/client/css/admin.css +1114 -328
- package/dist/client/css/admin.css.map +1 -0
- package/dist/client/grapesjs-filter-styles/dist/index.d.ts +4 -0
- package/dist/client/grapesjs-fonts/dist/index.d.ts +4 -0
- package/dist/client/grapesjs-fonts/src/commands.d.ts +3 -0
- package/dist/client/grapesjs-fonts/src/fonts.d.ts +4 -0
- package/dist/client/grapesjs-storage-rate-limit/dist/index.d.ts +4 -0
- package/dist/client/grapesjs-ui-suggest-classes/dist/index.d.ts +4 -0
- package/dist/client/index.html +3 -2
- package/dist/client/js/main.js +356 -131
- package/dist/client/js/main.js.LICENSE.txt +15 -1
- package/dist/client/page.d.ts +2 -0
- package/dist/client/silex-lib/src/ts/client/api.d.ts +90 -0
- package/dist/client/silex-lib/src/ts/client/assetUrl.d.ts +54 -0
- package/dist/client/silex-lib/src/ts/client/config.d.ts +102 -0
- package/dist/client/silex-lib/src/ts/client/events.d.ts +23 -0
- package/dist/client/silex-lib/src/ts/client/expose.d.ts +8 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/LoginDialog.d.ts +15 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/PublicationManager.d.ts +73 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/PublicationUi.d.ts +59 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/blocks.d.ts +1 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/breadcrumbs.d.ts +1 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/css-props.d.ts +6 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/footer.d.ts +2 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/img.d.ts +6 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/index.d.ts +9 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/internal-links.d.ts +1 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/keymaps.d.ts +123 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/new-page-dialog.d.ts +2 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/openImport.d.ts +9 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/page-panel.d.ts +9 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/project-bar.d.ts +21 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/resize-panel.d.ts +1 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/rich-text.d.ts +4 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/semantic.d.ts +1 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/settings-sections.d.ts +16 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/settings.d.ts +4 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/storage.d.ts +2 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/symbolDialogs.d.ts +3 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/view-buttons.d.ts +1 -0
- package/dist/client/silex-lib/src/ts/client/index.d.ts +10 -0
- package/dist/client/silex-lib/src/ts/client/publication-transformers.d.ts +42 -0
- package/dist/client/silex-lib/src/ts/client/publish-custom-code-block.d.ts +4 -0
- package/dist/client/silex-lib/src/ts/client/publish-fonts.d.ts +4 -0
- package/dist/client/silex-lib/src/ts/client/utils.d.ts +22 -0
- package/dist/client/silex-lib/src/ts/constants.d.ts +32 -0
- package/dist/client/silex-lib/src/ts/page.d.ts +2 -0
- package/dist/client/silex-lib/src/ts/types.d.ts +339 -0
- package/dist/client/types.d.ts +339 -0
- package/dist/plugins/client/client/events.d.ts +23 -0
- package/dist/plugins/client/client/events.js +43 -0
- package/dist/plugins/client/client/events.js.map +1 -0
- package/dist/plugins/client/plugins/client/template.d.ts +2 -0
- package/dist/plugins/client/plugins/client/template.js +234 -0
- package/dist/plugins/client/plugins/client/template.js.map +1 -0
- package/dist/plugins/client/plugins/client/website-info.d.ts +1 -0
- package/dist/plugins/client/plugins/client/website-info.js +72 -0
- package/dist/plugins/client/plugins/client/website-info.js.map +1 -0
- package/dist/plugins/client/types.d.ts +339 -0
- package/dist/plugins/client/types.js +80 -0
- package/dist/plugins/client/types.js.map +1 -0
- package/dist/plugins/server/constants.d.ts +32 -0
- package/dist/plugins/server/constants.js +76 -0
- package/dist/plugins/server/constants.js.map +1 -0
- package/dist/plugins/server/plugins/server/Cache.d.ts +2 -0
- package/dist/plugins/server/plugins/server/Cache.js +36 -0
- package/dist/plugins/server/plugins/server/Cache.js.map +1 -0
- package/dist/plugins/server/plugins/server/DownloadConnector.d.ts +29 -0
- package/dist/plugins/server/plugins/server/DownloadConnector.js +127 -0
- package/dist/plugins/server/plugins/server/DownloadConnector.js.map +1 -0
- package/dist/plugins/server/plugins/server/FtpConnector.d.ts +86 -0
- package/dist/plugins/server/plugins/server/FtpConnector.js +536 -0
- package/dist/plugins/server/plugins/server/FtpConnector.js.map +1 -0
- package/dist/plugins/server/plugins/server/GitlabConnector.d.ts +135 -0
- package/dist/plugins/server/plugins/server/GitlabConnector.js +649 -0
- package/dist/plugins/server/plugins/server/GitlabConnector.js.map +1 -0
- package/dist/plugins/server/plugins/server/GitlabHostingConnector.d.ts +16 -0
- package/dist/plugins/server/plugins/server/GitlabHostingConnector.js +209 -0
- package/dist/plugins/server/plugins/server/GitlabHostingConnector.js.map +1 -0
- package/dist/plugins/server/plugins/server/PublicationOptimizer.d.ts +0 -0
- package/dist/plugins/server/plugins/server/PublicationOptimizer.js +14 -0
- package/dist/plugins/server/plugins/server/PublicationOptimizer.js.map +1 -0
- package/dist/plugins/server/plugins/server/SslPlugin.d.ts +1 -0
- package/dist/plugins/server/plugins/server/SslPlugin.js +86 -0
- package/dist/plugins/server/plugins/server/SslPlugin.js.map +1 -0
- package/dist/plugins/server/plugins/server/StaticPlugin.d.ts +8 -0
- package/dist/plugins/server/plugins/server/StaticPlugin.js +45 -0
- package/dist/plugins/server/plugins/server/StaticPlugin.js.map +1 -0
- package/dist/plugins/server/server/config.d.ts +59 -0
- package/dist/plugins/server/server/config.js +181 -0
- package/dist/plugins/server/server/config.js.map +1 -0
- package/dist/plugins/server/server/connectors/FsHosting.d.ts +14 -0
- package/dist/plugins/server/server/connectors/FsHosting.js +66 -0
- package/dist/plugins/server/server/connectors/FsHosting.js.map +1 -0
- package/dist/plugins/server/server/connectors/FsStorage.d.ts +44 -0
- package/dist/plugins/server/server/connectors/FsStorage.js +261 -0
- package/dist/plugins/server/server/connectors/FsStorage.js.map +1 -0
- package/dist/plugins/server/server/connectors/connectors.d.ts +86 -0
- package/dist/plugins/server/server/connectors/connectors.js +126 -0
- package/dist/plugins/server/server/connectors/connectors.js.map +1 -0
- package/dist/plugins/server/server/events.d.ts +26 -0
- package/dist/plugins/server/server/events.js +31 -0
- package/dist/plugins/server/server/events.js.map +1 -0
- package/dist/plugins/server/server/jobs.d.ts +12 -0
- package/dist/plugins/server/server/jobs.js +79 -0
- package/dist/plugins/server/server/jobs.js.map +1 -0
- package/dist/plugins/server/server/utils/validation.d.ts +7 -0
- package/dist/plugins/server/server/utils/validation.js +37 -0
- package/dist/plugins/server/server/utils/validation.js.map +1 -0
- package/dist/plugins/server/types.d.ts +339 -0
- package/dist/plugins/server/types.js +84 -0
- package/dist/plugins/server/types.js.map +1 -0
- package/dist/server/constants.js +55 -0
- package/dist/server/plugins/server/Cache.js +19 -0
- package/dist/server/server/api/api.js +20 -0
- package/dist/server/server/api/connectorApi.js +334 -0
- package/dist/server/server/api/publicationApi.js +106 -0
- package/dist/server/server/api/websiteApi.js +315 -0
- package/dist/server/server/cli.js +71 -0
- package/dist/server/server/config.js +120 -0
- package/dist/server/server/connectors/FsHosting.js +47 -0
- package/dist/server/server/connectors/FsStorage.js +228 -0
- package/dist/server/server/connectors/connectors.js +88 -0
- package/dist/server/server/events.js +14 -0
- package/dist/server/server/expose.js +27 -0
- package/dist/server/server/express.js +46 -0
- package/dist/server/server/index.js +92 -0
- package/dist/server/server/jobs.js +50 -0
- package/dist/server/server/utils/validation.js +14 -0
- package/dist/server/types.js +40 -0
- package/package.json +99 -49
- package/public/assets/download.png +0 -0
- package/public/assets/favicon.ico +0 -0
- package/public/assets/favicon.png +0 -0
- package/public/assets/ftp.png +0 -0
- package/public/assets/gitlab.png +0 -0
- package/public/assets/gitlab2.png +0 -0
- package/public/assets/laptop.png +0 -0
- package/public/assets/logo-silex-small.png +0 -0
- package/public/silex.js +22 -23
- package/src/css/_variables.scss +24 -20
- package/src/css/dialog.scss +35 -2
- package/src/css/fonts.scss +31 -0
- package/src/css/footer.scss +12 -0
- package/src/css/grapesjs-light.scss +180 -0
- package/src/css/grapesjs-plugins.scss +227 -0
- package/src/css/grapesjs.scss +159 -1
- package/src/css/index.scss +12 -1
- package/src/css/light.scss +7 -0
- package/src/css/loader.scss +2 -0
- package/src/css/project-bar.scss +32 -16
- package/src/css/silex.scss +79 -5
- package/src/css/website-info.scss +14 -0
- package/src/html/index.pug +3 -3
- package/src/html/templates/includes.pug +0 -1
- package/src/ts/client/api.ts +156 -0
- package/src/ts/client/assetUrl.test.ts +164 -0
- package/src/ts/client/assetUrl.ts +254 -0
- package/src/ts/client/config.ts +175 -13
- package/src/ts/client/events.ts +43 -0
- package/src/ts/client/expose.ts +27 -1
- package/src/ts/client/grapesjs/LoginDialog.ts +204 -0
- package/src/ts/client/grapesjs/PublicationManager.ts +433 -0
- package/src/ts/client/grapesjs/PublicationUi.ts +366 -0
- package/src/ts/client/grapesjs/blocks.ts +17 -0
- package/src/ts/client/grapesjs/breadcrumbs.ts +79 -0
- package/src/ts/client/grapesjs/css-props.ts +910 -0
- package/src/ts/client/grapesjs/eleventy.ts +30 -7
- package/src/ts/client/grapesjs/footer.ts +42 -0
- package/src/ts/client/grapesjs/img.ts +147 -0
- package/src/ts/client/grapesjs/index.ts +311 -122
- package/src/ts/client/grapesjs/internal-links.test.ts +19 -1
- package/src/ts/client/grapesjs/internal-links.ts +31 -7
- package/src/ts/client/grapesjs/keymaps.ts +207 -0
- package/src/ts/client/grapesjs/new-page-dialog.ts +19 -3
- package/src/ts/client/grapesjs/openImport.ts +22 -0
- package/src/ts/client/grapesjs/page-panel.ts +109 -7
- package/src/ts/client/grapesjs/project-bar.ts +133 -72
- package/src/ts/client/grapesjs/resize-panel.ts +20 -0
- package/src/ts/client/grapesjs/rich-text.ts +38 -13
- package/src/ts/client/grapesjs/semantic.ts +20 -3
- package/src/ts/client/grapesjs/settings-sections.ts +117 -0
- package/src/ts/client/grapesjs/settings.ts +191 -96
- package/src/ts/client/grapesjs/storage.ts +187 -0
- package/src/ts/client/grapesjs/symbolDialogs.ts +21 -5
- package/src/ts/client/grapesjs/view-buttons.ts +42 -0
- package/src/ts/client/index.ts +80 -21
- package/src/ts/client/publication-transformers.test.ts +207 -0
- package/src/ts/client/publication-transformers.ts +280 -0
- package/src/ts/client/publish-custom-code-block.ts +31 -0
- package/src/ts/client/publish-fonts.ts +36 -0
- package/src/ts/client/utils.ts +70 -1
- package/src/ts/constants.ts +78 -0
- package/src/ts/externs.ts +17 -0
- package/src/ts/page.ts +20 -3
- package/src/ts/plugins/client/template.ts +246 -0
- package/src/ts/plugins/client/website-info.ts +84 -0
- package/src/ts/plugins/server/Cache.ts +32 -0
- package/src/ts/plugins/server/DownloadConnector.ts +146 -0
- package/src/ts/plugins/server/FtpConnector.test.ts +82 -0
- package/src/ts/plugins/server/FtpConnector.ts +621 -0
- package/src/ts/plugins/server/GitlabConnector.ts +749 -0
- package/src/ts/plugins/server/GitlabHostingConnector.ts +215 -0
- package/src/ts/plugins/server/PublicationOptimizer.ts +14 -0
- package/src/ts/plugins/server/SslPlugin.ts +84 -0
- package/src/ts/plugins/server/StaticPlugin.ts +45 -0
- package/src/ts/server/api/api.ts +19 -0
- package/src/ts/server/api/connectorApi.ts +464 -0
- package/src/ts/server/api/publicationApi.ts +159 -0
- package/src/ts/server/api/websiteApi.ts +462 -0
- package/src/ts/server/cli.ts +48 -14
- package/src/ts/server/config.ts +158 -60
- package/src/ts/server/connectors/FsHosting.ts +66 -0
- package/src/ts/server/connectors/FsStorage.test.ts +51 -0
- package/src/ts/server/connectors/FsStorage.ts +290 -0
- package/src/ts/server/connectors/connectors.ts +203 -0
- package/src/ts/server/events.ts +37 -0
- package/src/ts/server/expose.ts +19 -0
- package/src/ts/server/express.ts +57 -5
- package/src/ts/server/index.ts +80 -19
- package/src/ts/server/jobs.test.ts +75 -0
- package/src/ts/server/jobs.ts +89 -0
- package/src/ts/server/utils/BackwardCompat.ts +546 -528
- package/src/ts/server/utils/validation.ts +34 -0
- package/src/ts/types.ts +295 -85
- package/.dockerignore +0 -5
- package/.editorconfig +0 -14
- package/.eslintrc.json +0 -43
- package/.github/FUNDING.yml +0 -13
- package/.github/workflows/docker-hub-publish.yml +0 -36
- package/.github/workflows/npm-publish.yml +0 -19
- package/.github/workflows/test.yml +0 -26
- package/.nvmrc +0 -1
- package/.silex/default/.silex.data.json +0 -1
- package/CODE_OF_CONDUCT.md +0 -128
- package/CONTRIBUTING.md +0 -131
- package/Dockerfile +0 -15
- package/directus.silex.js +0 -7
- package/dist/constants.js +0 -4
- package/dist/events.js +0 -5
- package/dist/page.js +0 -15
- package/dist/server/cli.js +0 -43
- package/dist/server/config.js +0 -70
- package/dist/server/express.js +0 -18
- package/dist/server/index.js +0 -58
- package/dist/types.js +0 -34
- package/jest.config.js +0 -10
- package/logos/logo_by_ceubri/silex-icon-2018@200px.png +0 -0
- package/logos/logo_by_ceubri/silex-icon-2018@4x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white-transp.pdf +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white.jpg +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white.pdf +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white@0.5x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white@0.75x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white@4x.jpg +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white@4x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018.ai +5 -2521
- package/logos/logo_by_ceubri/silex-logo-2018.jpg +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018.pdf +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018@0.5x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018@0.75x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018@4x.jpg +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018@4x.png +0 -0
- package/logos/silexlabs.png +0 -0
- package/src/example-client-config.js +0 -47
- package/src/plugins/AuthPlugin.js +0 -21
- package/src/plugins/Cache.js +0 -11
- package/src/plugins/DefaultBackend.js +0 -149
- package/src/plugins/ExpressPlugin.js +0 -39
- package/src/plugins/HooksPlugin.js +0 -58
- package/src/plugins/PublishPlugin.js +0 -69
- package/src/plugins/SslPlugin.js +0 -66
- package/src/plugins/StaticPlugin.js +0 -48
- package/src/plugins/WebsitePlugin.js +0 -207
- package/src/ts/client/grapesjs/publish.ts +0 -386
- package/src/ts/client/grapesjs/template.ts +0 -166
- package/src/ts/events.ts +0 -2
- package/tsconfig-server-debug.json +0 -19
- package/tsconfig-server-release.json +0 -19
- package/tsconfig.jest.json +0 -9
- package/tsconfig.json +0 -26
- package/webpack.config.js +0 -28
- package/webpack.debug.config.js +0 -8
|
@@ -0,0 +1,749 @@
|
|
|
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, JobStatus } from '../../types'
|
|
22
|
+
import fetch from 'node-fetch'
|
|
23
|
+
import crypto, { createHash } from 'crypto'
|
|
24
|
+
import { join } from 'path'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Gitlab connector
|
|
28
|
+
* @fileoverview Gitlab connector for Silex, connect to the user's Gitlab account to store websites
|
|
29
|
+
* @see https://docs.gitlab.com/ee/api/oauth2.html
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
export interface GitlabOptions {
|
|
33
|
+
clientId: string
|
|
34
|
+
clientSecret: string
|
|
35
|
+
branch: string
|
|
36
|
+
assetsFolder: string
|
|
37
|
+
//metaRepo: string
|
|
38
|
+
//metaRepoFile: string
|
|
39
|
+
repoPrefix: string
|
|
40
|
+
scope: string
|
|
41
|
+
domain: string
|
|
42
|
+
timeOut: number
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface GitlabToken {
|
|
46
|
+
state?: string
|
|
47
|
+
codeVerifier?: string
|
|
48
|
+
codeChallenge?: string
|
|
49
|
+
token?: {
|
|
50
|
+
access_token: string
|
|
51
|
+
token_type: string
|
|
52
|
+
expires_in: number
|
|
53
|
+
refresh_token: string
|
|
54
|
+
created_at: number
|
|
55
|
+
id_token: string
|
|
56
|
+
scope: string
|
|
57
|
+
}
|
|
58
|
+
userId?: number
|
|
59
|
+
username?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type GitlabSession = Record<string, GitlabToken>
|
|
63
|
+
|
|
64
|
+
interface GitlabAction {
|
|
65
|
+
action: 'create' | 'delete' | 'move' | 'update' | 'cherry-pick'
|
|
66
|
+
file_path?: string
|
|
67
|
+
content?: string
|
|
68
|
+
commit_id?: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface GitlabWriteFile {
|
|
72
|
+
branch: string
|
|
73
|
+
commit_message: string
|
|
74
|
+
id?: string
|
|
75
|
+
actions?: GitlabAction[]
|
|
76
|
+
content?: string
|
|
77
|
+
file_path?: string
|
|
78
|
+
encoding?: 'base64' | 'text'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface GitlabGetToken {
|
|
82
|
+
grant_type: 'authorization_code'
|
|
83
|
+
client_id: string
|
|
84
|
+
client_secret: string
|
|
85
|
+
code: string
|
|
86
|
+
redirect_uri: string
|
|
87
|
+
code_verifier: string
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface GitlabWebsiteName {
|
|
91
|
+
name: string
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface GitlabCreateBranch {
|
|
95
|
+
branch: string
|
|
96
|
+
ref: string
|
|
97
|
+
}
|
|
98
|
+
|
|
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
|
+
}
|
|
122
|
+
}
|
|
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
|
+
|
|
182
|
+
export default class GitlabConnector implements StorageConnector {
|
|
183
|
+
connectorId = 'gitlab'
|
|
184
|
+
connectorType = ConnectorType.STORAGE
|
|
185
|
+
displayName = 'GitLab'
|
|
186
|
+
icon = ICON
|
|
187
|
+
disableLogout = false
|
|
188
|
+
color = '#2B1B63'
|
|
189
|
+
background = 'rgba(252, 109, 38, 0.2)'
|
|
190
|
+
options: GitlabOptions
|
|
191
|
+
|
|
192
|
+
constructor(private config: ServerConfig, opts: Partial<GitlabOptions>) {
|
|
193
|
+
this.options = {
|
|
194
|
+
branch: 'main',
|
|
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'
|
|
200
|
+
...opts,
|
|
201
|
+
} as GitlabOptions
|
|
202
|
+
if(!this.options.clientId) throw new Error('Missing Gitlab client ID')
|
|
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 */
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// **
|
|
209
|
+
// Convenience methods for the Gitlab API
|
|
210
|
+
private getAssetPath(path: string): string {
|
|
211
|
+
return encodeURIComponent(join(this.options.assetsFolder, path))
|
|
212
|
+
}
|
|
213
|
+
|
|
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> {
|
|
220
|
+
// Remove leading slash
|
|
221
|
+
const safePath = path.replace(/^\//, '')
|
|
222
|
+
const encodePath = decodeURIComponent(path)
|
|
223
|
+
return this.callApi(session, `api/v4/projects/${websiteId}/repository/files/${safePath}`, 'POST', {
|
|
224
|
+
id: websiteId,
|
|
225
|
+
branch: this.options.branch,
|
|
226
|
+
content,
|
|
227
|
+
commit_message: `Create file ${encodePath} from Silex`,
|
|
228
|
+
encoding: isBase64 ? 'base64' : undefined,
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async updateFile(session: GitlabSession, websiteId: WebsiteId, path: string, content: string, isBase64 = false): Promise<void> {
|
|
233
|
+
// Remove leading slash
|
|
234
|
+
const safePath = path.replace(/^\//, '')
|
|
235
|
+
const encodePath = decodeURIComponent(path)
|
|
236
|
+
return this.callApi(session, `api/v4/projects/${websiteId}/repository/files/${safePath}`, 'PUT', {
|
|
237
|
+
id: websiteId,
|
|
238
|
+
branch: this.options.branch,
|
|
239
|
+
content: await contentToString(content),
|
|
240
|
+
commit_message: `Update website asset ${encodePath} from Silex`,
|
|
241
|
+
encoding: isBase64 ? 'base64' : undefined,
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
|
|
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}`
|
|
249
|
+
const response = await fetch(url, {
|
|
250
|
+
method: 'GET',
|
|
251
|
+
headers: {
|
|
252
|
+
'Content-Type': 'application/json',
|
|
253
|
+
},
|
|
254
|
+
})
|
|
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
|
|
306
|
+
try {
|
|
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
|
+
})
|
|
315
|
+
} catch (e) {
|
|
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)
|
|
318
|
+
}
|
|
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) {
|
|
326
|
+
// Refresh the token
|
|
327
|
+
const token = this.getSessionToken(session).token
|
|
328
|
+
const body = {
|
|
329
|
+
grant_type: 'refresh_token',
|
|
330
|
+
refresh_token: token?.refresh_token,
|
|
331
|
+
client_id: this.options.clientId,
|
|
332
|
+
client_secret: this.options.clientSecret,
|
|
333
|
+
}
|
|
334
|
+
const response = await fetch(this.options.domain + '/oauth/token', {
|
|
335
|
+
method: 'POST',
|
|
336
|
+
headers: {
|
|
337
|
+
'Content-Type': 'application/json',
|
|
338
|
+
},
|
|
339
|
+
body: JSON.stringify(body)
|
|
340
|
+
})
|
|
341
|
+
const refreshJson = await response.json()
|
|
342
|
+
if (response.ok) {
|
|
343
|
+
this.setSessionToken(session, {
|
|
344
|
+
token: {
|
|
345
|
+
...token,
|
|
346
|
+
...refreshJson,
|
|
347
|
+
},
|
|
348
|
+
} as GitlabToken)
|
|
349
|
+
return await this.callApi(session, path, method, body as any, params)
|
|
350
|
+
} else {
|
|
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)
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
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
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return json
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private generateCodeVerifier() {
|
|
381
|
+
return crypto.randomBytes(64).toString('base64')
|
|
382
|
+
.replace(/\+/g, '-')
|
|
383
|
+
.replace(/\//g, '_')
|
|
384
|
+
.replace(/=/g, '')
|
|
385
|
+
.substr(0, 128)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private async generateCodeChallenge(verifier) {
|
|
389
|
+
const hashed = createHash('sha256').update(verifier).digest()
|
|
390
|
+
let base64Url = hashed.toString('base64')
|
|
391
|
+
// Replace '+' with '-', '/' with '_', and remove '='
|
|
392
|
+
base64Url = base64Url.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
|
|
393
|
+
return base64Url
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
private getRedirect() {
|
|
398
|
+
const params = `connectorId=${this.connectorId}&type=${this.connectorType}`
|
|
399
|
+
return `${this.config.url}${API_PATH}${API_CONNECTOR_PATH}${API_CONNECTOR_LOGIN_CALLBACK}?${params}`
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Get the OAuth URL to redirect the user to
|
|
404
|
+
* The URL should look like
|
|
405
|
+
* 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
|
|
406
|
+
* OAuth2 Step #1 from https://docs.gitlab.com/ee/api/oauth2.html#authorization-code-with-proof-key-for-code-exchange-pkce
|
|
407
|
+
*/
|
|
408
|
+
async getOAuthUrl(session: GitlabSession): Promise<string> {
|
|
409
|
+
const redirect_uri = encodeURIComponent(this.getRedirect())
|
|
410
|
+
|
|
411
|
+
const state = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
|
|
412
|
+
const codeVerifier = this.generateCodeVerifier()
|
|
413
|
+
|
|
414
|
+
// Create the code challenge
|
|
415
|
+
const codeChallenge = await this.generateCodeChallenge(codeVerifier)
|
|
416
|
+
|
|
417
|
+
// Store the code verifier and code challenge in the session
|
|
418
|
+
this.setSessionToken(session, {
|
|
419
|
+
...this.getSessionToken(session),
|
|
420
|
+
state,
|
|
421
|
+
codeVerifier,
|
|
422
|
+
codeChallenge,
|
|
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]
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
getOptions(formData: object): object {
|
|
438
|
+
return {} // FIXME: store branch
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async getLoginForm(session: GitlabSession, redirectTo: string): Promise<null> {
|
|
442
|
+
return null
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async getSettingsForm(session: GitlabSession, redirectTo: string): Promise<null> {
|
|
446
|
+
return null
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async isLoggedIn(session: GitlabSession): Promise<boolean> {
|
|
450
|
+
return !!this.getSessionToken(session).token
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Get the token from return code
|
|
455
|
+
* Set the token in the session
|
|
456
|
+
* OAuth2 Step #2 from https://docs.gitlab.com/ee/api/oauth2.html#authorization-code-with-proof-key-for-code-exchange-pkce
|
|
457
|
+
*/
|
|
458
|
+
async setToken(session: GitlabSession, loginResult: any): Promise<void> {
|
|
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
|
+
}
|
|
472
|
+
|
|
473
|
+
const response = await fetch(this.options.domain + '/oauth/token', {
|
|
474
|
+
method: 'POST',
|
|
475
|
+
headers: {
|
|
476
|
+
'Content-Type': 'application/json',
|
|
477
|
+
},
|
|
478
|
+
body: JSON.stringify({
|
|
479
|
+
client_id: this.options.clientId,
|
|
480
|
+
client_secret: this.options.clientSecret,
|
|
481
|
+
code: loginResult.code,
|
|
482
|
+
grant_type: 'authorization_code',
|
|
483
|
+
redirect_uri: this.getRedirect(),
|
|
484
|
+
code_verifier: sessionToken.codeVerifier,
|
|
485
|
+
}),
|
|
486
|
+
})
|
|
487
|
+
|
|
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
|
+
})
|
|
495
|
+
|
|
496
|
+
// We need to get the user ID for listWebsites
|
|
497
|
+
const user = await this.callApi(session, 'api/v4/user') as any
|
|
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
|
+
})
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async logout(session: GitlabSession): Promise<void> {
|
|
508
|
+
this.resetSessionToken(session)
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
async getUser(session: GitlabSession): Promise<ConnectorUser> {
|
|
512
|
+
const user = await this.callApi(session, 'api/v4/user') as any
|
|
513
|
+
return {
|
|
514
|
+
name: user.name,
|
|
515
|
+
email: user.email,
|
|
516
|
+
picture: user.avatar_url,
|
|
517
|
+
storage: await toConnectorData(session, this as StorageConnector),
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async listWebsites(session: GitlabSession): Promise<WebsiteMeta[]> {
|
|
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
|
+
}))
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
async readWebsite(session: GitlabSession, websiteId: string): Promise<WebsiteData> {
|
|
562
|
+
const result = await this.callApi(session, `api/v4/projects/${websiteId}/repository/files/${WEBSITE_DATA_FILE}`, 'GET', null, {
|
|
563
|
+
ref: this.options.branch,
|
|
564
|
+
}) as any
|
|
565
|
+
const { content } = result
|
|
566
|
+
const contentDecoded = Buffer.from(content, 'base64').toString('utf8')
|
|
567
|
+
const websiteData = JSON.parse(contentDecoded) as WebsiteData
|
|
568
|
+
return websiteData
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
async createWebsite(session: GitlabSession, websiteMeta: WebsiteMetaFileContent): Promise<WebsiteId> {
|
|
572
|
+
const project = await this.callApi(session, 'api/v4/projects/', 'POST', {
|
|
573
|
+
name: this.options.repoPrefix + websiteMeta.name,
|
|
574
|
+
}) as any
|
|
575
|
+
await this.createFile(session, project.id, WEBSITE_DATA_FILE, JSON.stringify({} as WebsiteData))
|
|
576
|
+
//await this.createFile(session, project.id, WEBSITE_META_DATA_FILE, JSON.stringify(websiteMeta))
|
|
577
|
+
//await this.updateWebsite(session, project.id, {} as WebsiteData)
|
|
578
|
+
//await this.setWebsiteMeta(session, project.id, websiteMeta)
|
|
579
|
+
return project.id
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
async updateWebsite(session: GitlabSession, websiteId: WebsiteId, websiteData: WebsiteData): Promise<void> {
|
|
583
|
+
const project = await this.callApi(session, `api/v4/projects/${websiteId}/repository/files/${WEBSITE_DATA_FILE}`, 'PUT', {
|
|
584
|
+
branch: this.options.branch,
|
|
585
|
+
commit_message: 'Update website data from Silex',
|
|
586
|
+
content: JSON.stringify(websiteData),
|
|
587
|
+
id: websiteId,
|
|
588
|
+
})
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
async deleteWebsite(session: GitlabSession, websiteId: WebsiteId): Promise<void> {
|
|
592
|
+
// Delete repo
|
|
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
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
async getWebsiteMeta(session: GitlabSession, websiteId: WebsiteId): Promise<WebsiteMeta> {
|
|
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}`)
|
|
662
|
+
return {
|
|
663
|
+
websiteId,
|
|
664
|
+
name: project.name.replace(this.options.repoPrefix, ''),
|
|
665
|
+
imageUrl: project.avatar_url,
|
|
666
|
+
createdAt: project.created_at,
|
|
667
|
+
updatedAt: project.last_activity_at,
|
|
668
|
+
connectorUserSettings: {},
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
async setWebsiteMeta(session: GitlabSession, websiteId: WebsiteId, websiteMeta: WebsiteMetaFileContent): Promise<void> {
|
|
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
|
+
//})
|
|
707
|
+
}
|
|
708
|
+
|
|
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 })
|
|
711
|
+
// For each file
|
|
712
|
+
for (const file of files) {
|
|
713
|
+
// Convert to base64
|
|
714
|
+
const content = (await contentToBuffer(file.content)).toString('base64')
|
|
715
|
+
const path = this.getAssetPath(file.path)
|
|
716
|
+
|
|
717
|
+
try {
|
|
718
|
+
await this.updateFile(session, websiteId, path, content, true)
|
|
719
|
+
} catch (e) {
|
|
720
|
+
// If the file does not exist, create it
|
|
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)
|
|
723
|
+
} else {
|
|
724
|
+
status && await status({ message: 'Error', status: JobStatus.ERROR })
|
|
725
|
+
throw e
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
status && await status({ message: 'Successfull', status: JobStatus.SUCCESS })
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
async readAsset(session: GitlabSession, websiteId: string, fileName: string): Promise<ConnectorFileContent> {
|
|
733
|
+
// Remove leading slash
|
|
734
|
+
const finalPath = this.getAssetPath(fileName)
|
|
735
|
+
return this.readFile(session, websiteId, finalPath)
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
async deleteAssets(session: GitlabSession, websiteId: string, fileNames: string[]): Promise<void> {
|
|
739
|
+
return this.callApi(session, `api/v4/projects/${websiteId}/repository/commits`, 'POST', {
|
|
740
|
+
id: websiteId,
|
|
741
|
+
branch: this.options.branch,
|
|
742
|
+
commit_message: `Delete assets from Silex: ${fileNames.join(', ')}`,
|
|
743
|
+
actions: fileNames.map(f => ({
|
|
744
|
+
action: 'delete',
|
|
745
|
+
file_path: this.getAssetPath(f),
|
|
746
|
+
})),
|
|
747
|
+
})
|
|
748
|
+
}
|
|
749
|
+
}
|