Remove Apprwite-related code

This commit is contained in:
Maciej Pędzich 2023-06-07 10:31:06 +02:00
parent 7bf979101f
commit 7c548bf1e4
9 changed files with 3 additions and 287 deletions

View File

@ -1,4 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run prettier-format && npm run lint

View File

@ -1,54 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { useAuth } from './composables/useAuth';
import { useSnackbar } from './composables/useSnackbar';
import NavMenu from './components/ui/NavMenu.vue'; import NavMenu from './components/ui/NavMenu.vue';
import Snackbar from './components/ui/Snackbar.vue'; import Snackbar from './components/ui/Snackbar.vue';
const router = useRouter();
const { loadingUserFinished } = useAuth();
const { showSnackbar } = useSnackbar();
onMounted(async () => {
await router.isReady();
const loginStatusHashes = ['#login-error', '#login-success'];
const routeHash = router.currentRoute.value.hash;
if (loginStatusHashes.includes(routeHash)) {
localStorage.removeItem('redirectPath');
showSnackbar({
status: routeHash.replace('#login-', '') as 'error' | 'success',
message:
routeHash === '#login-error'
? 'Failed to log you in'
: "You're logged in"
});
}
});
</script> </script>
<template> <template>
<v-app> <v-app>
<Snackbar />
<NavMenu /> <NavMenu />
<v-main> <v-main>
<section <RouterView />
v-if="!loadingUserFinished"
class="w-100 h-100 pb-4 d-flex justify-center align-center"
>
<v-progress-circular
:size="120"
:width="7"
color="primary"
indeterminate
></v-progress-circular>
</section>
<RouterView v-show="loadingUserFinished" />
</v-main> </v-main>
<Snackbar />
</v-app> </v-app>
</template> </template>

View File

@ -1,12 +0,0 @@
import { Account, Avatars, Client, Databases, Storage } from 'appwrite';
const appwriteClient = new Client();
appwriteClient
.setEndpoint(import.meta.env.VITE_APPWRITE_ENDPOINT)
.setProject(import.meta.env.VITE_APPWRITE_PROJECT_ID);
export const account = new Account(appwriteClient);
export const avatars = new Avatars(appwriteClient);
export const databases = new Databases(appwriteClient);
export const storage = new Storage(appwriteClient);

View File

@ -1,56 +0,0 @@
import { ref, computed } from 'vue';
import { Models } from 'appwrite';
import { account } from '@/appwrite';
const user = ref<Models.User<Models.Preferences> | null>(null);
const loadingUserFinished = ref(false);
const isLoggedIn = computed(() => !!user.value);
export function useAuth() {
const logIn = (provider: 'github' | 'discord') => {
const redirectPath = localStorage.getItem('redirectPath') || '/';
const permissionScopes =
provider === 'github'
? ['read:user', 'user:email']
: ['identify', 'email'];
account.createOAuth2Session(
provider,
`${location.origin}${redirectPath}#login-success`,
`${location.origin}/log-in#login-error`,
permissionScopes
);
};
const loadUser = async () => {
try {
if (loadingUserFinished.value) return;
const currentUser = await account.get();
user.value = currentUser;
} catch (error) {
if (import.meta.env.DEV) {
console.error(error);
}
user.value = null;
} finally {
loadingUserFinished.value = true;
}
};
const logOut = async () => {
await account.deleteSession('current');
user.value = null;
};
return {
user,
loadingUserFinished,
isLoggedIn,
logIn,
loadUser,
logOut
};
}

10
src/global.d.ts vendored
View File

@ -1,10 +0,0 @@
interface ImportMetaEnv {
BASE_URL: string;
VITE_APPWRITE_ENDPOINT: string;
VITE_APPWRITE_PROJECT_ID: string;
VITE_DATABASE_ID: string;
VITE_PHOTOS_COLLECTION_ID: string;
VITE_VOTES_COLLECTION_ID: string;
VITE_PHOTO_APPEARANCE_COUNT_COLLECTION_ID: string;
VITE_NUMBER_OF_PHOTOS: number;
}

View File

@ -1,19 +0,0 @@
import { RouteLocationNormalized, NavigationGuardNext } from 'vue-router';
import { useAuth } from '@/composables/useAuth';
export async function authGuard(
to: RouteLocationNormalized,
from: RouteLocationNormalized,
next: NavigationGuardNext
) {
const { isLoggedIn, loadUser } = useAuth();
await loadUser();
if (!to.meta.authRequired || isLoggedIn.value) {
return next();
} else {
localStorage.setItem('redirectPath', to.fullPath);
return next('/log-in');
}
}

View File

@ -1,6 +1,5 @@
import { createRouter, createWebHistory } from 'vue-router'; import { createRouter, createWebHistory } from 'vue-router';
import { authGuard } from '@/guards/auth';
import Home from '@/views/Home.vue'; import Home from '@/views/Home.vue';
const routes = [ const routes = [
@ -27,6 +26,4 @@ const router = createRouter({
routes routes
}); });
router.beforeEach(authGuard);
export default router; export default router;

View File

@ -1,25 +0,0 @@
<script lang="ts" setup>
import { useAuth } from '@/composables/useAuth';
const { logIn } = useAuth();
</script>
<template>
<section class="w-100 h-100 d-flex flex-column justify-center align-center">
<h1 class="text-h3 mb-3">Log in via:</h1>
<v-container>
<v-row align="center" justify="center">
<v-col cols="auto">
<v-btn color="github" size="large" @click="logIn('github')"
>GitHub</v-btn
>
</v-col>
<v-col cols="auto">
<v-btn color="discord" size="large" @click="logIn('discord')"
>Discord</v-btn
>
</v-col>
</v-row>
</v-container>
</section>
</template>

View File

@ -1,9 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted, ref, reactive } from 'vue'; import { computed, onMounted, ref } from 'vue';
import { Query } from 'appwrite';
import { databases } from '@/appwrite';
import { useAuth } from '@/composables/useAuth';
import { useSnackbar } from '@/composables/useSnackbar'; import { useSnackbar } from '@/composables/useSnackbar';
import { useRandomNumber } from '@/composables/useRandomNumber'; import { useRandomNumber } from '@/composables/useRandomNumber';
@ -11,123 +8,11 @@ import { Vote } from '@/models/vote';
import { Photo } from '@/models/photo'; import { Photo } from '@/models/photo';
import { PhotoAppearanceCount } from '@/models/photoAppearanceCount'; import { PhotoAppearanceCount } from '@/models/photoAppearanceCount';
const { user } = useAuth();
const { showSnackbar } = useSnackbar(); const { showSnackbar } = useSnackbar();
const randomNumber = useRandomNumber(); const randomNumber = useRandomNumber();
const isLoading = ref(true); const isLoading = ref(true);
const photoAppearanceCount = reactive<Record<string, number>>({});
const photosInCurrentVote = ref<Photo[]>([]); const photosInCurrentVote = ref<Photo[]>([]);
// Get IDs of photos that have appeared in votes between all the other photos
const idsOfPhotosPairedWithAll = computed(() =>
Object.entries(JSON.parse(JSON.stringify(photoAppearanceCount)))
// You shouldn't be able to vote between two identical photos.
// That's why we need to subtract one from the number of photos.
.filter(([, count]) => count === import.meta.env.VITE_NUMBER_OF_PHOTOS - 1)
.map(([photoId]) => photoId)
);
const loadPhotosAppearanceCount = async () => {
const { documents: photoAppearanceCountEntries } =
await databases.listDocuments<PhotoAppearanceCount>(
import.meta.env.VITE_DATABASE_ID,
import.meta.env.VITE_PHOTO_APPEARANCE_COUNT_COLLECTION_ID,
[Query.equal('voterId', user.value?.$id as string)]
);
const appearanceCountRecord = photoAppearanceCountEntries.reduce(
(record, { photoId, count }) => {
record[photoId] = count;
return record;
},
{} as Record<string, number>
);
Object.assign(photoAppearanceCount, appearanceCountRecord);
};
const pickRandomPhotosAndCreateVote = async () => {
// Pick a random photo that hasn't been paired with every other photo in the user's votes
const { documents: allPhotos } = await databases.listDocuments<Photo>(
import.meta.env.VITE_DATABASE_ID,
import.meta.env.VITE_PHOTOS_COLLECTION_ID
// [
// Query.select(['photoId', 'url', 'altText']) -> Unsupported in Appwrite v1.1.2,
// Query.notEqual('photoId', idsOfPhotosPairedWithAll.value) -> Throws ambiguous Server Error
// ]
);
const photo1Candidates = allPhotos.filter(
({ $id }) => !idsOfPhotosPairedWithAll.value.includes($id)
);
const maxPhoto1Index =
import.meta.env.VITE_NUMBER_OF_PHOTOS -
idsOfPhotosPairedWithAll.value.length -
1;
const photo1 = photo1Candidates[randomNumber(0, maxPhoto1Index)];
// Get IDs of photos that have already been paired with photo1 in user's votes
const idsOfPhotosPairedWithPhoto1 =
// Select votes where photo1Id is equal to photo1's ID
(
await databases.listDocuments<Vote>(
import.meta.env.VITE_DATABASE_ID,
import.meta.env.VITE_VOTES_COLLECTION_ID,
[
// Query.select(['photo2Id']),
Query.equal('voterId', [user.value?.$id as string]),
Query.search('photos', photo1.$id)
]
)
).documents.flatMap((doc) =>
Object.entries(doc)
.filter(([key]) => key === 'photos')
.map(([, arrayOfPhotoIds]) =>
arrayOfPhotoIds.filter((id: string) => id !== photo1.$id)
)
);
// Pick a random photo that isn't photo1 and that hasn't been paired with it in any of the user's votes
const photo2IdsToExclude = [
photo1.$id,
...idsOfPhotosPairedWithPhoto1,
...idsOfPhotosPairedWithAll.value
];
const { documents: photo2Candidates } = await databases.listDocuments<Photo>(
import.meta.env.VITE_DATABASE_ID,
import.meta.env.VITE_PHOTOS_COLLECTION_ID,
[Query.notEqual('$id', photo2IdsToExclude)]
);
const maxPhoto2Index =
import.meta.env.VITE_NUMBER_OF_PHOTOS - photo2IdsToExclude.length - 1;
const photo2 = photo2Candidates[randomNumber(0, maxPhoto2Index)];
photosInCurrentVote.value.push(photo1, photo2);
};
onMounted(async () => {
try {
await loadPhotosAppearanceCount();
await pickRandomPhotosAndCreateVote();
} catch (error) {
console.error(error);
showSnackbar({
status: 'error',
message: 'Failed to load photos'
});
} finally {
isLoading.value = false;
}
});
</script> </script>
<template> <template>