Skip to content

Commit

Permalink
Add delete comment API (#115)
Browse files Browse the repository at this point in the history
* Fix user comment API where a post API may be null after being deleted by
a user

* Rename the post/comment counter to be more accurate

* Fix profile tab's synchronization issue

* Add api to delete comments by comment slug ID

* Delete all comments automatically when a post is deleted

* Removed the unnecessary SQL null check from the user comment fetch API

* Remove unused import

* Remove debug line
  • Loading branch information
e32wong authored Dec 4, 2024
1 parent 96d00b0 commit c55ee85
Show file tree
Hide file tree
Showing 25 changed files with 416 additions and 94 deletions.
92 changes: 85 additions & 7 deletions services/agora/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,19 @@ export interface ApiV1CommentCreatePostRequest {
*/
'commentBody': string;
}
/**
*
* @export
* @interface ApiV1CommentDeletePostRequest
*/
export interface ApiV1CommentDeletePostRequest {
/**
*
* @type {string}
* @memberof ApiV1CommentDeletePostRequest
*/
'commentSlugId': string;
}
/**
*
* @export
Expand Down Expand Up @@ -687,13 +700,7 @@ export interface ApiV1UserFetchUserProfilePost200Response {
* @type {number}
* @memberof ApiV1UserFetchUserProfilePost200Response
*/
'commentCount': number;
/**
*
* @type {number}
* @memberof ApiV1UserFetchUserProfilePost200Response
*/
'postCount': number;
'activePostCount': number;
/**
*
* @type {string}
Expand Down Expand Up @@ -965,6 +972,45 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati
options: localVarRequestOptions,
};
},
/**
*
* @param {ApiV1CommentDeletePostRequest} apiV1CommentDeletePostRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
apiV1CommentDeletePost: async (apiV1CommentDeletePostRequest: ApiV1CommentDeletePostRequest, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'apiV1CommentDeletePostRequest' is not null or undefined
assertParamExists('apiV1CommentDeletePost', 'apiV1CommentDeletePostRequest', apiV1CommentDeletePostRequest)
const localVarPath = `/api/v1/comment/delete`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}

const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;

// authentication BearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)



localVarHeaderParameter['Content-Type'] = 'application/json';

setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(apiV1CommentDeletePostRequest, localVarRequestOptions, configuration)

return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {ApiV1CommentFetchCommentsByPostSlugIdPostRequest} apiV1CommentFetchCommentsByPostSlugIdPostRequest
Expand Down Expand Up @@ -1489,6 +1535,18 @@ export const DefaultApiFp = function(configuration?: Configuration) {
const localVarOperationServerBasePath = operationServerMap['DefaultApi.apiV1CommentCreatePost']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
*
* @param {ApiV1CommentDeletePostRequest} apiV1CommentDeletePostRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiV1CommentDeletePost(apiV1CommentDeletePostRequest: ApiV1CommentDeletePostRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.apiV1CommentDeletePost(apiV1CommentDeletePostRequest, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['DefaultApi.apiV1CommentDeletePost']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
*
* @param {ApiV1CommentFetchCommentsByPostSlugIdPostRequest} apiV1CommentFetchCommentsByPostSlugIdPostRequest
Expand Down Expand Up @@ -1685,6 +1743,15 @@ export const DefaultApiFactory = function (configuration?: Configuration, basePa
apiV1CommentCreatePost(apiV1CommentCreatePostRequest: ApiV1CommentCreatePostRequest, options?: any): AxiosPromise<ApiV1CommentCreatePost200Response> {
return localVarFp.apiV1CommentCreatePost(apiV1CommentCreatePostRequest, options).then((request) => request(axios, basePath));
},
/**
*
* @param {ApiV1CommentDeletePostRequest} apiV1CommentDeletePostRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
apiV1CommentDeletePost(apiV1CommentDeletePostRequest: ApiV1CommentDeletePostRequest, options?: any): AxiosPromise<void> {
return localVarFp.apiV1CommentDeletePost(apiV1CommentDeletePostRequest, options).then((request) => request(axios, basePath));
},
/**
*
* @param {ApiV1CommentFetchCommentsByPostSlugIdPostRequest} apiV1CommentFetchCommentsByPostSlugIdPostRequest
Expand Down Expand Up @@ -1855,6 +1922,17 @@ export class DefaultApi extends BaseAPI {
return DefaultApiFp(this.configuration).apiV1CommentCreatePost(apiV1CommentCreatePostRequest, options).then((request) => request(this.axios, this.basePath));
}

/**
*
* @param {ApiV1CommentDeletePostRequest} apiV1CommentDeletePostRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof DefaultApi
*/
public apiV1CommentDeletePost(apiV1CommentDeletePostRequest: ApiV1CommentDeletePostRequest, options?: RawAxiosRequestConfig) {
return DefaultApiFp(this.configuration).apiV1CommentDeletePost(apiV1CommentDeletePostRequest, options).then((request) => request(this.axios, this.basePath));
}

/**
*
* @param {ApiV1CommentFetchCommentsByPostSlugIdPostRequest} apiV1CommentFetchCommentsByPostSlugIdPostRequest
Expand Down
6 changes: 5 additions & 1 deletion services/agora/src/components/post/PostDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@

<div v-if="!compactMode" ref="commentSectionRef">
<CommentSection :key="commentCountOffset" :post-slug-id="extendedPostData.metadata.postSlugId"
:initial-comment-slug-id="commentSlugId" />
:initial-comment-slug-id="commentSlugId" @deleted="decrementCommentCount()" />
</div>
</div>
</ZKHoverEffect>
Expand Down Expand Up @@ -125,6 +125,10 @@ onMounted(() => {
}
});

function decrementCommentCount() {
commentCountOffset.value -= 1;
}

function scrollToCommentSection() {
console.log(commentSectionRef.value);
commentSectionRef.value?.scrollIntoView({
Expand Down
13 changes: 12 additions & 1 deletion services/agora/src/components/post/views/CommentActionBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import { type CommentItem, type VotingAction } from "src/shared/types/zod";
import { useAuthenticationStore } from "src/stores/authentication";
import { useDialog } from "src/utils/ui/dialog";
const emit = defineEmits(["deleted"])
const props = defineProps<{
commentItem: CommentItem;
postSlugId: string;
Expand Down Expand Up @@ -110,7 +112,16 @@ function shareButtonClicked() {
}
function optionButtonClicked() {
bottomSheet.showCommentOptionSelector();
const deleteCommentCallback = (deleted: boolean) => {
if (deleted) {
emit("deleted");
}
}
bottomSheet.showCommentOptionSelector(
props.commentItem.commentSlugId,
props.commentItem.userName,
deleteCommentCallback);
}
async function castPersonalVote(
Expand Down
8 changes: 7 additions & 1 deletion services/agora/src/components/post/views/CommentSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<div v-for="commentItem in commentItems" :id="commentItem.commentSlugId" :key="commentItem.commentSlugId">
<CommentSingle :comment-item="commentItem" :post-slug-id="postSlugId"
:highlight="initialCommentSlugId == commentItem.commentSlugId"
:comment-slug-id-liked-map="commentSlugIdLikedMap" />
:comment-slug-id-liked-map="commentSlugIdLikedMap" @deleted="deletedComment()" />

<Divider :style="{ width: '100%' }" />
</div>
Expand Down Expand Up @@ -42,6 +42,8 @@ import { useBackendVoteApi } from "src/utils/api/vote";
import { useAuthenticationStore } from "src/stores/authentication";
import { type CommentItem } from "src/shared/types/zod";
const emit = defineEmits(["deleted"])
const props = defineProps<{
postSlugId: string;
initialCommentSlugId: string;
Expand All @@ -64,6 +66,10 @@ onMounted(() => {
fetchPersonalLikes();
});
function deletedComment() {
emit("deleted");
}
async function fetchPersonalLikes() {
if (isAuthenticated.value) {
commentSlugIdLikedMap.value.clear();
Expand Down
23 changes: 21 additions & 2 deletions services/agora/src/components/post/views/CommentSingle.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<!-- eslint-disable vue/no-v-html -->
<template>
<div>
<div class="contentLayout">
<div v-if="deleted" class="deletedMessage">
Deleted
</div>
<div v-if="!deleted" class="contentLayout">
<div class="metadata">
<UserAvatar :user-name="commentItem.userName" :size="40" class="avatarIcon" />

Expand All @@ -24,7 +27,7 @@

<div class="actionBarPaddings">
<CommentActionBar :comment-item="commentItem" :post-slug-id="postSlugId"
:comment-slug-id-liked-map="commentSlugIdLikedMap" />
:comment-slug-id-liked-map="commentSlugIdLikedMap" @deleted="deletedComment()" />
</div>
</div>
</div>
Expand All @@ -36,13 +39,24 @@ import CommentActionBar from "./CommentActionBar.vue";
import UserAvatar from "src/components/account/UserAvatar.vue";
import { formatTimeAgo } from "@vueuse/core";
import type { CommentItem } from "src/shared/types/zod";
import { ref } from "vue";

const emit = defineEmits(["deleted"])

defineProps<{
commentItem: CommentItem;
postSlugId: string;
highlight: boolean;
commentSlugIdLikedMap: Map<string, "like" | "dislike">;
}>();

const deleted = ref(false);

function deletedComment() {
deleted.value = true
emit("deleted");
}

</script>

<style scoped lang="scss">
Expand Down Expand Up @@ -80,4 +94,9 @@ defineProps<{
display: flex;
flex-direction: column;
}

.deletedMessage {
display: flex;
justify-content: center;
}
</style>
13 changes: 10 additions & 3 deletions services/agora/src/components/profile/UserCommentList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,25 @@
import { useElementVisibility, useTimeAgo } from "@vueuse/core";
import { useUserStore } from "src/stores/user";
import ZKHoverEffect from "../ui-library/ZKHoverEffect.vue";
import { ref, watch } from "vue";
import { onMounted, ref, watch } from "vue";

const { profileData, loadMoreUserComments } = useUserStore();
const { profileData, loadMoreUserComments, loadUserProfile } = useUserStore();

const endOfFeed = ref(false);
let isExpandingPosts = false;

const bottomOfPostDiv = ref(null);
const targetIsVisible = useElementVisibility(bottomOfPostDiv);

let isLoaded = false;

onMounted(async () => {
await loadUserProfile();
isLoaded = true;
});

watch(targetIsVisible, async () => {
if (targetIsVisible.value && !isExpandingPosts && !endOfFeed.value) {
if (targetIsVisible.value && !isExpandingPosts && !endOfFeed.value && isLoaded) {
isExpandingPosts = true;

const response = await loadMoreUserComments();
Expand Down
13 changes: 10 additions & 3 deletions services/agora/src/components/profile/UserPostList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
import { useElementVisibility } from "@vueuse/core";
import PostDetails from "src/components/post/PostDetails.vue";
import { useUserStore } from "src/stores/user";
import { ref, watch } from "vue";
import { onMounted, ref, watch } from "vue";
import { useRouter } from "vue-router";
const { loadMoreUserPosts, profileData } = useUserStore();
const { loadUserProfile, loadMoreUserPosts, profileData } = useUserStore();
const router = useRouter();
Expand All @@ -32,8 +32,15 @@ let isExpandingPosts = false;
const bottomOfPostDiv = ref(null);
const targetIsVisible = useElementVisibility(bottomOfPostDiv);
let isLoaded = false;
onMounted(async () => {
await loadUserProfile();
isLoaded = true;
});
watch(targetIsVisible, async () => {
if (targetIsVisible.value && !isExpandingPosts && !endOfFeed.value) {
if (targetIsVisible.value && !isExpandingPosts && !endOfFeed.value && isLoaded) {
isExpandingPosts = true;
const response = await loadMoreUserPosts();
Expand Down
11 changes: 2 additions & 9 deletions services/agora/src/pages/user-profile/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
</div>

<div class="profileMetadataBar">
<div>{{ profileData.postCount }} conservations <span class="dotPadding">•</span></div>
<div>{{ profileData.commentCount }} opinions <span class="dotPadding">•</span></div>
<div>{{ profileData.activePostCount }} conservations <span class="dotPadding">•</span></div>
<div>{{ getDateString(new Date(profileData.createdAt)) }}</div>
</div>

Expand All @@ -37,7 +36,7 @@ import Tab from "primevue/tab";
import TabList from "primevue/tablist";
import UserAvatar from "src/components/account/UserAvatar.vue";
import { useUserStore } from "src/stores/user";
import { onMounted, ref, watch } from "vue";
import { ref, watch } from "vue";
import { useRoute } from "vue-router";
import { getDateString } from "src/utils/common";
Expand All @@ -49,12 +48,6 @@ const route = useRoute();
applyCurrentTab();
const { loadUserProfile } = useUserStore();
onMounted(() => {
loadUserProfile();
});
watch(route, () => {
applyCurrentTab();
});
Expand Down
8 changes: 6 additions & 2 deletions services/agora/src/shared/types/dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,7 @@ export class Dto {
chosenOption: zodVotingAction
}).strict();
static fetchUserProfileResponse = z.object({
commentCount: z.number().gte(0),
postCount: z.number().gte(0),
activePostCount: z.number().gte(0),
createdAt: z.date(),
userName: zodUserName,
}).strict();
Expand All @@ -161,6 +160,11 @@ export class Dto {
postSlugId: zodSlugId
})
.strict();
static deleteCommentBySlugIdRequest = z
.object({
commentSlugId: zodSlugId
})
.strict();
}

export type AuthenticateRequestBody = z.infer<
Expand Down
Loading

0 comments on commit c55ee85

Please sign in to comment.