diff --git a/.eslintrc.json b/.eslintrc.json index e8147b8c..b20a733c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,6 +4,20 @@ "eslint:recommended", "plugin:@typescript-eslint/recommended" ], + "rules": { + "array-callback-return": ["error"], + "no-await-in-loop": ["error"], + "no-constant-binary-expression": ["error"], + "no-constructor-return": ["error"], + "no-duplicate-imports": ["error"], + "no-new-native-nonconstructor": ["error"], + "no-self-compare": ["error"], + "no-template-curly-in-string": ["error"], + "no-unmodified-loop-condition": ["error"], + "no-unreachable-loop": ["error"], + "no-unused-private-class-members": ["error"], + "require-atomic-updates": ["error"] + }, "parser": "@typescript-eslint/parser", "plugins": [ "@typescript-eslint" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..ef6397a1 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,17 @@ +name: Lint +on: + workflow_dispatch: {} + pull_request: {} +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true +jobs: + build: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install modules + run: npm install + - name: Run ESLint + run: npm run lint diff --git a/__test__/auth/AccessTokenService.test.ts b/__test__/auth/AccessTokenService.test.ts index 907e7914..757bfda1 100644 --- a/__test__/auth/AccessTokenService.test.ts +++ b/__test__/auth/AccessTokenService.test.ts @@ -1,5 +1,4 @@ import AccessTokenService from "../../src/features/auth/domain/AccessTokenService" -import { IOAuthToken } from "../../src/features/auth/domain/IOAuthTokenRepository" test("It reads the access token from the repository", async () => { const sut = new AccessTokenService({ @@ -11,9 +10,9 @@ test("It reads the access token from the repository", async () => { refreshTokenExpiryDate: new Date(new Date().getTime() + 3600 * 1000) } }, - async storeOAuthToken(_token: IOAuthToken) {} + async storeOAuthToken() {} }, { - async refreshAccessToken(_refreshToken: string) { + async refreshAccessToken() { return { accessToken: "foo", refreshToken: "bar", @@ -36,9 +35,9 @@ test("It refreshes an expired access token", async () => { refreshTokenExpiryDate: new Date(new Date().getTime() + 3600 * 1000) } }, - async storeOAuthToken(_token: IOAuthToken) {} + async storeOAuthToken() {} }, { - async refreshAccessToken(_refreshToken: string) { + async refreshAccessToken() { return { accessToken: "new", refreshToken: "bar", @@ -62,11 +61,11 @@ test("It stores the refreshed access token", async () => { refreshTokenExpiryDate: new Date(new Date().getTime() + 3600 * 1000) } }, - async storeOAuthToken(_token: IOAuthToken) { + async storeOAuthToken() { didStoreRefreshedToken = true } }, { - async refreshAccessToken(_refreshToken: string) { + async refreshAccessToken() { return { accessToken: "new", refreshToken: "bar", @@ -89,9 +88,9 @@ test("It errors when the refresh token has expired", async () => { refreshTokenExpiryDate: new Date(new Date().getTime() - 3600 * 1000) } }, - async storeOAuthToken(_token: IOAuthToken) {} + async storeOAuthToken() {} }, { - async refreshAccessToken(_refreshToken: string) { + async refreshAccessToken() { return { accessToken: "new", refreshToken: "bar", diff --git a/__test__/hooks/ExistingCommentCheckingPullRequestEventHandler.test.ts b/__test__/hooks/ExistingCommentCheckingPullRequestEventHandler.test.ts index 4b80d11e..69c90250 100644 --- a/__test__/hooks/ExistingCommentCheckingPullRequestEventHandler.test.ts +++ b/__test__/hooks/ExistingCommentCheckingPullRequestEventHandler.test.ts @@ -3,13 +3,13 @@ import ExistingCommentCheckingPullRequestEventHandler from "../../src/features/h test("It fetches comments from the repository", async () => { let didFetchComments = false const sut = new ExistingCommentCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) {} + async pullRequestOpened() {} }, { - async getComments(_operation) { + async getComments() { didFetchComments = true return [] }, - async addComment(_operation) {} + async addComment() {} }, "https://docs.shapetools.io") await sut.pullRequestOpened({ appInstallationId: 42, @@ -24,14 +24,14 @@ test("It fetches comments from the repository", async () => { test("It does calls decorated event handler if a comment does not exist in the repository", async () => { let didCallEventHandler = false const sut = new ExistingCommentCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, { - async getComments(_operation) { + async getComments() { return [] }, - async addComment(_operation) {} + async addComment() {} }, "https://docs.shapetools.io") await sut.pullRequestOpened({ appInstallationId: 42, @@ -46,17 +46,17 @@ test("It does calls decorated event handler if a comment does not exist in the r test("It does not call the event handler if a comment already exists in the repository", async () => { let didCallEventHandler = false const sut = new ExistingCommentCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, { - async getComments(_operation) { + async getComments() { return [{ body: "The documentation is available on https://docs.shapetools.io", isFromBot: true }] }, - async addComment(_operation) {} + async addComment() {} }, "https://docs.shapetools.io") await sut.pullRequestOpened({ appInstallationId: 42, @@ -71,17 +71,17 @@ test("It does not call the event handler if a comment already exists in the repo test("It calls the event handler if a comment exists matching the needle domain but that comment is not from a bot", async () => { let didCallEventHandler = false const sut = new ExistingCommentCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, { - async getComments(_operation) { + async getComments() { return [{ body: "The documentation is available on https://docs.shapetools.io", isFromBot: false }] }, - async addComment(_operation) {} + async addComment() {} }, "https://docs.shapetools.io") await sut.pullRequestOpened({ appInstallationId: 42, @@ -96,17 +96,17 @@ test("It calls the event handler if a comment exists matching the needle domain test("It calls the event handler if the repository contains a comment from a bot but that comment does not contain the needle domain", async () => { let didCallEventHandler = false const sut = new ExistingCommentCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, { - async getComments(_operation) { + async getComments() { return [{ body: "Hello world!", isFromBot: true }] }, - async addComment(_operation) {} + async addComment() {} }, "https://docs.shapetools.io") await sut.pullRequestOpened({ appInstallationId: 42, diff --git a/__test__/hooks/PostCommentPullRequestEventHandler.test.ts b/__test__/hooks/PostCommentPullRequestEventHandler.test.ts index 2b2fe619..5bc4fb3c 100644 --- a/__test__/hooks/PostCommentPullRequestEventHandler.test.ts +++ b/__test__/hooks/PostCommentPullRequestEventHandler.test.ts @@ -3,10 +3,10 @@ import PostCommentPullRequestEventHandler from "../../src/features/hooks/domain/ test("It adds a comment to the repository", async () => { let didAddComment = false const sut = new PostCommentPullRequestEventHandler({ - async getComments(_operation) { + async getComments() { return [] }, - async addComment(_operation) { + async addComment() { didAddComment = true } }, "https://docs.shapetools.io") @@ -23,7 +23,7 @@ test("It adds a comment to the repository", async () => { test("It adds a comment containing a link to the documentation", async () => { let commentBody: string | undefined const sut = new PostCommentPullRequestEventHandler({ - async getComments(_operation) { + async getComments() { return [] }, async addComment(operation) { @@ -43,7 +43,7 @@ test("It adds a comment containing a link to the documentation", async () => { test("It removes the \"openapi\" suffix of the repository name", async () => { let commentBody: string | undefined const sut = new PostCommentPullRequestEventHandler({ - async getComments(_operation) { + async getComments() { return [] }, async addComment(operation) { diff --git a/__test__/hooks/RepositoryNameCheckingPullRequestEventHandler.test.ts b/__test__/hooks/RepositoryNameCheckingPullRequestEventHandler.test.ts index f527577f..25aab3f7 100644 --- a/__test__/hooks/RepositoryNameCheckingPullRequestEventHandler.test.ts +++ b/__test__/hooks/RepositoryNameCheckingPullRequestEventHandler.test.ts @@ -3,7 +3,7 @@ import RepositoryNameCheckingPullRequestEventHandler from "../../src/features/ho test("It does not call event handler when repository name does not have \"-openapi\" suffix", async () => { let didCallEventHandler = false const sut = new RepositoryNameCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, [], []) @@ -20,7 +20,7 @@ test("It does not call event handler when repository name does not have \"-opena test("It does not call event handler when repository name contains \"-openapi\" but it is not the last part of the repository name", async () => { let didCallEventHandler = false const sut = new RepositoryNameCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, [], []) @@ -37,7 +37,7 @@ test("It does not call event handler when repository name contains \"-openapi\" test("It calls event handler when no repositories have been allowed or disallowed", async () => { let didCallEventHandler = false const sut = new RepositoryNameCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, [], []) @@ -54,7 +54,7 @@ test("It calls event handler when no repositories have been allowed or disallowe test("It does not call event handler for repository that is not on the allowlist", async () => { let didCallEventHandler = false const sut = new RepositoryNameCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, ["example-openapi"], []) @@ -71,7 +71,7 @@ test("It does not call event handler for repository that is not on the allowlist test("It does not call event handler for repository that is on the disallowlist", async () => { let didCallEventHandler = false const sut = new RepositoryNameCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, [], ["example-openapi"]) @@ -88,7 +88,7 @@ test("It does not call event handler for repository that is on the disallowlist" test("It lets the disallowlist takes precedence over the allowlist", async () => { let didCallEventHandler = false const sut = new RepositoryNameCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, ["example-openapi"], ["example-openapi"]) diff --git a/src/app/api/github/blob/[owner]/[repository]/[...path]/route.ts b/src/app/api/github/blob/[owner]/[repository]/[...path]/route.ts index 3c0aca58..5d808450 100644 --- a/src/app/api/github/blob/[owner]/[repository]/[...path]/route.ts +++ b/src/app/api/github/blob/[owner]/[repository]/[...path]/route.ts @@ -21,6 +21,6 @@ export async function GET(req: NextRequest, { params }: { params: GetBlobParams path: fullPath, ref: ref || undefined }) - let item = response.data as GitHubContentItem + const item = response.data as GitHubContentItem return NextResponse.redirect(new URL(item.download_url)) } diff --git a/src/app/api/hooks/github/route.ts b/src/app/api/hooks/github/route.ts index 5d227f36..757d5b37 100644 --- a/src/app/api/hooks/github/route.ts +++ b/src/app/api/hooks/github/route.ts @@ -28,7 +28,7 @@ const commentRepository = new GitHubPullRequestCommentRepository({ appId: GITHUB_APP_ID, privateKey: privateKey, clientId: GITHUB_CLIENT_ID, - clientSecret: GITHUB_WEBHOOK_SECRET + clientSecret: GITHUB_CLIENT_SECRET }) const hookHandler = new GitHubHookHandler({ secret: GITHUB_WEBHOOK_SECRET, diff --git a/src/app/api/user/projects/route.ts b/src/app/api/user/projects/route.ts index 0d959f1f..5a5b067a 100644 --- a/src/app/api/user/projects/route.ts +++ b/src/app/api/user/projects/route.ts @@ -1,7 +1,7 @@ -import { NextRequest, NextResponse } from "next/server" +import { NextResponse } from "next/server" import { projectRepository } from "@/common/startup" -export async function GET(_req: NextRequest) { +export async function GET() { const projects = await projectRepository.getProjects() return NextResponse.json({projects}) } diff --git a/src/common/client/ThemeRegistry.tsx b/src/common/client/ThemeRegistry.tsx index 18e71bff..ffb45ab7 100644 --- a/src/common/client/ThemeRegistry.tsx +++ b/src/common/client/ThemeRegistry.tsx @@ -1,19 +1,22 @@ "use client" -import { useState } from "react" -import createCache from "@emotion/cache" +import { ReactNode, useState } from "react" +import createCache, { Options } from "@emotion/cache" import { useServerInsertedHTML } from "next/navigation" import { CacheProvider } from "@emotion/react" import { ThemeProvider } from "@mui/material/styles" import CssBaseline from "@mui/material/CssBaseline" import theme from "./theme" -import useMediaQuery from "@mui/material/useMediaQuery" + +type ThemeRegistryProps = { + options: Options + children: ReactNode +} // This implementation is from emotion-js // https://github.com/emotion-js/emotion/issues/2928#issuecomment-1319747902 -export default function ThemeRegistry(props: any) { +export default function ThemeRegistry(props: ThemeRegistryProps) { const { options, children } = props; - const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); const [{ cache, flush }] = useState(() => { const cache = createCache(options); cache.compat = true; @@ -56,7 +59,7 @@ export default function ThemeRegistry(props: any) { return ( - + {children} diff --git a/src/common/client/theme.ts b/src/common/client/theme.ts index 73886874..38b69d88 100644 --- a/src/common/client/theme.ts +++ b/src/common/client/theme.ts @@ -1,7 +1,7 @@ import { createTheme } from "@mui/material/styles" import { blue } from "@mui/material/colors" -const theme = (_prefersDarkMode: boolean) => createTheme({ +const theme = () => createTheme({ palette: { mode: "light", primary: { diff --git a/src/common/events/utils.ts b/src/common/events/utils.ts index 44fe2dd8..e81fa739 100644 --- a/src/common/events/utils.ts +++ b/src/common/events/utils.ts @@ -1,18 +1,18 @@ import BaseEvent, { Events } from "./BaseEvent" -function subscribe(eventName: Events, listener: (event: CustomEvent) => void) { - document.addEventListener(eventName, listener as () => void); +function subscribe(eventName: Events, listener: () => void) { + document.addEventListener(eventName, listener) } -function unsubscribe(eventName: Events, listener: (event: CustomEvent) => void) { - document.removeEventListener(eventName, listener as () => void); +function unsubscribe(eventName: Events, listener: () => void) { + document.removeEventListener(eventName, listener) } function publish(event: BaseEvent) { const customEvent = new CustomEvent(event.name, { detail: event.data - }); - document.dispatchEvent(customEvent); + }) + document.dispatchEvent(customEvent) } -export { publish, subscribe, unsubscribe }; \ No newline at end of file +export { publish, subscribe, unsubscribe } \ No newline at end of file diff --git a/src/common/fetcher.ts b/src/common/fetcher.ts index 715ce2f3..4f97e40b 100644 --- a/src/common/fetcher.ts +++ b/src/common/fetcher.ts @@ -1,4 +1,5 @@ -export default async function fetcher( + /* eslint-disable @typescript-eslint/no-explicit-any */ + export default async function fetcher( input: RequestInfo, init?: RequestInit ): Promise { diff --git a/src/features/auth/data/Auth0OAuthTokenRepository.ts b/src/features/auth/data/Auth0OAuthTokenRepository.ts index 635b8546..4c3abc79 100644 --- a/src/features/auth/data/Auth0OAuthTokenRepository.ts +++ b/src/features/auth/data/Auth0OAuthTokenRepository.ts @@ -2,6 +2,14 @@ import { ManagementClient } from "auth0" import { getSession } from "@auth0/nextjs-auth0" import IOAuthTokenRepository, { IOAuthToken } from "../domain/IOAuthTokenRepository" +type Auth0User = { + readonly user_id: string + readonly identities: Auth0UserIdentity[] + readonly app_metadata?: Auth0UserAppMetadata +} + +type Auth0UserAppMetadata = {[key: string]: Auth0UserAppMetadataAuthToken | undefined} + type Auth0UserAppMetadataAuthToken = { readonly access_token: string readonly refresh_token: string @@ -15,11 +23,7 @@ type Auth0UserIdentity = { readonly refresh_token: string } -type Auth0User = { - readonly user_id: string - readonly identities: Auth0UserIdentity[] - readonly app_metadata?: {[key: string]: any} -} + interface Auth0OAuthIdentityProviderConfig { readonly domain: string @@ -70,7 +74,7 @@ export default class Auth0OAuthTokenRepository implements IOAuthTokenRepository access_token_expires_at: token.accessTokenExpiryDate.toISOString(), refresh_token_expires_at: token.refreshTokenExpiryDate.toISOString() } - const appMetadata: any = {} + const appMetadata: Auth0UserAppMetadata = {} appMetadata[authTokenKey] = appMetadataToken await this.managementClient.users.update({ id: user.user_id }, { app_metadata: appMetadata @@ -106,6 +110,7 @@ export default class Auth0OAuthTokenRepository implements IOAuthTokenRepository const authTokenKey = this.getAuthTokenMetadataKey(this.connection) const authToken = user.app_metadata[authTokenKey] if ( + authToken && authToken.access_token && authToken.access_token.length > 0 && authToken.refresh_token && authToken.refresh_token.length > 0 && authToken.access_token_expires_at && authToken.access_token_expires_at.length > 0 && diff --git a/src/features/hooks/data/GitHubHookHandler.ts b/src/features/hooks/data/GitHubHookHandler.ts index 3a4afa05..d8ec1fc0 100644 --- a/src/features/hooks/data/GitHubHookHandler.ts +++ b/src/features/hooks/data/GitHubHookHandler.ts @@ -1,5 +1,5 @@ import { NextRequest } from "next/server" -import { Webhooks } from "@octokit/webhooks" +import { Webhooks, EmitterWebhookEventName } from "@octokit/webhooks" import IPullRequestEventHandler from "../domain/IPullRequestEventHandler" interface GitHubHookHandlerConfig { @@ -20,7 +20,7 @@ class GitHubHookHandler { async handle(req: NextRequest): Promise { await this.webhooks.verifyAndReceive({ id: req.headers.get('X-GitHub-Delivery') as string, - name: req.headers.get('X-GitHub-Event') as any, + name: req.headers.get('X-GitHub-Event') as EmitterWebhookEventName, payload: await req.text(), signature: req.headers.get('X-Hub-Signature') as string, }).catch((error) => { diff --git a/src/features/hooks/data/GitHubPullRequestCommentRepository.ts b/src/features/hooks/data/GitHubPullRequestCommentRepository.ts index 79ead466..a8f41fe8 100644 --- a/src/features/hooks/data/GitHubPullRequestCommentRepository.ts +++ b/src/features/hooks/data/GitHubPullRequestCommentRepository.ts @@ -7,10 +7,10 @@ import IPullRequestCommentRepository, { } from "../domain/IPullRequestCommentRepository" export type GitHubPullRequestCommentRepositoryConfig = { - appId: string, - privateKey: string, - clientId: string, + appId: string + clientId: string clientSecret: string + privateKey: string } type InstallationAuthenticator = (installationId: number) => Promise<{token: string}> @@ -19,7 +19,12 @@ export default class GitHubPullRequestCommentRepository implements IPullRequestC readonly auth: InstallationAuthenticator constructor(config: GitHubPullRequestCommentRepositoryConfig) { - const appAuth = createAppAuth(config) + const appAuth = createAppAuth({ + appId: config.appId, + clientId: config.clientId, + clientSecret: config.clientSecret, + privateKey: config.privateKey + }) this.auth = async (installationId: number) => { return await appAuth({ type: "installation", installationId }) } @@ -35,7 +40,7 @@ export default class GitHubPullRequestCommentRepository implements IPullRequestC repo: operation.repositoryName, issue_number: operation.pullRequestNumber, }) - let result: PullRequestComment[] = [] + const result: PullRequestComment[] = [] for await (const comment of comments) { result.push({ body: comment.body || "", diff --git a/src/features/projects/data/GitHubProjectRepository.ts b/src/features/projects/data/GitHubProjectRepository.ts index 07cdc9e8..3c2788d2 100644 --- a/src/features/projects/data/GitHubProjectRepository.ts +++ b/src/features/projects/data/GitHubProjectRepository.ts @@ -1,13 +1,48 @@ import { Octokit } from "octokit" +import { GraphQlQueryResponseData } from "@octokit/graphql" import AccessTokenService from "@/features/auth/domain/AccessTokenService" import IProject from "../domain/IProject" import IProjectConfig from "../domain/IProjectConfig" import IProjectRepository from "../domain/IProjectRepository" import IVersion from "../domain/IVersion" -import IOpenApiSpecification from "../domain/IOpenApiSpecification" import ProjectConfigParser from "../domain/ProjectConfigParser" import IGitHubOrganizationNameProvider from "./IGitHubOrganizationNameProvider" +type SearchResult = { + readonly name: string + readonly owner: { + readonly login: string + } + readonly defaultBranchRef: { + readonly name: string + } + readonly configYml?: { + readonly text: string + } + readonly configYaml?: { + readonly text: string + } + readonly branches: NodesContainer + readonly tags: NodesContainer +} + +type NodesContainer = { + readonly nodes: T[] +} + +type Ref = { + readonly name: string + readonly target: { + readonly tree: { + readonly entries: File[] + } + } +} + +type File = { + readonly name: string +} + export default class GitHubProjectRepository implements IProjectRepository { private organizationNameProvider: IGitHubOrganizationNameProvider private accessTokenService: AccessTokenService @@ -24,7 +59,7 @@ export default class GitHubProjectRepository implements IProjectRepository { - return this.mapProject(e) + return response.search.results.map((searchResult: SearchResult) => { + return this.mapProject(searchResult) }) - .filter((e: IProject) => { - return e.versions.length > 0 + .filter((project: IProject) => { + return project.versions.length > 0 }) - .sort((a: any, b: any) => { + .sort((a: IProject, b: IProject) => { return a.name.localeCompare(b.name) }) } - private mapProject(searchResult: any): IProject { + private mapProject(searchResult: SearchResult): IProject { const config = this.getConfig(searchResult) let imageURL: string | undefined if (config && config.image) { @@ -100,19 +135,19 @@ export default class GitHubProjectRepository implements IProjectRepository { - return e.specifications.length > 0 + versions: this.getVersions(searchResult).filter(version => { + return version.specifications.length > 0 }), imageURL: imageURL } } - private getConfig(searchResult: any): IProjectConfig | null { + private getConfig(searchResult: SearchResult): IProjectConfig | null { const yml = searchResult.configYml || searchResult.configYaml if (!yml || !yml.text || yml.text.length == 0) { return null @@ -121,11 +156,11 @@ export default class GitHubProjectRepository implements IProjectRepository { + private getVersions(searchResult: SearchResult): IVersion[] { + const branchVersions = searchResult.branches.nodes.map((ref: Ref) => { return this.mapVersionFromRef(searchResult.owner.login, searchResult.name, ref) }) - const tagVersions = searchResult.tags.nodes.map((ref: any) => { + const tagVersions = searchResult.tags.nodes.map((ref: Ref) => { return this.mapVersionFromRef(searchResult.owner.login, searchResult.name, ref) }) const defaultBranchName = searchResult.defaultBranchRef.name @@ -134,13 +169,13 @@ export default class GitHubProjectRepository implements IProjectRepository { + const allVersions = branchVersions.concat(tagVersions).sort((a: IVersion, b: IVersion) => { return a.name.localeCompare(b.name) }) // Move the top-priority branches to the top of the list. for (const candidateDefaultBranch of candidateDefaultBranches) { - const defaultBranchIndex = allVersions.findIndex((e: any) => { - return e.name === candidateDefaultBranch + const defaultBranchIndex = allVersions.findIndex((version: IVersion) => { + return version.name === candidateDefaultBranch }) if (defaultBranchIndex !== -1) { const branchVersion = allVersions[defaultBranchIndex] @@ -151,26 +186,22 @@ export default class GitHubProjectRepository implements IProjectRepository { - if (!this.isOpenAPISpecification(item.name)) { - return null - } + private mapVersionFromRef(owner: string, repository: string, ref: Ref): IVersion { + const specifications = ref.target.tree.entries.filter(file => { + return this.isOpenAPISpecification(file.name) + }).map(file => { return { - id: item.name, - name: item.name, + id: file.name, + name: file.name, url: this.getGitHubBlobURL( owner, repository, - item.name, + file.name, ref.name ), - editURL: `https://github.com/${owner}/${repository}/edit/${ref.name}/${item.name}` + editURL: `https://github.com/${owner}/${repository}/edit/${ref.name}/${file.name}` } }) - .filter((e: IOpenApiSpecification | null) => { - return e != null - }) return { id: ref.name, name: ref.name, diff --git a/src/features/projects/data/useProjects.ts b/src/features/projects/data/useProjects.ts index d7c80c69..8c52298c 100644 --- a/src/features/projects/data/useProjects.ts +++ b/src/features/projects/data/useProjects.ts @@ -5,7 +5,7 @@ import IProject from "../domain/IProject" type ProjectContainer = { projects: IProject[] } export default function useProjects() { - const { data, error, isLoading } = useSWR( + const { data, error, isLoading } = useSWR( "/api/user/projects", fetcher ) diff --git a/src/features/projects/domain/projectNavigator.ts b/src/features/projects/domain/projectNavigator.ts index 1282c83a..04427567 100644 --- a/src/features/projects/domain/projectNavigator.ts +++ b/src/features/projects/domain/projectNavigator.ts @@ -5,7 +5,7 @@ export interface IProjectRouter { replace(path: string): void } -export default { +const projectNavigator = { navigateToVersion( selection: ProjectPageSelection, versionId: string, @@ -55,3 +55,5 @@ export default { } } } + +export default projectNavigator \ No newline at end of file