diff --git a/assets/scripts/GitAgent.ts b/assets/scripts/GitAgent.ts
index 0d4e8f16..ff0319d0 100644
--- a/assets/scripts/GitAgent.ts
+++ b/assets/scripts/GitAgent.ts
@@ -13,16 +13,15 @@ import FS from '@isomorphic-git/lightning-fs'
import git from 'isomorphic-git'
import http from 'isomorphic-git/http/web'
-const DEFAULT_CORS_PROXY_URL = 'https://cors.isomorphic-git.org'
-
-import type { CommitObject, GitAuth } from 'isomorphic-git'
+import type { CommitObject } from 'isomorphic-git'
import type { ResolutionOption } from './store.ts'
+import type { OAuthServiceAPI } from './types/git.ts'
export default class GitAgent {
#fs
#remoteURL
#repoId
- #corsProxyURL
+ #corsProxyURL: string | undefined = undefined
#onAuth
#onMergeConflict
@@ -34,14 +33,14 @@ export default class GitAgent {
constructor({
repoId,
remoteURL,
- corsProxyURL = DEFAULT_CORS_PROXY_URL,
- auth,
+ corsProxyURL,
+ gitServiceProvider,
onMergeConflict,
}: {
repoId: string
remoteURL: string
corsProxyURL?: string
- auth: GitAuth
+ gitServiceProvider: OAuthServiceAPI
onMergeConflict?:
| ((resolutionOptions: ResolutionOption[]) => void)
| undefined
@@ -50,6 +49,8 @@ export default class GitAgent {
this.#repoId = repoId
this.#remoteURL = remoteURL
+ const auth = gitServiceProvider.getOauthUsernameAndPassword()
+
this.#onAuth = () => auth
this.#onMergeConflict = onMergeConflict
this.#corsProxyURL = corsProxyURL
@@ -83,6 +84,7 @@ export default class GitAgent {
singleBranch: true,
corsProxy: this.#corsProxyURL,
depth: 5,
+ onAuth: this.#onAuth,
})
}
@@ -191,6 +193,7 @@ export default class GitAgent {
singleBranch: false, // we want all the branches
dir: this.#repoDirectory,
corsProxy: this.#corsProxyURL,
+ onAuth: this.#onAuth,
})
}
diff --git a/assets/scripts/actions/current-repository.ts b/assets/scripts/actions/current-repository.ts
index 1075b45e..52324ec4 100644
--- a/assets/scripts/actions/current-repository.ts
+++ b/assets/scripts/actions/current-repository.ts
@@ -2,10 +2,7 @@ import page from 'page'
import yaml from 'js-yaml'
import store, { type PartialStore } from './../store.ts'
-import ScribouilliGitRepo, {
- makeRepoId,
- makePublicRepositoryURL,
-} from './../scribouilliGitRepo.ts'
+import ScribouilliGitRepo from './../scribouilliGitRepo.ts'
import GitAgent from '../GitAgent.ts'
import { handleErrors, logMessage } from './../utils.ts'
import { fetchAuthenticatedUserLogin } from './current-user.ts'
@@ -17,7 +14,7 @@ import { file } from './file.ts'
import { getPagesList } from './page.ts'
import { getArticlesList } from './article.ts'
import { getOAuthServiceAPI } from '../oauth-services-api/index.ts'
-import { CUSTOM_CSS_PATH } from '../config.ts'
+import { CUSTOM_CSS_PATH, PROVIDERS_MAP } from '../config.ts'
import type { BuildStatus } from '../types/git.ts'
export const getCurrentRepoPages = () => {
@@ -65,26 +62,30 @@ export const setCurrentRepositoryFromQuerystring = async (
}
const origin = oAuthProvider.origin
- const repoId = makeRepoId(owner, repoName)
+ const provider = PROVIDERS_MAP.get(oAuthProvider.id)
+ if (!provider) {
+ throw new TypeError(`Unkown provider ${oAuthProvider.id}`)
+ }
+ const oAuthServiceAPI = getOAuthServiceAPI()
const scribouilliGitRepo = new ScribouilliGitRepo({
owner,
repoName,
- repoId,
+ repoType: provider.type,
origin: origin,
- publicRepositoryURL: makePublicRepositoryURL(owner, repoName, origin),
gitServiceProvider: getOAuthServiceAPI(),
})
store.mutations.setCurrentRepository(scribouilliGitRepo)
const gitAgent = new GitAgent({
- repoId,
- remoteURL: `${origin}/${repoId}.git`,
+ repoId: oAuthServiceAPI.makeRepoId(owner, repoName),
+ remoteURL: oAuthServiceAPI.makePublicRepositoryURL(owner, repoName),
+ corsProxyURL: provider.corsProxy,
+ gitServiceProvider: oAuthServiceAPI,
onMergeConflict: resolutionOptions => {
store.mutations.setConflict(resolutionOptions)
},
- auth: getOAuthServiceAPI().getOauthUsernameAndPassword(),
})
store.mutations.setGitAgent(gitAgent)
diff --git a/assets/scripts/actions/setup.ts b/assets/scripts/actions/setup.ts
index 73726ec2..3876020c 100644
--- a/assets/scripts/actions/setup.ts
+++ b/assets/scripts/actions/setup.ts
@@ -1,10 +1,7 @@
import page from 'page'
import store, { ResolutionOption } from './../store.ts'
-import ScribouilliGitRepo, {
- makePublicRepositoryURL,
- makeRepoId,
-} from './../scribouilliGitRepo.ts'
+import ScribouilliGitRepo from './../scribouilliGitRepo.ts'
import { getOAuthServiceAPI } from './../oauth-services-api/index.ts'
import { makeAtelierListPageURL } from './../routes/urls.ts'
import { logMessage } from './../utils.ts'
@@ -12,6 +9,7 @@ import { setBaseUrlInConfigIfNecessary } from './current-repository.ts'
import GitAgent from '../GitAgent.ts'
import git from 'isomorphic-git'
import type { GitSiteTemplate } from '../types/git.ts'
+import { PROVIDERS_MAP } from '../config.ts'
const waitRepoReady = (
scribouilliGitRepo: ScribouilliGitRepo,
@@ -130,34 +128,36 @@ export const createRepositoryForCurrentAccount = async (
}
const origin = oAuthProvider.origin
+ const provider = PROVIDERS_MAP.get(oAuthProvider.id)
+ if (!provider) {
+ throw new TypeError(`Unkown provider ${oAuthProvider.id}`)
+ }
+ const oAuthServiceAPI = getOAuthServiceAPI()
const scribouilliGitRepo = new ScribouilliGitRepo({
owner: owner,
repoName: escapedRepoName,
+ repoType: provider.type,
origin: origin,
- publicRepositoryURL: makePublicRepositoryURL(
- owner,
- escapedRepoName,
- origin,
- ),
- gitServiceProvider: getOAuthServiceAPI(),
+ gitServiceProvider: oAuthServiceAPI,
})
store.mutations.setCurrentRepository(scribouilliGitRepo)
return (
- getOAuthServiceAPI()
+ oAuthServiceAPI
.createDefaultRepository(scribouilliGitRepo, template)
.then(({ remoteURL }) => {
const gitAgent = new GitAgent({
- repoId: makeRepoId(owner, escapedRepoName),
+ repoId: oAuthServiceAPI.makeRepoId(owner, escapedRepoName),
remoteURL: remoteURL,
+ corsProxyURL: provider.corsProxy,
onMergeConflict: (
resolutionOptions: ResolutionOption[] | undefined,
) => {
store.mutations.setConflict(resolutionOptions)
},
- auth: getOAuthServiceAPI().getOauthUsernameAndPassword(),
+ gitServiceProvider: oAuthServiceAPI,
})
store.mutations.setGitAgent(gitAgent)
diff --git a/assets/scripts/components/Header.svelte b/assets/scripts/components/Header.svelte
index 2eae015a..1b982e9e 100644
--- a/assets/scripts/components/Header.svelte
+++ b/assets/scripts/components/Header.svelte
@@ -1,6 +1,7 @@
- {#if gitProvider === 'github.com'}
-
-
-
Avez-vous un compte GitHub ?
-
-
-
-
- {/if}
-
- {#if gitProvider === 'gitlab.com'}
-
-
-
Avez-vous un compte sur gitlab.com ?
-
-
-
-
- {/if}
-
- {#if gitProvider === 'git.scribouilli.org'}
-
Avez-vous un compte sur git.scribouilli.org ?
+ {#if provider.type === 'github' }
+
Avez-vous un compte GitHub ?
+ {:else}
+
Avez-vous un compte sur { provider.id } ?
+ {/if}
- {/if}
-
-
diff --git a/assets/scripts/components/screens/CreateNewSite.svelte b/assets/scripts/components/screens/CreateNewSite.svelte
index d09a380b..06caff7a 100644
--- a/assets/scripts/components/screens/CreateNewSite.svelte
+++ b/assets/scripts/components/screens/CreateNewSite.svelte
@@ -2,7 +2,7 @@
import Skeleton from './../Skeleton.svelte';
import SiteCreationLoader from './../loaders/SiteCreationLoader.svelte';
import { createRepositoryForCurrentAccount } from '../../actions/setup.ts';
- import { DEFAULT_TEMPLATE, templates } from '../../config.ts';
+ import { DEFAULT_TEMPLATE, TEMPLATES } from '../../config.ts';
import type { GitSiteTemplate } from '../../types/git'
let name = $state("");
@@ -24,7 +24,8 @@
// dépôt perso dans une organisation, via l'interface GitHub, pour les
// utilisateurices avancé.es
createRepositoryForCurrentAccount(name, selectedTemplate)
- .catch(() => {
+ .catch((e) => {
+ console.warn(`Failed to create repository: ${e}`);
loading = false;
hasError = true;
});
@@ -53,7 +54,7 @@
Je veux créer :
- {#each templates as template}
+ {#each TEMPLATES as template}
{template.description}
{/each}
diff --git a/assets/scripts/components/screens/Login.svelte b/assets/scripts/components/screens/Login.svelte
index 24d1fb14..a89fc1b6 100644
--- a/assets/scripts/components/screens/Login.svelte
+++ b/assets/scripts/components/screens/Login.svelte
@@ -1,35 +1,37 @@
- {#if gitProvider === 'github.com'}
+ {#if providerType === 'github'}
{/if}
- {#if gitProvider === 'gitlab.com'}
+ {#if providerType === 'gitlab'}
{/if}
- {#if gitProvider === 'git.scribouilli.org'}
+ {#if providerType === 'scribouilli'}
{/if}
diff --git a/assets/scripts/components/screens/intern/Editeur.svelte b/assets/scripts/components/screens/intern/Editeur.svelte
index a6e2d14d..7dcafa8f 100644
--- a/assets/scripts/components/screens/intern/Editeur.svelte
+++ b/assets/scripts/components/screens/intern/Editeur.svelte
@@ -7,7 +7,7 @@
import { writeFileAndCommit } from '../../../actions/file'
import './../../../../styles/editeur-preview/framalibre.css'
import type { EditeurFile, FileContenu } from "../../../types/atelier"
-
+
interface Props {
fileP: Promise
buildStatus: any
@@ -173,14 +173,14 @@
>
avec du Markdown
- … ou avec du HTML grâce
+ … ou avec du HTML grâce
à nos exemples
- d'encart ou de bouton, ou
+ d'encart ou de bouton, ou
-
+
Aperçu
{#await preview}
-
+
{:then preview}
{@html preview}
- {/await}
+ {/await}
diff --git a/assets/scripts/config.ts b/assets/scripts/config.ts
index 243a0069..80effdf4 100644
--- a/assets/scripts/config.ts
+++ b/assets/scripts/config.ts
@@ -1,6 +1,9 @@
+import type { ScribouilliBackendProvider } from './types/atelier'
import type { GitSiteTemplate } from './types/git'
+export const DEFAULT_CORS_PROXY_URL = 'https://cors.isomorphic-git.org'
export const OAUTH_PROVIDER_STORAGE_KEY = 'scribouilli_oauth_provider'
+export const TOCTOCTOC_ORIGIN = `https://toctoctoc.lechappeebelle.team`
export const TOCTOCTOC_ACCESS_TOKEN_URL_PARAMETER = 'access_token'
export const TOCTOCTOC_OAUTH_PROVIDER_URL_PARAMETER = 'type'
export const TOCTOCTOC_OAUTH_PROVIDER_ORIGIN_PARAMETER = 'origin'
@@ -9,7 +12,7 @@ export const gitHubApiBaseUrl = 'https://api.github.com'
export const CUSTOM_CSS_PATH = 'assets/css/custom.css'
-export const templates: GitSiteTemplate[] = [
+export const TEMPLATES: GitSiteTemplate[] = [
{
url: 'https://github.com/Scribouilli/site-template.git',
description: 'mon site vitrine ou mon blog',
@@ -22,6 +25,96 @@ export const templates: GitSiteTemplate[] = [
},
]
-export const DEFAULT_TEMPLATE = templates[0]
+export const DEFAULT_TEMPLATE = TEMPLATES[0]
+
+export const PROVIDERS: ScribouilliBackendProvider[] = [
+ // {
+ // id: 'localhost',
+ // type: 'scribouilli',
+ // origin: 'http://localhost:3000',
+ // name: 'Scribouilli',
+ // description: `Scribouilli , vous pouvez utilizer Scribouilli pour héberger directement votre site.`,
+ // signupEnabled: false,
+ // },
+ {
+ id: 'gitlab.com',
+ type: 'gitlab',
+ origin: 'https://gitlab.com',
+ clientId:
+ 'b943c32d1a30f316cf4a72b5e40b05b6e71a1e3df34e2233c51e79838b22f7e8',
+ name: 'Gitlab',
+ description: `
+ Gitlab.com qui est un hébergeur professionnel.
+ Si vous n'avez pas encore de compte, Gitlab demandera à vérifier votre identité avec un n° de téléphone ou de carte bleue.
+ `,
+ signupEnabled: true,
+ signupLink: 'https://gitlab.com/users/sign_up',
+ corsProxy: DEFAULT_CORS_PROXY_URL,
+ },
+ {
+ id: 'git.scribouilli.org',
+ type: 'gitlab',
+ origin: 'https://git.scribouilli.org',
+ clientId:
+ '3e8ac6636615d396a8f73e02fa3880e7e2140981b0ca27b0f240a450f69f1c76',
+ name: 'ScribouGit',
+ description: `
+ ScribouGit , l'hébergement géré par l'équipe de Scribouilli.
+ Si vous n'avez pas encore de compte, nous prendrons le temps de le valider manuellement (cela peut prendre quelques jours).
+ `,
+ signupEnabled: false,
+ signupInstructions: `
+
+ Pour vérifier que vous n'êtes pas un robot, envoyez-nous un mail à
+ coucou@scribouilli.org en indiquant :
+
+
+
+ le pseudo que vous souhaitez,
+ l'email avec lequel vous voulez créer votre compte,
+ un message pour nous indiquer quel genre de petit site vous voulez
+ créer.
+
+ Pour information, la création du compte pourrait prendre quelques jours de notre côté, on vous préviendra par mail quand c'est fait !
+
+ `,
+ corsProxy: DEFAULT_CORS_PROXY_URL,
+ },
+ {
+ id: 'github.com',
+ origin: 'https://github.com',
+ clientId: '64ecce0b01397c2499a6',
+ type: 'github',
+ description: `Microsoft GitHub®, si vous l'utilisez déjà.`,
+ name: 'GitHub',
+ signupEnabled: true,
+ signupLink: 'https://github.com/signup',
+ signupInstructions: `
+
+ Pour pouvoir publier votre contenu, il faut que Scribouilli se connecte
+ à un compte GitHub .
+
+ La création va se passer sur GitHub.com.
+ Elle comporte 3 étapes :
+
+
+ Rentrez votre mail, mot de passe, et votre nom d'utilisateur·ice
+
+
+ Ouvrez le mail que GitHub vous a envoyé, et copiez le code pour
+ confirmer votre compte
+
+
+ Dès que le code est validé, revenez sur Scribouilli et
+ cliquez sur "J'ai créé un compte"
+
+
+ `,
+ corsProxy: DEFAULT_CORS_PROXY_URL,
+ },
+]
+export const PROVIDERS_MAP = new Map(
+ PROVIDERS.map(provider => [provider.id, provider]),
+)
export const svelteTarget: Element = document.body
diff --git a/assets/scripts/oauth-services-api/github.ts b/assets/scripts/oauth-services-api/github.ts
index 69222e14..9456cba5 100644
--- a/assets/scripts/oauth-services-api/github.ts
+++ b/assets/scripts/oauth-services-api/github.ts
@@ -1,6 +1,7 @@
import { gitHubApiBaseUrl } from './../config.ts'
import type { GitSiteTemplate, OAuthServiceAPI } from '../types/git.ts'
import ScribouilliGitRepo from '../scribouilliGitRepo.ts'
+import { defaultMakePublicRepositoryURL, defaultMakeRepoId } from './index.ts'
const GITHUB_JSON_ACCEPT_HEADER = 'application/vnd.github+json'
@@ -195,4 +196,10 @@ export default class GitHubAPI implements OAuthServiceAPI {
return httpResp
})
}
+
+ makeRepoId = defaultMakeRepoId
+
+ makePublicRepositoryURL(owner: string, repoName: string): string {
+ return defaultMakePublicRepositoryURL(owner, repoName, 'https://github.com')
+ }
}
diff --git a/assets/scripts/oauth-services-api/gitlab.ts b/assets/scripts/oauth-services-api/gitlab.ts
index 01e62473..a04a24c0 100644
--- a/assets/scripts/oauth-services-api/gitlab.ts
+++ b/assets/scripts/oauth-services-api/gitlab.ts
@@ -5,6 +5,7 @@ import type {
GitSiteTemplate,
OAuthServiceAPI,
} from '../types/git.ts'
+import { defaultMakePublicRepositoryURL, defaultMakeRepoId } from './index.ts'
export default class GitLabAPI implements OAuthServiceAPI {
#gitAgentGetter
@@ -236,4 +237,10 @@ export default class GitLabAPI implements OAuthServiceAPI {
}
return httpResp
}
+
+ makeRepoId = defaultMakeRepoId
+
+ makePublicRepositoryURL(owner: string, repoName: string): string {
+ return defaultMakePublicRepositoryURL(owner, repoName, this.origin)
+ }
}
diff --git a/assets/scripts/oauth-services-api/index.ts b/assets/scripts/oauth-services-api/index.ts
index c6a31a0d..a109ac61 100644
--- a/assets/scripts/oauth-services-api/index.ts
+++ b/assets/scripts/oauth-services-api/index.ts
@@ -3,22 +3,33 @@ import store, { type OAuthProvider } from '../store.ts'
import GitHubAPI from './github.ts'
import GitlabAPI from './gitlab.ts'
import type { OAuthServiceAPI } from '../types/git.ts'
+import ScribouilliBackend from './scribouilli.ts'
+import { PROVIDERS_MAP } from '../config.ts'
const makeOAuthServiceAPI = ({
accessToken,
origin,
+ id,
}: OAuthProvider): OAuthServiceAPI => {
- const hostname = new URL(origin).hostname
+ let provider = PROVIDERS_MAP.get(id)
- if (hostname === 'github.com') return new GitHubAPI(accessToken)
- else {
- // assuming a gitlab instance
+ if (!provider) {
+ throw new TypeError(`Unkown provider ${id}`)
+ }
+
+ if (provider.type === 'github') {
+ return new GitHubAPI(accessToken)
+ } else if (provider.type === 'gitlab') {
return new GitlabAPI(accessToken, origin, () => {
if (!store.state.gitAgent) {
throw new TypeError('store.state.gitAgent is undefined')
}
return store.state.gitAgent
})
+ } else if (provider.type === 'scribouilli') {
+ return new ScribouilliBackend(accessToken, origin)
+ } else {
+ throw new Error('unreachable')
}
}
@@ -43,3 +54,21 @@ export const getOAuthServiceAPI = (): OAuthServiceAPI => {
return oAuthServiceAPI
}
+
+/**
+ * @param owner may be an individual Github user or an organisation
+ */
+export function defaultMakeRepoId(owner: string, repoName: string): string {
+ return `${owner}/${repoName}`
+}
+
+/**
+ * @param owner may be an individual Github user or an organisation
+ */
+export function defaultMakePublicRepositoryURL(
+ owner: string,
+ repoName: string,
+ origin: string,
+): string {
+ return `${origin}/${owner}/${repoName}`
+}
diff --git a/assets/scripts/oauth-services-api/scribouilli.ts b/assets/scripts/oauth-services-api/scribouilli.ts
new file mode 100644
index 00000000..31b42bcf
--- /dev/null
+++ b/assets/scripts/oauth-services-api/scribouilli.ts
@@ -0,0 +1,179 @@
+import ScribouilliGitRepo from '../scribouilliGitRepo.ts'
+import type {
+ BuildStatus,
+ GithubRepository,
+ GitSiteTemplate,
+ OAuthServiceAPI,
+} from '../types/git.ts'
+import z from 'zod'
+
+const WEBSITE_LIST_SCHEMA = z.array(
+ z.object({
+ name: z.string(),
+ is_ready: z.boolean(),
+ role: z.union([z.literal('editor'), z.literal('owner')]),
+ }),
+)
+
+export default class ScribouilliBackend implements OAuthServiceAPI {
+ private accessToken: string | undefined
+ private origin
+ private authenticatedUser:
+ | undefined
+ | { id: string; login: string; email: string }
+
+ constructor(accessToken: string, origin: string) {
+ 7
+ this.accessToken = accessToken
+ this.origin = origin
+ this.authenticatedUser = undefined
+ }
+
+ get apiBaseUrl() {
+ return `${this.origin}/api`
+ }
+
+ async callAPI(
+ url: string,
+ requestParams: RequestInit & { headers?: Record } = {},
+ ) {
+ requestParams.headers ??= {}
+ requestParams.headers['Authorization'] = 'Bearer ' + this.accessToken
+
+ const httpResp = await fetch(`${this.apiBaseUrl}${url}`, requestParams)
+ if (httpResp.status === 404) {
+ throw 'NOT_FOUND'
+ }
+ if (httpResp.status === 401) {
+ this.accessToken = undefined
+ console.debug('this accessToken : ', this.accessToken)
+ throw 'INVALIDATE_TOKEN'
+ }
+ return httpResp
+ }
+
+ getOauthUsernameAndPassword() {
+ if (!this.accessToken) {
+ throw new TypeError('Missing accessToken')
+ }
+
+ return { username: 'token', password: this.accessToken }
+ }
+
+ async getAuthenticatedUser() {
+ if (this.authenticatedUser) {
+ return this.authenticatedUser
+ }
+
+ const response = await this.callAPI(`/profile`)
+ const user = await response.json()
+ this.authenticatedUser = {
+ login: user.email,
+ email: user.email,
+ id: user.id,
+ }
+ return this.authenticatedUser
+ }
+
+ async getUserEmails() {
+ const { email } = await this.getAuthenticatedUser()
+ return [
+ {
+ email,
+ primary: true,
+ },
+ ]
+ }
+
+ async createDefaultRepository(
+ scribouilliGitRepo: ScribouilliGitRepo,
+ template: GitSiteTemplate,
+ ): Promise<{ remoteURL: string }> {
+ const { repoName } = scribouilliGitRepo
+ await this.callAPI(`/websites`, {
+ method: 'POST',
+ headers: {
+ 'content-type': 'application/json',
+ },
+ body: JSON.stringify({
+ name: repoName,
+ template_url: template.url,
+ }),
+ })
+
+ return { remoteURL: this.makePublicRepositoryURL('', repoName) }
+ }
+
+ async isRepositoryReady(
+ scribouilliGitRepo: ScribouilliGitRepo,
+ ): Promise {
+ const response = await this.callAPI(
+ `/websites/${scribouilliGitRepo.repoName}/ready`,
+ )
+ const { is_ready } = await response.json()
+ return is_ready
+ }
+
+ async getCurrentUserRepositories(): Promise {
+ const response = await this.callAPI(`/websites`)
+ const rawRepos = await response.json()
+ const repos = z.parse(WEBSITE_LIST_SCHEMA, rawRepos)
+ const { email } = await this.getAuthenticatedUser()
+ const githubRepos = repos.map(repo => {
+ return {
+ id: repo.name,
+ name: repo.name,
+ owner: {
+ login: email,
+ },
+ }
+ })
+ return githubRepos
+ }
+
+ async deploy(scribouilliGitRepo: ScribouilliGitRepo): Promise {
+ await this.callAPI(`/websites/${scribouilliGitRepo.repoName}/deployment`, {
+ method: 'POST',
+ })
+ }
+
+ async getPagesWebsiteDeploymentStatus(
+ scribouilliGitRepo: ScribouilliGitRepo,
+ ): Promise {
+ const data = await this.callAPI(
+ `/websites/${scribouilliGitRepo.repoName}/deployment`,
+ )
+ const { status } = await data.json()
+ return status
+ }
+
+ async isPagesWebsiteBuilt(
+ scribouilliGitRepo: ScribouilliGitRepo,
+ ): Promise {
+ try {
+ const response =
+ await this.getPagesWebsiteDeploymentStatus(scribouilliGitRepo)
+ return response === 'success'
+ } catch {
+ return false
+ }
+ }
+
+ async getPublishedWebsiteURL(
+ scribouilliGitRepo: ScribouilliGitRepo,
+ ): Promise {
+ const data = await this.callAPI(
+ `/websites/${scribouilliGitRepo.repoName}/url`,
+ )
+ const { url } = await data.json()
+ return url
+ }
+
+ makeRepoId(_owner: string, repoName: string): string {
+ return `websites/${repoName}`
+ }
+
+ makePublicRepositoryURL(_owner: string, repoName: string): string {
+ return `${this.origin}/websites/${repoName}`
+ }
+}
diff --git a/assets/scripts/routes/account.ts b/assets/scripts/routes/account.ts
index 4993eaf7..5f32cb36 100644
--- a/assets/scripts/routes/account.ts
+++ b/assets/scripts/routes/account.ts
@@ -1,20 +1,27 @@
import { Context } from 'page'
import Account from '../components/screens/Account.svelte'
import { replaceComponent } from '../routeComponentLifeCycle.svelte.ts'
+import { PROVIDERS_MAP } from '../config.ts'
export default ({ querystring }: Context) => {
const params = new URLSearchParams(querystring)
- const gitProvider = params.get('provider')
+ const providerId = params.get('provider')
- console.log('gitProvider', gitProvider)
+ console.log('providerId', providerId)
- if (!gitProvider) {
+ if (!providerId) {
throw new TypeError(`Missing 'provider' parameter`)
}
+ const provider = PROVIDERS_MAP.get(providerId)
+
+ if (!provider) {
+ throw new TypeError(`Unkown provider ${providerId}`)
+ }
+
// TODO: vérifier que c'est ok d'avoir des props qui viennent pas du state,
// mais du monde extérieur (ici l'URL de la page)
replaceComponent(Account, () => {
- return { gitProvider }
+ return { provider }
})
}
diff --git a/assets/scripts/routes/after-oauth-login.ts b/assets/scripts/routes/after-oauth-login.ts
index 82a4297c..4baf0840 100644
--- a/assets/scripts/routes/after-oauth-login.ts
+++ b/assets/scripts/routes/after-oauth-login.ts
@@ -15,33 +15,34 @@ import { fetchCurrentUserRepositories } from '../actions/current-user.ts'
const storeOAuthProviderAccess = () => {
const url = new URL(location.href)
- console.log(
- 'type',
- url.searchParams.get(TOCTOCTOC_OAUTH_PROVIDER_URL_PARAMETER),
- )
-
const accessToken = url.searchParams.get(TOCTOCTOC_ACCESS_TOKEN_URL_PARAMETER)
- const providerName = url.searchParams.get(
+ const providerType = url.searchParams.get(
TOCTOCTOC_OAUTH_PROVIDER_URL_PARAMETER,
)
-
let origin = url.searchParams.get(TOCTOCTOC_OAUTH_PROVIDER_ORIGIN_PARAMETER)
+ console.log('type', providerType, 'origin', origin)
+
if (!origin) {
- if (providerName === 'github') {
+ if (providerType === 'github') {
origin = 'https://github.com'
} else {
throw new TypeError('missing origin')
}
}
- if (accessToken && providerName) {
+ const providerId = new URL(origin).hostname
+
+ if (providerType && accessToken) {
const oAuthProvider = {
- name: providerName,
+ type: providerType,
accessToken,
origin,
+ id: providerId,
}
+ console.log(oAuthProvider)
+
store.mutations.setOAuthProvider(oAuthProvider)
remember(OAUTH_PROVIDER_STORAGE_KEY, oAuthProvider)
@@ -52,7 +53,7 @@ export default () => {
storeOAuthProviderAccess()
const oAuthProvider = store.state.oAuthProvider
- let type = oAuthProvider?.name
+ let type = oAuthProvider?.type
console.log('type', type)
console.log('oAuthProvider', oAuthProvider)
@@ -64,7 +65,7 @@ export default () => {
let currentUserReposP
- if (type === 'github' || type === 'gitlab') {
+ if (type === 'github' || type === 'gitlab' || type == 'scribouilli') {
currentUserReposP = fetchCurrentUserRepositories().then(repos => {
if (repos.length === 0) {
page.redirect('/creer-un-nouveau-site')
diff --git a/assets/scripts/routes/create-account.ts b/assets/scripts/routes/create-account.ts
index eec3646b..6dc04ade 100644
--- a/assets/scripts/routes/create-account.ts
+++ b/assets/scripts/routes/create-account.ts
@@ -1,16 +1,23 @@
import { Context } from 'page'
import CreateAccount from '../components/screens/CreateAccount.svelte'
import { replaceComponent } from '../routeComponentLifeCycle.svelte'
+import { PROVIDERS_MAP } from '../config'
export default ({ querystring }: Context) => {
const params = new URLSearchParams(querystring)
- const gitProvider = params.get('provider')
+ const providerId = params.get('provider')
- if (!gitProvider) {
+ if (!providerId) {
throw new TypeError(`Missing 'provider' parameter`)
}
+ const provider = PROVIDERS_MAP.get(providerId)
+
+ if (!provider) {
+ throw new TypeError(`Unkown provider ${providerId}`)
+ }
+
replaceComponent(CreateAccount, () => {
- return { gitProvider }
+ return { provider }
})
}
diff --git a/assets/scripts/routes/login.ts b/assets/scripts/routes/login.ts
index 11c056c4..4d506e7a 100644
--- a/assets/scripts/routes/login.ts
+++ b/assets/scripts/routes/login.ts
@@ -2,54 +2,39 @@ import { replaceComponent } from '../routeComponentLifeCycle.svelte'
import store from '../store'
import Login from '../components/screens/Login.svelte'
import { Context } from 'page'
+import {
+ TOCTOCTOC_ORIGIN,
+ TOCTOCTOC_OAUTH_PROVIDER_ORIGIN_PARAMETER,
+ TOCTOCTOC_OAUTH_PROVIDER_URL_PARAMETER,
+ PROVIDERS_MAP,
+} from '../config'
+import { ScribouilliBackendProvider } from '../types/atelier'
-const TOCTOCTOC_ORIGIN = `https://toctoctoc.lechappeebelle.team`
-
-const oAuthAppByProvider = new Map([
- [
- 'github.com',
- {
- origin: 'https://github.com',
- client_id: '64ecce0b01397c2499a6',
- },
- ],
- [
- 'gitlab.com',
- {
- origin: 'https://gitlab.com',
- client_id:
- 'b943c32d1a30f316cf4a72b5e40b05b6e71a1e3df34e2233c51e79838b22f7e8',
- },
- ],
- [
- 'git.scribouilli.org',
- {
- origin: 'https://git.scribouilli.org',
- client_id:
- '3e8ac6636615d396a8f73e02fa3880e7e2140981b0ca27b0f240a450f69f1c76',
- },
- ],
-])
-
-function redirectURLByProvider(gitProvider: string, destination: string) {
- if (gitProvider === 'github.com') {
+function redirectURLByProvider(
+ { type: providerType, origin }: ScribouilliBackendProvider,
+ destination: string,
+) {
+ if (providerType === 'github') {
return `${TOCTOCTOC_ORIGIN}/github-callback?destination=${destination}`
+ } else if (providerType === 'gitlab') {
+ return `${TOCTOCTOC_ORIGIN}/gitlab-callback/${origin}/?destination=${destination}`
+ } else if (providerType === 'scribouilli') {
+ return `${destination}?${TOCTOCTOC_OAUTH_PROVIDER_URL_PARAMETER}=scribouilli&${TOCTOCTOC_OAUTH_PROVIDER_ORIGIN_PARAMETER}=${origin}`
} else {
- // assume Gitlab and assume HTTPS
- return `${TOCTOCTOC_ORIGIN}/gitlab-callback/https://${gitProvider}/?destination=${destination}`
+ throw new Error('unreachable')
}
}
function makeLoginHref(
- gitProvider: string,
- client_id: string,
- redirect_url: string,
+ { clientId, type: providerType, origin }: ScribouilliBackendProvider,
+ redirectUrl: string,
) {
- if (gitProvider === 'github.com') {
- return `https://github.com/login/oauth/authorize?client_id=${client_id}&scope=public_repo,user:email&redirect_uri=${redirect_url}`
+ if (providerType === 'github') {
+ return `${origin}/login/oauth/authorize?client_id=${clientId}&scope=public_repo,user:email&redirect_uri=${redirectUrl}`
+ } else if (providerType == 'gitlab') {
+ return `${origin}/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=api+read_api`
} else {
- // assume HTTPS
- return `https://${gitProvider}/oauth/authorize?client_id=${client_id}&redirect_uri=${redirect_url}&response_type=code&scope=api+read_api`
+ return `${origin}/login?callback=${encodeURIComponent(redirectUrl)}`
}
}
@@ -65,18 +50,21 @@ export default ({ querystring }: Context) => {
const destination =
location.origin + store.state.basePath + '/after-oauth-login'
- const client_id = oAuthAppByProvider.get(gitProvider)?.client_id
- if (!client_id) {
- throw new TypeError(`Missing client_id`)
+
+ const provider = PROVIDERS_MAP.get(gitProvider)
+
+ if (!provider) {
+ throw new TypeError(`Unknown provider ${gitProvider}`)
}
- const redirect_url = redirectURLByProvider(gitProvider, destination)
- const loginHref = makeLoginHref(gitProvider, client_id, redirect_url)
+ const redirectUrl = redirectURLByProvider(provider, destination)
+ const loginHref = makeLoginHref(provider, redirectUrl)
replaceComponent(Login, () => {
return {
href: loginHref,
- gitProvider,
+ providerType: provider.type,
+ providerId: provider.id,
}
})
}
diff --git a/assets/scripts/scribouilliGitRepo.ts b/assets/scripts/scribouilliGitRepo.ts
index 493c6b54..0066d925 100644
--- a/assets/scripts/scribouilliGitRepo.ts
+++ b/assets/scripts/scribouilliGitRepo.ts
@@ -1,3 +1,4 @@
+import type { BackendType } from './types/atelier.ts'
import type { OAuthServiceAPI } from './types/git.ts'
export default class ScribouilliGitRepo {
@@ -6,30 +7,37 @@ export default class ScribouilliGitRepo {
public publicRepositoryURL
public owner
public repoName
+ public repoType: BackendType
public repoId
public publishedWebsiteURL: Promise
constructor({
repoId,
origin,
- publicRepositoryURL,
owner,
repoName,
+ repoType,
gitServiceProvider,
}: {
repoId?: string
origin: string
- publicRepositoryURL: string
owner: string
repoName: string
+ repoType: BackendType
gitServiceProvider: OAuthServiceAPI
}) {
this.origin = origin
- this.publicRepositoryURL = publicRepositoryURL
+ this.publicRepositoryURL = gitServiceProvider.makePublicRepositoryURL(
+ owner,
+ repoName,
+ )
this.owner = owner
this.repoName = repoName
+ this.repoType = repoType
- this.repoId = repoId ? repoId : makeRepoId(owner, repoName)
+ this.repoId = repoId
+ ? repoId
+ : gitServiceProvider.makeRepoId(owner, repoName)
this.publishedWebsiteURL = new Promise(resolve => {
const interval = setInterval(() => {
@@ -43,21 +51,3 @@ export default class ScribouilliGitRepo {
})
}
}
-
-/**
- * @param owner may be an individual Github user or an organisation
- */
-export function makeRepoId(owner: string, repoName: string): string {
- return `${owner}/${repoName}`
-}
-
-/**
- * @param owner may be an individual Github user or an organisation
- */
-export function makePublicRepositoryURL(
- owner: string,
- repoName: string,
- origin: string,
-): string {
- return `${origin}/${owner}/${repoName}`
-}
diff --git a/assets/scripts/store.ts b/assets/scripts/store.ts
index 90875ab5..cbc4d483 100644
--- a/assets/scripts/store.ts
+++ b/assets/scripts/store.ts
@@ -22,9 +22,10 @@ export interface ResolutionOption {
}
export interface OAuthProvider {
- name: string
+ type: string
accessToken: string
origin: string
+ id: string
}
export interface ScribouilliState {
diff --git a/assets/scripts/types/atelier.ts b/assets/scripts/types/atelier.ts
index 69daccee..d9aa8d04 100644
--- a/assets/scripts/types/atelier.ts
+++ b/assets/scripts/types/atelier.ts
@@ -31,3 +31,18 @@ export interface FileContenu {
inMenu: boolean
blogIndex: boolean
}
+
+export type BackendType = 'github' | 'gitlab' | 'scribouilli'
+
+export interface ScribouilliBackendProvider {
+ id: string
+ origin: string
+ clientId?: string
+ type: BackendType
+ description: string
+ name: string
+ signupInstructions?: string
+ signupEnabled: boolean
+ signupLink?: string
+ corsProxy?: string
+}
diff --git a/assets/scripts/types/git.ts b/assets/scripts/types/git.ts
index aae18fdc..e2d2dc98 100644
--- a/assets/scripts/types/git.ts
+++ b/assets/scripts/types/git.ts
@@ -1,7 +1,10 @@
+import type { BackendType } from './atelier'
+
interface ScribouilliGitRepo {
repoId: string
owner: string
repoName: string
+ repoType: BackendType
origin: string
publishedWebsiteURL: Promise
publicRepositoryURL: string
@@ -20,7 +23,10 @@ export type BuildStatus =
| 'needs_account_verification'
export interface OAuthServiceAPI {
- callAPI: (url: string, requestParams?: RequestInit) => Promise
+ callAPI: (
+ url: string,
+ requestParams?: RequestInit & { headers?: Record },
+ ) => Promise
getOauthUsernameAndPassword: () => { username: string; password: string }
getAuthenticatedUser: () => Promise
getUserEmails: () => Promise
@@ -42,6 +48,8 @@ export interface OAuthServiceAPI {
getPublishedWebsiteURL: (
scribouilliGitRepo: ScribouilliGitRepo,
) => Promise
+ makeRepoId: (owner: string, repoName: string) => string
+ makePublicRepositoryURL: (owner: string, repoName: string) => string
}
interface AuthenticatedUserEmails {
diff --git a/assets/styles/styles.css b/assets/styles/styles.css
index b4d6569f..ac52d154 100644
--- a/assets/styles/styles.css
+++ b/assets/styles/styles.css
@@ -357,4 +357,25 @@ a[href]:not(:where([href^="#"],
ul.list-with-dot {
padding-left: 1rem;
list-style-type: disc;
-}
\ No newline at end of file
+}
+
+.config-content {
+ ol {
+ text-align: left;
+
+ li {
+ margin-bottom: 1rem;
+ }
+ }
+
+ .text-align-start {
+ text-align: start;
+ }
+
+ .simple-list {
+ padding: 1rem 5rem;
+ text-align: left;
+ list-style-type: disc;
+ list-style-position: inside;
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index 4ce00845..567cc4b8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,7 +19,8 @@
"js-yaml": "^3.14.1",
"marked": "^11.0.0",
"page": "^1.11.6",
- "remember": "github:DavidBruant/remember#v1.0.2"
+ "remember": "github:DavidBruant/remember#v1.0.2",
+ "zod": "^4.5.2"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^29.0.3",
@@ -8041,6 +8042,15 @@
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
"dev": true,
"license": "MIT"
+ },
+ "node_modules/zod": {
+ "version": "4.5.2",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.2.tgz",
+ "integrity": "sha512-XkYXCol10+ba/6F/cueWV+TezUeOqXW0hdeJt5CdXjTYeAgAQg5N03RQdJ80mhfFE72+pblvYMW4wy2Qp4Qbrg==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
}
}
}
diff --git a/package.json b/package.json
index 36e3371f..5fb6a562 100644
--- a/package.json
+++ b/package.json
@@ -74,6 +74,7 @@
"js-yaml": "^3.14.1",
"marked": "^11.0.0",
"page": "^1.11.6",
- "remember": "github:DavidBruant/remember#v1.0.2"
+ "remember": "github:DavidBruant/remember#v1.0.2",
+ "zod": "^4.5.2"
}
}
diff --git a/tests/actions/current-repository.test.ts b/tests/actions/current-repository.test.ts
index 4f433773..1eb56521 100644
--- a/tests/actions/current-repository.test.ts
+++ b/tests/actions/current-repository.test.ts
@@ -6,6 +6,7 @@ import { describe } from 'mocha'
import sinon from 'sinon'
import { expect } from 'chai'
import GitAgent from '../../assets/scripts/GitAgent.ts'
+import GitHubAPI from '../../assets/scripts/oauth-services-api/github.ts'
describe('actions/current-repository.ts', () => {
describe('saveCustomCSS', () => {
@@ -22,11 +23,12 @@ describe('actions/current-repository.ts', () => {
repoName: 'site',
repoId: 'test-site',
publishedWebsiteURL: Promise.resolve('https://test.github.io/site'),
+ repoType: 'github' as const
},
gitAgent: new GitAgent({
- auth: {},
remoteURL: 'https://github.com',
repoId: 'test-site',
+ gitServiceProvider: new GitHubAPI('fake token')
}),
},
subscribe: sinon.stub(),