Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
minwoox committed Dec 27, 2024
1 parent c1fd9c6 commit 8598156
Show file tree
Hide file tree
Showing 50 changed files with 876 additions and 494 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,12 @@ private static CompletableFuture<List<Credential>> credentials(User loginUser,
if (loginUser.isSystemAdmin()) {
return future;
}
return future.thenApply(credentials -> credentials.stream()
.map(Credential::withoutSecret)
.collect(toImmutableList()));
return future.thenApply(credentials -> {
return credentials
.stream()
.map(Credential::withoutSecret)
.collect(toImmutableList());
});
}

/**
Expand Down Expand Up @@ -169,7 +172,7 @@ private MetaRepository metaRepo(String projectName, User user) {
// Repository level credential management APIs.

/**
* GET /projects/{projectName}/repos/{repoName}credentials
* GET /projects/{projectName}/repos/{repoName}/credentials
*
* <p>Returns the list of the credentials in the repository.
*/
Expand Down Expand Up @@ -207,10 +210,10 @@ public CompletableFuture<Credential> getRepoCredentialById(User loginUser,
*
* <p>Creates a new credential.
*/
@RequiresRepositoryRole(RepositoryRole.ADMIN)
@Post("/projects/{projectName}/repos/{repoName}/credentials")
@ConsumesJson
@StatusCode(201)
@RequiresRepositoryRole(RepositoryRole.ADMIN)
@Post("/projects/{projectName}/repos/{repoName}/credentials")
public CompletableFuture<PushResultDto> createRepoCredential(@Param String projectName,
Repository repository,
Credential credential, Author author,
Expand All @@ -223,9 +226,9 @@ public CompletableFuture<PushResultDto> createRepoCredential(@Param String proje
*
* <p>Update the existing credential.
*/
@ConsumesJson
@RequiresRepositoryRole(RepositoryRole.ADMIN)
@Put("/projects/{projectName}/repos/{repoName}/credentials/{id}")
@ConsumesJson
public CompletableFuture<PushResultDto> updateRepoCredential(@Param String projectName,
Repository repository,
@Param String id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,11 @@ public final class DefaultMetaRepository extends RepositoryWrapper implements Me

public static boolean isMetaFile(String path) {
return "/mirrors.json".equals(path) || "/credentials.json".equals(path) ||
(path.endsWith(".json") &&
(path.startsWith(PATH_CREDENTIALS) || path.startsWith(PATH_MIRRORS)));
(path.endsWith(".json") && (path.startsWith(PATH_CREDENTIALS) || path.startsWith(PATH_MIRRORS)));
}

public static boolean isMirrorFile(String path) {
return path.endsWith(".json") &&
(path.startsWith(PATH_CREDENTIALS) || path.startsWith(PATH_MIRRORS));
return path.endsWith(".json") && (path.startsWith(PATH_CREDENTIALS) || path.startsWith(PATH_MIRRORS));
}

public static String credentialFile(String credentialId) {
Expand Down Expand Up @@ -208,26 +206,22 @@ private List<Credential> credentials(Map<String, Entry<?>> entries, @Nullable St
@Override
public CompletableFuture<Credential> credential(String credentialId) {
final String credentialFile = credentialFile(credentialId);
return credential(null, credentialId, credentialFile);
return credential0(credentialFile);
}

@Override
public CompletableFuture<Credential> credential(String repoName, String id) {
final String credentialFile = credentialFile(repoName, id);
return credential(repoName, id, credentialFile);
return credential0(credentialFile);
}

private CompletableFuture<Credential> credential(@Nullable String repoName, String id,
String credentialFile) {
private CompletableFuture<Credential> credential0(String credentialFile) {
return find(credentialFile).thenApply(entries -> {
@SuppressWarnings("unchecked")
final Entry<JsonNode> entry = (Entry<JsonNode>) entries.get(credentialFile);
if (entry == null) {
String path = parent().name() + '/' + name();
if (repoName != null) {
path += "/repos/" + repoName;
}
throw new EntryNotFoundException("failed to find credential '" + id + "' in " + path);
throw new EntryNotFoundException("failed to find credential file '" + credentialFile + "' in " +
parent().name() + '/' + name());
}

try {
Expand Down Expand Up @@ -323,9 +317,9 @@ public CompletableFuture<Command<CommitResult>> createCredentialPushCommand(Stri
return credential(repoName, credential.id()).thenApply(c -> {
final String summary =
"Update the mirror credential '" + repoName + '/' + credential.id() + '\'';
return newCredentialCommand(credentialFile(repoName, credential.id()), credential, author, summary);
return newCredentialCommand(
credentialFile(repoName, credential.id()), credential, author, summary);
});

}
final String summary = "Create a new mirror credential for " + repoName + '/' + credential.id();
return UnmodifiableFuture.completedFuture(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,4 @@ CompletableFuture<Command<CommitResult>> createCredentialPushCommand(Credential
*/
CompletableFuture<Command<CommitResult>> createCredentialPushCommand(String repoName, Credential credential,
Author author, boolean update);

}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class CredentialServiceV1Test {
@Override
protected void configure(CentralDogmaBuilder builder) {
builder.authProviderFactory(new TestAuthProviderFactory());
builder.administrators(USERNAME);
builder.systemAdministrators(USERNAME);
}

@Override
Expand Down
57 changes: 51 additions & 6 deletions webapp/src/dogma/features/api/apiSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ import { FileContentDto } from 'dogma/features/file/FileContentDto';
import { RevisionDto } from 'dogma/features/history/RevisionDto';
import { TokenDto } from 'dogma/features/token/TokenDto';
import { FetchBaseQueryError } from '@reduxjs/toolkit/query';
import { DeleteUserRepositoryRoleDto } from 'dogma/features/repo/roles/DeleteUserRepositoryRoleDto';
import { AddUserRepositoryRoleDto } from 'dogma/features/repo/roles/AddUserRepositoryRoleDto';
import { DeleteUserOrTokenRepositoryRoleDto } from 'dogma/features/repo/settings/DeleteUserOrTokenRepositoryRoleDto';
import { AddUserOrTokenRepositoryRoleDto } from 'dogma/features/repo/settings/AddUserOrTokenRepositoryRoleDto';
import { DeleteMemberDto } from 'dogma/features/project/settings/members/DeleteMemberDto';
import { MirrorDto } from 'dogma/features/project/settings/mirrors/MirrorDto';
import { CredentialDto } from 'dogma/features/project/settings/credentials/CredentialDto';
Expand Down Expand Up @@ -186,30 +186,30 @@ export const apiSlice = createApi({
}),
invalidatesTags: ['Metadata'],
}),
addUserRepositoryRole: builder.mutation<void, AddUserRepositoryRoleDto>({
addUserRepositoryRole: builder.mutation<void, AddUserOrTokenRepositoryRoleDto>({
query: ({ projectName, repoName, data }) => ({
url: `/api/v1/metadata/${projectName}/repos/${repoName}/roles/users`,
method: 'POST',
body: data,
}),
invalidatesTags: ['Metadata'],
}),
deleteUserRepositoryRole: builder.mutation<void, DeleteUserRepositoryRoleDto>({
deleteUserRepositoryRole: builder.mutation<void, DeleteUserOrTokenRepositoryRoleDto>({
query: ({ projectName, repoName, id }) => ({
url: `/api/v1/metadata/${projectName}/repos/${repoName}/roles/users/${id}`,
method: 'DELETE',
}),
invalidatesTags: ['Metadata'],
}),
addTokenRepositoryRole: builder.mutation<void, AddUserRepositoryRoleDto>({
addTokenRepositoryRole: builder.mutation<void, AddUserOrTokenRepositoryRoleDto>({
query: ({ projectName, repoName, data }) => ({
url: `/api/v1/metadata/${projectName}/repos/${repoName}/roles/tokens`,
method: 'POST',
body: data,
}),
invalidatesTags: ['Metadata'],
}),
deleteTokenRepositoryRole: builder.mutation<void, DeleteUserRepositoryRoleDto>({
deleteTokenRepositoryRole: builder.mutation<void, DeleteUserOrTokenRepositoryRoleDto>({
query: ({ projectName, repoName, id }) => ({
url: `/api/v1/metadata/${projectName}/repos/${repoName}/roles/tokens/${id}`,
method: 'DELETE',
Expand Down Expand Up @@ -410,6 +410,46 @@ export const apiSlice = createApi({
}),
invalidatesTags: ['Metadata'],
}),
getRepoCredentials: builder.query<CredentialDto[], { projectName: string; repoName: string }>({
query: ({ projectName, repoName }) => `/api/v1/projects/${projectName}/repos/${repoName}/credentials`,
providesTags: ['Metadata'],
}),
getRepoCredential: builder.query<CredentialDto, { projectName: string; id: string; repoName: string }>({
query: ({ projectName, id, repoName }) =>
`/api/v1/projects/${projectName}/repos/${repoName}/credentials/${id}`,
providesTags: ['Metadata'],
}),
addNewRepoCredential: builder.mutation<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
any,
{ projectName: string; credential: CredentialDto; repoName: string }
>({
query: ({ projectName, credential, repoName }) => ({
url: `/api/v1/projects/${projectName}/repos/${repoName}/credentials`,
method: 'POST',
body: credential,
}),
invalidatesTags: ['Metadata'],
}),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
updateRepoCredential: builder.mutation<
any,
{ projectName: string; id: string; credential: CredentialDto; repoName: string }
>({
query: ({ projectName, id, credential, repoName }) => ({
url: `/api/v1/projects/${projectName}/repos/${repoName}/credentials/${id}`,
method: 'PUT',
body: credential,
}),
invalidatesTags: ['Metadata'],
}),
deleteRepoCredential: builder.mutation({
query: ({ projectName, id, repoName }) => ({
url: `/api/v1/projects/${projectName}/repos/${repoName}/credentials/${id}`,
method: 'DELETE',
}),
invalidatesTags: ['Metadata'],
}),
getTitle: builder.query<TitleDto, void>({
query: () => ({
url: `/title`,
Expand Down Expand Up @@ -468,6 +508,11 @@ export const {
useAddNewCredentialMutation,
useUpdateCredentialMutation,
useDeleteCredentialMutation,
useGetRepoCredentialsQuery,
useGetRepoCredentialQuery,
useAddNewRepoCredentialMutation,
useUpdateRepoCredentialMutation,
useDeleteRepoCredentialMutation,
// Title
useGetTitleQuery,
} = apiSlice;
2 changes: 1 addition & 1 deletion webapp/src/dogma/features/auth/ProjectRole.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ReactNode } from 'react';
import { UserDto } from './UserDto';
import { ProjectMetadataDto } from '../project/ProjectMetadataDto';

type ProjectRole = 'OWNER' | 'MEMBER' | 'GUEST';
export type ProjectRole = 'OWNER' | 'MEMBER' | 'GUEST';

type WithProjectRoleProps = {
projectName: string;
Expand Down
32 changes: 32 additions & 0 deletions webapp/src/dogma/features/auth/RepositoryRole.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { UserDto } from './UserDto';
import { ProjectMetadataDto } from '../project/ProjectMetadataDto';
import { ProjectRole } from './ProjectRole';

export type RepositoryRole = 'READ' | 'WRITE' | 'ADMIN';

export function findUserRepositoryRole(repoName: string, user: UserDto, metadata: ProjectMetadataDto) {
if (user && user.systemAdmin) {
return 'ADMIN';
}
const projectRole = metadata.members[user.email]?.role as ProjectRole;
if (projectRole === 'OWNER') {
return 'ADMIN';
}

const roles = metadata.repos[repoName]?.roles;
const memberOrGuestRole = projectRole === 'MEMBER' ? roles?.projects.member : roles?.projects.guest;
const userRepositoryRole = roles?.users[user.email];

if (userRepositoryRole === 'ADMIN' || memberOrGuestRole === 'ADMIN') {
return 'ADMIN';
}

if (userRepositoryRole === 'WRITE' || memberOrGuestRole === 'WRITE') {
return 'WRITE';
}

if (userRepositoryRole === 'READ' || memberOrGuestRole === 'READ') {
return 'READ';
}
return null;
}
5 changes: 2 additions & 3 deletions webapp/src/dogma/features/project/ProjectDto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@
* under the License.
*/

import { ProjectRole } from 'dogma/features/auth/ProjectRole';
import { CreatorDto } from 'dogma/features/repo/RepoDto';

export type ProjectUserRole = 'OWNER' | 'MEMBER' | 'GUEST';

export interface ProjectDto {
name: string;
url?: string;
creator?: CreatorDto;
createdAt?: string;
userRole?: ProjectUserRole;
userRole?: ProjectRole;
}
87 changes: 87 additions & 0 deletions webapp/src/dogma/features/project/settings/AppEntityList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Text, VStack } from '@chakra-ui/react';
import { ColumnDef, createColumnHelper } from '@tanstack/react-table';
import { useMemo } from 'react';
import { DateWithTooltip } from 'dogma/common/components/DateWithTooltip';
import { UserRole } from 'dogma/common/components/UserRole';
import { DataTableClientPagination } from 'dogma/common/components/table/DataTableClientPagination';
import { DeleteAppEntity } from 'dogma/features/project/settings/DeleteAppEntity';

export type AppEntityListProps<Data extends object> = {
data: Data[];
projectName: string;
entityType: 'member' | 'token';
getId: (row: Data) => string;
getRole: (row: Data) => string;
getAddedBy: (row: Data) => string;
getTimestamp: (row: Data) => string;
showDeleteButton?: (row: Data) => boolean;
deleteMutation: (projectName: string, id: string) => Promise<void>;
isLoading: boolean;
};

const AppEntityList = <Data extends object>({
data,
projectName,
entityType,
getId,
getRole,
getAddedBy,
getTimestamp,
showDeleteButton = () => true,
deleteMutation,
isLoading,
}: AppEntityListProps<Data>): JSX.Element => {
const columnHelper = createColumnHelper<Data>();
const columns = useMemo(
() => [
columnHelper.accessor((row: Data) => getId(row), {
cell: (info) => (
<VStack alignItems="left">
<Text>{info.getValue()}</Text>
<Text>
<UserRole role={getRole(info.row.original)} />
</Text>
</VStack>
),
header: entityType === 'member' ? 'Login ID' : 'App ID',
}),
columnHelper.accessor((row: Data) => getAddedBy(row), {
cell: (info) => info.getValue(),
header: 'Added By',
}),
columnHelper.accessor((row: Data) => getTimestamp(row), {
cell: (info) => <DateWithTooltip date={info.getValue()} />,
header: 'Added At',
}),
columnHelper.accessor((row: Data) => getId(row), {
cell: (info) =>
showDeleteButton(info.row.original) ? (
<DeleteAppEntity
projectName={projectName}
id={info.getValue()}
entityType={entityType}
deleteEntity={deleteMutation}
isLoading={isLoading}
/>
) : null,
header: 'Actions',
enableSorting: false,
}),
],
[
columnHelper,
projectName,
entityType,
getId,
getRole,
getAddedBy,
getTimestamp,
showDeleteButton,
deleteMutation,
isLoading,
],
);
return <DataTableClientPagination columns={columns as ColumnDef<Data>[]} data={data} />;
};

export default AppEntityList;
Loading

0 comments on commit 8598156

Please sign in to comment.