Skip to content

Commit

Permalink
Add "Repair all" button (#2642)
Browse files Browse the repository at this point in the history
to the admin repo list

ref #2639
  • Loading branch information
qwerty287 authored Oct 24, 2023
1 parent d5e68ef commit 69917c1
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 61 deletions.
30 changes: 28 additions & 2 deletions cmd/server/docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1471,6 +1471,32 @@ const docTemplate = `{
}
}
},
"/repos/repair": {
"post": {
"produces": [
"text/plain"
],
"tags": [
"Repositories"
],
"summary": "Repair all repositories on the server. Requires admin rights.",
"parameters": [
{
"type": "string",
"default": "Bearer \u003cpersonal access token\u003e",
"description": "Insert your personal access token",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/repos/{repo_id}": {
"get": {
"produces": [
Expand Down Expand Up @@ -2797,8 +2823,8 @@ const docTemplate = `{
}
],
"responses": {
"200": {
"description": "OK"
"204": {
"description": "No Content"
}
}
}
Expand Down
149 changes: 91 additions & 58 deletions server/api/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,70 +401,15 @@ func DeleteRepo(c *gin.Context) {
// @Summary Repair a repository
// @Router /repos/{repo_id}/repair [post]
// @Produce plain
// @Success 200
// @Success 204
// @Tags Repositories
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param repo_id path int true "the repository id"
func RepairRepo(c *gin.Context) {
forge := server.Config.Services.Forge
_store := store.FromContext(c)
repo := session.Repo(c)
user := session.User(c)

// creates the jwt token used to verify the repository
t := token.New(token.HookToken, repo.FullName)
sig, err := t.Sign(repo.Hash)
if err != nil {
c.String(http.StatusInternalServerError, err.Error())
return
}

// reconstruct the link
host := server.Config.Server.WebhookHost
link := fmt.Sprintf(
"%s/api/hook?access_token=%s",
host,
sig,
)

from, err := forge.Repo(c, user, repo.ForgeRemoteID, repo.Owner, repo.Name)
if err != nil {
log.Error().Err(err).Msgf("get repo '%s/%s' from forge", repo.Owner, repo.Name)
c.AbortWithStatus(http.StatusInternalServerError)
return
}
repairRepo(c, repo, true)

if repo.FullName != from.FullName {
// create a redirection
err = _store.CreateRedirection(&model.Redirection{RepoID: repo.ID, FullName: repo.FullName})
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
}

repo.Update(from)
if err := _store.UpdateRepo(repo); err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
repo.Perm.Pull = from.Perm.Pull
repo.Perm.Push = from.Perm.Push
repo.Perm.Admin = from.Perm.Admin
if err := _store.PermUpsert(repo.Perm); err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}

if err := forge.Deactivate(c, user, repo, host); err != nil {
log.Trace().Err(err).Msgf("deactivate repo '%s' to repair failed", repo.FullName)
}
if err := forge.Activate(c, user, repo, link); err != nil {
c.String(http.StatusInternalServerError, err.Error())
return
}

c.Status(http.StatusOK)
c.Status(http.StatusNoContent)
}

// MoveRepo
Expand Down Expand Up @@ -575,3 +520,91 @@ func GetAllRepos(c *gin.Context) {

c.JSON(http.StatusOK, repos)
}

// RepairAllRepos
//
// @Summary Repair all repositories on the server. Requires admin rights.
// @Router /repos/repair [post]
// @Produce plain
// @Success 204
// @Tags Repositories
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
func RepairAllRepos(c *gin.Context) {
_store := store.FromContext(c)

repos, err := _store.RepoListAll(true, &model.ListOptions{All: true})
if err != nil {
c.String(http.StatusInternalServerError, "Error fetching repository list. %s", err)
return
}

for _, r := range repos {
repairRepo(c, r, false)
if c.Writer.Written() {
return
}
}

c.Status(http.StatusNoContent)
}

func repairRepo(c *gin.Context, repo *model.Repo, withPerms bool) {
forge := server.Config.Services.Forge
_store := store.FromContext(c)
user := session.User(c)

// creates the jwt token used to verify the repository
t := token.New(token.HookToken, repo.FullName)
sig, err := t.Sign(repo.Hash)
if err != nil {
c.String(http.StatusInternalServerError, err.Error())
return
}

// reconstruct the link
host := server.Config.Server.WebhookHost
link := fmt.Sprintf(
"%s/api/hook?access_token=%s",
host,
sig,
)

from, err := forge.Repo(c, user, repo.ForgeRemoteID, repo.Owner, repo.Name)
if err != nil {
log.Error().Err(err).Msgf("get repo '%s/%s' from forge", repo.Owner, repo.Name)
c.AbortWithStatus(http.StatusInternalServerError)
return
}

if repo.FullName != from.FullName {
// create a redirection
err = _store.CreateRedirection(&model.Redirection{RepoID: repo.ID, FullName: repo.FullName})
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
}

repo.Update(from)
if err := _store.UpdateRepo(repo); err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
if withPerms {
repo.Perm.Pull = from.Perm.Pull
repo.Perm.Push = from.Perm.Push
repo.Perm.Admin = from.Perm.Admin
if err := _store.PermUpsert(repo.Perm); err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
}

if err := forge.Deactivate(c, user, repo, host); err != nil {
log.Trace().Err(err).Msgf("deactivate repo '%s' to repair failed", repo.FullName)
}
if err := forge.Activate(c, user, repo, link); err != nil {
c.String(http.StatusInternalServerError, err.Error())
return
}
}
1 change: 1 addition & 0 deletions server/router/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func apiRoutes(e *gin.RouterGroup) {
repo.GET("/lookup/*repo_full_name", session.SetRepo(), session.SetPerm(), session.MustPull, api.LookupRepo)
repo.POST("", session.MustUser(), api.PostRepo)
repo.GET("", session.MustAdmin(), api.GetAllRepos)
repo.POST("/repair", session.MustAdmin(), api.RepairAllRepos)
repoBase := repo.Group("/:repo_id")
{
repoBase.Use(session.SetRepo())
Expand Down
6 changes: 5 additions & 1 deletion web/src/assets/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,11 @@
"none": "There are no repositories yet.",
"view": "View repository",
"settings": "Repository settings",
"disabled": "Disabled"
"disabled": "Disabled",
"repair": {
"repair": "Repair all",
"success": "Repositories repaired"
}
}
}
},
Expand Down
20 changes: 20 additions & 0 deletions web/src/components/admin/settings/AdminReposTab.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
<template>
<Settings :title="$t('admin.settings.repos.repos')" :desc="$t('admin.settings.repos.desc')">
<template #titleActions>
<Button
start-icon="heal"
:is-loading="isRepairingRepos"
:text="$t('admin.settings.repos.repair.repair')"
@click="repairRepos"
/>
</template>

<div class="space-y-4 text-wp-text-100">
<ListItem
v-for="repo in repos"
Expand Down Expand Up @@ -30,19 +39,30 @@
</template>
<script lang="ts" setup>
import { useI18n } from 'vue-i18n';
import Badge from '~/components/atomic/Badge.vue';
import IconButton from '~/components/atomic/IconButton.vue';
import ListItem from '~/components/atomic/ListItem.vue';
import Settings from '~/components/layout/Settings.vue';
import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate';
import { Repo } from '~/lib/api/types';
const apiClient = useApiClient();
const notifications = useNotifications();
const i18n = useI18n();
async function loadRepos(page: number): Promise<Repo[] | null> {
return apiClient.getAllRepos(page);
}
const { data: repos } = usePagination(loadRepos);
const { doSubmit: repairRepos, isLoading: isRepairingRepos } = useAsyncAction(async () => {
await apiClient.repairAllRepos();
notifications.notify({ title: i18n.t('admin.settings.repos.repair.success'), type: 'success' });
});
</script>
4 changes: 4 additions & 0 deletions web/src/lib/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,10 @@ export default class WoodpeckerClient extends ApiClient {
return this._get(`/api/repos?page=${page}`) as Promise<Repo[] | null>;
}

repairAllRepos(): Promise<unknown> {
return this._post(`/api/repos/repair`) as Promise<unknown>;
}

// eslint-disable-next-line promise/prefer-await-to-callbacks
on(callback: (data: { pipeline?: Pipeline; repo?: Repo }) => void): EventSource {
return this._subscribe('/api/stream/events', callback, {
Expand Down

0 comments on commit 69917c1

Please sign in to comment.