mirror of
https://github.com/maciejpedzich/racemash.git
synced 2024-11-27 16:15:47 +01:00
Remove Apprwite-related code
This commit is contained in:
parent
7bf979101f
commit
7c548bf1e4
@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
. "$(dirname -- "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
npm run prettier-format && npm run lint
|
|
44
src/App.vue
44
src/App.vue
@ -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>
|
||||||
|
@ -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);
|
|
@ -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
10
src/global.d.ts
vendored
@ -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;
|
|
||||||
}
|
|
@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
||||||
|
@ -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>
|
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user