Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
rizen authored Apr 7, 2024
2 parents ed0f42b + 08b527f commit 085cedc
Show file tree
Hide file tree
Showing 22 changed files with 1,535 additions and 3 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,5 @@ logs
dist
**/node_modules
*.zip
ving/drizzle/migrations
docs/.vitepress/cache
docs/.vitepress/dist
4 changes: 2 additions & 2 deletions layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
</template>
</Menubar>
</client-only>

<client-only>
<SystemWideAlert />
</client-only>
Expand All @@ -78,6 +77,7 @@ onMounted(async () => {
})
const topNav = [
{ label: 'Home', to: '/', icon: 'prime:home' },
{ label: 'Games', to: '/game', icon: "prime:star" },
{ label: 'Ving Documentation', to: 'https://plainblack.github.io/ving/', icon: "prime:book" },
{
label: 'Sample Dropdown', icon: "prime:thumbs-down", items: [
Expand All @@ -97,4 +97,4 @@ const userMenu = computed(() => {
out.unshift({ label: 'Admin', to: '/admin', icon: 'prime:user-plus' });
return out;
})
</script>
</script>
82 changes: 82 additions & 0 deletions pages/game/[id]/edit.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<template>
<Crumbtrail :crumbs="breadcrumbs" />
<h1>Edit Game</h1>

<FieldsetNav v-if="game.props">
<FieldsetItem name="Properties">

<div class="mb-4">
<FormInput name="name" type="text" v-model="game.props.name" required label="Name"
@change="game.update()" />
</div>
<div class="mb-4">
<FormInput name="notes" type="text" v-model="game.props.notes" label="Notes" @change="game.update()" />
</div>
<div class="mb-4">
<FormInput name="userId" type="text" v-model="game.props.userId" required label="User Id"
@change="game.update()" />
</div>
<div class="mb-4">
<FormSelect name="archived" :options="game.options.archived" v-model="game.props.archived"
label="Archived" @change="game.update()" />
</div>
<div class="mb-4">
<FormInput name="collection" type="text" v-model="game.props.collection" required label="Collection"
@change="game.update()" />
</div>
</FieldsetItem>

<FieldsetItem name="Statistics">

<div class="mb-4"><b>Id</b>: {{ game.props?.id }}</div>

<div class="mb-4"><b>Created At</b>: {{ dt.formatDateTime(game.props.createdAt) }}</div>

<div class="mb-4"><b>Updated At</b>: {{ dt.formatDateTime(game.props.updatedAt) }}</div>

<div class="mb-4"><b>Field Schema</b>: {{ game.props?.fieldSchema }}</div>

<div class="mb-4"><b>Fields</b>: {{ game.props?.fields }}</div>

</FieldsetItem>

<FieldsetItem name="Actions">
<NuxtLink :to="`/game/${game.props?.id}`" class="no-underline">
<Button title="View" alt="View Game" class="mr-2 mb-2"><i class="pi pi-eye mr-1"></i> View</Button>
</NuxtLink>
<Button @click="game.delete()" severity="danger" class="mr-2 mb-2" title="Delete" alt="Delete Game"><i
class="pi pi-trash mr-1"></i> Delete</Button>
</FieldsetItem>

</FieldsetNav>
</template>

<script setup>
definePageMeta({
middleware: ['auth']
});
const route = useRoute();
const dt = useDateTime();
const notify = useNotifyStore();
const id = route.params.id.toString();
const game = useVingRecord({
id,
fetchApi: `/api/${restVersion()}/game/${id}`,
createApi: `/api/${restVersion()}/game`,
query: { includeMeta: true, includeOptions: true },
onUpdate() {
notify.success('Updated Game.');
},
async onDelete() {
await navigateTo('/game');
},
});
await game.fetch()
onBeforeRouteLeave(() => game.dispose());

const breadcrumbs = [
{ label: 'Games', to: '/game' },
{ label: 'View', to: '/game/' + game.props.id },
{ label: 'Edit' },
];
</script>
57 changes: 57 additions & 0 deletions pages/game/[id]/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<template>
<Crumbtrail :crumbs="breadcrumbs" />
<h1>{{ game.props?.name }}</h1>
<div v-if="game.props?.id" class="surface-card p-4 border-1 surface-border border-round flex-auto">

<div><b>Id</b>: {{ game.props?.id }}</div>

<div><b>Created At</b>: {{ dt.formatDateTime(game.props?.createdAt) }}</div>

<div><b>Updated At</b>: {{ dt.formatDateTime(game.props?.updatedAt) }}</div>

<div><b>Name</b>: {{ game.props?.name }}</div>

<div><b>Notes</b>: {{ game.props?.notes }}</div>

<div><b>Field Schema</b>: {{ game.props?.fieldSchema }}</div>

<div><b>Fields</b>: {{ game.props?.fields }}</div>

<div><b>User Id</b>: {{ game.props?.userId }}</div>

<div><b>Archived</b>: {{ game.props?.archived }}</div>

<div><b>Collection</b>: {{ game.props?.collection }}</div>

</div>
<div class="mt-3" v-if="game.meta?.isOwner">
<NuxtLink :to="`/game/${game.props?.id}/edit`" class="no-underline mr-2 mb-2">
<Button severity="success" title="Edit" alt="Edit Game"><i class="pi pi-pencil mr-1"></i> Edit</Button>
</NuxtLink>
<Button @click="game.delete()" severity="danger" title="Delete" alt="Delete Game"><i
class="pi pi-trash mr-1"></i> Delete</Button>
</div>
</template>

<script setup>
definePageMeta({
middleware: ['auth']
});
const route = useRoute();
const id = route.params.id.toString();
const game = useVingRecord({
id,
fetchApi: `/api/${restVersion()}/game/${id}`,
query: { includeMeta: true, includeOptions: true },
async onDelete() {
await navigateTo('/game');
},
});
await game.fetch();
onBeforeRouteLeave(() => game.dispose());
const dt = useDateTime();
const breadcrumbs = [
{ label: 'Games', to: '/game' },
{ label: 'View' },
];
</script>
75 changes: 75 additions & 0 deletions pages/game/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<template>
<h1>Games</h1>

<div class="surface-card p-4 border-1 surface-border border-round">

<InputGroup>
<InputGroupAddon>
<i class="pi pi-search" />
</InputGroupAddon>
<InputText type="text" placeholder="Search Games" class="w-full" v-model="games.query.search"
@keydown.enter="games.search()" />
<Button label="Search" @click="games.search()" />
</InputGroup>

<DataTable :value="games.records" stripedRows @sort="(e) => games.sortDataTable(e)">

<Column field="props.name" header="Name" sortable></Column>
<Column field="props.archived" header="Archived" sortable></Column>
<Column field="props.collection" header="Collection" sortable></Column>
<Column header="Manage">
<template #body="slotProps">
<NuxtLink :to="`/game/${slotProps.data.props.id}`" class="mr-2 no-underline">
<Button icon="pi pi-eye" title="View" alt="View Game" />
</NuxtLink>
<NuxtLink v-if="slotProps.data.meta?.isOwner" :to="`/game/${slotProps.data.props.id}/edit`"
class="mr-2 no-underline">
<Button icon="pi pi-pencil" severity="success" title="Edit" alt="Edit Game" />
</NuxtLink>
<Button v-if="slotProps.data.meta?.isOwner" title="Delete" alt="Delete Game" icon="pi pi-trash"
severity="danger" @click="slotProps.data.delete()" />
</template>
</Column>
</DataTable>
<Pager :kind="games" />
</div>
<div class="mt-5 surface-card p-5 border-1 surface-border border-round">
<h2 class="mt-0">Create Game</h2>
<Form :send="() => games.create()">
<div class="flex gap-5 flex-column-reverse md:flex-row">
<div class="flex-auto p-fluid">
<div class="mb-4">
<FormInput name="name" type="text" v-model="games.new.name" required label="Name" />
</div>
<div>
<Button type="submit" class="w-auto" severity="success">
<i class="pi pi-plus mr-1"></i> Create Game
</Button>
</div>
</div>
</div>
</Form>
</div>
</template>
<script setup>
definePageMeta({
middleware: ['auth']
});
const dt = useDateTime();
const currentUser = useCurrentUserStore();
const games = useVingKind({
listApi: `/api/${restVersion()}/game`,
createApi: `/api/${restVersion()}/game`,
query: { includeMeta: true, sortBy: 'name', sortOrder: 'asc' },
newDefaults: { name: '', userId: currentUser.props?.id },
});
await Promise.all([
games.search(),
games.fetchPropsOptions(),
]);
onBeforeRouteLeave(() => games.dispose());
</script>
12 changes: 12 additions & 0 deletions server/api/v1/game/[id].delete.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useKind } from '#ving/record/utils.mjs';
import { obtainSession, describeParams } from '#ving/utils/rest.mjs';
import { defineEventHandler, getRouterParams } from 'h3';
export default defineEventHandler(async (event) => {
const games = await useKind('Game');
const { id } = getRouterParams(event);
const game = await games.findOrDie(id);
const session = obtainSession(event);
game.canEdit(session);
await game.delete();
return game.describe(describeParams(event, session));
});
9 changes: 9 additions & 0 deletions server/api/v1/game/[id].get.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useKind } from '#ving/record/utils.mjs';
import { describeParams } from '#ving/utils/rest.mjs';
import {defineEventHandler, getRouterParams} from 'h3';
export default defineEventHandler(async (event) => {
const games = await useKind('Game');
const { id } = getRouterParams(event);
const game = await games.findOrDie(id);
return game.describe(describeParams(event));
});
12 changes: 12 additions & 0 deletions server/api/v1/game/[id].put.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useKind } from '#ving/record/utils.mjs';
import { describeParams, obtainSession, getBody } from '#ving/utils/rest.mjs';
import {defineEventHandler, getRouterParams} from 'h3';
export default defineEventHandler(async (event) => {
const games = await useKind('Game');
const { id } = getRouterParams(event);
const game = await games.findOrDie(id);
const session = obtainSession(event);
game.canEdit(session);
await game.updateAndVerify(await getBody(event), session);
return game.describe(describeParams(event, session));
});
10 changes: 10 additions & 0 deletions server/api/v1/game/[id]/user.get.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useKind } from '#ving/record/utils.mjs';
import { describeParams } from '#ving/utils/rest.mjs';
import {defineEventHandler, getRouterParams} from 'h3';
export default defineEventHandler(async (event) => {
const games = await useKind('Game');
const { id } = getRouterParams(event);
const game = await games.findOrDie(id);
const user = await game.parent('user');
return await user.describe(describeParams(event));
});
7 changes: 7 additions & 0 deletions server/api/v1/game/index.get.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { useKind } from '#ving/record/utils.mjs';
import { describeListParams, describeListWhere } from '#ving/utils/rest.mjs';
import {defineEventHandler} from 'h3';
export default defineEventHandler(async (event) => {
const games = await useKind('Game');
return await games.describeList(describeListParams(event), describeListWhere(event, games.describeListFilter()));
});
9 changes: 9 additions & 0 deletions server/api/v1/game/index.post.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useKind } from '#ving/record/utils.mjs';
import { describeParams, getBody, obtainSessionIfRole } from '#ving/utils/rest.mjs';
import {defineEventHandler} from 'h3';
export default defineEventHandler(async (event) => {
const games = await useKind('Game');
const session = obtainSessionIfRole(event, 'verifiedEmail');
const game = await games.createAndVerify(await getBody(event), session);
return game.describe(describeParams(event, session));
});
7 changes: 7 additions & 0 deletions server/api/v1/game/options.get.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { useKind } from '#ving/record/utils.mjs';
import { describeParams } from '#ving/utils/rest.mjs';
import {defineEventHandler} from 'h3';
export default defineEventHandler(async (event) => {
const games = await useKind('Game');
return games.mint().propOptions(describeParams(event), true);
});
51 changes: 51 additions & 0 deletions ving/drizzle/migrations/0000_goofy_moonstone.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
CREATE TABLE `apikeys` (
`id` varchar(36) NOT NULL DEFAULT 'uuid-will-be-generated',
`createdAt` timestamp NOT NULL DEFAULT (now()),
`updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,
`name` varchar(60) NOT NULL DEFAULT '',
`url` text NOT NULL,
`reason` text NOT NULL,
`privateKey` varchar(39) NOT NULL DEFAULT '',
`userId` varchar(36) NOT NULL,
CONSTRAINT `apikeys_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `s3files` (
`id` varchar(36) NOT NULL DEFAULT 'uuid-will-be-generated',
`createdAt` timestamp NOT NULL DEFAULT (now()),
`updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,
`filename` varchar(256) NOT NULL DEFAULT '',
`extension` varchar(10) NOT NULL DEFAULT '',
`contentType` varchar(256) NOT NULL DEFAULT '',
`s3folder` varchar(256) NOT NULL DEFAULT '',
`sizeInBytes` int NOT NULL DEFAULT 0,
`metadata` json NOT NULL DEFAULT ('{}'),
`status` enum('pending','ready','postProcessingFailed','verifyFailed') NOT NULL DEFAULT 'pending',
`icon` enum('pending','thumbnail','extension','self') NOT NULL DEFAULT 'pending',
`userId` varchar(36) NOT NULL,
CONSTRAINT `s3files_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `users` (
`id` varchar(36) NOT NULL DEFAULT 'uuid-will-be-generated',
`createdAt` timestamp NOT NULL DEFAULT (now()),
`updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,
`username` varchar(60) NOT NULL DEFAULT '',
`email` varchar(256) NOT NULL DEFAULT '',
`realName` varchar(60) NOT NULL DEFAULT '',
`password` varchar(256) NOT NULL DEFAULT 'no-password-specified',
`passwordType` enum('bcrypt') NOT NULL DEFAULT 'bcrypt',
`useAsDisplayName` enum('username','email','realName') NOT NULL DEFAULT 'username',
`verifiedEmail` boolean NOT NULL DEFAULT false,
`admin` boolean NOT NULL DEFAULT false,
`developer` boolean NOT NULL DEFAULT false,
`avatarType` enum('robot','uploaded') NOT NULL DEFAULT 'robot',
`avatarId` varchar(36) DEFAULT null,
CONSTRAINT `users_id` PRIMARY KEY(`id`),
CONSTRAINT `usernameIndex` UNIQUE(`username`),
CONSTRAINT `emailIndex` UNIQUE(`email`)
);
--> statement-breakpoint
ALTER TABLE `apikeys` ADD CONSTRAINT `apikeys_user_90ada4_fk` FOREIGN KEY (`userId`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint
ALTER TABLE `s3files` ADD CONSTRAINT `s3files_user_40cb3d4d_fk` FOREIGN KEY (`userId`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint
ALTER TABLE `users` ADD CONSTRAINT `users_avatar_39d62890_fk` FOREIGN KEY (`avatarId`) REFERENCES `s3files`(`id`) ON DELETE set null ON UPDATE no action;
15 changes: 15 additions & 0 deletions ving/drizzle/migrations/0001_thick_nico_minoru.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
CREATE TABLE `games` (
`id` varchar(36) NOT NULL DEFAULT 'uuid-will-be-generated',
`createdAt` timestamp NOT NULL DEFAULT (now()),
`updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,
`name` varchar(60) NOT NULL DEFAULT '',
`notes` mediumtext NOT NULL,
`fieldSchema` json NOT NULL DEFAULT ('{}'),
`fields` json NOT NULL DEFAULT ('{}'),
`userId` varchar(36) NOT NULL,
`archived` boolean NOT NULL DEFAULT false,
`collection` varchar(60) NOT NULL DEFAULT '',
CONSTRAINT `games_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
ALTER TABLE `games` ADD CONSTRAINT `games_user_5adf0289_fk` FOREIGN KEY (`userId`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE cascade;
Loading

0 comments on commit 085cedc

Please sign in to comment.