Skip to content

Commit

Permalink
Add Gitea / Forgejo provider (#271).
Browse files Browse the repository at this point in the history
  • Loading branch information
jimafisk committed Nov 8, 2024
1 parent 41d16da commit 3be7302
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 24 deletions.
8 changes: 6 additions & 2 deletions cmd/build/data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ type env struct {
cms cms
}
type cms struct {
provider string
repo string
redirectUrl string
appId string
Expand All @@ -92,6 +93,7 @@ func DataSource(buildPath string, spaPath string, siteConfig readers.SiteConfig)
entrypointHTML: siteConfig.EntryPointHTML,
entrypointJS: siteConfig.EntryPointJS,
cms: cms{
provider: siteConfig.CMS.Provider,
repo: siteConfig.CMS.Repo,
redirectUrl: siteConfig.CMS.RedirectUrl,
appId: siteConfig.CMS.AppId,
Expand Down Expand Up @@ -122,7 +124,8 @@ func DataSource(buildPath string, spaPath string, siteConfig readers.SiteConfig)
", fingerprint: '" + env.fingerprint +
"', entrypointHTML: '" + env.entrypointHTML +
"', entrypointJS: '" + env.entrypointJS +
"', cms: { repo: '" + env.cms.repo +
"', cms: { provider: '" + env.cms.provider +
"', repo: '" + env.cms.repo +
"', redirectUrl: '" + env.cms.redirectUrl +
"', appId: '" + env.cms.appId +
"', branch: '" + env.cms.branch +
Expand Down Expand Up @@ -522,7 +525,8 @@ func createProps(currentContent content, allContentStr string, env env) error {
", baseurl: '"+env.baseurl+
"', fingerprint: '"+env.fingerprint+
"', entrypointJS: '"+env.entrypointJS+
"', cms: { repo: '"+env.cms.repo+
"', cms: { provider: '"+env.cms.provider+
"', repo: '"+env.cms.repo+
"', redirectUrl: '"+env.cms.redirectUrl+
"', appId: '"+env.cms.appId+
"', branch: '"+env.cms.branch+
Expand Down
36 changes: 25 additions & 11 deletions defaults/core/cms/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,22 @@ import adminMenu from './admin_menu.svelte';

export const repoUrl = makeUrl(env.cms.repo);
const local = env.local;
const provider = env.cms.provider.toLowerCase();

let authorization_endpoint, access_token_endpoint;
if (!provider || provider === "gitlab") {
authorization_endpoint = "/oauth/authorize";
access_token_endpoint = "/oauth/token";
}
if (provider === "gitea" || provider === "forgejo") {
authorization_endpoint = "/login/oauth/authorize";
access_token_endpoint = "/login/oauth/access_token";
}

const settings = {
provider: provider,
authorization_endpoint: authorization_endpoint,
access_token_endpoint: access_token_endpoint,
server: repoUrl.origin,
group: repoUrl.pathname.split('/')[1],
repository: repoUrl.pathname.split('/')[2],
Expand All @@ -21,18 +35,18 @@ let localTokens;
localTokenStore.subscribe(value => {
localTokens = value;
});
const tokenStore = createDataStore('gitlab_tokens');
const tokenStore = createDataStore('tokens');
let tokens, isExpired;
tokenStore.subscribe(value => {
tokens = value;
isExpired = tokens && Date.now() > (tokens.created_at + tokens.expires_in) * 1000;
});

const codeVerifierStore = createDataStore('gitlab_code_verifier');
const codeVerifierStore = createDataStore('code_verifier');
let codeVerifier;
codeVerifierStore.subscribe(value => codeVerifier = value);

const stateStore = createSessionStore('gitlab_state');
const stateStore = createSessionStore('state');
let state;
stateStore.subscribe(value => state = value);

Expand All @@ -56,7 +70,7 @@ const getUser = () => ({
},

refresh() {
let authTokens = JSON.parse(localStorage.getItem('PLENTI_CMS_GITLAB_TOKENS'));
let authTokens = JSON.parse(localStorage.getItem('PLENTI_CMS_TOKENS'));
this.isAuthenticated = typeof authTokens?.access_token !== 'undefined';
this.tokens = authTokens;
},
Expand Down Expand Up @@ -109,19 +123,19 @@ const requestAuthCode = async () => {
codeVerifierStore.set(generateString());
const codeChallenge = await hash(codeVerifier);

const { server, redirectUrl, appId } = settings;
window.location.href = server + "/oauth/authorize"
const { authorization_endpoint, server, redirectUrl, appId } = settings;
window.location.href = server + authorization_endpoint
+ "?client_id=" + encodeURIComponent(appId)
+ "&redirect_uri=" + encodeURIComponent(redirectUrl)
+ "&response_type=code"
+ "&state=" + encodeURIComponent(state)
+ "&code_challenge=" + encodeURIComponent(codeChallenge)
+ "&code_challenge_method=S256";
+ "&code_challenge_method=S256";
};

const requestAccessToken = async code => {
const { server, redirectUrl, appId } = settings;
const response = await fetch(server + "/oauth/token"
const { access_token_endpoint, server, redirectUrl, appId } = settings;
const response = await fetch(server + access_token_endpoint
+ "?client_id=" + encodeURIComponent(appId)
+ "&code=" + encodeURIComponent(code)
+ "&grant_type=authorization_code"
Expand All @@ -137,11 +151,11 @@ const requestAccessToken = async code => {
};

const requestRefreshToken = async () => {
const { server, redirectUrl, appId } = settings;
const { access_token_endpoint, server, redirectUrl, appId } = settings;
if (!codeVerifier) {
throw new Error("Code verifier not saved to session storage");
}
const response = await fetch(server + "/oauth/token"
const response = await fetch(server + access_token_endpoint
+ "?client_id=" + encodeURIComponent(appId)
+ "&refresh_token=" + encodeURIComponent(tokens.refresh_token)
+ "&grant_type=refresh_token"
Expand Down
12 changes: 8 additions & 4 deletions defaults/core/cms/button.svelte
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
<script>
import { publish } from './publish.js';
import { postLocal } from './post_local.js';
import { commitGitlab } from './providers/gitlab.js';
import { commitGitea } from './providers/gitea.js';
import { postLocal } from './providers/local.js';
import { env } from '../../generated/env.js';
import { findFileReferences } from './file_references.js';
export let commitList, shadowContent, buttonText, action, encoding, user, afterSubmit, status;
export let buttonStyle = "primary";
const local = env.local ?? false;
const provider = env.cms.provider.toLowerCase();
let confirmTooltip;
const onSubmit = async () => {
Expand All @@ -15,8 +17,10 @@
try {
if (local) {
await postLocal(commitList, shadowContent, action, encoding, user);
} else {
await publish(commitList, shadowContent, action, encoding, user);
} else if (!provider || provider === "gitlab") {
await commitGitlab(commitList, shadowContent, action, encoding, user);
} else if (provider === "gitea" || provider === "forgejo") {
await commitGitea(commitList, shadowContent, action, encoding, user);
}
status = "sent";
afterSubmit?.();
Expand Down
116 changes: 116 additions & 0 deletions defaults/core/cms/providers/gitea.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { env } from '../../../generated/env.js';
import { makeUrl } from '../url_checker.js';
import evaluateRoute from '../route_eval.js';

const repoUrl = makeUrl(env.cms.repo);
const owner = repoUrl.pathname.split('/')[1];
const repo = repoUrl.pathname.split('/')[2];
const apiBaseUrl = `${repoUrl.origin}/api/v1`;

const capitalizeFirstLetter = string => {
return string.charAt(0).toUpperCase() + string.slice(1);
}

/**
* @param {string} file
* @param {string} contents
* @param {string} action
*/
export async function commitGitea(commitList, shadowContent, action, encoding, user) {
// Keep track of current user and promise it's availability.
let currentUser;
const userAvailable = new Promise(resolve => {
user.subscribe(user => {
currentUser = user;
resolve();
});
});

await userAvailable;
if (!currentUser.isAuthenticated) {
throw new Error('Authentication required');
}

const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${currentUser.tokens.access_token}`,
};

// Set default Gitea User
let giteaUser = {
email: 'cms@plenti.co',
name: 'CMS'
};

await fetch(`${apiBaseUrl}` + `/user`, {
method: 'GET',
headers
}).then(response => {
return response.json();
}).then(data => {
// Get actual Gitea User
giteaUser = data;
});

for (const commitItem of commitList) {
const url = `${apiBaseUrl}/repos/${owner}/${repo}/contents/` + commitItem.file;

const makeDataStr = base64Str => base64Str.split(',')[1];
let message = capitalizeFirstLetter(action) + ' ' + (commitList.length > 1 ? commitList.length + ' files' : commitList[0].file);
let content = encoding === "base64" ? makeDataStr(commitItem.contents) : btoa(unescape(encodeURIComponent(commitItem.contents)));

const payload = {
author: {
email: giteaUser?.email,
name: giteaUser.login
},
branch: env.cms.branch,
message: message,
content: content,
};

if (action === 'update' || action === 'delete') {
// Get details about existing file from Gitea
await fetch(url, {
method: 'GET',
headers,
}).then(response => {
return response.json();
}).then(data => {
// Set the required SHA in payload
payload.sha = data.sha;
});
}

let method = action === 'create' ? 'POST' : action === 'update' ? 'PUT' : action === 'delete' ? 'DELETE' : '';

const response = await fetch(url, {
method: method,
headers,
body: JSON.stringify(payload),
});
if (response.ok) {
if (action === 'create' || action === 'update') {
shadowContent?.onSave?.();
// Make sure saving single content file, not list of media items
if (commitList.length === 1 && commitList[0].file.lastIndexOf('.json') > 0) {
let evaluatedRoute = evaluateRoute(commitList[0]);
// Redirect only if new route is being created
if (evaluatedRoute !== location.pathname) {
history.pushState({
isNew: true,
route: evaluatedRoute
}, '', evaluatedRoute);
}
}
}
if (action === 'delete') {
shadowContent?.onDelete?.();
history.pushState(null, '', env.baseurl && !env.local ? env.baseurl : '/');
}
} else {
const { error, message } = await response.json();
throw new Error(`Publish failed: ${error || message}`);
}
};
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { env } from '../../generated/env.js';
import { makeUrl } from './url_checker.js';
import evaluateRoute from './route_eval.js';
import { env } from '../../../generated/env.js';
import { makeUrl } from '../url_checker.js';
import evaluateRoute from '../route_eval.js';

const repoUrl = makeUrl(env.cms.repo);
const apiBaseUrl = `${repoUrl.origin}/api/v4`;
Expand All @@ -14,7 +14,7 @@ const capitalizeFirstLetter = string => {
* @param {string} contents
* @param {string} action
*/
export async function publish(commitList, shadowContent, action, encoding, user) {
export async function commitGitlab(commitList, shadowContent, action, encoding, user) {
// Keep track of current user and promise it's availability.
let currentUser;
const userAvailable = new Promise(resolve => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { env } from '../../generated/env.js';
import evaluateRoute from './route_eval.js';
import { env } from '../../../generated/env.js';
import evaluateRoute from '../route_eval.js';

export async function postLocal(commitList, shadowContent, action, encoding) {
let url = '/postlocal';
Expand Down
3 changes: 2 additions & 1 deletion defaults/starters/learner/plenti.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"port": 3000
},
"cms": {
"repo": "https://gitlab.com/YourGroup/YourRepo",
"provider": "gitlab",
"repo": "https://gitlab.com/your-group/your-repo",
"redirect_url": "http://localhost:3000/admin/",
"app_id": "",
"branch": "main"
Expand Down
1 change: 1 addition & 0 deletions readers/site_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type SiteConfig struct {
} `json:"local"`
Routes map[string]string `json:"routes"`
CMS struct {
Provider string `json:"provider"`
Repo string `json:"repo"`
RedirectUrl string `json:"redirect_url"`
AppId string `json:"app_id"`
Expand Down

0 comments on commit 3be7302

Please sign in to comment.