Skip to content
This repository was archived by the owner on May 30, 2024. It is now read-only.

Provide to update a repository name by API #419

Merged
merged 4 commits into from
Apr 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions internal/pkg/store/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,13 @@ func (s *Store) SyncRepo(ctx context.Context, r *extent.RemoteRepo) (*ent.Repo,
func (s *Store) UpdateRepo(ctx context.Context, r *ent.Repo) (*ent.Repo, error) {
ret, err := s.c.Repo.
UpdateOne(r).
SetName(r.Name).
SetConfigPath(r.ConfigPath).
Save(ctx)
if ent.IsValidationError(err) {
return nil, e.NewErrorWithMessage(
e.ErrorCodeEntityUnprocessable,
fmt.Sprintf("The value of \"%s\" field is invalid.", err.(*ent.ValidationError).Name),
err)
return nil, e.NewErrorWithMessage(e.ErrorCodeEntityUnprocessable, fmt.Sprintf("The value of \"%s\" field is invalid.", err.(*ent.ValidationError).Name), err)
} else if ent.IsConstraintError(err) {
return nil, e.NewError(e.ErrorRepoUniqueName, err)
} else if err != nil {
return nil, e.NewError(e.ErrorCodeInternalError, err)
}
Expand Down
67 changes: 67 additions & 0 deletions internal/pkg/store/repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/gitploy-io/gitploy/model/ent/enttest"
"github.com/gitploy-io/gitploy/model/ent/migrate"
"github.com/gitploy-io/gitploy/model/extent"
"github.com/gitploy-io/gitploy/pkg/e"
)

func TestStore_ListReposOfUser(t *testing.T) {
Expand Down Expand Up @@ -197,6 +198,72 @@ func TestStore_SyncRepo(t *testing.T) {
})
}

func TestStore_UpdateRepo(t *testing.T) {
t.Run("Update the repository name.", func(t *testing.T) {
client := enttest.Open(t, "sqlite3", "file:ent?mode=memory&cache=shared&_fk=1",
enttest.WithMigrateOptions(migrate.WithForeignKeys(false)),
)
defer client.Close()

repo := client.Repo.Create().
SetNamespace("gitploy-io").
SetName("gitploy").
SetDescription("").
SaveX(context.Background())

s := NewStore(client)

// Replace values
repo.Name = "gitploy-next"
repo.ConfigPath = "deploy-next.yml"

var (
ret *ent.Repo
err error
)
ret, err = s.UpdateRepo(context.Background(), repo)
if err != nil {
t.Fatalf("UpdateRepo return an error: %s", err)
}

if repo.Name != "gitploy-next" ||
repo.ConfigPath != "deploy-next.yml" {
t.Fatalf("UpdateRepo = %s, wanted %s", repo, ret)
}
})

t.Run("Return an error if the same repository name exists.", func(t *testing.T) {
client := enttest.Open(t, "sqlite3", "file:ent?mode=memory&cache=shared&_fk=1",
enttest.WithMigrateOptions(migrate.WithForeignKeys(true)),
)
defer client.Close()

client.Repo.Create().
SetNamespace("gitploy-io").
SetName("gitploy-next").
SetDescription("").
SaveX(context.Background())

repo := client.Repo.Create().
SetNamespace("gitploy-io").
SetName("gitploy").
SetDescription("").
SaveX(context.Background())

s := NewStore(client)

repo.Name = "gitploy-next"

var (
err error
)
_, err = s.UpdateRepo(context.Background(), repo)
if !e.HasErrorCode(err, e.ErrorRepoUniqueName) {
t.Fatalf("UpdateRepo doesn't return the ErrorRepoUniqueName error: %s", err)
}
})
}

func TestStore_Activate(t *testing.T) {
t.Run("Update webhook ID and owner ID.", func(t *testing.T) {
client := enttest.Open(t, "sqlite3", "file:ent?mode=memory&cache=shared&_fk=1",
Expand Down
22 changes: 14 additions & 8 deletions internal/server/api/v1/repos/repo_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

type (
RepoPatchPayload struct {
Name *string `json:"name"`
ConfigPath *string `json:"config_path"`
Active *bool `json:"active"`
}
Expand Down Expand Up @@ -57,17 +58,22 @@ func (s *RepoAPI) Update(c *gin.Context) {
}
}

if p.Name != nil {
s.log.Debug("Set the name field.", zap.String("value", *p.Name))
re.Name = *p.Name
}

Comment on lines +61 to +65
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update the repository name if a payload has the value of name.

if p.ConfigPath != nil {
if *p.ConfigPath != re.ConfigPath {
re.ConfigPath = *p.ConfigPath
s.log.Debug("Set the config_path field.", zap.String("value", *p.ConfigPath))
re.ConfigPath = *p.ConfigPath
}

if re, err = s.i.UpdateRepo(ctx, re); err != nil {
s.log.Check(gb.GetZapLogLevel(err), "Failed to update the repository.").Write(zap.Error(err))
gb.ResponseWithError(c, err)
return
}
}
if re, err = s.i.UpdateRepo(ctx, re); err != nil {
s.log.Check(gb.GetZapLogLevel(err), "Failed to update the repository.").Write(zap.Error(err))
gb.ResponseWithError(c, err)
return
}

s.log.Info("Update the repository.", zap.Int64("repo_id", re.ID))
gb.Response(c, http.StatusOK, re)
}
6 changes: 6 additions & 0 deletions internal/server/api/v1/repos/repo_update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ func TestRepoAPI_UpdateRepo(t *testing.T) {
return r, nil
})

m.EXPECT().
UpdateRepo(gomock.Any(), gomock.AssignableToTypeOf(&ent.Repo{})).
DoAndReturn(func(ctx context.Context, r *ent.Repo) (*ent.Repo, error) {
return r, nil
})

gin.SetMode(gin.ReleaseMode)
router := gin.New()

Expand Down
3 changes: 3 additions & 0 deletions pkg/e/code.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ const (

// ErrorPermissionRequired is the permission is required to access.
ErrorPermissionRequired ErrorCode = "permission_required"

// ErrorRepoUniqueName is the repository name must be unique.
ErrorRepoUniqueName ErrorCode = "repo_unique_name"
)

type (
Expand Down
4 changes: 3 additions & 1 deletion pkg/e/trans.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ var messages = map[ErrorCode]string{
ErrorCodeLicenseDecode: "Decoding the license is failed.",
ErrorCodeLicenseRequired: "The license is required.",
ErrorCodeParameterInvalid: "Invalid request parameter.",
ErrorPermissionRequired: "The permission is required",
ErrorPermissionRequired: "The permission is required.",
ErrorRepoUniqueName: "The same repository name already exists.",
}

func GetMessage(code ErrorCode) string {
Expand Down Expand Up @@ -49,6 +50,7 @@ var httpCodes = map[ErrorCode]int{
ErrorCodeLicenseRequired: http.StatusPaymentRequired,
ErrorCodeParameterInvalid: http.StatusBadRequest,
ErrorPermissionRequired: http.StatusForbidden,
ErrorRepoUniqueName: http.StatusUnprocessableEntity,
}

func GetHttpCode(code ErrorCode) int {
Expand Down
10 changes: 8 additions & 2 deletions ui/src/apis/repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { instance, headers } from './setting'
import { _fetch } from "./_base"
import { DeploymentData, mapDataToDeployment } from "./deployment"

import { Repo, HttpForbiddenError, Deployment } from '../models'
import { Repo, HttpForbiddenError, Deployment, HttpUnprocessableEntityError } from '../models'

export interface RepoData {
id: number
Expand Down Expand Up @@ -66,7 +66,10 @@ export const getRepo = async (namespace: string, name: string): Promise<Repo> =>
return repo
}

export const updateRepo = async (namespace: string, name: string, payload: {config_path: string}): Promise<Repo> => {
export const updateRepo = async (namespace: string, name: string, payload: {
name?: string,
config_path?: string,
}): Promise<Repo> => {
const res = await _fetch(`${instance}/api/v1/repos/${namespace}/${name}`, {
headers,
credentials: 'same-origin',
Expand All @@ -76,6 +79,9 @@ export const updateRepo = async (namespace: string, name: string, payload: {conf
if (res.status === StatusCodes.FORBIDDEN) {
const message = await res.json().then(data => data.message)
throw new HttpForbiddenError(message)
} else if (res.status === StatusCodes.UNPROCESSABLE_ENTITY) {
const message = await res.json().then(data => data.message)
throw new HttpUnprocessableEntityError(message)
}

const ret: Repo = await res
Expand Down
36 changes: 20 additions & 16 deletions ui/src/redux/repoSettings.ts → ui/src/redux/repoSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"
import { message } from "antd"

import { getRepo, updateRepo, deactivateRepo } from "../apis"
import { Repo, RequestStatus, HttpForbiddenError } from "../models"
import { Repo, RequestStatus, HttpForbiddenError, HttpUnprocessableEntityError } from "../models"

interface RepoSettingsState {
repo?: Repo
Expand All @@ -25,9 +25,16 @@ export const init = createAsyncThunk<Repo, {namespace: string, name: string}, {
},
)

export const save = createAsyncThunk<Repo, void, { state: {repoSettings: RepoSettingsState} }>(
export const save = createAsyncThunk<
Repo,
{
name: string,
config_path: string
},
{ state: {repoSettings: RepoSettingsState} }
>(
'repoSettings/save',
async (_, { getState, rejectWithValue, requestId } ) => {
async (values, { getState, rejectWithValue, requestId } ) => {
const { repo, saveId, saving } = getState().repoSettings
if (!repo) {
throw new Error("There is no repo.")
Expand All @@ -38,13 +45,19 @@ export const save = createAsyncThunk<Repo, void, { state: {repoSettings: RepoSet
}

try {
const nr = await updateRepo(repo.namespace, repo.name, {config_path: repo.configPath})
const nr = await updateRepo(repo.namespace, repo.name, values)
message.success("Success to save.", 3)
return nr
} catch(e) {
if (e instanceof HttpForbiddenError) {
message.warn("Only admin permission can update.", 3)
}
} else if (e instanceof HttpUnprocessableEntityError) {
message.error(<>
<span>It is unprocesable entity.</span><br/>
<span className="gitploy-quote">{e.message}</span>
</>, 3)
}


return rejectWithValue(e)
}
Expand All @@ -65,7 +78,6 @@ export const deactivate = createAsyncThunk<Repo, void, { state: {repoSettings: R
if (e instanceof HttpForbiddenError) {
message.warn("Only admin permission can deactivate.", 3)
}

return rejectWithValue(e)
}
},
Expand All @@ -74,15 +86,7 @@ export const deactivate = createAsyncThunk<Repo, void, { state: {repoSettings: R
export const repoSettingsSlice = createSlice({
name: "repoSettings",
initialState,
reducers: {
setConfigPath: (state, action: PayloadAction<string>) => {
if (!state.repo) {
return
}

state.repo.configPath = action.payload
}
},
reducers: {},
extraReducers: builder => {
builder
.addCase(init.fulfilled, (state, action) => {
Expand Down
44 changes: 30 additions & 14 deletions ui/src/views/repoSettings/SettingsForm.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,68 @@
import { Form, Input, Button, Space, Typography } from "antd"

import { Repo } from "../../models"
import { useState } from "react"

export interface SettingFormProps {
saving: boolean
repo?: Repo
onClickFinish(values: any): void
configLink: string
initialValues?: SettingFormValues
onClickFinish(values: SettingFormValues): void
onClickDeactivate(): void
}

export interface SettingFormValues {
name: string
config_path: string
}

export default function SettingForm({
saving,
repo,
configLink,
initialValues,
onClickFinish,
onClickDeactivate,
}: SettingFormProps): JSX.Element {
const [saving, setSaving] = useState(false)

const layout = {
labelCol: { span: 5},
wrapperCol: { span: 12 },
};
}

const submitLayout = {
wrapperCol: { offset: 5, span: 12 },
};
}

const initialValues = {
"config": repo?.configPath
const onFinish = (values: any) => {
setSaving(true)
onClickFinish(values)
setSaving(false)
}

return (
<Form
name="setting"
initialValues={initialValues}
onFinish={onClickFinish}
onFinish={onFinish}
>
<Form.Item
label="Name"
name="name"
{...layout}
rules={[{required: true}]}
>
<Input />
</Form.Item>
<Form.Item
label="Config"
{...layout}
>
<Space>
<Form.Item
name="config"
name="config_path"
rules={[{required: true}]}
noStyle
>
<Input />
</Form.Item>
<Typography.Link target="_blank" href={`/link/${repo?.namespace}/${repo?.name}/config`}>
<Typography.Link target="_blank" href={configLink}>
Link
</Typography.Link>
</Space>
Expand Down
Loading