Added endpoint for reporting users.
This commit is contained in:
parent
3653fe697e
commit
346c5d9813
|
@ -136,4 +136,10 @@ public class UserController {
|
|||
public Page<UserResponse> getUserFollowing(@PathVariable String userId, Pageable pageable) {
|
||||
return userService.getFollowing(userId, pageable);
|
||||
}
|
||||
|
||||
@PostMapping(path = "/auth/users/{userId}/reports")
|
||||
public ResponseEntity<Void> reportUser(@PathVariable String userId, @RequestBody UserReportPayload payload) {
|
||||
userService.reportUser(userId, payload);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package nl.andrewlalis.gymboard_api.domains.auth.dao;
|
||||
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.model.UserReport;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface UserReportRepository extends JpaRepository<UserReport, Long> {
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package nl.andrewlalis.gymboard_api.domains.auth.dto;
|
||||
|
||||
public record UserReportPayload(
|
||||
String reason,
|
||||
String description
|
||||
) {}
|
|
@ -17,6 +17,7 @@ import org.springframework.http.HttpStatus;
|
|||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -43,6 +44,7 @@ public class UserService {
|
|||
private final UserFollowingRepository userFollowingRepository;
|
||||
private final UserFollowRequestRepository followRequestRepository;
|
||||
private final UserAccessService userAccessService;
|
||||
private final UserReportRepository userReportRepository;
|
||||
private final ULID ulid;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final JavaMailSender mailSender;
|
||||
|
@ -59,7 +61,7 @@ public class UserService {
|
|||
EmailResetCodeRepository emailResetCodeRepository, UserFollowingRepository userFollowingRepository,
|
||||
UserFollowRequestRepository followRequestRepository,
|
||||
UserAccessService userAccessService,
|
||||
ULID ulid,
|
||||
UserReportRepository userReportRepository, ULID ulid,
|
||||
PasswordEncoder passwordEncoder,
|
||||
JavaMailSender mailSender
|
||||
) {
|
||||
|
@ -72,6 +74,7 @@ public class UserService {
|
|||
this.userFollowingRepository = userFollowingRepository;
|
||||
this.followRequestRepository = followRequestRepository;
|
||||
this.userAccessService = userAccessService;
|
||||
this.userReportRepository = userReportRepository;
|
||||
this.ulid = ulid;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.mailSender = mailSender;
|
||||
|
@ -439,4 +442,19 @@ public class UserService {
|
|||
user1FollowedByUser2
|
||||
);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void reportUser(String userId, UserReportPayload payload) {
|
||||
User user = findByIdOrThrow(userId, userRepository);
|
||||
User reporter = null;
|
||||
if (SecurityContextHolder.getContext().getAuthentication() instanceof TokenAuthentication t) {
|
||||
reporter = findByIdOrThrow(t.user().getId(), userRepository);
|
||||
}
|
||||
userReportRepository.save(new UserReport(
|
||||
user,
|
||||
reporter,
|
||||
payload.reason(),
|
||||
payload.description()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {api} from 'src/api/main/index';
|
||||
import {AuthStoreType} from 'stores/auth-store';
|
||||
import { api } from 'src/api/main/index';
|
||||
import { AuthStoreType } from 'stores/auth-store';
|
||||
import Timeout = NodeJS.Timeout;
|
||||
import {WeightUnit} from 'src/api/main/submission';
|
||||
import { WeightUnit } from 'src/api/main/submission';
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
|
@ -15,7 +15,7 @@ export interface User {
|
|||
export enum PersonSex {
|
||||
MALE = 'MALE',
|
||||
FEMALE = 'FEMALE',
|
||||
UNKNOWN = 'UNKNOWN'
|
||||
UNKNOWN = 'UNKNOWN',
|
||||
}
|
||||
|
||||
export interface UserPersonalDetails {
|
||||
|
@ -104,13 +104,25 @@ class AuthModule {
|
|||
return response.data;
|
||||
}
|
||||
|
||||
public async getUser(userId: string, authStore: AuthStoreType): Promise<User> {
|
||||
const response = await api.get(`/auth/users/${userId}`, authStore.axiosConfig);
|
||||
public async getUser(
|
||||
userId: string,
|
||||
authStore: AuthStoreType
|
||||
): Promise<User> {
|
||||
const response = await api.get(
|
||||
`/auth/users/${userId}`,
|
||||
authStore.axiosConfig
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async isUserAccessible(userId: string, authStore: AuthStoreType): Promise<boolean> {
|
||||
const response = await api.get(`/auth/users/${userId}/access`, authStore.axiosConfig);
|
||||
public async isUserAccessible(
|
||||
userId: string,
|
||||
authStore: AuthStoreType
|
||||
): Promise<boolean> {
|
||||
const response = await api.get(
|
||||
`/auth/users/${userId}/access`,
|
||||
authStore.axiosConfig
|
||||
);
|
||||
return response.data.accessible;
|
||||
}
|
||||
|
||||
|
@ -133,48 +145,131 @@ class AuthModule {
|
|||
});
|
||||
}
|
||||
|
||||
public async getMyPersonalDetails(authStore: AuthStoreType): Promise<UserPersonalDetails> {
|
||||
const response = await api.get('/auth/me/personal-details', authStore.axiosConfig);
|
||||
public async generateEmailResetCode(
|
||||
newEmail: string,
|
||||
authStore: AuthStoreType
|
||||
) {
|
||||
await api.post(
|
||||
'/auth/me/email-reset-code',
|
||||
{ newEmail: newEmail },
|
||||
authStore.axiosConfig
|
||||
);
|
||||
}
|
||||
|
||||
public async updateMyEmail(code: string, authStore: AuthStoreType) {
|
||||
await api.post('/auth/me/email?code=' + code, null, authStore.axiosConfig);
|
||||
}
|
||||
|
||||
public async getMyPersonalDetails(
|
||||
authStore: AuthStoreType
|
||||
): Promise<UserPersonalDetails> {
|
||||
const response = await api.get(
|
||||
'/auth/me/personal-details',
|
||||
authStore.axiosConfig
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateMyPersonalDetails(authStore: AuthStoreType, newPersonalDetails: UserPersonalDetails): Promise<UserPersonalDetails> {
|
||||
const response = await api.post('/auth/me/personal-details', newPersonalDetails, authStore.axiosConfig);
|
||||
public async updateMyPersonalDetails(
|
||||
authStore: AuthStoreType,
|
||||
newPersonalDetails: UserPersonalDetails
|
||||
): Promise<UserPersonalDetails> {
|
||||
const response = await api.post(
|
||||
'/auth/me/personal-details',
|
||||
newPersonalDetails,
|
||||
authStore.axiosConfig
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getMyPreferences(authStore: AuthStoreType): Promise<UserPreferences> {
|
||||
const response = await api.get('/auth/me/preferences', authStore.axiosConfig);
|
||||
public async getMyPreferences(
|
||||
authStore: AuthStoreType
|
||||
): Promise<UserPreferences> {
|
||||
const response = await api.get(
|
||||
'/auth/me/preferences',
|
||||
authStore.axiosConfig
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateMyPreferences(authStore: AuthStoreType, newPreferences: UserPreferences): Promise<UserPreferences> {
|
||||
const response = await api.post('/auth/me/preferences', newPreferences, authStore.axiosConfig);
|
||||
public async updateMyPreferences(
|
||||
authStore: AuthStoreType,
|
||||
newPreferences: UserPreferences
|
||||
): Promise<UserPreferences> {
|
||||
const response = await api.post(
|
||||
'/auth/me/preferences',
|
||||
newPreferences,
|
||||
authStore.axiosConfig
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getFollowers(userId: string, authStore: AuthStoreType, page: number, count: number): Promise<User[]> {
|
||||
const response = await api.get(`/auth/users/${userId}/followers?page=${page}&count=${count}`, authStore.axiosConfig);
|
||||
public async getFollowers(
|
||||
userId: string,
|
||||
authStore: AuthStoreType,
|
||||
page: number,
|
||||
count: number
|
||||
): Promise<User[]> {
|
||||
const response = await api.get(
|
||||
`/auth/users/${userId}/followers?page=${page}&count=${count}`,
|
||||
authStore.axiosConfig
|
||||
);
|
||||
return response.data.content;
|
||||
}
|
||||
|
||||
public async getFollowing(userId: string, authStore: AuthStoreType, page: number, count: number): Promise<User[]> {
|
||||
const response = await api.get(`/auth/users/${userId}/following?page=${page}&count=${count}`, authStore.axiosConfig);
|
||||
public async getFollowing(
|
||||
userId: string,
|
||||
authStore: AuthStoreType,
|
||||
page: number,
|
||||
count: number
|
||||
): Promise<User[]> {
|
||||
const response = await api.get(
|
||||
`/auth/users/${userId}/following?page=${page}&count=${count}`,
|
||||
authStore.axiosConfig
|
||||
);
|
||||
return response.data.content;
|
||||
}
|
||||
|
||||
public async getRelationshipTo(userId: string, targetUserId: string, authStore: AuthStoreType): Promise<UserRelationship> {
|
||||
const response = await api.get(`/auth/users/${userId}/relationship-to/${targetUserId}`, authStore.axiosConfig);
|
||||
public async getRelationshipTo(
|
||||
userId: string,
|
||||
targetUserId: string,
|
||||
authStore: AuthStoreType
|
||||
): Promise<UserRelationship> {
|
||||
const response = await api.get(
|
||||
`/auth/users/${userId}/relationship-to/${targetUserId}`,
|
||||
authStore.axiosConfig
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async followUser(userId: string, authStore: AuthStoreType) {
|
||||
await api.post(`/auth/users/${userId}/followers`, undefined, authStore.axiosConfig);
|
||||
public async followUser(
|
||||
userId: string,
|
||||
authStore: AuthStoreType
|
||||
): Promise<string> {
|
||||
const response = await api.post(
|
||||
`/auth/users/${userId}/followers`,
|
||||
undefined,
|
||||
authStore.axiosConfig
|
||||
);
|
||||
return response.data.result;
|
||||
}
|
||||
|
||||
public async unfollowUser(userId: string, authStore: AuthStoreType) {
|
||||
await api.delete(`/auth/users/${userId}/followers`, authStore.axiosConfig);
|
||||
}
|
||||
|
||||
public async reportUser(
|
||||
userId: string,
|
||||
reason: string,
|
||||
description: string | null,
|
||||
authStore?: AuthStoreType
|
||||
) {
|
||||
await api.post(
|
||||
`/auth/users/${userId}/reports`,
|
||||
{ reason, description },
|
||||
authStore?.axiosConfig
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AuthModule;
|
||||
|
|
|
@ -83,24 +83,30 @@ A high-level overview of the submission process is as follows:
|
|||
<!-- If the user is not logged in, show a link to log in. -->
|
||||
<q-page v-if="!authStore.loggedIn">
|
||||
<div class="q-mt-lg text-center">
|
||||
<router-link :to="`/login?next=${route.fullPath}`" class="text-primary">Login or register to submit your lift</router-link>
|
||||
<router-link :to="`/login?next=${route.fullPath}`" class="text-primary"
|
||||
>Login or register to submit your lift</router-link
|
||||
>
|
||||
</div>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {onMounted, ref, Ref} from 'vue';
|
||||
import {getGymFromRoute} from 'src/router/gym-routing';
|
||||
import { onMounted, ref, Ref } from 'vue';
|
||||
import { getGymFromRoute } from 'src/router/gym-routing';
|
||||
import SlimForm from 'components/SlimForm.vue';
|
||||
import api from 'src/api/main';
|
||||
import {Gym} from 'src/api/main/gyms';
|
||||
import {Exercise} from 'src/api/main/exercises';
|
||||
import {useRoute, useRouter} from 'vue-router';
|
||||
import {showApiErrorToast, sleep} from 'src/utils';
|
||||
import {uploadVideoToCDN, VideoProcessingStatus, waitUntilVideoProcessingComplete} from 'src/api/cdn';
|
||||
import {useAuthStore} from 'stores/auth-store';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {useQuasar} from "quasar";
|
||||
import { Gym } from 'src/api/main/gyms';
|
||||
import { Exercise } from 'src/api/main/exercises';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { showApiErrorToast, sleep } from 'src/utils';
|
||||
import {
|
||||
uploadVideoToCDN,
|
||||
VideoProcessingStatus,
|
||||
waitUntilVideoProcessingComplete,
|
||||
} from 'src/api/cdn';
|
||||
import { useAuthStore } from 'stores/auth-store';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const router = useRouter();
|
||||
|
@ -167,16 +173,24 @@ async function onSubmitted() {
|
|||
// 1. Upload the video to the CDN.
|
||||
submitButtonLabel.value = i18n.t('gymPage.submitPage.submitUploading');
|
||||
await sleep(1000);
|
||||
submissionModel.value.videoFileId = await uploadVideoToCDN(selectedVideoFile.value);
|
||||
submissionModel.value.videoFileId = await uploadVideoToCDN(
|
||||
selectedVideoFile.value
|
||||
);
|
||||
|
||||
// 2. Wait for the video to be processed.
|
||||
submitButtonLabel.value = i18n.t('gymPage.submitPage.submitVideoProcessing');
|
||||
const processingStatus = await waitUntilVideoProcessingComplete(submissionModel.value.videoFileId);
|
||||
submitButtonLabel.value = i18n.t(
|
||||
'gymPage.submitPage.submitVideoProcessing'
|
||||
);
|
||||
const processingStatus = await waitUntilVideoProcessingComplete(
|
||||
submissionModel.value.videoFileId
|
||||
);
|
||||
|
||||
// 3. If successful upload, create the submission.
|
||||
if (processingStatus === VideoProcessingStatus.COMPLETED) {
|
||||
try {
|
||||
submitButtonLabel.value = i18n.t('gymPage.submitPage.submitCreatingSubmission');
|
||||
submitButtonLabel.value = i18n.t(
|
||||
'gymPage.submitPage.submitCreatingSubmission'
|
||||
);
|
||||
await sleep(1000);
|
||||
const submission = await api.gyms.submissions.createSubmission(
|
||||
gym.value,
|
||||
|
@ -191,7 +205,7 @@ async function onSubmitted() {
|
|||
quasar.notify({
|
||||
message: error.response.data.message,
|
||||
type: 'warning',
|
||||
position: 'top'
|
||||
position: 'top',
|
||||
});
|
||||
submitButtonLabel.value = i18n.t('gymPage.submitPage.submitFailed');
|
||||
await sleep(3000);
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
<template>
|
||||
<q-list separator>
|
||||
<q-item
|
||||
v-for="user in following" :key="user.id"
|
||||
:to="`/users/${user.id}`"
|
||||
>
|
||||
<q-item v-for="user in following" :key="user.id" :to="`/users/${user.id}`">
|
||||
<q-item-section>
|
||||
<q-item-label>{{ user.name }}</q-item-label>
|
||||
</q-item-section>
|
||||
|
@ -12,9 +9,9 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {User} from "src/api/main/auth";
|
||||
import {useAuthStore} from "stores/auth-store";
|
||||
import {onMounted, ref, Ref} from "vue";
|
||||
import { User } from 'src/api/main/auth';
|
||||
import { useAuthStore } from 'stores/auth-store';
|
||||
import { onMounted, ref, Ref } from 'vue';
|
||||
import api from 'src/api/main';
|
||||
|
||||
interface Props {
|
||||
|
@ -25,10 +22,13 @@ const authStore = useAuthStore();
|
|||
const following: Ref<User[]> = ref([]);
|
||||
|
||||
onMounted(async () => {
|
||||
following.value = await api.auth.getFollowing(props.user.id, authStore, 0, 10);
|
||||
following.value = await api.auth.getFollowing(
|
||||
props.user.id,
|
||||
authStore,
|
||||
0,
|
||||
10
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
Loading…
Reference in New Issue