Zapier integration (#491)

* create zapier app

* install sanctum

* move OAuthProviderController

* make `api-external` middleware

* add zapier endpoints

* add tests

* token management

* zapier event handler

* add policy

* use `slug` instead of `id`

* wip

* check policies

* change api prefix to `external`

* ui tweaks

* validate token abilities

* open zapier URL

* zapier ui tweaks

* update zap

* Fix linting

* Added sample endpoints + minor UI changes

* Run PHP code linter

---------

Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
Boris Lepikhin
2024-08-12 02:14:02 -07:00
committed by GitHub
parent 7ad62fb3ea
commit 517bccc695
61 changed files with 5799 additions and 51 deletions

View File

@@ -0,0 +1,83 @@
<template>
<div
class="text-gray-500 border shadow rounded-md p-5 mt-4 relative flex items-center"
>
<div class="flex-grow flex items-center">
<div>
<div class="flex space-x-3 font-semibold mr-2">
{{ token.name }}
</div>
<div class="">
<span
v-for="(ability, index) in token.abilities"
:key="index"
>
{{ accessTokenStore.getAbility(ability).title }}
<template v-if="index !== token.abilities.length - 1">
,&nbsp;
</template>
</span>
</div>
</div>
</div>
<div class="flex items-center gap-4">
<dropdown
class="inline"
>
<template #trigger="{ toggle }">
<v-button
color="white"
@click="toggle"
>
<Icon
name="heroicons:ellipsis-horizontal"
class="w-4 h-4 -mt-1"
/>
</v-button>
</template>
<a
href="#"
class="flex px-4 py-2 text-md text-red-600 hover:bg-red-50 hover:no-underline items-center"
@click.prevent="destroy"
>
<Icon
name="heroicons:trash"
class="w-5 h-5 mr-2"
/>
Delete
</a>
</dropdown>
</div>
</div>
</template>
<script setup>
const props = defineProps({
token: Object
})
const accessTokenStore = useAccessTokenStore()
const alert = useAlert()
function destroy() {
alert.confirm("Do you really want to delete this token?", () => {
opnFetch(`/settings/tokens/${props.token.id}`, {
method: 'DELETE'
})
.then(() => {
accessTokenStore.remove(props.token.id)
})
.catch((error) => {
try {
alert.error(error.data.message)
} catch (e) {
alert.error("An error occurred while disconnecting an account")
}
})
})
}
</script>

View File

@@ -0,0 +1,143 @@
<template>
<modal
:show="show"
max-width="lg"
@close="emit('close')"
>
<template #icon>
<svg
class="w-8 h-8"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 8V16M8 12H16M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
<template #title>
Create an access token
</template>
<div class="px-4">
<form
@submit.prevent="createToken"
@keydown="form.onKeydown($event)"
>
<div v-if="!token">
<text-input
name="name"
class="mt-4"
:form="form"
:required="true"
label="Name"
/>
<div>
<label class="input-label">Abilities</label>
<div class="mt-2">
<checkbox-input
v-for="ability in abilities"
:key="ability.name"
v-model="form.abilities"
:value="ability.name"
:label="ability.title"
disabled
/>
</div>
</div>
</div>
<UAlert
v-if="token"
icon="i-heroicons-key-20-solid"
color="green"
variant="subtle"
title="Copy your access token"
description="Your token will only be shown once. Make sure to save it safely."
/>
<div class="flex">
<copy-content
v-if="token"
:content="token"
>
<template #icon>
<Icon
name="heroicons:link"
class="w-4 h-4 -mt-1 text-blue-600 mr-3"
/>
</template>
Copy Token
</copy-content>
</div>
<div class="w-full mt-6">
<v-button
v-if="!token"
:loading="form.busy"
class="w-full my-3"
>
Save
</v-button>
<v-button
v-else
class="w-full my-3"
@click.prevent="emit('close')"
>
Close
</v-button>
</div>
</form>
</div>
</modal>
</template>
<script setup>
import CopyContent from "../open/forms/components/CopyContent.vue"
const props = defineProps({
show: Boolean
})
const emit = defineEmits(['close'])
const accessTokenStore = useAccessTokenStore()
const abilities = computed(() => accessTokenStore.abilities)
const form = useForm({
name: "",
abilities: abilities.value.map(ability => ability.name),
})
const token = ref('')
function createToken() {
form.post("/settings/tokens").then((data) => {
// workspacesStore.save(data.workspace)
// workspacesStore.currentId = data.workspace.id
// workspaceModal.value = false
token.value = data.token
accessTokenStore.fetchTokens()
useAlert().success(
"Access token successfully created!",
)
})
}
watch(() => props.show, () => {
form.name = ''
form.abilities = abilities.value.map(ability => ability.name),
token.value = ''
})
</script>