Compare commits

...

2 Commits

17 changed files with 130 additions and 33 deletions

4
package-lock.json generated
View File

@ -1,11 +1,11 @@
{
"name": "traxxx-web",
"version": "0.15.0",
"version": "0.15.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "0.15.0",
"version": "0.15.1",
"dependencies": {
"@brillout/json-serializer": "^0.5.8",
"@dicebear/collection": "^7.0.5",

View File

@ -71,5 +71,5 @@
"postcss-custom-media": "^10.0.2",
"postcss-nesting": "^12.0.2"
},
"version": "0.15.0"
"version": "0.15.1"
}

View File

@ -1,14 +1,16 @@
<template>
<div v-if="is404">
<h1>404 Page Not Found</h1>
<div class="page">
<div v-if="is404">
<h1>404 Not Found</h1>
<p v-if="abortReason">{{ abortReason }}</p>
<p v-else>This page could not be found.</p>
</div>
<p v-if="abortReason">{{ abortReason }}</p>
<p v-else>This page could not be found.</p>
</div>
<div v-else>
<h1>500 Internal Error</h1>
<p>Something went wrong.</p>
<div v-else>
<h1>500 Internal Error</h1>
<p>Something went wrong.</p>
</div>
</div>
</template>
@ -25,3 +27,10 @@ defineProps({
const pageContext = inject('pageContext');
const { abortReason } = pageContext;
</script>
<style scoped>
.page {
display: flex;
justify-content: center;
}
</style>

View File

@ -16,18 +16,34 @@
:key="`stash-${stash.id}`"
>
<div class="stash">
<a
:href="`/stash/${profile.username}/${stash.slug}`"
class="stash-name nolink"
>
{{ stash.name }}
<div class="stash-header">
<a
:href="`/stash/${profile.username}/${stash.slug}`"
class="stash-name ellipsis nolink"
>
<span class="ellipsis">{{ stash.name }}</span>
<Icon
v-if="stash.primary"
icon="heart7"
class="primary"
/>
</a>
<Icon
v-if="stash.primary"
icon="heart7"
class="primary"
v-if="profile.id === user?.id && stash.public"
icon="eye"
class="public noselect"
@click="setStashPublic(stash, false)"
/>
</a>
<Icon
v-else-if="profile.id === user?.id"
icon="eye-blocked"
class="public noselect"
@click="setStashPublic(stash, true)"
/>
</div>
<div class="stash-counts">
<a
@ -52,14 +68,48 @@
</template>
<script setup>
import { inject } from 'vue';
import { ref, inject } from 'vue';
import { get, patch } from '#/src/api.js';
import events from '#/src/events.js';
const pageContext = inject('pageContext');
const profile = pageContext.pageProps.profile;
const profile = ref(pageContext.pageProps.profile);
const user = pageContext.user;
const done = ref(true);
function abbreviateNumber(number) {
return number?.toLocaleString('en-US', { notation: 'compact' }) || 0;
}
async function setStashPublic(stash, isPublic) {
if (done.value === false) {
return;
}
try {
done.value = false;
await patch(`/stashes/${stash.id}`, { public: isPublic });
profile.value = await get(`/users/${profile.value.id}`);
events.emit('feedback', {
type: isPublic ? 'success' : 'remove',
message: isPublic
? `Stash '${stash.name}' is public`
: `Stash '${stash.name}' is private`,
});
} catch (error) {
console.error(error);
events.emit('feedback', {
type: 'error',
message: 'Failed to update stash',
});
}
done.value = true;
}
</script>
<style scoped>
@ -73,11 +123,12 @@ function abbreviateNumber(number) {
.username {
margin: 0;
font-size: 1.25rem;
}
.avatar {
width: 2rem;
height: 2rem;
width: 1.5rem;
height: 1.5rem;
border-radius: .25rem;
margin-right: 1rem;
}
@ -99,11 +150,30 @@ function abbreviateNumber(number) {
}
}
.stash-name {
.stash-header {
display: flex;
padding: .5rem;
align-items: stretch;
border-bottom: solid 1px var(--shadow-weak-30);
font-weight: bold;
}
.icon.public {
display: flex;
height: auto;
padding: 0 .75rem;
fill: var(--shadow);
&:hover {
cursor: pointer;
fill: var(--shadow-strong-30);
}
}
.stash-name {
display: flex;
flex-grow: 1;
padding: .5rem;
overflow: hidden;
.icon {
margin-left: .75rem;
@ -111,6 +181,7 @@ function abbreviateNumber(number) {
.icon.primary {
fill: var(--primary);
transform: translateY(1px);
}
}

View File

@ -3,7 +3,7 @@ import { render } from 'vike/abort'; /* eslint-disable-line import/extensions */
import { fetchUser } from '#/src/users.js';
export async function onBeforeRender(pageContext) {
const profile = await fetchUser(pageContext.routeParams.username);
const profile = await fetchUser(pageContext.routeParams.username, {}, pageContext.user);
if (!profile) {
throw render(404, `Cannot find user '${pageContext.routeParams.username}'.`);

View File

@ -25,20 +25,20 @@ export function curateUser(user, assets = {}) {
}
function whereUser(builder, userId, options = {}) {
if (typeof userId === 'number') {
builder.where('users.id', userId);
}
if (typeof userId === 'string') {
if (Number.isNaN(Number(userId))) {
builder.where(knex.raw('lower(users.username)'), userId.toLowerCase());
if (options.email) {
builder.orWhere(knex.raw('lower(users.email)'), userId.toLowerCase());
}
return;
}
builder.where('users.id', Number(userId));
}
export async function fetchUser(userId, options = {}) {
export async function fetchUser(userId, options = {}, reqUser) {
const user = await knex('users')
.select(knex.raw('users.*, users_roles.abilities as role_abilities'))
.modify((builder) => whereUser(builder, userId, options))
@ -52,6 +52,11 @@ export async function fetchUser(userId, options = {}) {
const stashes = await knex('stashes')
.where('user_id', user.id)
.modify((builder) => {
if (reqUser?.id !== user.id) {
builder.where('public', true);
}
})
.leftJoin('stashes_meta', 'stashes_meta.stash_id', 'stashes.id');
if (options.raw) {

View File

@ -24,6 +24,10 @@ import {
signupApi,
} from './auth.js';
import {
fetchUserApi
} from './users.js';
import {
createStashApi,
removeStashApi,
@ -96,6 +100,7 @@ export default async function initServer() {
router.delete('/api/session', logoutApi);
// USERS
router.get('/api/users/:userId', fetchUserApi);
router.post('/api/users', signupApi);
// STASHES

7
src/web/users.js Normal file
View File

@ -0,0 +1,7 @@
import { fetchUser } from '../users.js';
export async function fetchUserApi(req, res) {
const user = await fetchUser(req.params.userId, {}, req.user);
res.send(user);
}