Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 2 additions & 15 deletions components/Modal/CreateCollection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ const emit = defineEmits<{

const open = defineModel<boolean>({ required: true });

const { t } = useI18n();
const collectionName = ref("");
const createCollectionLoading = ref(false);
const collections = await useCollections();
Expand All @@ -74,6 +73,7 @@ async function createCollection() {
const response = await $dropFetch("/api/v1/collection", {
method: "POST",
body: { name: collectionName.value },
failTitle: "Failed to create collection",
});

// Add the game if provided
Expand All @@ -83,6 +83,7 @@ async function createCollection() {
>(`/api/v1/collection/${response.id}/entry`, {
method: "POST",
body: { id: props.gameId },
failTitle: "Failed to add game to collection",
});
response.entries.push(entry);
}
Expand All @@ -94,20 +95,6 @@ async function createCollection() {
open.value = false;

emit("created", response.id);
} catch (error) {
console.error("Failed to create collection:", error);

const err = error as { statusMessage?: string };
createModal(
ModalType.Notification,
{
title: t("errors.library.collection.create.title"),
description: t("errors.library.collection.create.desc", [
err?.statusMessage ?? t("errors.unknown"),
]),
},
(_, c) => c(),
);
} finally {
createCollectionLoading.value = false;
}
Expand Down
39 changes: 39 additions & 0 deletions components/UserHeader/StoreNav.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<template>
<div
class="w-full bg-zinc-950 p-1 inline-flex items-center gap-x-2 fixed inset-x-0 top-0 z-100"
>
<button
class="p-1 text-zinc-300 hover:text-zinc-100 hover:bg-zinc-900 transition-all rounded"
@click="() => router.back()"
>
<ChevronLeftIcon class="size-4" />
</button>
<button
class="p-1 text-zinc-300 hover:text-zinc-100 hover:bg-zinc-900 transition-all rounded"
@click="() => router.forward()"
>
<ChevronRightIcon class="size-4" />
</button>
<span class="text-zinc-400 text-sm">
{{ title }}
</span>
</div>
</template>
<script setup lang="ts">
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/vue/24/outline";

const router = useRouter();
const title = ref("Loading...");

onMounted(() => {
title.value = document.title;
});

router.afterEach(() => {
title.value = "Loading...";
// TODO: more robust after-render "detection"
setTimeout(() => {
title.value = document.title;
}, 500);
});
</script>
6 changes: 4 additions & 2 deletions dev-tools/compose.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
services:
postgres:
image: postgres:14-alpine
user: "1000:1000"
ports:
- 5432:5432
volumes:
- ../.data/db:/var/lib/postgresql/data
- postgres-data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=drop
- POSTGRES_USER=drop
- POSTGRES_DB=drop

volumes:
postgres-data:
2 changes: 1 addition & 1 deletion i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
"register": {
"confirmPasswordFormat": "Muss mit oben genanntem übereinstimmen",
"emailFormat": "Muss im Format nutzer{'@'}beispiel.de sein",
"passwordFormat": "Muss mindestens 14 Zeichen enthalten",
"passwordFormat": "Muss mindestens 8 Zeichen enthalten",
"subheader": "Gebe unten deine Daten ein, um dein Konto zu erstellen.",
"title": "Erstelle dein Drop Konto",
"usernameFormat": "Muss mindestens 5 Zeichen enthalten und aus Kleinbuchstaben bestehen"
Expand Down
2 changes: 1 addition & 1 deletion i18n/locales/en_pirate.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"register": {
"confirmPasswordFormat": "Must be the same as above, savvy?",
"emailFormat": "Must be in the fashion of a true scallywag {'@'} example.com",
"passwordFormat": "Must be 14 or more marks, ye landlubber!",
"passwordFormat": "Must be 8 or more marks, ye landlubber!",
"subheader": "Fill in yer details below to make yer mark.",
"title": "Forge yer Drop Mark",
"usernameFormat": "Must be 5 or more marks, and all lowercase, argh!"
Expand Down
2 changes: 1 addition & 1 deletion i18n/locales/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
"register": {
"confirmPasswordFormat": "Must be the same as above",
"emailFormat": "Must be in the format user{'@'}example.com",
"passwordFormat": "Must be 14 or more characters",
"passwordFormat": "Must be 8 or more characters",
"subheader": "Fill in your details below to create your account.",
"title": "Create your Drop account",
"usernameFormat": "Must be 5 or more characters, and lowercase"
Expand Down
2 changes: 1 addition & 1 deletion i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
"register": {
"confirmPasswordFormat": "Doit être pareil qu'au dessus",
"emailFormat": "Doit être au format utilisateur{'@'}exemple.com",
"passwordFormat": "Doit être au moins 14 caractères ou plus",
"passwordFormat": "Doit être au moins 8 caractères ou plus",
"subheader": "Remplissez vos coordonnées pour créer votre compte.",
"title": "Créer votre compte Drop",
"usernameFormat": "Doit être au moins 5 caractères et en minuscules"
Expand Down
2 changes: 1 addition & 1 deletion i18n/locales/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
"register": {
"confirmPasswordFormat": "Musi być takie samo jak powyżej",
"emailFormat": "Musi być w formacie uzytkownik{'@'}example.com",
"passwordFormat": "Musi mieć conajmniej 14 znaków",
"passwordFormat": "Musi mieć conajmniej 8 znaków",
"subheader": "Wpisz poniżej swoje dane, aby utworzyć swoje konto.",
"title": "Stwórz swoje konto Drop",
"usernameFormat": "Musi mieć co najmniej 5 znaków i małe litery"
Expand Down
3 changes: 2 additions & 1 deletion layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
</div>
<LazyUserFooter class="z-50" hydrate-on-interaction />
</div>
<div v-else class="flex w-full min-h-screen bg-zinc-900">
<div v-else class="flex flex-col w-full min-h-screen bg-zinc-900">
<NuxtPage />
<LazyUserHeaderStoreNav />
</div>
</template>

Expand Down
2 changes: 1 addition & 1 deletion nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ export default defineNuxtConfig({
"https://images.pcgamingwiki.com",
"https://images.igdb.com",
"https://*.steamstatic.com",
],
]
},
strictTransportSecurity: false,
},
Expand Down
50 changes: 23 additions & 27 deletions pages/admin/task/[id]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,35 @@
</i18n-t>
</NuxtLink>

<div
v-if="task && task.error"
class="grow w-full flex items-center justify-center"
>
<div class="flex flex-col items-center">
<ExclamationCircleIcon
class="h-12 w-12 text-red-600"
aria-hidden="true"
/>
<div class="mt-3 text-center sm:mt-5">
<h1
class="text-3xl font-semibold font-display leading-6 text-zinc-100"
>
{{ task.error.title }}
</h1>
<div class="mt-4">
<p class="text-sm text-zinc-400 max-w-md">
{{ task.error.description }}
</p>
</div>
</div>
</div>
</div>
<div v-else-if="task" class="flex flex-col w-full gap-y-4">
<div v-if="task" class="flex flex-col w-full gap-y-4">
<h1
class="inline-flex items-center gap-x-3 text-3xl text-zinc-100 font-bold font-display"
>
<div>
<CheckCircleIcon v-if="task.success" class="size-5 text-green-600" />
<CheckCircleIcon v-if="task.success" class="size-8 text-green-600" />
<XMarkIcon v-else-if="task.error" class="size-8 text-red-600" />
<div v-else class="size-4 bg-blue-600 rounded-full animate-pulse" />
</div>
{{ task.name }}
</h1>
<div
v-if="task.error"
class="rounded-md bg-red-500/15 p-4 outline outline-red-500/25"
>
<div class="flex">
<div class="shrink-0">
<XCircleIcon class="size-5 text-red-400" aria-hidden="true" />
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-red-200">
{{ task.error.title }}
</h3>
<div class="mt-2 text-sm text-red-200/80">
{{ task.error.description }}
</div>
</div>
</div>
</div>
<ul class="flex flex-row items-center h-12 gap-x-3">
<li
v-for="[name, link] in task.actions.map((v) => v.split(':'))"
Expand Down Expand Up @@ -95,8 +91,8 @@
</template>

<script setup lang="ts">
import { CheckCircleIcon } from "@heroicons/vue/16/solid";
import { ExclamationCircleIcon } from "@heroicons/vue/24/solid";
import { CheckCircleIcon } from "@heroicons/vue/24/solid";
import { XMarkIcon, XCircleIcon } from "@heroicons/vue/24/outline";

const route = useRoute();
const taskId = route.params.id.toString();
Expand Down
2 changes: 1 addition & 1 deletion pages/auth/register.vue
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ const validUsername = computed(
!((usernameValidator(username.value) as unknown) instanceof type.errors),
);

const passwordValidator = type("string >= 14");
const passwordValidator = type("string >= 8");
const validPassword = computed(
() =>
!((passwordValidator(password.value) as unknown) instanceof type.errors),
Expand Down
4 changes: 3 additions & 1 deletion pages/store/[id]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
<AddLibraryButton :game-id="game.id" />
</div>
<NuxtLink
v-if="user?.admin"
v-if="user?.admin && !isClient"
:href="`/admin/library/${game.id}`"
type="button"
class="inline-flex items-center gap-x-2 rounded-md bg-zinc-800 px-3 py-1 text-sm font-semibold font-display text-white shadow-sm hover:bg-zinc-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 duration-200 hover:scale-105 active:scale-95"
Expand Down Expand Up @@ -254,6 +254,8 @@ const user = useUser();

const { game, rating, size } = await $dropFetch(`/api/v1/games/${gameId}`);

const isClient = isClientRequest();

const descriptionHTML = micromark(game.mDescription);

const platforms = game.versions
Expand Down
2 changes: 1 addition & 1 deletion server/api/v1/auth/signup/simple.post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const SharedRegisterValidator = type({

const CreateUserValidator = SharedRegisterValidator.and({
invitation: "string",
password: "string >= 14",
password: "string >= 8",
"displayName?": "string | undefined",
}).configure(throwingArktype);

Expand Down
12 changes: 2 additions & 10 deletions server/api/v1/client/user/webtoken.post.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,21 @@
import { APITokenMode } from "~/prisma/client/enums";
import { DateTime } from "luxon";
import type { UserACL } from "~/server/internal/acls";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import prisma from "~/server/internal/db/database";
import { CLIENT_WEBTOKEN_ACLS } from "~/server/plugins/04.auth-init";

export default defineClientEventHandler(
async (h3, { fetchUser, fetchClient, clientId }) => {
const user = await fetchUser();
const client = await fetchClient();

const acls: UserACL = [
"read",
"store:read",
"collections:read",
"object:read",
"settings:read",
];

const token = await prisma.aPIToken.create({
data: {
name: `${client.name} Web Access Token ${DateTime.now().toISO()}`,
clientId,
userId: user.id,
mode: APITokenMode.Client,
acls,
acls: CLIENT_WEBTOKEN_ACLS,
},
});

Expand Down
16 changes: 9 additions & 7 deletions server/api/v1/collection/index.post.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls";
import userLibraryManager from "~/server/internal/userlibrary";

const CreateCollection = type({
name: "string"
}).configure(throwingArktype);

export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["collections:read"]);
const userId = await aclManager.getUserIdACL(h3, ["collections:new"]);
if (!userId)
throw createError({
statusCode: 403,
});

const body = await readBody(h3);

const name = body.name;
if (!name)
throw createError({ statusCode: 400, statusMessage: "Requires name" });
const body = await readDropValidatedBody(h3, CreateCollection);

// Create the collection using the manager
const newCollection = await userLibraryManager.collectionCreate(name, userId);
const newCollection = await userLibraryManager.collectionCreate(body.name, userId);
return newCollection;
});
28 changes: 28 additions & 0 deletions server/plugins/04.auth-init.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
import authManager from "~/server/internal/auth";
import prisma from "../internal/db/database";
import { APITokenMode } from "~/prisma/client/enums";
import type { UserACL } from "../internal/acls";

export const CLIENT_WEBTOKEN_ACLS: UserACL = [
"read",
"store:read",
"object:read",
"settings:read",

"collections:read",
"collections:new",
"collections:add",
"collections:remove",
"collections:delete",

"library:add",
"library:remove",
];

export default defineNitroPlugin(async () => {
await authManager.init();

await prisma.aPIToken.updateMany({
where: {
mode: APITokenMode.Client,
},
data: {
acls: CLIENT_WEBTOKEN_ACLS,
},
});
});
Loading