using appwrite package

This commit is contained in:
Ludvík Prokopec 2023-01-02 15:15:50 +01:00
parent 0b4d5ca994
commit f4f5eb0061
8 changed files with 44 additions and 449 deletions

29
package-lock.json generated
View File

@ -11,6 +11,7 @@
"@bytemd/plugin-gfm": "^1.17.4", "@bytemd/plugin-gfm": "^1.17.4",
"appwrite": "^10.1.0", "appwrite": "^10.1.0",
"bytemd": "^1.17.4", "bytemd": "^1.17.4",
"svelte-appwrite-client": "^0.2.3",
"svelte-i18n": "^3.6.0", "svelte-i18n": "^3.6.0",
"svelte-routing": "^1.6.0" "svelte-routing": "^1.6.0"
}, },
@ -335,9 +336,9 @@
} }
}, },
"node_modules/appwrite": { "node_modules/appwrite": {
"version": "10.1.0", "version": "10.2.0",
"resolved": "https://registry.npmjs.org/appwrite/-/appwrite-10.1.0.tgz", "resolved": "https://registry.npmjs.org/appwrite/-/appwrite-10.2.0.tgz",
"integrity": "sha512-kHtPqKf0X+mxmkS47G3F5vVY5wKMVRv7ZTpTvd9H3m1KBIm3aDAEBCEUt6bGQdE8XKgqLFzhqWFdQWkxX6I0xA==", "integrity": "sha512-1cQoFWj9XlbfFP2heq5phRGjXtygpnJZY3MsAN8IWJuPvCKcVxq7Ntkz7IZm48OhLe74ByMvHCSLcOZ2BD1VsQ==",
"dependencies": { "dependencies": {
"cross-fetch": "3.1.5", "cross-fetch": "3.1.5",
"isomorphic-form-data": "2.0.0" "isomorphic-form-data": "2.0.0"
@ -3299,6 +3300,14 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/svelte-appwrite-client": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/svelte-appwrite-client/-/svelte-appwrite-client-0.2.3.tgz",
"integrity": "sha512-LxXbi15DGcCW0DOP+jteQY2rjYUzjtvYLVu+KT6NJiWr9zc8eZoYRNdh3Bds62uQuSKLf8cVUlNONujH1TD8JQ==",
"dependencies": {
"appwrite": "^10.2.0"
}
},
"node_modules/svelte-hmr": { "node_modules/svelte-hmr": {
"version": "0.15.1", "version": "0.15.1",
"resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.1.tgz", "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.1.tgz",
@ -4116,9 +4125,9 @@
} }
}, },
"appwrite": { "appwrite": {
"version": "10.1.0", "version": "10.2.0",
"resolved": "https://registry.npmjs.org/appwrite/-/appwrite-10.1.0.tgz", "resolved": "https://registry.npmjs.org/appwrite/-/appwrite-10.2.0.tgz",
"integrity": "sha512-kHtPqKf0X+mxmkS47G3F5vVY5wKMVRv7ZTpTvd9H3m1KBIm3aDAEBCEUt6bGQdE8XKgqLFzhqWFdQWkxX6I0xA==", "integrity": "sha512-1cQoFWj9XlbfFP2heq5phRGjXtygpnJZY3MsAN8IWJuPvCKcVxq7Ntkz7IZm48OhLe74ByMvHCSLcOZ2BD1VsQ==",
"requires": { "requires": {
"cross-fetch": "3.1.5", "cross-fetch": "3.1.5",
"isomorphic-form-data": "2.0.0" "isomorphic-form-data": "2.0.0"
@ -6066,6 +6075,14 @@
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.53.1.tgz", "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.53.1.tgz",
"integrity": "sha512-Q4/hHkktZogGhN5iqxqSi9sjEVoe/NbIxX4hXEHoasTxj+TxEQVAq66LnDMdAZxjmsodkoI5F3slqsS68U7FNw==" "integrity": "sha512-Q4/hHkktZogGhN5iqxqSi9sjEVoe/NbIxX4hXEHoasTxj+TxEQVAq66LnDMdAZxjmsodkoI5F3slqsS68U7FNw=="
}, },
"svelte-appwrite-client": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/svelte-appwrite-client/-/svelte-appwrite-client-0.2.3.tgz",
"integrity": "sha512-LxXbi15DGcCW0DOP+jteQY2rjYUzjtvYLVu+KT6NJiWr9zc8eZoYRNdh3Bds62uQuSKLf8cVUlNONujH1TD8JQ==",
"requires": {
"appwrite": "^10.2.0"
}
},
"svelte-hmr": { "svelte-hmr": {
"version": "0.15.1", "version": "0.15.1",
"resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.1.tgz", "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.1.tgz",

View File

@ -25,6 +25,7 @@
"@bytemd/plugin-gfm": "^1.17.4", "@bytemd/plugin-gfm": "^1.17.4",
"appwrite": "^10.1.0", "appwrite": "^10.1.0",
"bytemd": "^1.17.4", "bytemd": "^1.17.4",
"svelte-appwrite-client": "^0.2.3",
"svelte-i18n": "^3.6.0", "svelte-i18n": "^3.6.0",
"svelte-routing": "^1.6.0" "svelte-routing": "^1.6.0"
} }

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import './main.scss' import './main.scss'
import { i18n, isLoading as localeLoading } from '$lib/locales' import { i18n, isLoading as localeLoading } from '$lib/locales'
import { isLoading as authLoading } from '$lib/auth' import { isLoading as authLoading } from '$lib/appwrite'
import { onMount } from 'svelte' import { onMount } from 'svelte'
import Routes from './__routes.svelte' import Routes from './__routes.svelte'

View File

@ -1,12 +1,25 @@
import { Client, Teams, Functions, Locale, Avatars } from 'appwrite' import { Client, Teams, Functions, Locale, Avatars, Graphql, Account, Storage, Databases } from 'appwrite'
import { createAuthDispatcher, createBucketDispatcher, createCollectionDispatcher } from 'svelte-appwrite-client';
const client = new Client() const client = new Client()
const teams = new Teams(client) const teams = new Teams(client)
const functions = new Functions(client) const functions = new Functions(client)
const locale = new Locale(client) const locale = new Locale(client)
const avatars = new Avatars(client) const avatars = new Avatars(client)
const graphql = new Graphql(client)
const account = new Account(client)
const databases = new Databases(client)
const storage = new Storage(client)
const Auth = createAuthDispatcher(account)
const Bucket = createBucketDispatcher(storage)
const Collection = createCollectionDispatcher(databases)
const user = new Auth()
const isLoading = user.isLoading
client.setEndpoint(import.meta.env.VITE_APPWRITE_ENDPOINT).setProject(import.meta.env.VITE_APPWRITE_PROJECT_ID) client.setEndpoint(import.meta.env.VITE_APPWRITE_ENDPOINT).setProject(import.meta.env.VITE_APPWRITE_PROJECT_ID)
export default client export default client
export { client, teams, functions, locale, avatars } export { client, teams, functions, locale, avatars, graphql, account, databases, storage, Bucket, Collection, user, isLoading }

View File

@ -1,31 +0,0 @@
import { Models, RealtimeResponseEvent, Account } from 'appwrite'
import { writable } from 'svelte/store'
import { client } from './appwrite'
const account = new Account(client)
const userStore = writable<Models.Account<Models.Preferences>>(null)
const isLoadingStore = writable(true)
client.subscribe('account', (response: RealtimeResponseEvent<any>) => {
if (response.events.includes('users.*.sessions.*.delete')) {
return userStore.set(null)
}
if (response.events.includes('users.*.sessions.*.update')) {
return userStore.set(response.payload)
}
if (response.events.includes('users.*.sessions.*.create')) {
return account.get().then(data => userStore.set(data))
}
})
account.get().then(data => {
userStore.set(data)
isLoadingStore.set(false)
}).catch(() => isLoadingStore.set(false))
const isLoading = { subscribe: isLoadingStore.subscribe }
const user = { subscribe: userStore.subscribe }
export { account, user, isLoading }

View File

@ -1,195 +0,0 @@
import { writable } from 'svelte/store'
import { client } from './appwrite'
import { Models, Query, RealtimeResponseEvent, Databases } from 'appwrite'
import { ID } from 'appwrite'
import type { Writable } from 'svelte/store'
const databases = new Databases(client)
class Collection {
constructor(protected databaseId: string, protected collectionId: string) { }
createDocument(data: { [key: string]: any } = {}, permissions: string[] = null) {
return databases.createDocument(this.databaseId, this.collectionId, ID.unique(), data, permissions)
}
updateDocument(documentId: string, data: { [key: string]: any } = {}, permissions: string[] = null) {
if (permissions.length === 0 && Object.keys(data).length === 0) return
return databases.updateDocument(this.databaseId, this.collectionId, documentId, data, permissions)
}
deleteDocument(documentId: string) {
return databases.deleteDocument(this.databaseId, this.collectionId, documentId)
}
createObserver() {
const dataStore = writable<Models.Document[]>([])
client.subscribe(`databases.${this.databaseId}.collections.${this.collectionId}.documents`, (response: RealtimeResponseEvent<any>) => {
if (response.events.includes(`databases.${this.databaseId}.collections.${this.collectionId}.documents.*.create`)) {
dataStore.update(current => {
current.push(response.payload)
return current
})
this.subscribeCollectionUpdate(response.payload, dataStore)
}
})
return { subscribe: dataStore.subscribe }
}
createSubscriber(queries: string[] = []) {
const loadingStore = writable(true)
const dataStore = writable<Models.Document[]>([])
databases.listDocuments(this.databaseId, this.collectionId, queries).then(data => {
data.documents.forEach((document) => this.subscribeCollectionUpdate(document, dataStore))
dataStore.set(data.documents)
loadingStore.set(false)
})
return [{ subscribe: dataStore.subscribe }, { subscribe: loadingStore.subscribe }] as const
}
createPaginate(limit: number, queries: string[] = []) {
const dataStore = writable<Models.Document[]>([])
const loadingStore = writable(true)
let offset = 0
const store = {
subscribe: dataStore.subscribe,
async next() {
const data = await databases.listDocuments(this.databaseId, this.collectionId, [...queries, Query.limit(limit), Query.offset(offset)])
data.documents.forEach((document) => this.subscribeCollectionUpdate(document, dataStore))
dataStore.update(current => [...current, ...data.documents])
offset += limit
}
}
store.next().then(() => loadingStore.set(false))
return [store, { subscribe: loadingStore.subscribe }] as const
}
createInfinityScrollDispatcher(limit: number, queries: string[] = [], observerOptions: IntersectionObserverInit = {}) {
const dataStore = writable<Models.Document[]>([])
let lastId: string = null
databases.listDocuments(this.databaseId, this.collectionId, [...queries, Query.limit(limit)]).then(firstData => {
dataStore.set(firstData.documents)
firstData.documents.forEach((document) => this.subscribeCollectionUpdate(document, dataStore))
lastId = firstData.documents[firstData.documents.length - 1].$id
})
const observer = new IntersectionObserver((entries, me) => {
if (lastId === null) return
entries.forEach(entry => {
if (!entry.isIntersecting) return
databases.listDocuments(this.databaseId, this.collectionId, [...queries, Query.limit(limit), Query.cursorAfter(lastId)]).then((data) => {
dataStore.update(current => {
current.push(...data.documents)
lastId = current[current.length - 1].$id
return current
})
data.documents.forEach((document) => this.subscribeCollectionUpdate(document, dataStore))
entry.target.dispatchEvent(new CustomEvent('fetch', entry.target as CustomEventInit<HTMLElement>))
})
})
}, observerOptions)
const directive = (node: HTMLElement) => {
observer.observe(node)
return {
destroy() {
observer.disconnect()
}
}
}
return [{ subscribe: dataStore.subscribe }, directive] as const
}
protected subscribeCollectionUpdate(document: Models.Document, store: Writable<Models.Document[]>) {
client.subscribe(`databases.${this.databaseId}.collections.${this.collectionId}.documents.${document.$id}`, (response: RealtimeResponseEvent<any>) => {
if (response.events.includes(`databases.${this.databaseId}.collections.${this.collectionId}.documents.${document.$id}.delete`)) {
store.update(current => {
current.splice(current.indexOf(document), 1)
return current
})
return
}
if (response.events.includes(`databases.${this.databaseId}.collections.${this.collectionId}.documents.${document.$id}.update`)) {
store.update(current => {
current[current.indexOf(document)] = response.payload
return current
})
return
}
})
}
}
class Document {
protected databaseId: string
protected collectionId: string
protected documentId: string
constructor(databaseId: string, collectionId: string, documentId: string)
constructor(document: Models.Document)
constructor(databaseId: string | Models.Document, collectionId?: string, documentId?: string) {
this.databaseId = typeof databaseId === 'string' ? databaseId : databaseId.$database
this.collectionId = typeof databaseId === 'string' ? collectionId : databaseId.$collection
this.documentId = typeof databaseId === 'string' ? documentId : databaseId.$id
}
createSubscriber() {
const dataStore = writable<Models.Document>(null)
const loadingStore = writable(true)
databases.getDocument(this.databaseId, this.collectionId, this.documentId).then(data => {
dataStore.set(data)
loadingStore.set(false)
})
client.subscribe(`databases.${this.databaseId}.collections.${this.collectionId}.documents.${this.documentId}`, (response: RealtimeResponseEvent<any>) => {
if (response.events.includes(`databases.${this.databaseId}.collections.${this.collectionId}.documents.${this.documentId}.update`)) {
dataStore.set(response.payload)
return
}
if (response.events.includes(`databases.${this.databaseId}.collections.${this.collectionId}.documents.${this.documentId}.delete`)) {
dataStore.set(null)
return
}
})
return [{ subscribe: dataStore.subscribe }, { subscribe: loadingStore.subscribe }] as const
}
delete() {
return databases.deleteDocument(this.databaseId, this.collectionId, this.documentId)
}
update(data: { [key: string]: any } = {}, permissions: string[] = []) {
if (permissions.length === 0 && Object.keys(data).length === 0) return
return databases.updateDocument(this.databaseId, this.collectionId, this.documentId, data, permissions)
}
static async create(databaseId: string, collectionId: string, data: { [key: string]: any } = {}, permissions: string[] = []) {
const created = await databases.createDocument(databaseId, collectionId, ID.unique(), data, permissions)
return new Document(created)
}
}
export { Collection, Document, databases }

View File

@ -1,209 +0,0 @@
import { client } from './appwrite'
import { ID, Models, RealtimeResponseEvent, Storage } from 'appwrite'
import { Writable, writable } from 'svelte/store'
const storage = new Storage(client)
class Bucket {
constructor(protected bucketId: string) { }
createFile(file, permissions: string[] = []) {
return storage.createFile(this.bucketId, ID.unique(), file, permissions)
}
deleteFile(file: string | Models.File) {
return storage.deleteFile(this.bucketId, typeof file === 'string' ? file : file.$id)
}
updateFile(file: string | Models.File, permissions: string[] = []) {
return storage.updateFile(this.bucketId, typeof file === 'string' ? file : file.$id, permissions)
}
getFilePreview(file: string | Models.File) {
return storage.getFilePreview(this.bucketId, typeof file === 'string' ? file : file.$id)
}
getFileDownload(file: string | Models.File) {
return storage.getFileDownload(this.bucketId, typeof file === 'string' ? file : file.$id)
}
getFileView(file: string | Models.File) {
return storage.getFileView(this.bucketId, typeof file === 'string' ? file : file.$id)
}
getFileContent(file: string | Models.File) {
const fileContent = writable('')
const loading = writable(true)
const { href } = storage.getFileView(this.bucketId, typeof file === 'string' ? file : file.$id)
this.subscribeFileUpdateCallback(file, () => fetch(href).then(res => res.ok ? res.text() : null).then(res => {
fileContent.set(res ?? '')
loading.set(false)
}))
fetch(href).then(res => res.ok ? res.text() : null).then(res => {
fileContent.set(res ?? '')
loading.set(false)
})
return [{ subscribe: fileContent.subscribe }, { subscribe: loading.subscribe }] as const
}
createUploadDispatcher(acceptManyFiles = false) {
let files = []
const eventUploadDirective = (node: HTMLInputElement) => {
const eventListener = (e) => files = acceptManyFiles ? Array.from(e.target.files) : [e.target.files[0]]
node.addEventListener('change', eventListener)
acceptManyFiles && node.setAttribute('multiple', 'multiple')
return {
destroy() {
node.removeEventListener('change', eventListener)
}
}
}
const dispatchUpload = (permissions: string[] = []) => {
return Promise.all(files.map(file => this.createFile(file, permissions)))
}
return [eventUploadDirective, dispatchUpload] as const
}
createSubsciber(queries: string[] = [], search = '') {
const filesStore = writable<Models.File[]>([])
const loadingStore = writable(true)
storage.listFiles(this.bucketId, queries, search).then(({ files }) => {
files.forEach(file => this.subscribeFileUpdate(file, filesStore))
filesStore.set(files)
loadingStore.set(false)
})
return [{ subscribe: filesStore.subscribe }, { subscribe: loadingStore.subscribe }] as const
}
createObserver() {
const dataStore = writable<Models.File[]>([])
client.subscribe(`buckets.${this.bucketId}.files`, (response: RealtimeResponseEvent<any>) => {
if (response.events.includes(`buckets.${this.bucketId}.files.*.create`)) {
dataStore.update(current => {
current.push(response.payload)
return current
})
this.subscribeFileUpdate(response.payload, dataStore)
}
})
return { subscribe: dataStore.subscribe }
}
protected subscribeFileUpdate(file: Models.File, filesStore: Writable<Models.File[]>) {
this.subscribeFileUpdateCallback(file, ({ event }) => {
if (event === 'update') return filesStore.update(current => {
current[current.indexOf(file)] = file
return current
})
filesStore.update(current => {
current.splice(current.indexOf(file), 1)
return current
})
})
}
protected subscribeFileUpdateCallback(file: string | Models.File, callback: ({ fileId, event }: { fileId: string, event: 'update' | 'delete' }) => any) {
client.subscribe(`buckets.${this.bucketId}.files.${typeof file === 'string' ? file : file.$id}`, (response: RealtimeResponseEvent<any>) => {
if (response.events.includes(`buckets.${this.bucketId}.files.${typeof file === 'string' ? file : file.$id}.update`)) {
return callback({ fileId: typeof file === 'string' ? file : file.$id, event: 'update' })
}
if (response.events.includes(`buckets.${this.bucketId}.files.${typeof file === 'string' ? file : file.$id}.delete`)) {
return callback({ fileId: typeof file === 'string' ? file : file.$id, event: 'delete' })
}
})
}
}
class File {
protected bucketId: string
protected fileId: string
constructor(bucketId: string, fileId: string)
constructor(file: Models.File)
constructor(bucketId: string | Models.File, fileId?: string) {
this.bucketId = typeof bucketId === 'string' ? bucketId : bucketId.bucketId
this.fileId = typeof bucketId === 'string' ? fileId : bucketId.$id
}
createSubscriber() {
const fileStore = writable<Models.File>(null)
const loadingStore = writable(true)
storage.getFile(this.bucketId, this.fileId).then((result) => {
fileStore.set(result)
loadingStore.set(false)
})
client.subscribe(`buckets.${this.bucketId}.files.${this.fileId}`, (response: RealtimeResponseEvent<any>) => {
if (response.events.includes(`buckets.${this.bucketId}.files.${this.fileId}.update`)) {
fileStore.set(response.payload)
return
}
if (response.events.includes(`buckets.${this.bucketId}.files.${this.fileId}.delete`)) {
fileStore.set(null)
return
}
})
return [{ subscribe: fileStore.subscribe }, { subscribe: loadingStore.subscribe }] as const
}
delete() {
return storage.deleteFile(this.bucketId, this.fileId)
}
update(permissions: string[] = []) {
return storage.updateFile(this.bucketId, this.fileId, permissions)
}
getPreview() {
return storage.getFilePreview(this.bucketId, this.fileId)
}
getDownload() {
return storage.getFileDownload(this.bucketId, this.fileId)
}
getView() {
return storage.getFileView(this.bucketId, this.fileId)
}
async getContent() {
const fileContent = writable('')
const loading = writable(true)
const { href } = storage.getFileView(this.bucketId, this.fileId)
fetch(href).then(res => res.ok ? res.text() : null).then(res => {
fileContent.set(res ?? '')
loading.set(false)
})
return [{ subscribe: fileContent.subscribe }, { subscribe: loading.subscribe }] as const
}
static async create(bucketId: string, file, permissions: string[] = []) {
const created = await storage.createFile(bucketId, ID.unique(), file, permissions)
return new File(created)
}
}
export { Bucket, File, storage }

View File

@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import { account, user } from '$lib/auth' import { user } from '$lib/appwrite'
import { Button } from '$lib/components/Common' import { Button } from '$lib/components/Common'
import { ID } from 'appwrite'
import { _ } from 'svelte-i18n' import { _ } from 'svelte-i18n'
</script> </script>
@ -20,7 +19,7 @@
{#if $user} {#if $user}
<div> <div>
<p> <p>
<Button class="underline" on:click={() => account.deleteSession('current')}>Logout</Button> <Button class="underline" on:click={() => user.deleteSession('current')}>Logout</Button>
</p> </p>
<p> <p>
@ -31,10 +30,10 @@
{:else} {:else}
<div> <div>
<p> <p>
<Button class="underline" on:click={() => account.create(ID.unique(), 'example@example.com', 'password')}>Register</Button> <Button class="underline" on:click={() => user.createAccount('example@example.com', 'password')}>Register</Button>
</p> </p>
<p> <p>
<Button class="underline" on:click={() => account.createEmailSession('example@example.com', 'password')}>Login</Button> <Button class="underline" on:click={() => user.createEmailSession('example@example.com', 'password')}>Login</Button>
</p> </p>
</div> </div>
{/if} {/if}