Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
6 changes: 6 additions & 0 deletions chart/templates/microblog-service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ spec:
value: {{ quote .Values.microblogService.deployment.container.env.REDIS_SERVICE_ADDRESS }}
- name: USER_AUTH_SERVICE_ADDRESS
value: {{ quote .Values.microblogService.deployment.container.env.USER_AUTH_SERVICE_ADDRESS }}
- name: RAG_SERVICE_ADDRESS
value: {{ quote .Values.microblogService.deployment.container.env.RAG_SERVICE_ADDRESS }}
- name: RAG_SERVICE_PORT
value: {{ quote .Values.microblogService.deployment.container.env.RAG_SERVICE_PORT }}
- name: RAG_SERVICE_ENABLED
value: {{ .Values.ragService.enabled | quote }}
- name: OPENTRACING_JAEGER_ENABLED
value: {{ quote .Values.microblogService.deployment.container.env.OPENTRACING_JAEGER_ENABLED }}
- name: JAEGER_SERVICE_NAME
Expand Down
3 changes: 3 additions & 0 deletions chart/templates/rag-service-configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ data:
USE_DATA_POISONING_DETECTION: {{ quote .Values.ragService.deployment.container.env.USE_DATA_POISONING_DETECTION }}
DATA_POISONING_DETECTION_STRATEGY: {{ quote .Values.ragService.deployment.container.env.DATA_POISONING_DETECTION_STRATEGY }}
LABEL_CONSISTENCY_DETECTION_DECISION_VARIANT: {{ quote .Values.ragService.deployment.container.env.LABEL_CONSISTENCY_DETECTION_DECISION_VARIANT }}
{{- with .Values.ragService.deployment.container.env.LANGDOCK_API_KEY }}
LANGDOCK_API_KEY: {{ quote . }}
{{- end }}
{{- end }}
3 changes: 3 additions & 0 deletions chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,9 @@ microblogService:
OPENTRACING_JAEGER_ENABLED: false
REDIS_SERVICE_ADDRESS: unguard-redis
USER_AUTH_SERVICE_ADDRESS: unguard-user-auth-service
RAG_SERVICE_ENABLED: "{{ .Values.ragService.enabled }}"
RAG_SERVICE_ADDRESS: unguard-rag-service
RAG_SERVICE_PORT: 8000

# Status Service
statusService:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NextResponse } from 'next/server';

import { addSpamPredictionUserRatingDownvote } from '@/services/api/SpamPredictionVotesService';

/**
* @swagger
* /ui/api/post/{postId}/spam-prediction-user-rating/downvote:
* post:
* description: Downvote the Spam Prediction.
*/

export type PostParams = {
postId: string;
};

export async function POST(req: Request, { params }: { params: Promise<PostParams> }): Promise<NextResponse> {
const { postId } = await params;
const res = await addSpamPredictionUserRatingDownvote(postId);

return NextResponse.json(res.data, { status: res.status });
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { NextResponse } from 'next/server';

import { fetchSpamPredictionUserRating } from '@/services/api/SpamPredictionVotesService';
import { PostParams } from '@/app/api/like/[postId]/route';

/**
* @swagger
* /ui/api/post/{postId}/spam-prediction-user-rating:
* get:
* description: Get the spam prediction user ratings for a post by its ID.
*/

export async function GET(req: Request, { params }: { params: Promise<PostParams> }): Promise<NextResponse> {
const { postId } = await params;
const res = await fetchSpamPredictionUserRating(postId);

return NextResponse.json(res.data);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NextResponse } from 'next/server';

import { addSpamPredictionUserRatingUpvote } from '@/services/api/SpamPredictionVotesService';

/**
* @swagger
* /ui/api/post/{postId}/spam-prediction-user-rating/upvote:
* post:
* description: Handle spam prediction upvote action
*/

export type PostParams = {
postId: string;
};

export async function POST(req: Request, { params }: { params: Promise<PostParams> }): Promise<NextResponse> {
const { postId } = await params;
const res = await addSpamPredictionUserRatingUpvote(postId);

return NextResponse.json(res.data, { status: res.status });
}
1 change: 1 addition & 0 deletions src/frontend-nextjs/app/api/post/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { createNewPost } from '@/services/api/CreatePostService';
export async function POST(request: Request): Promise<NextResponse> {
const body = await request.json();
let header = request.headers.get('header');

if (header) {
header = Buffer.from(header, 'latin1').toString('utf-8');
}
Expand Down
1 change: 1 addition & 0 deletions src/frontend-nextjs/app/post/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ function SinglePost() {
<PostComponent
body={postData.body}
imageUrl={postData.imageUrl}
isSpamPredictedLabel={postData.isSpamPredictedLabel}
postId={postData.postId}
timestamp={postData.timestamp}
username={postData.username}
Expand Down
1 change: 1 addition & 0 deletions src/frontend-nextjs/components/SwaggerUIReact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const HideTryItOutPlugin = () => ({

function ReactSwagger({ spec }: Props) {
const specWithoutServers = { ...spec };

delete specWithoutServers.servers;

return <SwaggerUI spec={specWithoutServers} plugins={[HideAuthorizePlugin, HideTryItOutPlugin]} />;
Expand Down
8 changes: 7 additions & 1 deletion src/frontend-nextjs/components/Timeline/Post.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import { BASE_PATH } from '@/constants';
import { useCheckLogin } from '@/hooks/queries/useCheckLogin';
import { LikeButton } from '@/components/Timeline/LikeButton';
import { ErrorCard } from '@/components/ErrorCard';
import { PostSpamPrediction } from '@/components/Timeline/PostSpamPrediction';

export interface PostProps {
username: string;
timestamp: string;
body: string;
imageUrl?: string;
postId: string;
isSpamPredictedLabel?: boolean;
}

export function Post(props: PostProps) {
Expand Down Expand Up @@ -69,7 +71,11 @@ export function Post(props: PostProps) {
)}
<p>{props.body}</p>
</CardBody>
<CardFooter className='gap-3 justify-end px-3'>
<CardFooter className='gap-3 justify-between px-3'>
<div className='flex items-center'>
<PostSpamPrediction isSpamPredictedLabel={props.isSpamPredictedLabel} postId={props.postId} />
</div>

{isLoggedIn && (
<div className='flex gap-1'>
<ErrorBoundary fallbackRender={(props) => <ErrorCard message={props.error.message} />}>
Expand Down
44 changes: 44 additions & 0 deletions src/frontend-nextjs/components/Timeline/PostSpamPrediction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use client';

import React from 'react';
import { Alert } from '@heroui/react';
import { ErrorBoundary } from 'react-error-boundary';

import { SpamPredictionUserRating } from '@/components/Timeline/SpamPredictionUserRating';
import { useCheckLogin } from '@/hooks/queries/useCheckLogin';
import { ErrorCard } from '@/components/ErrorCard';

export interface PostSpamPredictionProps {
isSpamPredictedLabel?: boolean | null;
postId: string;
}

export function PostSpamPrediction(props: Readonly<PostSpamPredictionProps>) {
const { isLoggedIn } = useCheckLogin();

if (props.isSpamPredictedLabel == null) return null;

const color = props.isSpamPredictedLabel ? 'danger' : 'primary';

return (
<div key={color} className='w-full flex items-center my-3'>
<div className='w-full'>
<Alert color={color}>
<div className='flex w-full items-center justify-between gap-3'>
<div className='font-semibold'>
{props.isSpamPredictedLabel ? 'Potential Spam Detected' : 'No Spam Detected'}
</div>
{isLoggedIn && (
<ErrorBoundary fallbackRender={(props) => <ErrorCard message={props.error.message} />}>
<SpamPredictionUserRating
isSpamPredictedLabel={props.isSpamPredictedLabel}
postId={props.postId}
/>
</ErrorBoundary>
)}
</div>
</Alert>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use client';

import React from 'react';
import { BsHandThumbsDown, BsHandThumbsDownFill, BsHandThumbsUp, BsHandThumbsUpFill } from 'react-icons/bs';
import { Button, Spinner } from '@heroui/react';

import { useSpamPredictionUserRating } from '@/hooks/queries/useSpamPredictionUserRating';
import { useRateSpamPrediction } from '@/hooks/mutations/useRateSpamPrediction';

export interface SpamPredictionUserRatingProps {
isSpamPredictedLabel?: boolean | null;
postId: string;
}

export function SpamPredictionUserRating(props: Readonly<SpamPredictionUserRatingProps>) {
const { data: spamPredictionUserRatingData, isLoading } = useSpamPredictionUserRating(props.postId);
const { handleSpamPredictionUpvote, handleSpamPredictionDownvote } = useRateSpamPrediction(props.postId);

if (isLoading) {
return <Spinner />;
}

return (
<div>
<Button
className='bg-transparent text-default-600 px-2 min-w-0 gap-1'
name='upvoteSpamRating'
onPress={() => handleSpamPredictionUpvote()}
>
<p>{spamPredictionUserRatingData?.spamPredictionUserUpvotes}</p>
{spamPredictionUserRatingData?.isUpvotedByUser ? <BsHandThumbsUpFill /> : <BsHandThumbsUp />}
</Button>

<Button
className='bg-transparent text-default-600 px-2 min-w-0 gap-1'
name='downvoteSpamRating'
onPress={() => handleSpamPredictionDownvote()}
>
<p>{spamPredictionUserRatingData?.spamPredictionUserDownvotes}</p>
{spamPredictionUserRatingData?.isDownvotedByUser ? <BsHandThumbsDownFill /> : <BsHandThumbsDown />}
</Button>
</div>
);
}
4 changes: 2 additions & 2 deletions src/frontend-nextjs/components/Timeline/Timeline.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
'use client';
import { Card, Spacer, Spinner } from '@heroui/react';

import { Post } from '@/components/Timeline/Post';
import { PostProps } from '@/components/Timeline/Post';
import { Post, PostProps } from '@/components/Timeline/Post';

interface TimelineProps {
posts: PostProps[] | undefined;
Expand All @@ -28,6 +27,7 @@ export function Timeline({ posts, isLoading }: TimelineProps) {
<Post
body={post.body}
imageUrl={post.imageUrl}
isSpamPredictedLabel={post.isSpamPredictedLabel}
postId={post.postId}
timestamp={post.timestamp}
username={post.username}
Expand Down
1 change: 1 addition & 0 deletions src/frontend-nextjs/enums/queryKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ export enum QUERY_KEYS {
ad_manager = 'ad-manager',
ad_list = 'ad-list',
deployment_health = 'deployment-health',
spam_prediction_user_rating = 'spam-prediction-user-rating',
}
27 changes: 27 additions & 0 deletions src/frontend-nextjs/hooks/mutations/useRateSpamPrediction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';

import { handleSpamPredictionDownvote, handleSpamPredictionUpvote } from '@/services/SpamPredictionVotingService';
import { QUERY_KEYS } from '@/enums/queryKeys';

export function useRateSpamPrediction(postId: string) {
const queryClient = useQueryClient();

const handleUpvoteMutation = useMutation({
mutationFn: () => handleSpamPredictionUpvote(postId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.spam_prediction_user_rating, postId] });
},
});

const handleDownvoteMutation = useMutation({
mutationFn: () => handleSpamPredictionDownvote(postId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.spam_prediction_user_rating, postId] });
},
});

return {
handleSpamPredictionUpvote: handleUpvoteMutation.mutate,
handleSpamPredictionDownvote: handleDownvoteMutation.mutate,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import path from 'path';

import { useQuery } from '@tanstack/react-query';

import { QUERY_KEYS } from '@/enums/queryKeys';
import { BASE_PATH } from '@/constants';

type SpamPredictionUserRating = {
spamPredictionUserUpvotes: number;
spamPredictionUserDownvotes: boolean;
isUpvotedByUser?: boolean;
isDownvotedByUser?: boolean;
};

async function fetchSpamPredictionUserRatings(postId: string): Promise<SpamPredictionUserRating> {
const res = await fetch(path.join(BASE_PATH, `/api/post/${postId}/spam-prediction-user-rating/`));

if (!res.ok) {
throw new Error('Failed to fetch spam prediction user ratings');
}

return await res.json();
}

export function useSpamPredictionUserRating(postId: string) {
return useQuery({
queryKey: [QUERY_KEYS.spam_prediction_user_rating, postId],
queryFn: () => fetchSpamPredictionUserRatings(postId),
throwOnError: true,
});
}
17 changes: 17 additions & 0 deletions src/frontend-nextjs/services/SpamPredictionVotingService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import path from 'path';

import { BASE_PATH } from '@/constants';

export async function handleSpamPredictionDownvote(postId: string): Promise<Response> {
return await fetch(path.join(BASE_PATH, `/api/post/${postId}/spam-prediction-user-rating/downvote/`), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
}

export async function handleSpamPredictionUpvote(postId: string): Promise<Response> {
return await fetch(path.join(BASE_PATH, `/api/post/${postId}/spam-prediction-user-rating/upvote/`), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
}
47 changes: 47 additions & 0 deletions src/frontend-nextjs/services/api/SpamPredictionVotesService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { AxiosHeaders, AxiosRequestConfig } from 'axios';

import { getMicroblogApi } from '@/axios';
import { getJwtFromCookie } from '@/services/api/AuthService';

async function withJwtCookie(): Promise<AxiosRequestConfig> {
const jwt = await getJwtFromCookie();

const headers = new AxiosHeaders();

headers.set('Cookie', `jwt=${jwt}`);

return { headers };
}

export async function fetchSpamPredictionUserRating(postId: string): Promise<any> {
return await getMicroblogApi()
.get(`/spam-prediction-user-rating/${postId}`, await withJwtCookie())
.then((response) => {
return response;
})
.catch((error) => {
return error.response;
});
}

export async function addSpamPredictionUserRatingUpvote(postId: string): Promise<any> {
return await getMicroblogApi()
.post(`/spam-prediction-user-rating/${postId}/upvote/`, {}, await withJwtCookie())
.then((response) => {
return response;
})
.catch((error) => {
return error.response;
});
}

export async function addSpamPredictionUserRatingDownvote(postId: string): Promise<any> {
return await getMicroblogApi()
.post(`/spam-prediction-user-rating/${postId}/downvote/`, {}, await withJwtCookie())
.then((response) => {
return response;
})
.catch((error) => {
return error.response;
});
}
2 changes: 1 addition & 1 deletion src/microblog-service/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM gradle:6.9.1-jdk11 as builder
FROM gradle:6.9.1-jdk11 AS builder

COPY --chown=gradle:gradle . /home/gradle/src
WORKDIR /home/gradle/src
Expand Down
2 changes: 2 additions & 0 deletions src/microblog-service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,5 @@ To get more information about the JAEGER config options, see https://www.jaegert
| JAEGER_SAMPLER_TYPE | const | (optional) Set to const to get all traces |
| JAEGER_SAMPLER_PARAM | 1 | (optional) Set to 1 while sampler is const to get all traces |
| USER_AUTH_SERVICE_ADDRESS | unguard-user-auth-service | Change to hostname/IP of user-auth-service instance |
| RAG_SERVICE_ADDRESS | unguard-rag-service | Change to hostname/IP of rag-service instance |
| RAG_SERVICE_PORT | 8000 | Change to port number of rag-service instance |
Loading
Loading