From eac9577af884224103617ddd45ce1ddc3aa7039a Mon Sep 17 00:00:00 2001 From: Hannaeko <19394895+hannaeko@users.noreply.github.com> Date: Mon, 10 Aug 2026 13:59:24 +0200 Subject: [PATCH 1/8] add support for scribouilli backend and refactor configuration --- assets/scripts/GitAgent.ts | 19 ++- assets/scripts/actions/current-repository.ts | 23 +-- assets/scripts/actions/setup.ts | 26 ++-- assets/scripts/components/Header.svelte | 12 +- assets/scripts/components/Skeleton.svelte | 2 +- .../scripts/components/screens/Account.svelte | 51 ++----- .../components/screens/ChooseAccount.svelte | 19 +-- .../components/screens/CreateAccount.svelte | 98 ++---------- .../components/screens/CreateNewSite.svelte | 7 +- .../scripts/components/screens/Login.svelte | 18 ++- .../components/screens/intern/Editeur.svelte | 12 +- assets/scripts/config.ts | 93 +++++++++++- assets/scripts/oauth-services-api/github.ts | 7 + assets/scripts/oauth-services-api/gitlab.ts | 7 + assets/scripts/oauth-services-api/index.ts | 38 ++++- .../scripts/oauth-services-api/scribouilli.ts | 142 ++++++++++++++++++ assets/scripts/routes/account.ts | 15 +- assets/scripts/routes/after-oauth-login.ts | 25 +-- assets/scripts/routes/create-account.ts | 13 +- assets/scripts/routes/login.ts | 80 +++++----- assets/scripts/scribouilliGitRepo.ts | 29 +--- assets/scripts/store.ts | 3 +- assets/scripts/types/atelier.ts | 18 +++ assets/scripts/types/git.ts | 5 + assets/styles/styles.css | 23 ++- 25 files changed, 503 insertions(+), 282 deletions(-) create mode 100644 assets/scripts/oauth-services-api/scribouilli.ts diff --git a/assets/scripts/GitAgent.ts b/assets/scripts/GitAgent.ts index 0d4e8f16..652a8204 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 { 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 + corsProxyURL?: string, + 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..8153c2de 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 ?

- -
- Oui, je me connecte - Non, je veux créer un compte -
-
-
- {/if} - - {#if gitProvider === 'gitlab.com'} -
-
-

Avez-vous un compte sur gitlab.com ?

- -
- Oui, je me connecte - Non, je veux créer un compte -
-
-
- {/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}
- Oui, je me connecte - Oui, je me connecte + Non, je veux créer un compte
- {/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 @@
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'}

Super, nous allons vous demander les clefs sur la page suivante.

Je me connecte via GitHub
{/if} - {#if gitProvider === 'gitlab.com'} + {#if providerType === 'gitlab'}

Super, nous allons vous demander les clefs sur la page suivante.

- Je me connecte via gitlab.com + Je me connecte via {providerId}
{/if} - {#if gitProvider === 'git.scribouilli.org'} + {#if providerType === 'scribouilli'}

Super, nous allons vous demander les clefs sur la page suivante.

- Je me connecte via git.scribouilli.org + Je me connecte
{/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..2c559a39 100644 --- a/assets/scripts/config.ts +++ b/assets/scripts/config.ts @@ -1,6 +1,9 @@ +import { 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,92 @@ 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 : +

+ + +

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 :

+
    +
  1. + Rentrez votre mail, mot de passe, et votre nom d'utilisateur·ice +
  2. +
  3. + Ouvrez le mail que GitHub vous a envoyé, et copiez le code pour + confirmer votre compte +
  4. +
  5. + Dès que le code est validé, revenez sur Scribouilli et + cliquez sur "J'ai créé un compte" +
  6. +
+ `, + 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..de2b0cef 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..94aba3f3 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..d5919e05 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,22 @@ 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..acc1779d --- /dev/null +++ b/assets/scripts/oauth-services-api/scribouilli.ts @@ -0,0 +1,142 @@ +import ScribouilliGitRepo from '../scribouilliGitRepo.ts' +import type { BuildStatus, GithubRepository, GitSiteTemplate, OAuthServiceAPI } from '../types/git.ts' + +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 = {}) { + 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 repos = await response.json() + const { email } = await this.getAuthenticatedUser(); + // @ts-ignore + 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..535a5d77 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..7dabf455 100644 --- a/assets/scripts/routes/login.ts +++ b/assets/scripts/routes/login.ts @@ -2,54 +2,42 @@ 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') { + // TODO: get rid of origin parameter when not used? + 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?redirect_to=${encodeURIComponent(redirectUrl)}` } } @@ -63,20 +51,24 @@ export default ({ querystring }: Context) => { throw new TypeError(`Missing 'provider' parameter`) } + 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`) + location.origin + store.state.basePath + '/after-oauth-login' + + 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..50e6af6d 100644 --- a/assets/scripts/scribouilliGitRepo.ts +++ b/assets/scripts/scribouilliGitRepo.ts @@ -1,3 +1,4 @@ +import { BackendType } from './types/atelier.ts' import type { OAuthServiceAPI } from './types/git.ts' export default class ScribouilliGitRepo { @@ -6,30 +7,32 @@ 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 +46,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..eb3a6a6c 100644 --- a/assets/scripts/types/atelier.ts +++ b/assets/scripts/types/atelier.ts @@ -31,3 +31,21 @@ 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..462c1897 100644 --- a/assets/scripts/types/git.ts +++ b/assets/scripts/types/git.ts @@ -1,7 +1,10 @@ +import { BackendType } from "./atelier" + interface ScribouilliGitRepo { repoId: string owner: string repoName: string + repoType: BackendType, origin: string publishedWebsiteURL: Promise publicRepositoryURL: string @@ -42,6 +45,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..a91c3ad7 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; + } +} From ec23bb71d785083c9e29ca73c26d591c1a0039a0 Mon Sep 17 00:00:00 2001 From: Hannaeko Date: Fri, 21 Aug 2026 18:29:19 +0200 Subject: [PATCH 2/8] change query param --- assets/scripts/routes/login.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/scripts/routes/login.ts b/assets/scripts/routes/login.ts index 7dabf455..325d29c4 100644 --- a/assets/scripts/routes/login.ts +++ b/assets/scripts/routes/login.ts @@ -37,7 +37,7 @@ function makeLoginHref( } else if (providerType == 'gitlab') { return `${origin}/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=api+read_api` } else { - return `${origin}/login?redirect_to=${encodeURIComponent(redirectUrl)}` + return `${origin}/login?callback=${encodeURIComponent(redirectUrl)}` } } From e0e588727fd18d9a05552839582196f2ba1bb05e Mon Sep 17 00:00:00 2001 From: Hannaeko Date: Sat, 29 Aug 2026 15:45:40 +0200 Subject: [PATCH 3/8] commenter la section scribouilli-backend de la config --- assets/scripts/config.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/assets/scripts/config.ts b/assets/scripts/config.ts index 2c559a39..54d3854a 100644 --- a/assets/scripts/config.ts +++ b/assets/scripts/config.ts @@ -28,14 +28,14 @@ export const TEMPLATES: GitSiteTemplate[] = [ 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: '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', From 8b438cea3d7dd3dc043e6282acffbc1b8ff93730 Mon Sep 17 00:00:00 2001 From: Ana Gelez Date: Sat, 29 Aug 2026 16:00:31 +0200 Subject: [PATCH 4/8] Formatting --- assets/scripts/GitAgent.ts | 4 +- assets/scripts/config.ts | 14 +- assets/scripts/oauth-services-api/github.ts | 4 +- assets/scripts/oauth-services-api/gitlab.ts | 4 +- assets/scripts/oauth-services-api/index.ts | 1 - .../scripts/oauth-services-api/scribouilli.ts | 281 ++++++++++-------- assets/scripts/routes/account.ts | 2 +- assets/scripts/routes/login.ts | 11 +- assets/scripts/scribouilliGitRepo.ts | 11 +- assets/scripts/types/atelier.ts | 25 +- assets/scripts/types/git.ts | 4 +- assets/styles/styles.css | 2 +- 12 files changed, 195 insertions(+), 168 deletions(-) diff --git a/assets/scripts/GitAgent.ts b/assets/scripts/GitAgent.ts index 652a8204..48098e59 100644 --- a/assets/scripts/GitAgent.ts +++ b/assets/scripts/GitAgent.ts @@ -39,8 +39,8 @@ export default class GitAgent { }: { repoId: string remoteURL: string - corsProxyURL?: string, - gitServiceProvider: OAuthServiceAPI, + corsProxyURL?: string + gitServiceProvider: OAuthServiceAPI onMergeConflict?: | ((resolutionOptions: ResolutionOption[]) => void) | undefined diff --git a/assets/scripts/config.ts b/assets/scripts/config.ts index 54d3854a..86cfa94b 100644 --- a/assets/scripts/config.ts +++ b/assets/scripts/config.ts @@ -40,7 +40,8 @@ export const PROVIDERS: ScribouilliBackendProvider[] = [ id: 'gitlab.com', type: 'gitlab', origin: 'https://gitlab.com', - clientId: 'b943c32d1a30f316cf4a72b5e40b05b6e71a1e3df34e2233c51e79838b22f7e8', + clientId: + 'b943c32d1a30f316cf4a72b5e40b05b6e71a1e3df34e2233c51e79838b22f7e8', name: 'Gitlab', description: ` Gitlab.com qui est un hébergeur professionnel.
@@ -54,7 +55,8 @@ export const PROVIDERS: ScribouilliBackendProvider[] = [ id: 'git.scribouilli.org', type: 'gitlab', origin: 'https://git.scribouilli.org', - clientId: '3e8ac6636615d396a8f73e02fa3880e7e2140981b0ca27b0f240a450f69f1c76', + clientId: + '3e8ac6636615d396a8f73e02fa3880e7e2140981b0ca27b0f240a450f69f1c76', name: 'ScribouGit', description: ` ScribouGit, l'hébergement géré par l'équipe de Scribouilli.
@@ -87,7 +89,7 @@ export const PROVIDERS: ScribouilliBackendProvider[] = [ name: 'GitHub', signupEnabled: true, signupLink: 'https://github.com/signup', - signupInstructions:` + signupInstructions: `

Pour pouvoir publier votre contenu, il faut que Scribouilli se connecte à un compte GitHub. @@ -109,8 +111,10 @@ export const PROVIDERS: ScribouilliBackendProvider[] = [ `, corsProxy: DEFAULT_CORS_PROXY_URL, - } + }, ] -export const PROVIDERS_MAP = new Map(PROVIDERS.map(provider => [provider.id, provider])) +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 de2b0cef..9456cba5 100644 --- a/assets/scripts/oauth-services-api/github.ts +++ b/assets/scripts/oauth-services-api/github.ts @@ -199,7 +199,7 @@ export default class GitHubAPI implements OAuthServiceAPI { makeRepoId = defaultMakeRepoId - makePublicRepositoryURL(owner: string, repoName: string ): string { - return defaultMakePublicRepositoryURL(owner, repoName, 'https://github.com') + 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 94aba3f3..a04a24c0 100644 --- a/assets/scripts/oauth-services-api/gitlab.ts +++ b/assets/scripts/oauth-services-api/gitlab.ts @@ -240,7 +240,7 @@ export default class GitLabAPI implements OAuthServiceAPI { makeRepoId = defaultMakeRepoId - makePublicRepositoryURL(owner: string, repoName: string ): string { - return defaultMakePublicRepositoryURL(owner, repoName, this.origin) + 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 d5919e05..a109ac61 100644 --- a/assets/scripts/oauth-services-api/index.ts +++ b/assets/scripts/oauth-services-api/index.ts @@ -55,7 +55,6 @@ export const getOAuthServiceAPI = (): OAuthServiceAPI => { return oAuthServiceAPI } - /** * @param owner may be an individual Github user or an organisation */ diff --git a/assets/scripts/oauth-services-api/scribouilli.ts b/assets/scripts/oauth-services-api/scribouilli.ts index acc1779d..77ba7cd9 100644 --- a/assets/scripts/oauth-services-api/scribouilli.ts +++ b/assets/scripts/oauth-services-api/scribouilli.ts @@ -1,142 +1,167 @@ import ScribouilliGitRepo from '../scribouilliGitRepo.ts' -import type { BuildStatus, GithubRepository, GitSiteTemplate, OAuthServiceAPI } from '../types/git.ts' +import type { + BuildStatus, + GithubRepository, + GitSiteTemplate, + OAuthServiceAPI, +} from '../types/git.ts' 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 + 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 = {}) { + requestParams.headers ??= {} + requestParams.headers['Authorization'] = 'Bearer ' + this.accessToken + + const httpResp = await fetch(`${this.apiBaseUrl}${url}`, requestParams) + if (httpResp.status === 404) { + throw 'NOT_FOUND' } - - get apiBaseUrl() { - return `${this.origin}/api` + if (httpResp.status === 401) { + this.accessToken = undefined + console.debug('this accessToken : ', this.accessToken) + throw 'INVALIDATE_TOKEN' } + return httpResp + } - async callAPI(url: string, requestParams: RequestInit = {}) { - 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') } - getOauthUsernameAndPassword() { - if (!this.accessToken) { - throw new TypeError('Missing accessToken') - } - - return { username: 'token', password: this.accessToken } - } - - - async getAuthenticatedUser() { - if (this.authenticatedUser) { - return this.authenticatedUser - } + return { username: 'token', password: this.accessToken } + } - 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 getAuthenticatedUser() { + if (this.authenticatedUser) { + return this.authenticatedUser } - async getUserEmails() { - const { email } = await this.getAuthenticatedUser(); - return [{ - email, - primary: true - }] + const response = await this.callAPI(`/profile`) + const user = await response.json() + this.authenticatedUser = { + login: user.email, + email: user.email, + id: user.id, } - - 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 repos = await response.json() - const { email } = await this.getAuthenticatedUser(); - // @ts-ignore - 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}` + 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 repos = await response.json() + const { email } = await this.getAuthenticatedUser() + // @ts-ignore + 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 535a5d77..5f32cb36 100644 --- a/assets/scripts/routes/account.ts +++ b/assets/scripts/routes/account.ts @@ -13,7 +13,7 @@ export default ({ querystring }: Context) => { throw new TypeError(`Missing 'provider' parameter`) } - const provider = PROVIDERS_MAP.get(providerId); + const provider = PROVIDERS_MAP.get(providerId) if (!provider) { throw new TypeError(`Unkown provider ${providerId}`) diff --git a/assets/scripts/routes/login.ts b/assets/scripts/routes/login.ts index 325d29c4..152634d4 100644 --- a/assets/scripts/routes/login.ts +++ b/assets/scripts/routes/login.ts @@ -6,15 +6,13 @@ import { TOCTOCTOC_ORIGIN, TOCTOCTOC_OAUTH_PROVIDER_ORIGIN_PARAMETER, TOCTOCTOC_OAUTH_PROVIDER_URL_PARAMETER, - PROVIDERS_MAP + PROVIDERS_MAP, } from '../config' import { ScribouilliBackendProvider } from '../types/atelier' - - function redirectURLByProvider( { type: providerType, origin }: ScribouilliBackendProvider, - destination: string + destination: string, ) { if (providerType === 'github') { return `${TOCTOCTOC_ORIGIN}/github-callback?destination=${destination}` @@ -29,7 +27,7 @@ function redirectURLByProvider( } function makeLoginHref( - {clientId, type: providerType, origin}: ScribouilliBackendProvider, + { clientId, type: providerType, origin }: ScribouilliBackendProvider, redirectUrl: string, ) { if (providerType === 'github') { @@ -51,9 +49,8 @@ export default ({ querystring }: Context) => { throw new TypeError(`Missing 'provider' parameter`) } - const destination = - location.origin + store.state.basePath + '/after-oauth-login' + location.origin + store.state.basePath + '/after-oauth-login' const provider = PROVIDERS_MAP.get(gitProvider) diff --git a/assets/scripts/scribouilliGitRepo.ts b/assets/scripts/scribouilliGitRepo.ts index 50e6af6d..cd2bd85d 100644 --- a/assets/scripts/scribouilliGitRepo.ts +++ b/assets/scripts/scribouilliGitRepo.ts @@ -23,16 +23,21 @@ export default class ScribouilliGitRepo { origin: string owner: string repoName: string - repoType: BackendType, + repoType: BackendType gitServiceProvider: OAuthServiceAPI }) { this.origin = origin - this.publicRepositoryURL = gitServiceProvider.makePublicRepositoryURL(owner, repoName) + this.publicRepositoryURL = gitServiceProvider.makePublicRepositoryURL( + owner, + repoName, + ) this.owner = owner this.repoName = repoName this.repoType = repoType - this.repoId = repoId ? repoId : gitServiceProvider.makeRepoId(owner, repoName) + this.repoId = repoId + ? repoId + : gitServiceProvider.makeRepoId(owner, repoName) this.publishedWebsiteURL = new Promise(resolve => { const interval = setInterval(() => { diff --git a/assets/scripts/types/atelier.ts b/assets/scripts/types/atelier.ts index eb3a6a6c..d9aa8d04 100644 --- a/assets/scripts/types/atelier.ts +++ b/assets/scripts/types/atelier.ts @@ -32,20 +32,17 @@ export interface FileContenu { blogIndex: boolean } -export type BackendType = - | 'github' - | 'gitlab' - | 'scribouilli' +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, + 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 462c1897..e8dbfe84 100644 --- a/assets/scripts/types/git.ts +++ b/assets/scripts/types/git.ts @@ -1,10 +1,10 @@ -import { BackendType } from "./atelier" +import { BackendType } from './atelier' interface ScribouilliGitRepo { repoId: string owner: string repoName: string - repoType: BackendType, + repoType: BackendType origin: string publishedWebsiteURL: Promise publicRepositoryURL: string diff --git a/assets/styles/styles.css b/assets/styles/styles.css index a91c3ad7..ac52d154 100644 --- a/assets/styles/styles.css +++ b/assets/styles/styles.css @@ -377,5 +377,5 @@ ul.list-with-dot { text-align: left; list-style-type: disc; list-style-position: inside; - } + } } From 404e9321ea8fd1498920068276bc4ba2893000f3 Mon Sep 17 00:00:00 2001 From: Ana Gelez Date: Sat, 29 Aug 2026 15:59:25 +0200 Subject: [PATCH 5/8] Fix HTTP header typing --- assets/scripts/oauth-services-api/scribouilli.ts | 5 ++++- assets/scripts/types/git.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/assets/scripts/oauth-services-api/scribouilli.ts b/assets/scripts/oauth-services-api/scribouilli.ts index 77ba7cd9..bafcb03b 100644 --- a/assets/scripts/oauth-services-api/scribouilli.ts +++ b/assets/scripts/oauth-services-api/scribouilli.ts @@ -24,7 +24,10 @@ export default class ScribouilliBackend implements OAuthServiceAPI { return `${this.origin}/api` } - async callAPI(url: string, requestParams: RequestInit = {}) { + async callAPI( + url: string, + requestParams: RequestInit & { headers?: Record } = {}, + ) { requestParams.headers ??= {} requestParams.headers['Authorization'] = 'Bearer ' + this.accessToken diff --git a/assets/scripts/types/git.ts b/assets/scripts/types/git.ts index e8dbfe84..2273403a 100644 --- a/assets/scripts/types/git.ts +++ b/assets/scripts/types/git.ts @@ -23,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 From ee97ace9a04e938340133549b2d3380a826abb62 Mon Sep 17 00:00:00 2001 From: Ana Gelez Date: Sat, 29 Aug 2026 16:06:35 +0200 Subject: [PATCH 6/8] Properly type API interactions --- assets/scripts/oauth-services-api/scribouilli.ts | 13 +++++++++++-- package-lock.json | 12 +++++++++++- package.json | 3 ++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/assets/scripts/oauth-services-api/scribouilli.ts b/assets/scripts/oauth-services-api/scribouilli.ts index bafcb03b..31b42bcf 100644 --- a/assets/scripts/oauth-services-api/scribouilli.ts +++ b/assets/scripts/oauth-services-api/scribouilli.ts @@ -5,6 +5,15 @@ import type { 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 @@ -107,9 +116,9 @@ export default class ScribouilliBackend implements OAuthServiceAPI { async getCurrentUserRepositories(): Promise { const response = await this.callAPI(`/websites`) - const repos = await response.json() + const rawRepos = await response.json() + const repos = z.parse(WEBSITE_LIST_SCHEMA, rawRepos) const { email } = await this.getAuthenticatedUser() - // @ts-ignore const githubRepos = repos.map(repo => { return { id: repo.name, 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" } } From cacdd816c8fdaf74458ba8d4fe36f6d0959b1202 Mon Sep 17 00:00:00 2001 From: Hannaeko Date: Sat, 29 Aug 2026 16:11:25 +0200 Subject: [PATCH 7/8] remove todo comment --- assets/scripts/routes/login.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/assets/scripts/routes/login.ts b/assets/scripts/routes/login.ts index 152634d4..4d506e7a 100644 --- a/assets/scripts/routes/login.ts +++ b/assets/scripts/routes/login.ts @@ -19,7 +19,6 @@ function redirectURLByProvider( } else if (providerType === 'gitlab') { return `${TOCTOCTOC_ORIGIN}/gitlab-callback/${origin}/?destination=${destination}` } else if (providerType === 'scribouilli') { - // TODO: get rid of origin parameter when not used? return `${destination}?${TOCTOCTOC_OAUTH_PROVIDER_URL_PARAMETER}=scribouilli&${TOCTOCTOC_OAUTH_PROVIDER_ORIGIN_PARAMETER}=${origin}` } else { throw new Error('unreachable') From 46e4b4e9c79301e0d26fd1742553cfffab833771 Mon Sep 17 00:00:00 2001 From: Hannaeko Date: Sat, 29 Aug 2026 16:51:31 +0200 Subject: [PATCH 8/8] fix tests --- assets/scripts/GitAgent.ts | 2 +- assets/scripts/components/Header.svelte | 2 +- assets/scripts/config.ts | 2 +- assets/scripts/scribouilliGitRepo.ts | 2 +- assets/scripts/types/git.ts | 2 +- tests/actions/current-repository.test.ts | 4 +++- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/assets/scripts/GitAgent.ts b/assets/scripts/GitAgent.ts index 48098e59..ff0319d0 100644 --- a/assets/scripts/GitAgent.ts +++ b/assets/scripts/GitAgent.ts @@ -15,7 +15,7 @@ import http from 'isomorphic-git/http/web' import type { CommitObject } from 'isomorphic-git' import type { ResolutionOption } from './store.ts' -import { OAuthServiceAPI } from './types/git.ts' +import type { OAuthServiceAPI } from './types/git.ts' export default class GitAgent { #fs diff --git a/assets/scripts/components/Header.svelte b/assets/scripts/components/Header.svelte index 8153c2de..1b982e9e 100644 --- a/assets/scripts/components/Header.svelte +++ b/assets/scripts/components/Header.svelte @@ -1,7 +1,7 @@