Skip to content

Commit

Permalink
Replace openapi-typescript-fetch with openapi-fetch in Toolshed
Browse files Browse the repository at this point in the history
  • Loading branch information
davelopez committed Jul 15, 2024
1 parent 8321590 commit 8f728b6
Showing 17 changed files with 178 additions and 153 deletions.
2 changes: 1 addition & 1 deletion lib/tool_shed/webapp/frontend/package.json
Original file line number Diff line number Diff line change
@@ -48,7 +48,7 @@
"e": "^0.2.2",
"graphql": "^16.6.0",
"graphql-tag": "^2.12.6",
"openapi-typescript-fetch": "^1.1.3",
"openapi-fetch": "^0.10.2",
"pinia": "^2.0.28",
"quasar": "^2.5.0",
"vue": "^3.2.6",
18 changes: 11 additions & 7 deletions lib/tool_shed/webapp/frontend/src/components/RegisterPage.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref } from "vue"
import ModalForm from "@/components/ModalForm.vue"
import { fetcher } from "@/schema"
import { client } from "@/schema"
import { notify } from "@/util"
import router from "@/router"
import { AUTH_FORM_INPUT_PROPS } from "@/constants"
@@ -12,19 +12,23 @@ const confirm = ref("")
const username = ref("")
const title = ref("Register")
const createFetcher = fetcher.path("/api_internal/register").method("post").create()
// type Response = components["schemas"]["UiRegisterResponse"]
async function onRegister() {
// TODO: handle confirm and implement bear_field.
// let data: Response
try {
const { data } = await createFetcher({
email: email.value,
password: password.value,
username: username.value,
bear_field: "",
const { data } = await client.POST("/api_internal/register", {
body: {
email: email.value,
password: password.value,
username: username.value,
bear_field: "",
},
})
if (!data) {
throw new Error("No data returned")
}
const query = {
activation_error: data.activation_error ? "true" : "false",
activation_sent: data.activation_sent ? "true" : "false",
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<script setup lang="ts">
import { fetcher } from "@/schema"
import { client } from "@/schema"
import { notify, notifyOnCatch } from "@/util"
const resetFetcher = fetcher.path("/api/repositories/{encoded_repository_id}/reset_metadata").method("post").create()
async function resetMetadata() {
resetFetcher({ encoded_repository_id: props.repositoryId })
client
.POST("/api/repositories/{encoded_repository_id}/reset_metadata", {
params: { path: { encoded_repository_id: props.repositoryId } },
})
.catch(notifyOnCatch)
.then(() => {
notify("Repository metadata reset.")
38 changes: 21 additions & 17 deletions lib/tool_shed/webapp/frontend/src/components/RevisionActions.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
<script setup lang="ts">
import { computed } from "vue"
import { RevisionMetadata } from "@/schema"
import { fetcher } from "@/schema"
import { client } from "@/schema"
import { notify, notifyOnCatch } from "@/util"
const setMaliciousFetcher = fetcher
.path("/api/repositories/{encoded_repository_id}/revisions/{changeset_revision}/malicious")
.method("put")
.create()
const unsetMaliciousFetcher = fetcher
.path("/api/repositories/{encoded_repository_id}/revisions/{changeset_revision}/malicious")
.method("delete")
.create()
interface RevisionActionsProps {
repositoryId: string
@@ -20,27 +12,39 @@ interface RevisionActionsProps {
const props = defineProps<RevisionActionsProps>()
async function setMalicious() {
setMaliciousFetcher({
encoded_repository_id: props.repositoryId,
changeset_revision: props.currentMetadata.changeset_revision,
})
client
.PUT("/api/repositories/{encoded_repository_id}/revisions/{changeset_revision}/malicious", {
params: {
path: {
encoded_repository_id: props.repositoryId,
changeset_revision: props.currentMetadata.changeset_revision,
},
},
})
.catch(notifyOnCatch)
.then(() => {
notify("Marked repository as malicious")
emits("update")
})
}
async function unsetMalicious() {
unsetMaliciousFetcher({
encoded_repository_id: props.repositoryId,
changeset_revision: props.currentMetadata.changeset_revision,
})
client
.DELETE("/api/repositories/{encoded_repository_id}/revisions/{changeset_revision}/malicious", {
params: {
path: {
encoded_repository_id: props.repositoryId,
changeset_revision: props.currentMetadata.changeset_revision,
},
},
})
.catch(notifyOnCatch)
.then(() => {
notify("Un-marked repository as malicious")
emits("update")
})
}
const malicious = computed(() => props.currentMetadata.malicious)
type Emits = {
(eventName: "update"): void
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
<script setup lang="ts">
import { ref } from "vue"
import { fetcher, components } from "@/schema"
import { client, components } from "@/schema"
import PageContainer from "@/components/PageContainer.vue"
const searchIndexer = fetcher.path("/api/tools/build_search_index").method("put").create()
type IndexResults = components["schemas"]["BuildSearchIndexResponse"]
const searchResults = ref(null as IndexResults | null)
const searchResults = ref<IndexResults>()
async function onIndex() {
const { data } = await searchIndexer({})
const { data } = await client.PUT("/api/tools/build_search_index")
searchResults.value = data
}
</script>
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
import { ref } from "vue"
import ModalForm from "@/components/ModalForm.vue"
import { AUTH_FORM_INPUT_PROPS } from "@/constants"
import { fetcher } from "@/schema"
import { client } from "@/schema"
import { errorMessage } from "@/util"
import ErrorBanner from "@/components/ErrorBanner.vue"
import router from "@/router"
@@ -11,13 +11,15 @@ const current = ref("")
const password = ref("")
const confirm = ref("")
const error = ref<string | null>(null)
const changePasswordFetcher = fetcher.path("/api_internal/change_password").method("put").create()
async function onChange() {
changePasswordFetcher({
current: current.value,
password: password.value,
})
client
.PUT("/api_internal/change_password", {
body: {
current: current.value,
password: password.value,
},
})
.then(() => {
router.push("/")
})
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
<script setup lang="ts">
import { ref, watch } from "vue"
import RepositoryPage from "./RepositoryPage.vue"
import { fetcher } from "@/schema"
import { client } from "@/schema"
import type { Repository } from "@/schema/types"
import LoadingDiv from "@/components/LoadingDiv.vue"
import ErrorBanner from "@/components/ErrorBanner.vue"
const indexFetcher = fetcher.path("/api/repositories").method("get").create()
interface CitableRepositoryPageProps {
username: string
repositoryName: string
@@ -18,7 +16,14 @@ const error = ref<string | null>(null)
async function update() {
error.value = null
const { data } = await indexFetcher({ owner: props.username, name: props.repositoryName })
const { data } = await client.GET("/api/repositories", {
params: {
query: {
owner: props.username,
name: props.repositoryName,
},
},
})
if (data instanceof Array) {
if (data.length == 0) {
error.value = `Repository ${props.username}/${props.repositoryName} is not found`
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
<script setup lang="ts">
import { ref, computed } from "vue"
import PageContainer from "@/components/PageContainer.vue"
import { fetcher } from "@/schema"
import { client } from "@/schema"
import { notify, copyAndNotify, notifyOnCatch } from "@/util"
import ConfigFileContents from "@/components/ConfigFileContents.vue"
const apiKeyFetcher = fetcher.path("/api/users/{encoded_user_id}/api_key").method("get").create()
const deleteKeyFetcher = fetcher.path("/api/users/{encoded_user_id}/api_key").method("delete").create()
const recreateKeyFetcher = fetcher.path("/api/users/{encoded_user_id}/api_key").method("post").create()
const apiKey = ref(null as string | null)
const planemoConfig = computed(
() =>
@@ -26,15 +22,28 @@ async function copyKey() {
const params = { encoded_user_id: "current" }
async function init() {
apiKeyFetcher(params)
client
.GET("/api/users/{encoded_user_id}/api_key", {
params: {
path: params,
},
})
.then(({ data }) => {
if (!data) {
throw Error("Error fetching API key")
}
apiKey.value = data
})
.catch(notifyOnCatch)
}
async function deleteKey() {
deleteKeyFetcher(params)
client
.DELETE("/api/users/{encoded_user_id}/api_key", {
params: {
path: params,
},
})
.then(() => {
apiKey.value = null
notify("API key deactivated")
@@ -43,8 +52,16 @@ async function deleteKey() {
}
async function recreateKey() {
recreateKeyFetcher(params)
client
.POST("/api/users/{encoded_user_id}/api_key", {
params: {
path: params,
},
})
.then(({ data }) => {
if (!data) {
throw Error("Error re-generating API key")
}
apiKey.value = data
notify("Re-generated API key")
})
@@ -76,7 +93,7 @@ void init()
alternate means to access your account and should be treated with the same care as your login password.
</p>
<p>
Add the following block to your Planemo configuration file (typically found in
Add the following block to your Planemo configuration file (typically) found in
<code>~/.planemo.yml</code> in your
</p>
<config-file-contents name=".planemo.yml" :contents="planemoConfig" what="Planemo configuration" />
Original file line number Diff line number Diff line change
@@ -3,8 +3,7 @@ import { computed, ref, watch } from "vue"
import PageContainer from "@/components/PageContainer.vue"
import RepositoryGrid from "@/components/RepositoriesGrid.vue"
import { type RepositoryGridItem, type OnScroll } from "@/components/RepositoriesGridInterface"
import { fetcher, components } from "@/schema"
const searchFetcher = fetcher.path("/api/repositories").method("get").create()
import { client, components } from "@/schema"
const query = ref("")
const page = ref(1)
@@ -15,11 +14,18 @@ type RepositorySearchHit = components["schemas"]["RepositorySearchHit"]
async function doQuery() {
const queryValue = query.value
const { data } = await searchFetcher({ q: queryValue, page: page.value, page_size: 10 })
const { data } = await client.GET("/api/repositories", {
params: {
query: { q: queryValue, page: page.value, page_size: 10 },
},
})
if (query.value != queryValue) {
console.log("query changed.... not using these results...")
return
}
if (!data) {
throw Error("Server response error.")
}
if ("hits" in data) {
if (page.value == 1) {
hits.value = data.hits
Original file line number Diff line number Diff line change
@@ -14,21 +14,10 @@ import ConfigFileContents from "@/components/ConfigFileContents.vue"
import RepositoryLinks from "@/components/RepositoryLinks.vue"
import RepositoryExplore from "@/components/RepositoryExplore.vue"
import { type RevisionMetadata } from "@/schema"
import { fetcher } from "@/schema"
import { client } from "@/schema"
import { notifyOnCatch } from "@/util"
import { UPDATING_WITH_PLANEMO_URL, EPHEMERIS_TRAINING } from "@/constants"
const readmeFetcher = fetcher
.path("/api/repositories/{encoded_repository_id}/revisions/{changeset_revision}/readmes")
.method("get")
.create()
const deprecateFetcher = fetcher.path("/api/repositories/{encoded_repository_id}/deprecated").method("put").create()
const undeprecateFetcher = fetcher
.path("/api/repositories/{encoded_repository_id}/deprecated")
.method("delete")
.create()
interface RepositoryProps {
repositoryId: string
changesetRevision?: string | null
@@ -41,14 +30,24 @@ function onUpdate() {
async function onDeprecate() {
const repositoryId = repository.value?.id
if (repositoryId) {
deprecateFetcher({ encoded_repository_id: repositoryId }).then(onUpdate).catch(notifyOnCatch)
client
.PUT("/api/repositories/{encoded_repository_id}/deprecated", {
params: { path: { encoded_repository_id: repositoryId } },
})
.then(onUpdate)
.catch(notifyOnCatch)
}
}
async function onUndeprecate() {
const repositoryId = repository.value?.id
if (repositoryId) {
undeprecateFetcher({ encoded_repository_id: repositoryId }).then(onUpdate).catch(notifyOnCatch)
client
.DELETE("/api/repositories/{encoded_repository_id}/deprecated", {
params: { path: { encoded_repository_id: repositoryId } },
})
.then(onUpdate)
.catch(notifyOnCatch)
}
}
@@ -133,10 +132,15 @@ watch(
watch(currentRevision, () => {
if (currentRevision.value) {
readmeFetcher({
encoded_repository_id: props.repositoryId,
changeset_revision: currentRevision.value,
})
client
.GET("/api/repositories/{encoded_repository_id}/revisions/{changeset_revision}/readmes", {
params: {
path: {
encoded_repository_id: props.repositoryId,
changeset_revision: currentRevision.value,
},
},
})
.then((response) => {
if (response.data) {
readmes.value = response.data
23 changes: 4 additions & 19 deletions lib/tool_shed/webapp/frontend/src/schema/fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,5 @@
import { Fetcher } from "openapi-typescript-fetch"
import type { paths } from "./schema"
import createClient from "openapi-fetch"
import type { paths as ToolShedApiPaths } from "./schema"

/*
import type { Middleware } from "openapi-typescript-fetch";
import { rethrowSimple } from "@/utils/simple-error";
const rethrowSimpleMiddleware: Middleware = async (url, init, next) => {
try {
const response = await next(url, init);
return response;
} catch (e) {
rethrowSimple(e);
}
};
use: [rethrowSimpleMiddleware]
*/

export const fetcher = Fetcher.for<paths>()
fetcher.configure({ baseUrl: "" })
const client = createClient<ToolShedApiPaths>({ baseUrl: "" })
export { type ToolShedApiPaths, client }
2 changes: 1 addition & 1 deletion lib/tool_shed/webapp/frontend/src/schema/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export type { components, operations, paths } from "./schema"
export { fetcher } from "./fetcher"
export { client, type ToolShedApiPaths } from "./fetcher"
export type { RepositoryTool, RevisionMetadata } from "./types"
28 changes: 16 additions & 12 deletions lib/tool_shed/webapp/frontend/src/stores/auth.store.ts
Original file line number Diff line number Diff line change
@@ -2,10 +2,7 @@ import { defineStore } from "pinia"
import { ensureCookie, notifyOnCatch } from "@/util"
import { getCurrentUser } from "@/apiUtil"

import { fetcher } from "@/schema"

const loginFetcher = fetcher.path("/api_internal/login").method("put").create()
const logoutFetcher = fetcher.path("/api_internal/logout").method("put").create()
import { client } from "@/schema"

export const useAuthStore = defineStore({
id: "auth",
@@ -24,11 +21,14 @@ export const useAuthStore = defineStore({
async login(username: string, password: string) {
const token = ensureCookie("session_csrf_token")
console.log(token)
loginFetcher({
login: username,
password: password,
session_csrf_token: token,
})
client
.PUT("/api_internal/login", {
body: {
login: username,
password: password,
session_csrf_token: token,
},
})
.then(async () => {
// We need to do this outside the router to get updated
// cookies and hence csrf token.
@@ -38,9 +38,13 @@ export const useAuthStore = defineStore({
},
async logout() {
const token = ensureCookie("session_csrf_token")
logoutFetcher({
session_csrf_token: token,
})
client
.PUT("/api_internal/logout", {
body: {
session_csrf_token: token,
logout_all: false,
},
})
.then(async () => {
this.user = null
localStorage.removeItem("user")
7 changes: 3 additions & 4 deletions lib/tool_shed/webapp/frontend/src/stores/categories.store.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { defineStore } from "pinia"

import { fetcher, components } from "@/schema"
const categoriesFetcher = fetcher.path("/api/categories").method("get").create()
import { client, components } from "@/schema"
type Category = components["schemas"]["Category"]

export const useCategoriesStore = defineStore({
@@ -13,8 +12,8 @@ export const useCategoriesStore = defineStore({
actions: {
async getAll() {
this.loading = true
const { data: categories } = await categoriesFetcher({})
this.categories = categories
const { data: categories } = await client.GET("/api/categories")
this.categories = categories ?? []
this.loading = false
},
},
69 changes: 33 additions & 36 deletions lib/tool_shed/webapp/frontend/src/stores/repository.store.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
import { defineStore } from "pinia"

import { fetcher, components } from "@/schema"
const repositoryFetcher = fetcher.path("/api/repositories/{encoded_repository_id}").method("get").create()
const repositoryMetadataFetcher = fetcher
.path("/api_internal/repositories/{encoded_repository_id}/metadata")
.method("get")
.create()
const repositoryPermissionsFetcher = fetcher
.path("/api/repositories/{encoded_repository_id}/permissions")
.method("get")
.create()
const repositoryPermissionsAdder = fetcher
.path("/api/repositories/{encoded_repository_id}/allow_push/{username}")
.method("post")
.create()
const repositoryPermissionsRemover = fetcher
.path("/api/repositories/{encoded_repository_id}/allow_push/{username}")
.method("delete")
.create()
const repositoryInstallInfoFetcher = fetcher.path("/api/repositories/install_info").method("get").create()
import { client, components } from "@/schema"

function fetchRepositoryPermissions(repositoryId: string) {
return client.GET("/api/repositories/{encoded_repository_id}/permissions", {
params: {
path: { encoded_repository_id: repositoryId },
},
})
}

type DetailedRepository = components["schemas"]["DetailedRepository"]
type InstallInfo = components["schemas"]["InstallInfo"]
@@ -45,9 +35,11 @@ export const useRepositoryStore = defineStore({
encoded_repository_id: this.repositoryId,
username: username,
}
await repositoryPermissionsAdder(params)
const { data: _repositoryPermissions } = await repositoryPermissionsFetcher(params)
this.repositoryPermissions = _repositoryPermissions
await client.POST("/api/repositories/{encoded_repository_id}/allow_push/{username}", {
params: { path: params },
})
const { data: _repositoryPermissions } = await fetchRepositoryPermissions(this.repositoryId)
this.repositoryPermissions = _repositoryPermissions ?? null
},
async disallowPush(username: string) {
if (this.repositoryId == null) {
@@ -57,9 +49,11 @@ export const useRepositoryStore = defineStore({
encoded_repository_id: this.repositoryId,
username: username,
}
await repositoryPermissionsRemover(params)
const { data: _repositoryPermissions } = await repositoryPermissionsFetcher(params)
this.repositoryPermissions = _repositoryPermissions
await client.DELETE("/api/repositories/{encoded_repository_id}/allow_push/{username}", {
params: { path: params },
})
const { data: _repositoryPermissions } = await fetchRepositoryPermissions(this.repositoryId)
this.repositoryPermissions = _repositoryPermissions ?? null
},
async setId(repositoryId: string) {
this.repositoryId = repositoryId
@@ -71,27 +65,28 @@ export const useRepositoryStore = defineStore({
}
this.loading = true
const params = { encoded_repository_id: this.repositoryId }
const metadataParams = { encoded_repository_id: this.repositoryId, downloadable_only: false }
const [{ data: repository }, { data: repositoryMetadata }] = await Promise.all([
repositoryFetcher(params),
repositoryMetadataFetcher(metadataParams),
client.GET("/api/repositories/{encoded_repository_id}", { params: { path: params } }),
client.GET("/api_internal/repositories/{encoded_repository_id}/metadata", {
params: { path: params, query: { downloadable_only: false } },
}),
])
this.repository = repository
this.repositoryMetadata = repositoryMetadata
this.repository = repository ?? null
this.repositoryMetadata = repositoryMetadata ?? null
let repositoryPermissions = {
can_manage: false,
can_push: false,
allow_push: [] as string[],
}
try {
const { data: _repositoryPermissions } = await repositoryPermissionsFetcher(params)
repositoryPermissions = _repositoryPermissions
const { data: _repositoryPermissions } = await fetchRepositoryPermissions(this.repositoryId)
repositoryPermissions = _repositoryPermissions ?? repositoryPermissions
this.repositoryPermissions = repositoryPermissions
} catch (e) {
// console.log(e)
}
const latestMetadata = Object.values(repositoryMetadata)[0]
if (!latestMetadata) {
const latestMetadata = Object.values(repositoryMetadata ?? {})[0]
if (!latestMetadata || !repository) {
this.empty = true
} else {
if (this.empty) {
@@ -102,8 +97,10 @@ export const useRepositoryStore = defineStore({
owner: repository.owner,
changeset_revision: latestMetadata.changeset_revision,
}
const { data: repositoryInstallInfo } = await repositoryInstallInfoFetcher(installParams)
this.repositoryInstallInfo = repositoryInstallInfo
const { data: repositoryInstallInfo } = await client.GET("/api/repositories/install_info", {
params: { query: installParams },
})
this.repositoryInstallInfo = repositoryInstallInfo ?? null
}
this.loading = false
},
7 changes: 3 additions & 4 deletions lib/tool_shed/webapp/frontend/src/stores/users.store.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { defineStore } from "pinia"

import { fetcher, components } from "@/schema"
const usersFetcher = fetcher.path("/api/users").method("get").create()
import { client, components } from "@/schema"

type User = components["schemas"]["UserV2"]

@@ -14,8 +13,8 @@ export const useUsersStore = defineStore({
actions: {
async getAll() {
this.loading = true
const { data: users } = await usersFetcher({})
this.users = users
const { data: users } = await client.GET("/api/users")
this.users = users ?? []
this.loading = false
},
},
8 changes: 3 additions & 5 deletions lib/tool_shed/webapp/frontend/src/util.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { copyToClipboard, Notify, Cookies } from "quasar"
import type { QNotifyCreateOptions } from "quasar"
import { type LocationQueryValue } from "vue-router"
import { ApiError } from "openapi-typescript-fetch"

export function getCookie(name: string): string | null {
return Cookies.get(name)
@@ -32,11 +31,10 @@ export async function copyAndNotify(value: string, notification: string) {
}

export function errorMessage(e: Error): string {
if (e instanceof ApiError) {
return e.data.err_msg
} else {
return JSON.stringify(e)
if (e.cause) {
return `${e.message}: ${e.cause}`
}
return e.message
}

export function queryParamToString(param: LocationQueryValue | LocationQueryValue[]): string | null {

0 comments on commit 8f728b6

Please sign in to comment.