Skip to content

Commit

Permalink
Only pass basic information to the client when getting post list
Browse files Browse the repository at this point in the history
  • Loading branch information
floscher committed Jun 21, 2024
1 parent 3a7bfa1 commit d04e1ed
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 50 deletions.
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
},
"devDependencies": {
"@intlify/unplugin-vue-i18n": "^4.0.0",
"@material/web": "^1.5.0",
"@rushstack/eslint-patch": "^1.10.3",
"@tsconfig/node20": "^20.1.4",
"@types/bootstrap": "^5.2.10",
Expand Down
36 changes: 10 additions & 26 deletions client/src/components/DisplayTags.vue
Original file line number Diff line number Diff line change
@@ -1,40 +1,24 @@
<template>
<div class="badge-container">
<div
v-for="tag in [...tags].sort((a, b) => a.name.localeCompare(b.name))"
:key="tag.id"
class="badge me-1"
@click="searchWord(tag.name)"
>
{{ tag.name }}
</div>
</div>
<md-chip-set>
<md-suggestion-chip v-for="tag in sortedTags()" v-bind:key="tag" :label="tag" @click="searchWord(tag)"></md-suggestion-chip>
</md-chip-set>
</template>

<style lang="scss">
.badge-container {
padding: 0;
margin: 0;
.badge {
background-color: $badge-background-color;
color: $badge-text-color;
cursor: pointer;
}
}
</style>

<script setup lang="ts">
import type { Tag } from "@fumix/fu-blog-common";
import type { PropType } from "vue";
import { useRouter } from "vue-router";
import "@material/web/chips/chip-set.js";
import "@material/web/chips/filter-chip.js";
import "@material/web/chips/suggestion-chip.js";
const router = useRouter();
const props = defineProps({ tags: { type: Array as PropType<Tag[]>, required: true } });
const props = defineProps({ tags: { type: Array as PropType<string[]>, required: true } });
const sortedTags = () => [...props.tags].sort((a, b) => a.localeCompare(b));
const searchWord = (word: string): void => {
if (word) {
router.push(`/posts/?search=${word}&operator=and`);
router.push(`/posts/?search=${encodeURIComponent(word)}&operator=and`);
}
};
</script>
20 changes: 10 additions & 10 deletions client/src/components/PostPreview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
<div class="card flex-md-row mb-4 box-shadow h-md-250">
<div v-bind:class="{ 'card-body': true, draft: post.draft }">
<div class="clearfix mb-4">
<button v-if="props.userPermissions?.canDeletePost" class="btn btn-sm btn-danger float-end" @click="$emit('deletePost', post)">
<button v-if="props.post.permissions.canDelete" class="btn btn-sm btn-danger float-end" @click="$emit('deletePost', post)">
<fa-icon :icon="faTrash" />
{{ t("app.base.delete") }}
</button>
<button
v-if="props.userPermissions?.canEditPost"
v-if="props.post.permissions.canEdit"
class="btn btn-sm btn-secondary float-end mx-2"
@click="$emit('changePost', post)"
>
Expand All @@ -24,16 +24,16 @@

<p class="card-text my-4">{{ post.description }}</p>

<div v-if="post.createdAt" class="mb-1 text-muted creator">
<div v-if="post.created" class="mb-1 text-muted creator">
<fa-icon :icon="faClock" />
{{ $luxonDateTime.fromISO(post.createdAt.toString(), { locale: locale }).toRelativeCalendar() }}
<i v-if="post.createdBy">{{ post.createdBy.fullName }}</i>
{{ $luxonDateTime.fromISO(post.created.at.toString(), { locale: locale }).toRelativeCalendar() }}
<i v-if="post.created.by">{{ post.created.by }}</i>
</div>

<div v-if="post.updatedBy && post.updatedAt" class="mb-1 text-muted editor">
<div v-if="post.updated" class="mb-1 text-muted editor">
<fa-icon :icon="faEdit" />
{{ $luxonDateTime.fromISO(post.updatedAt.toString(), { locale: locale }).toRelativeCalendar() }}
<i>{{ post.updatedBy.fullName }}</i>
{{ $luxonDateTime.fromISO(post.updated.at.toString(), { locale: locale }).toRelativeCalendar() }}
<i v-if="post.updated.by">{{ post.updated.by }}</i>
</div>

<display-tags v-if="post.tags" :tags="post.tags"></display-tags>
Expand Down Expand Up @@ -71,7 +71,7 @@
import DisplayTags from "@client/components/DisplayTags.vue";
import { faClock } from "@fortawesome/free-regular-svg-icons";
import { faBookReader, faEdit, faTrash } from "@fortawesome/free-solid-svg-icons";
import type { Post, UserRolePermissionsType } from "@fumix/fu-blog-common";
import type { Post, PublicPost, UserRolePermissionsType } from "@fumix/fu-blog-common";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useRouter } from "vue-router";
Expand All @@ -82,7 +82,7 @@ const locale = useI18n().locale.value;
const props = defineProps({
post: {
type: Object as PropType<Post>,
type: Object as PropType<PublicPost>,
required: true,
},
userPermissions: {
Expand Down
4 changes: 2 additions & 2 deletions client/src/util/api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {
JsonMimeType,
LoggedInUserInfo,
NewPostRequestDto,
Post,
PublicPost,
SupportedImageMimeType,
} from "@fumix/fu-blog-common";
import { HttpHeader, imageBytesToDataUrl } from "@fumix/fu-blog-common";
Expand Down Expand Up @@ -141,7 +141,7 @@ export class PostEndpoints {
}

static async findPosts(pageIndex: number, itemsPerPage = 12, search: string | undefined = undefined, operator: "and" | "or" = "and") {
return callServer<void, JsonMimeType, { data: [Post[], number | null] }>(
return callServer<void, JsonMimeType, { data: [PublicPost[], number | null] }>(
`/api/posts/page/${pageIndex}/count/${itemsPerPage}${search ? `/search/${encodeURIComponent(search)}/operator/${operator}` : ""}###`,
"GET",
"application/json",
Expand Down
2 changes: 1 addition & 1 deletion client/src/views/PostView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<i>{{ post.updatedBy.fullName }}</i>
</div>
<display-tags v-if="post.tags" :tags="post.tags"></display-tags>
<display-tags v-if="post?.tags" :tags="post?.tags?.map((it) => it.name) ?? []"></display-tags>
<div v-html="post.sanitizedHtml" class="mt-4"></div>
</div>
Expand Down
17 changes: 9 additions & 8 deletions client/src/views/PostsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ import WordCloud from "@client/components/WordCloud.vue";
import { PostEndpoints } from "@client/util/api-client";
import { faSadTear } from "@fortawesome/free-regular-svg-icons";
import { faAdd } from "@fortawesome/free-solid-svg-icons";
import { asSearchOperator, type ConfirmDialogData, type Post, type UserRolePermissionsType } from "@fumix/fu-blog-common";
import type { ConfirmDialogData, PublicPost, UserRolePermissionsType } from "@fumix/fu-blog-common";
import { asSearchOperator } from "@fumix/fu-blog-common";
import type { PropType } from "vue";
import { onMounted, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
Expand All @@ -88,10 +89,10 @@ const route = useRoute();
const router = useRouter();
const itemsPerPage = 12;
const loading = ref(true);
const posts = ref<Post[]>([]);
const posts = ref<PublicPost[]>([]);
const showDialog = ref<boolean>(false);
const dialogData = ref<ConfirmDialogData | null>(null);
const currentPost = ref<Post | null>(null);
const currentPost = ref<PublicPost | null>(null);
const totalPages = ref<number>(1);
const { t } = useI18n();
Expand Down Expand Up @@ -142,7 +143,7 @@ onMounted(() => {
loadPostsWithPagination(1, searchValue, operator);
});

const deletePost = async (post: Post) => {
const deletePost = async (post: PublicPost) => {
try {
const res = await fetch(`/api/posts/delete/${post.id}`);
await res.json();
Expand All @@ -162,8 +163,8 @@ const searchWord = (event: any) => {
goTo(`/posts/?search=${event[0]}&operator=and`);
};

const showConfirm = (post: Post) => {
currentPost.value = post as Post;
const showConfirm = (post: PublicPost) => {
currentPost.value = post as PublicPost;
dialogData.value = {
title: t("posts.confirm.title"),
message: t("posts.confirm.message", { post: currentPost.value.title }),
Expand All @@ -176,12 +177,12 @@ const canceled = () => {
};

const confirmed = () => {
deletePost(currentPost.value as Post);
deletePost(currentPost.value as PublicPost);
currentPost.value = null;
showDialog.value = false;
};

const changePost = (post: Post) => {
const changePost = (post: PublicPost) => {
goTo(`/posts/post/${post.id}/edit`);
};
</script>
2 changes: 2 additions & 0 deletions common/src/entity/UserRole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export const UserRoles = {
),
} as const;

export const DEFAULT_ROLE = new UserRolePermissions("Permissions of the logged out user (basically no permissions at all)", {});

export function permissionsForUser(user: User): UserRolePermissionsType {
return mergePermissions(user.roles.map((it) => UserRoles[it]));
}
Expand Down
1 change: 1 addition & 0 deletions common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export * from "./entity/UserRole.js";
export * from "./entity/permission/UserRolePermissions.js";
export * from "./markdown-converter-common.js";
export * from "./types/logged-in-user-info.js";
export * from "./types/public-post.js";
export * from "./types/search-operator.js";
export * from "./util/base64.js";
export * from "./util/cookie-header-helpers.js";
Expand Down
42 changes: 42 additions & 0 deletions common/src/types/public-post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Post } from "@common/entity/Post.js";
import { LoggedInUserInfo } from "@common/types/logged-in-user-info.js";

export type PublicPost = {
id: number | undefined;
title: string;
description: string;
created:
| {
at: Date;
by: string | undefined;
}
| undefined;
updated:
| {
at: Date;
by: string | undefined;
}
| undefined;
draft: boolean;
tags: string[];
permissions: {
canEdit: boolean;
canDelete: boolean;
};
};

export function createPublicPostFromPostEntity(loggedInUser: LoggedInUserInfo | undefined, post: Post): PublicPost {
return {
id: post.id,
title: post.title,
description: post.description,
tags: post.tags?.map((it) => it.name) ?? [],
created: post.createdAt ? { at: post.createdAt, by: post.createdBy?.fullName } : undefined,
updated: post.updatedAt ? { at: post.updatedAt, by: post.updatedBy?.fullName } : undefined,
draft: post.draft,
permissions: {
canEdit: loggedInUser?.permissions?.canEditPost === true || loggedInUser?.user?.id === post.createdBy?.id,
canDelete: loggedInUser?.permissions?.canDeletePost === true || loggedInUser?.user?.id === post.createdBy?.id,
},
};
}
60 changes: 60 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 12 additions & 3 deletions server/src/routes/posts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { DraftResponseDto, EditPostRequestDto, LoggedInUserInfo, NewPostRequestDto, PostRequestDto } from "@fumix/fu-blog-common";
import {
DraftResponseDto,
EditPostRequestDto,
LoggedInUserInfo,
NewPostRequestDto,
PostRequestDto,
createPublicPostFromPostEntity,
} from "@fumix/fu-blog-common";
import logger from "@server/logger.js";
import express, { NextFunction, Request, Response, Router } from "express";
import { In } from "typeorm";
Expand Down Expand Up @@ -64,7 +71,9 @@ router.get(
where: { id: In(idArray) },
relations: ["createdBy", "updatedBy", "tags"],
})
.then((result) => res.status(200).json({ data: [result, count[0]] }));
.then((result) =>
res.status(200).json({ data: [result.map((it) => createPublicPostFromPostEntity(loggedInUser, it)), count[0]] }),
);
})
.catch((err) => next(err));
},
Expand Down Expand Up @@ -101,7 +110,7 @@ router.get("/page/:page([0-9]+)/count/:count([0-9]+)/", authMiddleware, async (r
take: itemsPerPage,
relations: ["createdBy", "updatedBy", "tags"],
})
.then((result) => res.status(200).json({ data: result }))
.then(([posts, count]) => res.status(200).json({ data: [posts.map((it) => createPublicPostFromPostEntity(loggedInUser, it)), count] }))
.catch((error) => {
next(error);
});
Expand Down

0 comments on commit d04e1ed

Please sign in to comment.