mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-07-24 05:26:16 +00:00
Merge pull request #209 from nagisa77/eh4pzj-codex/add-whitelist-invitation-registration-mode
Implement whitelist registration mode
This commit is contained in:
@@ -9,11 +9,11 @@ import { checkToken, clearToken } from './utils/auth'
|
|||||||
import { initTheme } from './utils/theme'
|
import { initTheme } from './utils/theme'
|
||||||
|
|
||||||
// Configurable API domain and port
|
// Configurable API domain and port
|
||||||
// export const API_DOMAIN = 'http://127.0.0.1'
|
export const API_DOMAIN = 'http://127.0.0.1'
|
||||||
// export const API_PORT = 8081
|
export const API_PORT = 8081
|
||||||
|
|
||||||
export const API_DOMAIN = 'http://47.82.99.208'
|
// export const API_DOMAIN = 'http://47.82.99.208'
|
||||||
export const API_PORT = 8080
|
// export const API_PORT = 8080
|
||||||
|
|
||||||
// export const API_BASE_URL = API_PORT ? `${API_DOMAIN}:${API_PORT}` : API_DOMAIN
|
// export const API_BASE_URL = API_PORT ? `${API_DOMAIN}:${API_PORT}` : API_DOMAIN
|
||||||
export const API_BASE_URL = "";
|
export const API_BASE_URL = "";
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import SiteStatsPageView from '../views/SiteStatsPageView.vue'
|
|||||||
import PostPageView from '../views/PostPageView.vue'
|
import PostPageView from '../views/PostPageView.vue'
|
||||||
import LoginPageView from '../views/LoginPageView.vue'
|
import LoginPageView from '../views/LoginPageView.vue'
|
||||||
import SignupPageView from '../views/SignupPageView.vue'
|
import SignupPageView from '../views/SignupPageView.vue'
|
||||||
|
import SignupReasonPageView from '../views/SignupReasonPageView.vue'
|
||||||
import NewPostPageView from '../views/NewPostPageView.vue'
|
import NewPostPageView from '../views/NewPostPageView.vue'
|
||||||
import SettingsPageView from '../views/SettingsPageView.vue'
|
import SettingsPageView from '../views/SettingsPageView.vue'
|
||||||
import ProfileView from '../views/ProfileView.vue'
|
import ProfileView from '../views/ProfileView.vue'
|
||||||
@@ -52,6 +53,11 @@ const routes = [
|
|||||||
name: 'signup',
|
name: 'signup',
|
||||||
component: SignupPageView
|
component: SignupPageView
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/signup-reason',
|
||||||
|
name: 'signup-reason',
|
||||||
|
component: SignupReasonPageView
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
name: 'settings',
|
name: 'settings',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { API_BASE_URL, GOOGLE_CLIENT_ID, toast } from '../main'
|
import { API_BASE_URL, GOOGLE_CLIENT_ID, toast } from '../main'
|
||||||
import { setToken, loadCurrentUser } from './auth'
|
import { setToken, loadCurrentUser } from './auth'
|
||||||
|
|
||||||
export function googleSignIn(redirect) {
|
export function googleSignIn(redirect, reason) {
|
||||||
if (!window.google || !GOOGLE_CLIENT_ID) {
|
if (!window.google || !GOOGLE_CLIENT_ID) {
|
||||||
toast.error('Google 登录不可用')
|
toast.error('Google 登录不可用')
|
||||||
return
|
return
|
||||||
@@ -13,7 +13,7 @@ export function googleSignIn(redirect) {
|
|||||||
const res = await fetch(`${API_BASE_URL}/api/auth/google`, {
|
const res = await fetch(`${API_BASE_URL}/api/auth/google`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ idToken: credential })
|
body: JSON.stringify({ idToken: credential, reason })
|
||||||
})
|
})
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
if (res.ok && data.token) {
|
if (res.ok && data.token) {
|
||||||
|
|||||||
@@ -181,6 +181,15 @@
|
|||||||
已提交审核
|
已提交审核
|
||||||
</NotificationContainer>
|
</NotificationContainer>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="item.type === 'REGISTER_REQUEST'">
|
||||||
|
<NotificationContainer :item="item" :markRead="markRead">
|
||||||
|
{{ item.fromUser.username }} 希望注册为会员,理由是:{{ item.content }}
|
||||||
|
<template #actions v-if="authState.role === 'ADMIN'">
|
||||||
|
<button class="mark-read-button-item" @click="approve(item.fromUser.id, item.id)">同意</button>
|
||||||
|
<button class="mark-read-button-item" @click="reject(item.fromUser.id, item.id)">拒绝</button>
|
||||||
|
</template>
|
||||||
|
</NotificationContainer>
|
||||||
|
</template>
|
||||||
<template v-else-if="item.type === 'POST_REVIEWED' && item.approved">
|
<template v-else-if="item.type === 'POST_REVIEWED' && item.approved">
|
||||||
<NotificationContainer :item="item" :markRead="markRead">
|
<NotificationContainer :item="item" :markRead="markRead">
|
||||||
您发布的帖子
|
您发布的帖子
|
||||||
@@ -226,7 +235,7 @@ import { API_BASE_URL } from '../main'
|
|||||||
import BaseTimeline from '../components/BaseTimeline.vue'
|
import BaseTimeline from '../components/BaseTimeline.vue'
|
||||||
import BasePlaceholder from '../components/BasePlaceholder.vue'
|
import BasePlaceholder from '../components/BasePlaceholder.vue'
|
||||||
import NotificationContainer from '../components/NotificationContainer.vue'
|
import NotificationContainer from '../components/NotificationContainer.vue'
|
||||||
import { getToken } from '../utils/auth'
|
import { getToken, authState } from '../utils/auth'
|
||||||
import { markNotificationsRead } from '../utils/notification'
|
import { markNotificationsRead } from '../utils/notification'
|
||||||
import { toast } from '../main'
|
import { toast } from '../main'
|
||||||
import { stripMarkdown } from '../utils/markdown'
|
import { stripMarkdown } from '../utils/markdown'
|
||||||
@@ -276,7 +285,8 @@ export default {
|
|||||||
USER_FOLLOWED: 'fas fa-user-plus',
|
USER_FOLLOWED: 'fas fa-user-plus',
|
||||||
USER_UNFOLLOWED: 'fas fa-user-minus',
|
USER_UNFOLLOWED: 'fas fa-user-minus',
|
||||||
POST_SUBSCRIBED: 'fas fa-bookmark',
|
POST_SUBSCRIBED: 'fas fa-bookmark',
|
||||||
POST_UNSUBSCRIBED: 'fas fa-bookmark'
|
POST_UNSUBSCRIBED: 'fas fa-bookmark',
|
||||||
|
REGISTER_REQUEST: 'fas fa-user-clock'
|
||||||
}
|
}
|
||||||
|
|
||||||
const reactionEmojiMap = {
|
const reactionEmojiMap = {
|
||||||
@@ -393,7 +403,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else if (n.type === 'POST_REVIEW_REQUEST') {
|
} else if (n.type === 'POST_REVIEW_REQUEST') {
|
||||||
notifications.value.push({
|
notifications.value.push({
|
||||||
...n,
|
...n,
|
||||||
src: n.fromUser ? n.fromUser.avatar : null,
|
src: n.fromUser ? n.fromUser.avatar : null,
|
||||||
@@ -405,6 +415,12 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
} else if (n.type === 'REGISTER_REQUEST') {
|
||||||
|
notifications.value.push({
|
||||||
|
...n,
|
||||||
|
icon: iconMap[n.type],
|
||||||
|
iconClick: () => {}
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
notifications.value.push({
|
notifications.value.push({
|
||||||
...n,
|
...n,
|
||||||
@@ -417,6 +433,36 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const approve = async (id, nid) => {
|
||||||
|
const token = getToken()
|
||||||
|
if (!token) return
|
||||||
|
const res = await fetch(`${API_BASE_URL}/api/admin/users/${id}/approve`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
|
})
|
||||||
|
if (res.ok) {
|
||||||
|
markRead(nid)
|
||||||
|
toast.success('已同意')
|
||||||
|
} else {
|
||||||
|
toast.error('操作失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const reject = async (id, nid) => {
|
||||||
|
const token = getToken()
|
||||||
|
if (!token) return
|
||||||
|
const res = await fetch(`${API_BASE_URL}/api/admin/users/${id}/reject`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
|
})
|
||||||
|
if (res.ok) {
|
||||||
|
markRead(nid)
|
||||||
|
toast.success('已拒绝')
|
||||||
|
} else {
|
||||||
|
toast.error('操作失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const formatType = t => {
|
const formatType = t => {
|
||||||
switch (t) {
|
switch (t) {
|
||||||
case 'POST_VIEWED':
|
case 'POST_VIEWED':
|
||||||
@@ -456,10 +502,13 @@ export default {
|
|||||||
sanitizeDescription,
|
sanitizeDescription,
|
||||||
isLoadingMessage,
|
isLoadingMessage,
|
||||||
markRead,
|
markRead,
|
||||||
|
approve,
|
||||||
|
reject,
|
||||||
TimeManager,
|
TimeManager,
|
||||||
selectedTab,
|
selectedTab,
|
||||||
filteredNotifications,
|
filteredNotifications,
|
||||||
markAllRead
|
markAllRead,
|
||||||
|
authState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,10 @@
|
|||||||
<div class="setting-title">AI 优化次数</div>
|
<div class="setting-title">AI 优化次数</div>
|
||||||
<Dropdown v-model="aiFormatLimit" :fetch-options="fetchAiLimits" />
|
<Dropdown v-model="aiFormatLimit" :fetch-options="fetchAiLimits" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-row dropdown-row">
|
||||||
|
<div class="setting-title">注册模式</div>
|
||||||
|
<Dropdown v-model="registerMode" :fetch-options="fetchRegisterModes" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<div v-if="isSaving" class="save-button disabled">保存中...</div>
|
<div v-if="isSaving" class="save-button disabled">保存中...</div>
|
||||||
@@ -70,6 +74,7 @@ export default {
|
|||||||
publishMode: 'DIRECT',
|
publishMode: 'DIRECT',
|
||||||
passwordStrength: 'LOW',
|
passwordStrength: 'LOW',
|
||||||
aiFormatLimit: 3,
|
aiFormatLimit: 3,
|
||||||
|
registerMode: 'DIRECT',
|
||||||
isLoadingPage: false,
|
isLoadingPage: false,
|
||||||
isSaving: false
|
isSaving: false
|
||||||
}
|
}
|
||||||
@@ -122,6 +127,12 @@ export default {
|
|||||||
{ id: -1, name: '无限' }
|
{ id: -1, name: '无限' }
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
fetchRegisterModes() {
|
||||||
|
return Promise.resolve([
|
||||||
|
{ id: 'DIRECT', name: '直接注册', icon: 'fas fa-user-check' },
|
||||||
|
{ id: 'WHITELIST', name: '白名单邀请制', icon: 'fas fa-envelope' }
|
||||||
|
])
|
||||||
|
},
|
||||||
async loadAdminConfig() {
|
async loadAdminConfig() {
|
||||||
try {
|
try {
|
||||||
const token = getToken()
|
const token = getToken()
|
||||||
@@ -133,6 +144,7 @@ export default {
|
|||||||
this.publishMode = data.publishMode
|
this.publishMode = data.publishMode
|
||||||
this.passwordStrength = data.passwordStrength
|
this.passwordStrength = data.passwordStrength
|
||||||
this.aiFormatLimit = data.aiFormatLimit
|
this.aiFormatLimit = data.aiFormatLimit
|
||||||
|
this.registerMode = data.registerMode
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore
|
// ignore
|
||||||
@@ -183,7 +195,7 @@ export default {
|
|||||||
await fetch(`${API_BASE_URL}/api/admin/config`, {
|
await fetch(`${API_BASE_URL}/api/admin/config`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
|
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
|
||||||
body: JSON.stringify({ publishMode: this.publishMode, passwordStrength: this.passwordStrength, aiFormatLimit: this.aiFormatLimit })
|
body: JSON.stringify({ publishMode: this.publishMode, passwordStrength: this.passwordStrength, aiFormatLimit: this.aiFormatLimit, registerMode: this.registerMode })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
toast.success('保存成功')
|
toast.success('保存成功')
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ export default {
|
|||||||
email: '',
|
email: '',
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
|
registerMode: 'DIRECT',
|
||||||
emailError: '',
|
emailError: '',
|
||||||
usernameError: '',
|
usernameError: '',
|
||||||
passwordError: '',
|
passwordError: '',
|
||||||
@@ -103,6 +104,19 @@ export default {
|
|||||||
isWaitingForEmailVerified: false
|
isWaitingForEmailVerified: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async mounted() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_BASE_URL}/api/config`)
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json()
|
||||||
|
this.registerMode = data.registerMode
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
if (this.$route.query.verify) {
|
||||||
|
this.emailStep = 1
|
||||||
|
this.username = sessionStorage.getItem('signup_username') || ''
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
clearErrors() {
|
clearErrors() {
|
||||||
this.emailError = ''
|
this.emailError = ''
|
||||||
@@ -126,6 +140,13 @@ export default {
|
|||||||
if (this.emailError || this.passwordError || this.usernameError) {
|
if (this.emailError || this.passwordError || this.usernameError) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (this.registerMode === 'WHITELIST') {
|
||||||
|
sessionStorage.setItem('signup_username', this.username)
|
||||||
|
sessionStorage.setItem('signup_email', this.email)
|
||||||
|
sessionStorage.setItem('signup_password', this.password)
|
||||||
|
this.$router.push('/signup-reason')
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
console.log('base url: ', API_BASE_URL)
|
console.log('base url: ', API_BASE_URL)
|
||||||
this.isWaitingForEmailSent = true
|
this.isWaitingForEmailSent = true
|
||||||
@@ -135,7 +156,7 @@ export default {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
username: this.username,
|
username: this.username,
|
||||||
email: this.email,
|
email: this.email,
|
||||||
password: this.password,
|
password: this.password
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
this.isWaitingForEmailSent = false
|
this.isWaitingForEmailSent = false
|
||||||
@@ -175,9 +196,13 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
signupWithGoogle() {
|
signupWithGoogle() {
|
||||||
googleSignIn(() => {
|
if (this.registerMode === 'WHITELIST') {
|
||||||
this.$router.push('/')
|
this.$router.push('/signup-reason?google=1')
|
||||||
})
|
} else {
|
||||||
|
googleSignIn(() => {
|
||||||
|
this.$router.push('/')
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<template>
|
||||||
|
<div class="reason-page">
|
||||||
|
<div class="reason-content">
|
||||||
|
<BaseInput textarea rows="4" v-model="reason" placeholder="请填写注册理由" />
|
||||||
|
<div v-if="error" class="error-message">{{ error }}</div>
|
||||||
|
<div class="signup-page-button-primary" @click="submit" >提交</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BaseInput from '../components/BaseInput.vue'
|
||||||
|
import { API_BASE_URL, toast } from '../main'
|
||||||
|
import { googleSignIn } from '../utils/google'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SignupReasonPageView',
|
||||||
|
components: { BaseInput },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
reason: '',
|
||||||
|
error: '',
|
||||||
|
isGoogle: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.isGoogle = this.$route.query.google === '1'
|
||||||
|
if (!this.isGoogle) {
|
||||||
|
if (!sessionStorage.getItem('signup_username')) {
|
||||||
|
this.$router.push('/signup')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async submit() {
|
||||||
|
if (!this.reason || this.reason.length < 20) {
|
||||||
|
this.error = '请至少输入20个字'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.isGoogle) {
|
||||||
|
googleSignIn(() => { this.$router.push('/') }, this.reason)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_BASE_URL}/api/auth/register`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: sessionStorage.getItem('signup_username'),
|
||||||
|
email: sessionStorage.getItem('signup_email'),
|
||||||
|
password: sessionStorage.getItem('signup_password'),
|
||||||
|
reason: this.reason
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const data = await res.json()
|
||||||
|
if (res.ok) {
|
||||||
|
toast.success('验证码已发送,请查收邮箱')
|
||||||
|
this.$router.push('/signup?verify=1')
|
||||||
|
} else {
|
||||||
|
toast.error(data.error || '发送失败')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
toast.error('发送失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.reason-page { display: flex; justify-content: center; align-items: center; height: calc(100vh - var(--header-height)); }
|
||||||
|
.reason-content { display: flex; flex-direction: column; gap: 20px; width: 400px; }
|
||||||
|
.error-message { color: red; font-size: 14px; }
|
||||||
|
.signup-page-button-primary { background-color: var(--primary-color); color: white; padding: 10px 20px; border-radius: 10px; text-align: center; cursor: pointer; }
|
||||||
|
.signup-page-button-primary:hover { background-color: var(--primary-color-hover); }
|
||||||
|
</style>
|
||||||
@@ -7,7 +7,10 @@ import com.openisle.service.UserService;
|
|||||||
import com.openisle.service.CaptchaService;
|
import com.openisle.service.CaptchaService;
|
||||||
import com.openisle.service.GoogleAuthService;
|
import com.openisle.service.GoogleAuthService;
|
||||||
import com.openisle.service.RegisterModeService;
|
import com.openisle.service.RegisterModeService;
|
||||||
|
import com.openisle.service.NotificationService;
|
||||||
import com.openisle.model.RegisterMode;
|
import com.openisle.model.RegisterMode;
|
||||||
|
import com.openisle.model.NotificationType;
|
||||||
|
import com.openisle.repository.UserRepository;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -26,6 +29,8 @@ public class AuthController {
|
|||||||
private final CaptchaService captchaService;
|
private final CaptchaService captchaService;
|
||||||
private final GoogleAuthService googleAuthService;
|
private final GoogleAuthService googleAuthService;
|
||||||
private final RegisterModeService registerModeService;
|
private final RegisterModeService registerModeService;
|
||||||
|
private final NotificationService notificationService;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
@Value("${app.captcha.enabled:false}")
|
@Value("${app.captcha.enabled:false}")
|
||||||
private boolean captchaEnabled;
|
private boolean captchaEnabled;
|
||||||
@@ -44,6 +49,12 @@ public class AuthController {
|
|||||||
User user = userService.register(
|
User user = userService.register(
|
||||||
req.getUsername(), req.getEmail(), req.getPassword(), req.getReason(), registerModeService.getRegisterMode());
|
req.getUsername(), req.getEmail(), req.getPassword(), req.getReason(), registerModeService.getRegisterMode());
|
||||||
emailService.sendEmail(user.getEmail(), "Verification Code", "Your verification code is " + user.getVerificationCode());
|
emailService.sendEmail(user.getEmail(), "Verification Code", "Your verification code is " + user.getVerificationCode());
|
||||||
|
if (!user.isApproved()) {
|
||||||
|
for (User admin : userRepository.findByRole(com.openisle.model.Role.ADMIN)) {
|
||||||
|
notificationService.createNotification(admin, NotificationType.REGISTER_REQUEST, null, null,
|
||||||
|
null, user, null, user.getRegisterReason());
|
||||||
|
}
|
||||||
|
}
|
||||||
return ResponseEntity.ok(Map.of("message", "Verification code sent"));
|
return ResponseEntity.ok(Map.of("message", "Verification code sent"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ public class NotificationController {
|
|||||||
dto.setReactionType(n.getReactionType());
|
dto.setReactionType(n.getReactionType());
|
||||||
}
|
}
|
||||||
dto.setApproved(n.getApproved());
|
dto.setApproved(n.getApproved());
|
||||||
|
dto.setContent(n.getContent());
|
||||||
dto.setRead(n.isRead());
|
dto.setRead(n.isRead());
|
||||||
dto.setCreatedAt(n.getCreatedAt());
|
dto.setCreatedAt(n.getCreatedAt());
|
||||||
return dto;
|
return dto;
|
||||||
@@ -107,6 +108,7 @@ public class NotificationController {
|
|||||||
private CommentDto parentComment;
|
private CommentDto parentComment;
|
||||||
private AuthorDto fromUser;
|
private AuthorDto fromUser;
|
||||||
private ReactionType reactionType;
|
private ReactionType reactionType;
|
||||||
|
private String content;
|
||||||
private Boolean approved;
|
private Boolean approved;
|
||||||
private boolean read;
|
private boolean read;
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|||||||
Reference in New Issue
Block a user