lv8girl!
This commit is contained in:
53
internal/config/config.go
Normal file
53
internal/config/config.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package config
|
||||
|
||||
import "time"
|
||||
|
||||
type Config struct {
|
||||
Server ServerConfig
|
||||
Database DatabaseConfig
|
||||
Session SessionConfig
|
||||
App AppConfig
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Port string
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
type SessionConfig struct {
|
||||
Secret string
|
||||
Name string
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
Name string
|
||||
Version string
|
||||
}
|
||||
|
||||
var AppConfigInstance = &Config{
|
||||
Server: ServerConfig{
|
||||
Port: ":8080",
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
},
|
||||
Database: DatabaseConfig{
|
||||
Path: "data/lv8girl.db",
|
||||
},
|
||||
Session: SessionConfig{
|
||||
Secret: "lv8girl-secret-key-change-in-production",
|
||||
Name: "lv8girl_session",
|
||||
},
|
||||
App: AppConfig{
|
||||
Name: "lv8girl",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
}
|
||||
|
||||
func GetConfig() *Config {
|
||||
return AppConfigInstance
|
||||
}
|
||||
179
internal/controllers/admin.go
Normal file
179
internal/controllers/admin.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"lv8girl/internal/middleware"
|
||||
"lv8girl/internal/services"
|
||||
)
|
||||
|
||||
type AdminController struct {
|
||||
adminSvc *services.AdminService
|
||||
messageSvc *services.MessageService
|
||||
}
|
||||
|
||||
func NewAdminController() *AdminController {
|
||||
return &AdminController{
|
||||
adminSvc: services.NewAdminService(),
|
||||
messageSvc: services.NewMessageService(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *AdminController) Dashboard(ctx *gin.Context) {
|
||||
username, _ := ctx.Get("username")
|
||||
stats, _ := c.adminSvc.GetStats()
|
||||
|
||||
ctx.HTML(http.StatusOK, "admin_dashboard.html", gin.H{
|
||||
"Username": username,
|
||||
"Stats": stats,
|
||||
"Page": "dashboard",
|
||||
"Message": ctx.Query("msg"),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *AdminController) PendingPosts(ctx *gin.Context) {
|
||||
username, _ := ctx.Get("username")
|
||||
posts, _ := c.adminSvc.GetPendingPosts()
|
||||
|
||||
ctx.HTML(http.StatusOK, "admin_pending_posts.html", gin.H{
|
||||
"Username": username,
|
||||
"Posts": posts,
|
||||
"Page": "pending_posts",
|
||||
"Message": ctx.Query("msg"),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *AdminController) ApprovePost(ctx *gin.Context) {
|
||||
postID := parseUint(ctx.Param("id"))
|
||||
action := ctx.Param("action")
|
||||
|
||||
if action == "approve" {
|
||||
c.adminSvc.ApprovePost(postID)
|
||||
ctx.Redirect(http.StatusFound, "/admin/pending_posts?msg=帖子已通过审核")
|
||||
} else if action == "reject" {
|
||||
c.adminSvc.RejectPost(postID)
|
||||
ctx.Redirect(http.StatusFound, "/admin/pending_posts?msg=帖子已拒绝")
|
||||
} else {
|
||||
ctx.Redirect(http.StatusFound, "/admin/pending_posts")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *AdminController) PendingUsers(ctx *gin.Context) {
|
||||
username, _ := ctx.Get("username")
|
||||
users, _ := c.adminSvc.GetPendingUsers()
|
||||
|
||||
ctx.HTML(http.StatusOK, "admin_pending_users.html", gin.H{
|
||||
"Username": username,
|
||||
"Users": users,
|
||||
"Page": "pending_users",
|
||||
"Message": ctx.Query("msg"),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *AdminController) ApproveUser(ctx *gin.Context) {
|
||||
userID := parseUint(ctx.Param("id"))
|
||||
action := ctx.Param("action")
|
||||
adminID, _ := ctx.Get("user_id")
|
||||
|
||||
if action == "approve" {
|
||||
c.adminSvc.ApproveUser(userID)
|
||||
c.messageSvc.NotifyUserApproved(adminID.(uint), userID)
|
||||
ctx.Redirect(http.StatusFound, "/admin/pending_users?msg=用户已通过审核")
|
||||
} else if action == "reject" {
|
||||
c.adminSvc.RejectUser(userID)
|
||||
c.messageSvc.NotifyUserRejected(adminID.(uint), userID)
|
||||
ctx.Redirect(http.StatusFound, "/admin/pending_users?msg=用户已拒绝")
|
||||
} else {
|
||||
ctx.Redirect(http.StatusFound, "/admin/pending_users")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *AdminController) Posts(ctx *gin.Context) {
|
||||
username, _ := ctx.Get("username")
|
||||
posts, _ := c.adminSvc.GetAllPosts()
|
||||
|
||||
ctx.HTML(http.StatusOK, "admin_posts.html", gin.H{
|
||||
"Username": username,
|
||||
"Posts": posts,
|
||||
"Page": "posts",
|
||||
"Message": ctx.Query("msg"),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *AdminController) DeletePost(ctx *gin.Context) {
|
||||
postID := parseUint(ctx.Param("id"))
|
||||
c.adminSvc.DeletePost(postID)
|
||||
ctx.Redirect(http.StatusFound, "/admin/posts?msg=帖子已删除")
|
||||
}
|
||||
|
||||
func (c *AdminController) Users(ctx *gin.Context) {
|
||||
username, _ := ctx.Get("username")
|
||||
currentUserID, _ := ctx.Get("user_id")
|
||||
users, _ := c.adminSvc.GetAllUsers()
|
||||
|
||||
ctx.HTML(http.StatusOK, "admin_users.html", gin.H{
|
||||
"Username": username,
|
||||
"Users": users,
|
||||
"CurrentUserID": currentUserID,
|
||||
"Page": "users",
|
||||
"Message": ctx.Query("msg"),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *AdminController) UpdateUserRole(ctx *gin.Context) {
|
||||
userIDStr := ctx.PostForm("user_id")
|
||||
newRole := ctx.PostForm("new_role")
|
||||
currentUserID, _ := ctx.Get("user_id")
|
||||
|
||||
userID, _ := strconv.ParseUint(userIDStr, 10, 32)
|
||||
|
||||
if uint(userID) == currentUserID.(uint) {
|
||||
ctx.Redirect(http.StatusFound, "/admin/users?msg=不能修改自己的角色")
|
||||
return
|
||||
}
|
||||
|
||||
c.adminSvc.UpdateUserRole(uint(userID), newRole)
|
||||
|
||||
if newRole == "banned" {
|
||||
c.messageSvc.NotifyUserBanned(currentUserID.(uint), uint(userID))
|
||||
}
|
||||
|
||||
ctx.Redirect(http.StatusFound, "/admin/users?msg=用户角色已更新")
|
||||
}
|
||||
|
||||
func (c *AdminController) DeleteUser(ctx *gin.Context) {
|
||||
userID := parseUint(ctx.Param("id"))
|
||||
currentUserID, _ := ctx.Get("user_id")
|
||||
|
||||
if userID == currentUserID.(uint) {
|
||||
ctx.Redirect(http.StatusFound, "/admin/users?msg=不能删除自己")
|
||||
return
|
||||
}
|
||||
|
||||
c.adminSvc.DeleteUser(userID)
|
||||
ctx.Redirect(http.StatusFound, "/admin/users?msg=用户已删除")
|
||||
}
|
||||
|
||||
func (c *AdminController) Comments(ctx *gin.Context) {
|
||||
username, _ := ctx.Get("username")
|
||||
comments, _ := c.adminSvc.GetAllComments()
|
||||
|
||||
ctx.HTML(http.StatusOK, "admin_comments.html", gin.H{
|
||||
"Username": username,
|
||||
"Comments": comments,
|
||||
"Page": "comments",
|
||||
"Message": ctx.Query("msg"),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *AdminController) DeleteComment(ctx *gin.Context) {
|
||||
commentID := parseUint(ctx.Param("id"))
|
||||
c.adminSvc.DeleteComment(commentID)
|
||||
ctx.Redirect(http.StatusFound, "/admin/comments?msg=评论已删除")
|
||||
}
|
||||
|
||||
func init() {
|
||||
_ = middleware.GetCurrentUser
|
||||
}
|
||||
99
internal/controllers/auth.go
Normal file
99
internal/controllers/auth.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"lv8girl/internal/services"
|
||||
)
|
||||
|
||||
type AuthController struct {
|
||||
authService *services.AuthService
|
||||
}
|
||||
|
||||
func NewAuthController() *AuthController {
|
||||
return &AuthController{
|
||||
authService: services.NewAuthService(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *AuthController) ShowLogin(ctx *gin.Context) {
|
||||
session := sessions.Default(ctx)
|
||||
if session.Get("user_id") != nil {
|
||||
ctx.Redirect(http.StatusFound, "/")
|
||||
return
|
||||
}
|
||||
ctx.HTML(http.StatusOK, "login.html", gin.H{"Error": ""})
|
||||
}
|
||||
|
||||
func (c *AuthController) Login(ctx *gin.Context) {
|
||||
session := sessions.Default(ctx)
|
||||
if session.Get("user_id") != nil {
|
||||
ctx.Redirect(http.StatusFound, "/")
|
||||
return
|
||||
}
|
||||
|
||||
login := ctx.PostForm("login")
|
||||
password := ctx.PostForm("password")
|
||||
|
||||
if login == "" || password == "" {
|
||||
ctx.HTML(http.StatusOK, "login.html", gin.H{"Error": "请输入用户名/邮箱和密码"})
|
||||
return
|
||||
}
|
||||
|
||||
result := c.authService.Login(login, password)
|
||||
if !result.Success {
|
||||
ctx.HTML(http.StatusOK, "login.html", gin.H{"Error": result.Error})
|
||||
return
|
||||
}
|
||||
|
||||
session.Set("user_id", result.User.ID)
|
||||
session.Set("username", result.User.Username)
|
||||
session.Set("user_role", result.User.Role)
|
||||
session.Save()
|
||||
|
||||
ctx.Redirect(http.StatusFound, "/")
|
||||
}
|
||||
|
||||
func (c *AuthController) ShowRegister(ctx *gin.Context) {
|
||||
session := sessions.Default(ctx)
|
||||
if session.Get("user_id") != nil {
|
||||
ctx.Redirect(http.StatusFound, "/")
|
||||
return
|
||||
}
|
||||
ctx.HTML(http.StatusOK, "register.html", gin.H{"Error": "", "Success": ""})
|
||||
}
|
||||
|
||||
func (c *AuthController) Register(ctx *gin.Context) {
|
||||
session := sessions.Default(ctx)
|
||||
if session.Get("user_id") != nil {
|
||||
ctx.Redirect(http.StatusFound, "/")
|
||||
return
|
||||
}
|
||||
|
||||
username := ctx.PostForm("username")
|
||||
email := ctx.PostForm("email")
|
||||
password := ctx.PostForm("password")
|
||||
confirmPassword := ctx.PostForm("confirm_password")
|
||||
|
||||
if password != confirmPassword {
|
||||
ctx.HTML(http.StatusOK, "register.html", gin.H{"Error": "两次输入的密码不一致", "Success": ""})
|
||||
return
|
||||
}
|
||||
|
||||
result := c.authService.Register(username, email, password)
|
||||
if !result.Success {
|
||||
ctx.HTML(http.StatusOK, "register.html", gin.H{"Error": result.Error, "Success": ""})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, "register.html", gin.H{"Error": "", "Success": "注册成功!您的账号正在等待管理员审核,请耐心等待。"})
|
||||
}
|
||||
|
||||
func (c *AuthController) Logout(ctx *gin.Context) {
|
||||
session := sessions.Default(ctx)
|
||||
session.Clear()
|
||||
session.Save()
|
||||
ctx.Redirect(http.StatusFound, "/")
|
||||
}
|
||||
121
internal/controllers/discussion.go
Normal file
121
internal/controllers/discussion.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"lv8girl/internal/middleware"
|
||||
"lv8girl/internal/services"
|
||||
)
|
||||
|
||||
type DiscussionController struct {
|
||||
discussionSvc *services.DiscussionService
|
||||
}
|
||||
|
||||
func NewDiscussionController() *DiscussionController {
|
||||
return &DiscussionController{
|
||||
discussionSvc: services.NewDiscussionService(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DiscussionController) ShowNewPost(ctx *gin.Context) {
|
||||
userID, username, userRole, _ := middleware.GetCurrentUser(ctx)
|
||||
ctx.HTML(http.StatusOK, "post_discussion.html", gin.H{
|
||||
"IsLoggedIn": true,
|
||||
"UserID": userID,
|
||||
"Username": username,
|
||||
"UserRole": userRole,
|
||||
"Error": "",
|
||||
"Success": "",
|
||||
})
|
||||
}
|
||||
|
||||
func (c *DiscussionController) CreatePost(ctx *gin.Context) {
|
||||
userID, username, userRole, _ := middleware.GetCurrentUser(ctx)
|
||||
|
||||
title := strings.TrimSpace(ctx.PostForm("title"))
|
||||
content := strings.TrimSpace(ctx.PostForm("content"))
|
||||
|
||||
if title == "" || content == "" {
|
||||
ctx.HTML(http.StatusOK, "post_discussion.html", gin.H{
|
||||
"IsLoggedIn": true,
|
||||
"UserID": userID,
|
||||
"Username": username,
|
||||
"UserRole": userRole,
|
||||
"Error": "标题和内容不能为空",
|
||||
"Success": "",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var imagePath string
|
||||
file, err := ctx.FormFile("image")
|
||||
if err == nil {
|
||||
if file.Size > 2*1024*1024 {
|
||||
ctx.HTML(http.StatusOK, "post_discussion.html", gin.H{
|
||||
"IsLoggedIn": true,
|
||||
"UserID": userID,
|
||||
"Username": username,
|
||||
"UserRole": userRole,
|
||||
"Error": "图片大小不能超过2MB",
|
||||
"Success": "",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(file.Filename))
|
||||
filename := "post_" + time.Now().Format("20060102150405") + "_" + randomString(8) + ext
|
||||
uploadDir := "uploads/posts"
|
||||
|
||||
if _, err := os.Stat(uploadDir); os.IsNotExist(err) {
|
||||
os.MkdirAll(uploadDir, 0755)
|
||||
}
|
||||
|
||||
imagePath = filepath.Join(uploadDir, filename)
|
||||
if err := ctx.SaveUploadedFile(file, imagePath); err != nil {
|
||||
ctx.HTML(http.StatusOK, "post_discussion.html", gin.H{
|
||||
"IsLoggedIn": true,
|
||||
"UserID": userID,
|
||||
"Username": username,
|
||||
"UserRole": userRole,
|
||||
"Error": "图片保存失败",
|
||||
"Success": "",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.discussionSvc.CreatePost(userID, title, content, imagePath); err != nil {
|
||||
ctx.HTML(http.StatusOK, "post_discussion.html", gin.H{
|
||||
"IsLoggedIn": true,
|
||||
"UserID": userID,
|
||||
"Username": username,
|
||||
"UserRole": userRole,
|
||||
"Error": "发帖失败,请稍后重试",
|
||||
"Success": "",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, "post_discussion.html", gin.H{
|
||||
"IsLoggedIn": true,
|
||||
"UserID": userID,
|
||||
"Username": username,
|
||||
"UserRole": userRole,
|
||||
"Error": "",
|
||||
"Success": "帖子已提交,等待管理员审核。",
|
||||
})
|
||||
}
|
||||
|
||||
func randomString(n int) string {
|
||||
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = letters[time.Now().UnixNano()%int64(len(letters))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
150
internal/controllers/home.go
Normal file
150
internal/controllers/home.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"lv8girl/internal/middleware"
|
||||
"lv8girl/internal/repositories"
|
||||
"lv8girl/internal/services"
|
||||
)
|
||||
|
||||
type HomeController struct {
|
||||
discussionSvc *services.DiscussionService
|
||||
userSvc *services.UserService
|
||||
messageSvc *services.MessageService
|
||||
discussionRepo *repositories.DiscussionRepository
|
||||
}
|
||||
|
||||
func NewHomeController() *HomeController {
|
||||
return &HomeController{
|
||||
discussionSvc: services.NewDiscussionService(),
|
||||
userSvc: services.NewUserService(),
|
||||
messageSvc: services.NewMessageService(),
|
||||
discussionRepo: repositories.NewDiscussionRepository(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HomeController) Index(ctx *gin.Context) {
|
||||
session := sessions.Default(ctx)
|
||||
userID, _ := session.Get("user_id").(uint)
|
||||
username, _ := session.Get("username").(string)
|
||||
userRole, _ := session.Get("user_role").(string)
|
||||
isLoggedIn := userID != 0
|
||||
|
||||
if isLoggedIn {
|
||||
userRepo := repositories.NewUserRepository()
|
||||
userRepo.UpdateLastActive(userID)
|
||||
}
|
||||
|
||||
posts, _ := c.discussionSvc.GetApprovedPosts(30)
|
||||
|
||||
var postCount, userCount, onlineCount int64
|
||||
postCount, _ = c.discussionRepo.CountByStatus("approved")
|
||||
userCount, onlineCount, _ = c.userSvc.GetUserStats()
|
||||
|
||||
var unreadCount int64
|
||||
if isLoggedIn {
|
||||
unreadCount, _ = c.messageSvc.GetUnreadCount(userID)
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, "index.html", gin.H{
|
||||
"IsLoggedIn": isLoggedIn,
|
||||
"UserID": userID,
|
||||
"Username": username,
|
||||
"UserRole": userRole,
|
||||
"Posts": posts,
|
||||
"PostCount": postCount,
|
||||
"UserCount": userCount,
|
||||
"OnlineCount": onlineCount,
|
||||
"UnreadCount": unreadCount,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *HomeController) ShowPost(ctx *gin.Context) {
|
||||
postID := parseUint(ctx.Param("id"))
|
||||
userID, username, userRole, isLoggedIn := middleware.GetCurrentUser(ctx)
|
||||
|
||||
session := sessions.Default(ctx)
|
||||
viewedKey := "viewed_posts"
|
||||
viewedPosts := session.Get(viewedKey)
|
||||
var viewedMap map[uint]bool
|
||||
if viewedPosts == nil {
|
||||
viewedMap = make(map[uint]bool)
|
||||
} else {
|
||||
viewedMap = viewedPosts.(map[uint]bool)
|
||||
}
|
||||
|
||||
if !viewedMap[postID] {
|
||||
c.discussionSvc.IncrementViews(postID)
|
||||
viewedMap[postID] = true
|
||||
session.Set(viewedKey, viewedMap)
|
||||
session.Save()
|
||||
}
|
||||
|
||||
detail, err := c.discussionSvc.GetPostDetail(postID, userID)
|
||||
if err != nil {
|
||||
ctx.String(http.StatusNotFound, "帖子不存在")
|
||||
return
|
||||
}
|
||||
|
||||
comments, _ := c.discussionSvc.GetComments(postID)
|
||||
|
||||
avatar := ""
|
||||
if detail.Post.User.Avatar != "" {
|
||||
avatar = "/" + detail.Post.User.Avatar
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, "post.html", gin.H{
|
||||
"IsLoggedIn": isLoggedIn,
|
||||
"UserID": userID,
|
||||
"Username": username,
|
||||
"UserRole": userRole,
|
||||
"Post": detail.Post,
|
||||
"PostAvatar": avatar,
|
||||
"Comments": comments,
|
||||
"LikeCount": detail.LikeCount,
|
||||
"UserLiked": detail.UserLiked,
|
||||
"AuthorPostCount": detail.AuthorPostCount,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *HomeController) LikePost(ctx *gin.Context) {
|
||||
userID, _, _, isLoggedIn := middleware.GetCurrentUser(ctx)
|
||||
if !isLoggedIn {
|
||||
ctx.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"})
|
||||
return
|
||||
}
|
||||
|
||||
postID := parseUint(ctx.Param("id"))
|
||||
c.discussionSvc.AddLike(postID, userID)
|
||||
ctx.Redirect(http.StatusFound, "/post/"+ctx.Param("id"))
|
||||
}
|
||||
|
||||
func (c *HomeController) AddComment(ctx *gin.Context) {
|
||||
userID, _, _, isLoggedIn := middleware.GetCurrentUser(ctx)
|
||||
if !isLoggedIn {
|
||||
ctx.Redirect(http.StatusFound, "/login")
|
||||
return
|
||||
}
|
||||
|
||||
postID := parseUint(ctx.Param("id"))
|
||||
content := ctx.PostForm("content")
|
||||
|
||||
if content != "" {
|
||||
c.discussionSvc.AddComment(postID, userID, content)
|
||||
}
|
||||
|
||||
ctx.Redirect(http.StatusFound, "/post/"+ctx.Param("id"))
|
||||
}
|
||||
|
||||
func parseUint(s string) uint {
|
||||
var result uint
|
||||
for _, c := range s {
|
||||
if c >= '0' && c <= '9' {
|
||||
result = result*10 + uint(c-'0')
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
89
internal/controllers/message.go
Normal file
89
internal/controllers/message.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"lv8girl/internal/middleware"
|
||||
"lv8girl/internal/repositories"
|
||||
"lv8girl/internal/services"
|
||||
)
|
||||
|
||||
type MessageController struct {
|
||||
messageSvc *services.MessageService
|
||||
userRepo *repositories.UserRepository
|
||||
}
|
||||
|
||||
func NewMessageController() *MessageController {
|
||||
return &MessageController{
|
||||
messageSvc: services.NewMessageService(),
|
||||
userRepo: repositories.NewUserRepository(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *MessageController) ShowMessages(ctx *gin.Context) {
|
||||
currentUserID, currentUsername, currentUserRole, _ := middleware.GetCurrentUser(ctx)
|
||||
|
||||
conversations, _ := c.messageSvc.GetConversations(currentUserID)
|
||||
|
||||
ctx.HTML(http.StatusOK, "messages.html", gin.H{
|
||||
"IsLoggedIn": true,
|
||||
"UserID": currentUserID,
|
||||
"Username": currentUsername,
|
||||
"UserRole": currentUserRole,
|
||||
"Conversations": conversations,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *MessageController) ShowSendMessage(ctx *gin.Context) {
|
||||
currentUserID, currentUsername, currentUserRole, _ := middleware.GetCurrentUser(ctx)
|
||||
|
||||
toUserID := parseUint(ctx.Query("to"))
|
||||
if toUserID == 0 {
|
||||
ctx.Redirect(http.StatusFound, "/")
|
||||
return
|
||||
}
|
||||
|
||||
receiver, err := c.userRepo.FindByID(toUserID)
|
||||
if err != nil {
|
||||
ctx.String(http.StatusNotFound, "用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, "send_message.html", gin.H{
|
||||
"IsLoggedIn": true,
|
||||
"UserID": currentUserID,
|
||||
"Username": currentUsername,
|
||||
"UserRole": currentUserRole,
|
||||
"Receiver": receiver,
|
||||
"Error": "",
|
||||
"Success": "",
|
||||
})
|
||||
}
|
||||
|
||||
func (c *MessageController) SendMessage(ctx *gin.Context) {
|
||||
currentUserID, _, _, _ := middleware.GetCurrentUser(ctx)
|
||||
|
||||
toUserID := parseUint(ctx.Query("to"))
|
||||
content := strings.TrimSpace(ctx.PostForm("content"))
|
||||
|
||||
receiver, _ := c.userRepo.FindByID(toUserID)
|
||||
|
||||
if content == "" {
|
||||
ctx.HTML(http.StatusOK, "send_message.html", gin.H{
|
||||
"Receiver": receiver,
|
||||
"Error": "消息内容不能为空",
|
||||
"Success": "",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.messageSvc.SendMessage(currentUserID, toUserID, content)
|
||||
|
||||
ctx.HTML(http.StatusOK, "send_message.html", gin.H{
|
||||
"Receiver": receiver,
|
||||
"Error": "",
|
||||
"Success": "消息已发送!",
|
||||
})
|
||||
}
|
||||
98
internal/controllers/user.go
Normal file
98
internal/controllers/user.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"lv8girl/internal/middleware"
|
||||
"lv8girl/internal/services"
|
||||
)
|
||||
|
||||
type UserController struct {
|
||||
userSvc *services.UserService
|
||||
}
|
||||
|
||||
func NewUserController() *UserController {
|
||||
return &UserController{
|
||||
userSvc: services.NewUserService(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UserController) ShowProfile(ctx *gin.Context) {
|
||||
currentUserID, currentUsername, currentUserRole, isLoggedIn := middleware.GetCurrentUser(ctx)
|
||||
|
||||
userIDStr := ctx.Param("id")
|
||||
var userID uint
|
||||
if userIDStr == "" {
|
||||
userID = currentUserID
|
||||
} else {
|
||||
userID = parseUint(userIDStr)
|
||||
}
|
||||
|
||||
if userID == 0 {
|
||||
ctx.Redirect(http.StatusFound, "/")
|
||||
return
|
||||
}
|
||||
|
||||
profile, err := c.userSvc.GetUserProfile(currentUserID, userID)
|
||||
if err != nil {
|
||||
ctx.String(http.StatusNotFound, "用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
avatar := ""
|
||||
if profile.User.Avatar != "" {
|
||||
avatar = "/" + profile.User.Avatar
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, "profile.html", gin.H{
|
||||
"IsLoggedIn": isLoggedIn,
|
||||
"CurrentUserID": currentUserID,
|
||||
"CurrentUsername": currentUsername,
|
||||
"CurrentUserRole": currentUserRole,
|
||||
"User": profile.User,
|
||||
"UserAvatar": avatar,
|
||||
"Posts": profile.Posts,
|
||||
"PostCount": profile.PostCount,
|
||||
"IsOwner": profile.IsOwner,
|
||||
"UnreadCount": profile.UnreadCount,
|
||||
"Message": "",
|
||||
})
|
||||
}
|
||||
|
||||
func (c *UserController) UploadAvatar(ctx *gin.Context) {
|
||||
userID, _, _, _ := middleware.GetCurrentUser(ctx)
|
||||
|
||||
file, err := ctx.FormFile("avatar")
|
||||
if err != nil {
|
||||
ctx.Redirect(http.StatusFound, "/profile?error=上传失败")
|
||||
return
|
||||
}
|
||||
|
||||
if file.Size > 2*1024*1024 {
|
||||
ctx.Redirect(http.StatusFound, "/profile?error=图片大小不能超过2MB")
|
||||
return
|
||||
}
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(file.Filename))
|
||||
filename := "avatar_" + strconv.FormatUint(uint64(userID), 10) + "_" + time.Now().Format("20060102150405") + ext
|
||||
uploadDir := "uploads/avatars"
|
||||
|
||||
if _, err := os.Stat(uploadDir); os.IsNotExist(err) {
|
||||
os.MkdirAll(uploadDir, 0755)
|
||||
}
|
||||
|
||||
imagePath := filepath.Join(uploadDir, filename)
|
||||
if err := ctx.SaveUploadedFile(file, imagePath); err != nil {
|
||||
ctx.Redirect(http.StatusFound, "/profile?error=保存失败")
|
||||
return
|
||||
}
|
||||
|
||||
c.userSvc.UpdateAvatar(userID, imagePath)
|
||||
ctx.Redirect(http.StatusFound, "/profile?success=头像更新成功")
|
||||
}
|
||||
49
internal/middleware/auth.go
Normal file
49
internal/middleware/auth.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func AuthRequired() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
userID := session.Get("user_id")
|
||||
if userID == nil {
|
||||
c.Redirect(http.StatusFound, "/login")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Set("user_id", userID)
|
||||
c.Set("username", session.Get("username"))
|
||||
c.Set("user_role", session.Get("user_role"))
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func AdminRequired() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
userRole := session.Get("user_role")
|
||||
if userRole != "admin" {
|
||||
c.Redirect(http.StatusFound, "/")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Set("user_id", session.Get("user_id"))
|
||||
c.Set("username", session.Get("username"))
|
||||
c.Set("user_role", userRole)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func GetCurrentUser(c *gin.Context) (uint, string, string, bool) {
|
||||
session := sessions.Default(c)
|
||||
userID := session.Get("user_id")
|
||||
if userID == nil {
|
||||
return 0, "", "", false
|
||||
}
|
||||
return userID.(uint), session.Get("username").(string), session.Get("user_role").(string), true
|
||||
}
|
||||
84
internal/models/models.go
Normal file
84
internal/models/models.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Username string `gorm:"size:50;uniqueIndex;not null" json:"username"`
|
||||
Email string `gorm:"size:100;uniqueIndex;not null" json:"email"`
|
||||
PasswordHash string `gorm:"size:255;not null" json:"-"`
|
||||
Avatar string `gorm:"size:255" json:"avatar"`
|
||||
Role string `gorm:"size:20;default:user" json:"role"`
|
||||
Status string `gorm:"size:20;default:pending" json:"status"`
|
||||
LastActive *time.Time `json:"last_active"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
func (User) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
type Discussion struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
UserID uint `gorm:"not null;index" json:"user_id"`
|
||||
User User `gorm:"foreignKey:UserID" json:"user"`
|
||||
Title string `gorm:"size:200;not null" json:"title"`
|
||||
Content string `gorm:"type:text;not null" json:"content"`
|
||||
ImagePath string `gorm:"size:255" json:"image_path"`
|
||||
Status string `gorm:"size:20;default:pending" json:"status"`
|
||||
Views int `gorm:"default:0" json:"views"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
func (Discussion) TableName() string {
|
||||
return "discussions"
|
||||
}
|
||||
|
||||
type Comment struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
PostID uint `gorm:"not null;index" json:"post_id"`
|
||||
UserID uint `gorm:"not null;index" json:"user_id"`
|
||||
User User `gorm:"foreignKey:UserID" json:"user"`
|
||||
Content string `gorm:"type:text;not null" json:"content"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
func (Comment) TableName() string {
|
||||
return "comments"
|
||||
}
|
||||
|
||||
type Like struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
PostID uint `gorm:"not null;uniqueIndex:idx_post_user" json:"post_id"`
|
||||
UserID uint `gorm:"not null;uniqueIndex:idx_post_user" json:"user_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func (Like) TableName() string {
|
||||
return "likes"
|
||||
}
|
||||
|
||||
type PrivateMessage struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
FromUserID uint `gorm:"not null;index" json:"from_user_id"`
|
||||
FromUser User `gorm:"foreignKey:FromUserID" json:"from_user"`
|
||||
ToUserID uint `gorm:"not null;index" json:"to_user_id"`
|
||||
ToUser User `gorm:"foreignKey:ToUserID" json:"to_user"`
|
||||
Content string `gorm:"type:text;not null" json:"content"`
|
||||
IsRead bool `gorm:"default:false" json:"is_read"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
func (PrivateMessage) TableName() string {
|
||||
return "private_messages"
|
||||
}
|
||||
47
internal/repositories/comment.go
Normal file
47
internal/repositories/comment.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package repositories
|
||||
|
||||
import "lv8girl/internal/models"
|
||||
|
||||
type CommentRepository struct{}
|
||||
|
||||
func NewCommentRepository() *CommentRepository {
|
||||
return &CommentRepository{}
|
||||
}
|
||||
|
||||
func (r *CommentRepository) FindByPostID(postID uint) ([]models.Comment, error) {
|
||||
var comments []models.Comment
|
||||
err := DB.Preload("User").Where("post_id = ?", postID).Order("created_at DESC").Find(&comments).Error
|
||||
return comments, err
|
||||
}
|
||||
|
||||
func (r *CommentRepository) FindAll() ([]models.Comment, error) {
|
||||
var comments []models.Comment
|
||||
err := DB.Preload("User").Order("created_at DESC").Find(&comments).Error
|
||||
return comments, err
|
||||
}
|
||||
|
||||
func (r *CommentRepository) Create(comment *models.Comment) error {
|
||||
return DB.Create(comment).Error
|
||||
}
|
||||
|
||||
func (r *CommentRepository) Delete(id uint) error {
|
||||
return DB.Delete(&models.Comment{}, id).Error
|
||||
}
|
||||
|
||||
func (r *CommentRepository) Count() (int64, error) {
|
||||
var count int64
|
||||
err := DB.Model(&models.Comment{}).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *CommentRepository) CountByPostID(postID uint) (int64, error) {
|
||||
var count int64
|
||||
err := DB.Model(&models.Comment{}).Where("post_id = ?", postID).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *CommentRepository) CountByUserID(userID uint) (int64, error) {
|
||||
var count int64
|
||||
err := DB.Model(&models.Comment{}).Where("user_id = ?", userID).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
36
internal/repositories/db.go
Normal file
36
internal/repositories/db.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"lv8girl/internal/models"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
var DB *gorm.DB
|
||||
|
||||
func Init(databasePath string) error {
|
||||
var err error
|
||||
DB, err = gorm.Open(sqlite.Open(databasePath), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Info),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = DB.AutoMigrate(
|
||||
&models.User{},
|
||||
&models.Discussion{},
|
||||
&models.Comment{},
|
||||
&models.Like{},
|
||||
&models.PrivateMessage{},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
146
internal/repositories/discussion.go
Normal file
146
internal/repositories/discussion.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"lv8girl/internal/models"
|
||||
)
|
||||
|
||||
type DiscussionRepository struct{}
|
||||
|
||||
func NewDiscussionRepository() *DiscussionRepository {
|
||||
return &DiscussionRepository{}
|
||||
}
|
||||
|
||||
func (r *DiscussionRepository) FindByID(id uint) (*models.Discussion, error) {
|
||||
var discussion models.Discussion
|
||||
err := DB.Preload("User").First(&discussion, id).Error
|
||||
return &discussion, err
|
||||
}
|
||||
|
||||
func (r *DiscussionRepository) FindByIDWithStats(id uint) (*models.Discussion, int64, int64, error) {
|
||||
discussion, err := r.FindByID(id)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
var likeCount, commentCount int64
|
||||
DB.Model(&models.Like{}).Where("post_id = ?", id).Count(&likeCount)
|
||||
DB.Model(&models.Comment{}).Where("post_id = ?", id).Count(&commentCount)
|
||||
|
||||
return discussion, likeCount, commentCount, nil
|
||||
}
|
||||
|
||||
func (r *DiscussionRepository) FindApproved(limit int) ([]models.Discussion, error) {
|
||||
var discussions []models.Discussion
|
||||
err := DB.Preload("User").
|
||||
Where("status = ?", "approved").
|
||||
Order("created_at DESC").
|
||||
Limit(limit).
|
||||
Find(&discussions).Error
|
||||
return discussions, err
|
||||
}
|
||||
|
||||
func (r *DiscussionRepository) FindByUserID(userID uint) ([]models.Discussion, error) {
|
||||
var discussions []models.Discussion
|
||||
err := DB.Where("user_id = ? AND status = ?", userID, "approved").
|
||||
Order("created_at DESC").
|
||||
Find(&discussions).Error
|
||||
return discussions, err
|
||||
}
|
||||
|
||||
func (r *DiscussionRepository) FindPending() ([]models.Discussion, error) {
|
||||
var discussions []models.Discussion
|
||||
err := DB.Preload("User").
|
||||
Where("status = ?", "pending").
|
||||
Order("created_at DESC").
|
||||
Find(&discussions).Error
|
||||
return discussions, err
|
||||
}
|
||||
|
||||
func (r *DiscussionRepository) FindAll() ([]models.Discussion, error) {
|
||||
var discussions []models.Discussion
|
||||
err := DB.Preload("User").Order("created_at DESC").Find(&discussions).Error
|
||||
return discussions, err
|
||||
}
|
||||
|
||||
func (r *DiscussionRepository) Create(discussion *models.Discussion) error {
|
||||
return DB.Create(discussion).Error
|
||||
}
|
||||
|
||||
func (r *DiscussionRepository) Update(discussion *models.Discussion) error {
|
||||
return DB.Save(discussion).Error
|
||||
}
|
||||
|
||||
func (r *DiscussionRepository) UpdateStatus(id uint, status string) error {
|
||||
return DB.Model(&models.Discussion{}).Where("id = ?", id).Update("status", status).Error
|
||||
}
|
||||
|
||||
func (r *DiscussionRepository) IncrementViews(id uint) error {
|
||||
return DB.Model(&models.Discussion{}).Where("id = ?", id).
|
||||
UpdateColumn("views", gorm.Expr("views + ?", 1)).Error
|
||||
}
|
||||
|
||||
func (r *DiscussionRepository) Delete(id uint) error {
|
||||
return DB.Delete(&models.Discussion{}, id).Error
|
||||
}
|
||||
|
||||
func (r *DiscussionRepository) Count() (int64, error) {
|
||||
var count int64
|
||||
err := DB.Model(&models.Discussion{}).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *DiscussionRepository) CountByStatus(status string) (int64, error) {
|
||||
var count int64
|
||||
err := DB.Model(&models.Discussion{}).Where("status = ?", status).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *DiscussionRepository) CountByUserID(userID uint) (int64, error) {
|
||||
var count int64
|
||||
err := DB.Model(&models.Discussion{}).Where("user_id = ? AND status = ?", userID, "approved").Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
type PostListItem struct {
|
||||
ID uint
|
||||
Title string
|
||||
Content string
|
||||
Username string
|
||||
Avatar string
|
||||
UserID uint
|
||||
LikeCount int64
|
||||
CommentCount int64
|
||||
Views int
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func (r *DiscussionRepository) GetPostList(limit int) ([]PostListItem, error) {
|
||||
discussions, err := r.FindApproved(limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var posts []PostListItem
|
||||
for _, d := range discussions {
|
||||
var likeCount, commentCount int64
|
||||
DB.Model(&models.Like{}).Where("post_id = ?", d.ID).Count(&likeCount)
|
||||
DB.Model(&models.Comment{}).Where("post_id = ?", d.ID).Count(&commentCount)
|
||||
|
||||
posts = append(posts, PostListItem{
|
||||
ID: d.ID,
|
||||
Title: d.Title,
|
||||
Content: d.Content,
|
||||
Username: d.User.Username,
|
||||
Avatar: d.User.Avatar,
|
||||
UserID: d.User.ID,
|
||||
LikeCount: likeCount,
|
||||
CommentCount: commentCount,
|
||||
Views: d.Views,
|
||||
CreatedAt: d.CreatedAt,
|
||||
})
|
||||
}
|
||||
return posts, nil
|
||||
}
|
||||
37
internal/repositories/like.go
Normal file
37
internal/repositories/like.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package repositories
|
||||
|
||||
import "lv8girl/internal/models"
|
||||
|
||||
type LikeRepository struct{}
|
||||
|
||||
func NewLikeRepository() *LikeRepository {
|
||||
return &LikeRepository{}
|
||||
}
|
||||
|
||||
func (r *LikeRepository) FindByPostAndUser(postID, userID uint) (*models.Like, error) {
|
||||
var like models.Like
|
||||
err := DB.Where("post_id = ? AND user_id = ?", postID, userID).First(&like).Error
|
||||
return &like, err
|
||||
}
|
||||
|
||||
func (r *LikeRepository) Exists(postID, userID uint) bool {
|
||||
var count int64
|
||||
DB.Model(&models.Like{}).Where("post_id = ? AND user_id = ?", postID, userID).Count(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
func (r *LikeRepository) Create(like *models.Like) error {
|
||||
return DB.Create(like).Error
|
||||
}
|
||||
|
||||
func (r *LikeRepository) Count() (int64, error) {
|
||||
var count int64
|
||||
err := DB.Model(&models.Like{}).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *LikeRepository) CountByPostID(postID uint) (int64, error) {
|
||||
var count int64
|
||||
err := DB.Model(&models.Like{}).Where("post_id = ?", postID).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
69
internal/repositories/message.go
Normal file
69
internal/repositories/message.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"lv8girl/internal/models"
|
||||
)
|
||||
|
||||
type MessageRepository struct{}
|
||||
|
||||
func NewMessageRepository() *MessageRepository {
|
||||
return &MessageRepository{}
|
||||
}
|
||||
|
||||
func (r *MessageRepository) FindByID(id uint) (*models.PrivateMessage, error) {
|
||||
var msg models.PrivateMessage
|
||||
err := DB.First(&msg, id).Error
|
||||
return &msg, err
|
||||
}
|
||||
|
||||
func (r *MessageRepository) Create(message *models.PrivateMessage) error {
|
||||
return DB.Create(message).Error
|
||||
}
|
||||
|
||||
func (r *MessageRepository) MarkAsRead(id uint) error {
|
||||
return DB.Model(&models.PrivateMessage{}).Where("id = ?", id).Update("is_read", true).Error
|
||||
}
|
||||
|
||||
func (r *MessageRepository) CountUnread(userID uint) (int64, error) {
|
||||
var count int64
|
||||
err := DB.Model(&models.PrivateMessage{}).
|
||||
Where("to_user_id = ? AND is_read = ?", userID, false).
|
||||
Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *MessageRepository) FindConversations(userID uint) ([]models.PrivateMessage, error) {
|
||||
var messages []models.PrivateMessage
|
||||
err := DB.Where("from_user_id = ? OR to_user_id = ?", userID, userID).
|
||||
Order("created_at DESC").
|
||||
Find(&messages).Error
|
||||
return messages, err
|
||||
}
|
||||
|
||||
func (r *MessageRepository) FindLastMessage(userID, otherUserID uint) (*models.PrivateMessage, error) {
|
||||
var msg models.PrivateMessage
|
||||
err := DB.Where(
|
||||
"(from_user_id = ? AND to_user_id = ?) OR (from_user_id = ? AND to_user_id = ?)",
|
||||
userID, otherUserID, otherUserID, userID,
|
||||
).Order("created_at DESC").First(&msg).Error
|
||||
return &msg, err
|
||||
}
|
||||
|
||||
func (r *MessageRepository) CountUnreadFromUser(fromUserID, toUserID uint) (int64, error) {
|
||||
var count int64
|
||||
err := DB.Model(&models.PrivateMessage{}).
|
||||
Where("from_user_id = ? AND to_user_id = ? AND is_read = ?", fromUserID, toUserID, false).
|
||||
Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
type ConversationSummary struct {
|
||||
UserID uint
|
||||
Username string
|
||||
Avatar string
|
||||
LastMsg string
|
||||
Time time.Time
|
||||
Unread int64
|
||||
}
|
||||
123
internal/repositories/user.go
Normal file
123
internal/repositories/user.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"lv8girl/internal/models"
|
||||
)
|
||||
|
||||
type UserRepository struct{}
|
||||
|
||||
func NewUserRepository() *UserRepository {
|
||||
return &UserRepository{}
|
||||
}
|
||||
|
||||
func (r *UserRepository) FindByID(id uint) (*models.User, error) {
|
||||
var user models.User
|
||||
err := DB.First(&user, id).Error
|
||||
return &user, err
|
||||
}
|
||||
|
||||
func (r *UserRepository) FindByUsernameOrEmail(login string) (*models.User, error) {
|
||||
var user models.User
|
||||
err := DB.Where("username = ? OR email = ?", login, login).First(&user).Error
|
||||
return &user, err
|
||||
}
|
||||
|
||||
func (r *UserRepository) FindByUsername(username string) (*models.User, error) {
|
||||
var user models.User
|
||||
err := DB.Where("username = ?", username).First(&user).Error
|
||||
return &user, err
|
||||
}
|
||||
|
||||
func (r *UserRepository) FindByEmail(email string) (*models.User, error) {
|
||||
var user models.User
|
||||
err := DB.Where("email = ?", email).First(&user).Error
|
||||
return &user, err
|
||||
}
|
||||
|
||||
func (r *UserRepository) ExistsByUsernameOrEmail(username, email string) bool {
|
||||
var count int64
|
||||
DB.Model(&models.User{}).Where("username = ? OR email = ?", username, email).Count(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
func (r *UserRepository) Create(user *models.User) error {
|
||||
return DB.Create(user).Error
|
||||
}
|
||||
|
||||
func (r *UserRepository) Update(user *models.User) error {
|
||||
return DB.Save(user).Error
|
||||
}
|
||||
|
||||
func (r *UserRepository) UpdateField(id uint, field string, value interface{}) error {
|
||||
return DB.Model(&models.User{}).Where("id = ?", id).Update(field, value).Error
|
||||
}
|
||||
|
||||
func (r *UserRepository) UpdateLastActive(id uint) error {
|
||||
now := time.Now()
|
||||
return r.UpdateField(id, "last_active", now)
|
||||
}
|
||||
|
||||
func (r *UserRepository) Delete(id uint) error {
|
||||
return DB.Delete(&models.User{}, id).Error
|
||||
}
|
||||
|
||||
func (r *UserRepository) Count() (int64, error) {
|
||||
var count int64
|
||||
err := DB.Model(&models.User{}).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *UserRepository) CountOnline() (int64, error) {
|
||||
var count int64
|
||||
fiveMinutesAgo := time.Now().Add(-5 * time.Minute)
|
||||
err := DB.Model(&models.User{}).Where("last_active > ?", fiveMinutesAgo).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *UserRepository) CountByRole(role string) (int64, error) {
|
||||
var count int64
|
||||
err := DB.Model(&models.User{}).Where("role = ?", role).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *UserRepository) FindPending() ([]models.User, error) {
|
||||
var users []models.User
|
||||
err := DB.Where("status = ?", "pending").Order("created_at ASC").Find(&users).Error
|
||||
return users, err
|
||||
}
|
||||
|
||||
func (r *UserRepository) FindAll() ([]models.User, error) {
|
||||
var users []models.User
|
||||
err := DB.Order("id").Find(&users).Error
|
||||
return users, err
|
||||
}
|
||||
|
||||
func (r *UserRepository) HashPassword(password string) (string, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
func (r *UserRepository) CheckPassword(password, hash string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (r *UserRepository) CreateAdminIfNotExists() error {
|
||||
var count int64
|
||||
DB.Model(&models.User{}).Where("role = ?", "admin").Count(&count)
|
||||
if count == 0 {
|
||||
passwordHash, _ := r.HashPassword("admin123")
|
||||
admin := models.User{
|
||||
Username: "admin",
|
||||
Email: "admin@lv8girl.local",
|
||||
PasswordHash: passwordHash,
|
||||
Role: "admin",
|
||||
Status: "approved",
|
||||
}
|
||||
return DB.Create(&admin).Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
72
internal/routes/routes.go
Normal file
72
internal/routes/routes.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-contrib/sessions/cookie"
|
||||
"github.com/gin-gonic/gin"
|
||||
"lv8girl/internal/config"
|
||||
"lv8girl/internal/controllers"
|
||||
"lv8girl/internal/middleware"
|
||||
)
|
||||
|
||||
func SetupRouter() *gin.Engine {
|
||||
r := gin.Default()
|
||||
|
||||
cfg := config.GetConfig()
|
||||
store := cookie.NewStore([]byte(cfg.Session.Secret))
|
||||
r.Use(sessions.Sessions(cfg.Session.Name, store))
|
||||
|
||||
r.Static("/static", "./static")
|
||||
r.Static("/uploads", "./uploads")
|
||||
r.LoadHTMLGlob("templates/*")
|
||||
|
||||
authCtrl := controllers.NewAuthController()
|
||||
homeCtrl := controllers.NewHomeController()
|
||||
discussionCtrl := controllers.NewDiscussionController()
|
||||
userCtrl := controllers.NewUserController()
|
||||
messageCtrl := controllers.NewMessageController()
|
||||
adminCtrl := controllers.NewAdminController()
|
||||
|
||||
r.GET("/", homeCtrl.Index)
|
||||
|
||||
r.GET("/login", authCtrl.ShowLogin)
|
||||
r.POST("/login", authCtrl.Login)
|
||||
r.GET("/register", authCtrl.ShowRegister)
|
||||
r.POST("/register", authCtrl.Register)
|
||||
r.GET("/logout", authCtrl.Logout)
|
||||
|
||||
r.GET("/post/:id", homeCtrl.ShowPost)
|
||||
r.POST("/post/:id/like", middleware.AuthRequired(), homeCtrl.LikePost)
|
||||
r.POST("/post/:id/comment", middleware.AuthRequired(), homeCtrl.AddComment)
|
||||
|
||||
r.GET("/new-post", middleware.AuthRequired(), discussionCtrl.ShowNewPost)
|
||||
r.POST("/new-post", middleware.AuthRequired(), discussionCtrl.CreatePost)
|
||||
|
||||
r.GET("/profile", middleware.AuthRequired(), userCtrl.ShowProfile)
|
||||
r.GET("/profile/:id", userCtrl.ShowProfile)
|
||||
r.POST("/upload-avatar", middleware.AuthRequired(), userCtrl.UploadAvatar)
|
||||
|
||||
r.GET("/messages", middleware.AuthRequired(), messageCtrl.ShowMessages)
|
||||
r.GET("/send-message", middleware.AuthRequired(), messageCtrl.ShowSendMessage)
|
||||
r.POST("/send-message", middleware.AuthRequired(), messageCtrl.SendMessage)
|
||||
|
||||
admin := r.Group("/admin")
|
||||
admin.Use(middleware.AdminRequired())
|
||||
{
|
||||
admin.GET("", adminCtrl.Dashboard)
|
||||
admin.GET("/", adminCtrl.Dashboard)
|
||||
admin.GET("/pending_posts", adminCtrl.PendingPosts)
|
||||
admin.GET("/pending_posts/:action/:id", adminCtrl.ApprovePost)
|
||||
admin.GET("/pending_users", adminCtrl.PendingUsers)
|
||||
admin.GET("/pending_users/:action/:id", adminCtrl.ApproveUser)
|
||||
admin.GET("/posts", adminCtrl.Posts)
|
||||
admin.GET("/posts/delete/:id", adminCtrl.DeletePost)
|
||||
admin.GET("/users", adminCtrl.Users)
|
||||
admin.POST("/users/role", adminCtrl.UpdateUserRole)
|
||||
admin.GET("/users/delete/:id", adminCtrl.DeleteUser)
|
||||
admin.GET("/comments", adminCtrl.Comments)
|
||||
admin.GET("/comments/delete/:id", adminCtrl.DeleteComment)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
98
internal/services/admin.go
Normal file
98
internal/services/admin.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"lv8girl/internal/models"
|
||||
"lv8girl/internal/repositories"
|
||||
)
|
||||
|
||||
type AdminService struct {
|
||||
userRepo *repositories.UserRepository
|
||||
discussionRepo *repositories.DiscussionRepository
|
||||
commentRepo *repositories.CommentRepository
|
||||
likeRepo *repositories.LikeRepository
|
||||
}
|
||||
|
||||
func NewAdminService() *AdminService {
|
||||
return &AdminService{
|
||||
userRepo: repositories.NewUserRepository(),
|
||||
discussionRepo: repositories.NewDiscussionRepository(),
|
||||
commentRepo: repositories.NewCommentRepository(),
|
||||
likeRepo: repositories.NewLikeRepository(),
|
||||
}
|
||||
}
|
||||
|
||||
type Stats struct {
|
||||
Posts int64
|
||||
Users int64
|
||||
Comments int64
|
||||
Likes int64
|
||||
Online int64
|
||||
Approved int64
|
||||
Rejected int64
|
||||
Pending int64
|
||||
}
|
||||
|
||||
func (s *AdminService) GetStats() (*Stats, error) {
|
||||
stats := &Stats{}
|
||||
stats.Posts, _ = s.discussionRepo.Count()
|
||||
stats.Users, _ = s.userRepo.Count()
|
||||
stats.Comments, _ = s.commentRepo.Count()
|
||||
stats.Likes, _ = s.likeRepo.Count()
|
||||
stats.Online, _ = s.userRepo.CountOnline()
|
||||
stats.Approved, _ = s.discussionRepo.CountByStatus("approved")
|
||||
stats.Rejected, _ = s.discussionRepo.CountByStatus("rejected")
|
||||
stats.Pending, _ = s.discussionRepo.CountByStatus("pending")
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func (s *AdminService) GetPendingPosts() ([]models.Discussion, error) {
|
||||
return s.discussionRepo.FindPending()
|
||||
}
|
||||
|
||||
func (s *AdminService) GetPendingUsers() ([]models.User, error) {
|
||||
return s.userRepo.FindPending()
|
||||
}
|
||||
|
||||
func (s *AdminService) ApprovePost(id uint) error {
|
||||
return s.discussionRepo.UpdateStatus(id, "approved")
|
||||
}
|
||||
|
||||
func (s *AdminService) RejectPost(id uint) error {
|
||||
return s.discussionRepo.UpdateStatus(id, "rejected")
|
||||
}
|
||||
|
||||
func (s *AdminService) DeletePost(id uint) error {
|
||||
return s.discussionRepo.Delete(id)
|
||||
}
|
||||
|
||||
func (s *AdminService) ApproveUser(id uint) error {
|
||||
return s.userRepo.UpdateField(id, "status", "approved")
|
||||
}
|
||||
|
||||
func (s *AdminService) RejectUser(id uint) error {
|
||||
return s.userRepo.UpdateField(id, "status", "rejected")
|
||||
}
|
||||
|
||||
func (s *AdminService) UpdateUserRole(id uint, role string) error {
|
||||
return s.userRepo.UpdateField(id, "role", role)
|
||||
}
|
||||
|
||||
func (s *AdminService) DeleteUser(id uint) error {
|
||||
return s.userRepo.Delete(id)
|
||||
}
|
||||
|
||||
func (s *AdminService) GetAllPosts() ([]models.Discussion, error) {
|
||||
return s.discussionRepo.FindAll()
|
||||
}
|
||||
|
||||
func (s *AdminService) GetAllUsers() ([]models.User, error) {
|
||||
return s.userRepo.FindAll()
|
||||
}
|
||||
|
||||
func (s *AdminService) GetAllComments() ([]models.Comment, error) {
|
||||
return s.commentRepo.FindAll()
|
||||
}
|
||||
|
||||
func (s *AdminService) DeleteComment(id uint) error {
|
||||
return s.commentRepo.Delete(id)
|
||||
}
|
||||
87
internal/services/auth.go
Normal file
87
internal/services/auth.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"lv8girl/internal/models"
|
||||
"lv8girl/internal/repositories"
|
||||
)
|
||||
|
||||
type AuthService struct {
|
||||
userRepo *repositories.UserRepository
|
||||
}
|
||||
|
||||
func NewAuthService() *AuthService {
|
||||
return &AuthService{
|
||||
userRepo: repositories.NewUserRepository(),
|
||||
}
|
||||
}
|
||||
|
||||
type LoginResult struct {
|
||||
Success bool
|
||||
User *models.User
|
||||
Error string
|
||||
}
|
||||
|
||||
func (s *AuthService) Login(login, password string) LoginResult {
|
||||
user, err := s.userRepo.FindByUsernameOrEmail(login)
|
||||
if err != nil {
|
||||
return LoginResult{Success: false, Error: "用户名/邮箱或密码错误"}
|
||||
}
|
||||
|
||||
if !s.userRepo.CheckPassword(password, user.PasswordHash) {
|
||||
return LoginResult{Success: false, Error: "用户名/邮箱或密码错误"}
|
||||
}
|
||||
|
||||
switch user.Status {
|
||||
case "pending":
|
||||
return LoginResult{Success: false, Error: "您的账号正在等待管理员审核,请耐心等待。"}
|
||||
case "rejected":
|
||||
return LoginResult{Success: false, Error: "您的账号审核未通过,无法登录。如有疑问,请联系管理员。"}
|
||||
case "approved":
|
||||
if user.Role == "banned" {
|
||||
return LoginResult{Success: false, Error: "您的账号已被封禁,请联系管理员"}
|
||||
}
|
||||
default:
|
||||
return LoginResult{Success: false, Error: "账号状态异常,请联系管理员"}
|
||||
}
|
||||
|
||||
s.userRepo.UpdateLastActive(user.ID)
|
||||
return LoginResult{Success: true, User: user}
|
||||
}
|
||||
|
||||
type RegisterResult struct {
|
||||
Success bool
|
||||
Error string
|
||||
}
|
||||
|
||||
func (s *AuthService) Register(username, email, password string) RegisterResult {
|
||||
if len(username) < 3 || len(username) > 20 {
|
||||
return RegisterResult{Success: false, Error: "用户名长度必须在3-20个字符之间"}
|
||||
}
|
||||
|
||||
if s.userRepo.ExistsByUsernameOrEmail(username, email) {
|
||||
return RegisterResult{Success: false, Error: "用户名或邮箱已被注册"}
|
||||
}
|
||||
|
||||
passwordHash, err := s.userRepo.HashPassword(password)
|
||||
if err != nil {
|
||||
return RegisterResult{Success: false, Error: "注册失败,请稍后重试"}
|
||||
}
|
||||
|
||||
user := &models.User{
|
||||
Username: username,
|
||||
Email: email,
|
||||
PasswordHash: passwordHash,
|
||||
Role: "user",
|
||||
Status: "pending",
|
||||
}
|
||||
|
||||
if err := s.userRepo.Create(user); err != nil {
|
||||
return RegisterResult{Success: false, Error: "注册失败,请稍后重试"}
|
||||
}
|
||||
|
||||
return RegisterResult{Success: true}
|
||||
}
|
||||
|
||||
func (s *AuthService) InitAdmin() error {
|
||||
return s.userRepo.CreateAdminIfNotExists()
|
||||
}
|
||||
92
internal/services/discussion.go
Normal file
92
internal/services/discussion.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"lv8girl/internal/models"
|
||||
"lv8girl/internal/repositories"
|
||||
)
|
||||
|
||||
type DiscussionService struct {
|
||||
discussionRepo *repositories.DiscussionRepository
|
||||
likeRepo *repositories.LikeRepository
|
||||
commentRepo *repositories.CommentRepository
|
||||
}
|
||||
|
||||
func NewDiscussionService() *DiscussionService {
|
||||
return &DiscussionService{
|
||||
discussionRepo: repositories.NewDiscussionRepository(),
|
||||
likeRepo: repositories.NewLikeRepository(),
|
||||
commentRepo: repositories.NewCommentRepository(),
|
||||
}
|
||||
}
|
||||
|
||||
type PostDetailView struct {
|
||||
Post *models.Discussion
|
||||
LikeCount int64
|
||||
CommentCount int64
|
||||
UserLiked bool
|
||||
AuthorPostCount int64
|
||||
}
|
||||
|
||||
func (s *DiscussionService) GetPostDetail(postID, userID uint) (*PostDetailView, error) {
|
||||
post, err := s.discussionRepo.FindByID(postID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
likeCount, _ := s.likeRepo.CountByPostID(postID)
|
||||
commentCount, _ := s.commentRepo.CountByPostID(postID)
|
||||
userLiked := s.likeRepo.Exists(postID, userID)
|
||||
authorPostCount, _ := s.discussionRepo.CountByUserID(post.UserID)
|
||||
|
||||
return &PostDetailView{
|
||||
Post: post,
|
||||
LikeCount: likeCount,
|
||||
CommentCount: commentCount,
|
||||
UserLiked: userLiked,
|
||||
AuthorPostCount: authorPostCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *DiscussionService) IncrementViews(postID uint) error {
|
||||
return s.discussionRepo.IncrementViews(postID)
|
||||
}
|
||||
|
||||
func (s *DiscussionService) AddLike(postID, userID uint) error {
|
||||
if s.likeRepo.Exists(postID, userID) {
|
||||
return nil
|
||||
}
|
||||
|
||||
like := &models.Like{
|
||||
PostID: postID,
|
||||
UserID: userID,
|
||||
}
|
||||
return s.likeRepo.Create(like)
|
||||
}
|
||||
|
||||
func (s *DiscussionService) AddComment(postID, userID uint, content string) error {
|
||||
comment := &models.Comment{
|
||||
PostID: postID,
|
||||
UserID: userID,
|
||||
Content: content,
|
||||
}
|
||||
return s.commentRepo.Create(comment)
|
||||
}
|
||||
|
||||
func (s *DiscussionService) GetComments(postID uint) ([]models.Comment, error) {
|
||||
return s.commentRepo.FindByPostID(postID)
|
||||
}
|
||||
|
||||
func (s *DiscussionService) CreatePost(userID uint, title, content, imagePath string) error {
|
||||
post := &models.Discussion{
|
||||
UserID: userID,
|
||||
Title: title,
|
||||
Content: content,
|
||||
ImagePath: imagePath,
|
||||
Status: "pending",
|
||||
}
|
||||
return s.discussionRepo.Create(post)
|
||||
}
|
||||
|
||||
func (s *DiscussionService) GetApprovedPosts(limit int) ([]repositories.PostListItem, error) {
|
||||
return s.discussionRepo.GetPostList(limit)
|
||||
}
|
||||
97
internal/services/message.go
Normal file
97
internal/services/message.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"lv8girl/internal/models"
|
||||
"lv8girl/internal/repositories"
|
||||
)
|
||||
|
||||
type MessageService struct {
|
||||
messageRepo *repositories.MessageRepository
|
||||
userRepo *repositories.UserRepository
|
||||
}
|
||||
|
||||
func NewMessageService() *MessageService {
|
||||
return &MessageService{
|
||||
messageRepo: repositories.NewMessageRepository(),
|
||||
userRepo: repositories.NewUserRepository(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MessageService) SendMessage(fromUserID, toUserID uint, content string) error {
|
||||
message := &models.PrivateMessage{
|
||||
FromUserID: fromUserID,
|
||||
ToUserID: toUserID,
|
||||
Content: content,
|
||||
IsRead: false,
|
||||
}
|
||||
return s.messageRepo.Create(message)
|
||||
}
|
||||
|
||||
func (s *MessageService) GetUnreadCount(userID uint) (int64, error) {
|
||||
return s.messageRepo.CountUnread(userID)
|
||||
}
|
||||
|
||||
type ConversationView struct {
|
||||
UserID uint
|
||||
Username string
|
||||
Avatar string
|
||||
LastMsg string
|
||||
Time string
|
||||
Unread int64
|
||||
}
|
||||
|
||||
func (s *MessageService) GetConversations(userID uint) ([]ConversationView, error) {
|
||||
messages, err := s.messageRepo.FindConversations(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userMap := make(map[uint]bool)
|
||||
for _, msg := range messages {
|
||||
if msg.FromUserID != userID {
|
||||
userMap[msg.FromUserID] = true
|
||||
}
|
||||
if msg.ToUserID != userID {
|
||||
userMap[msg.ToUserID] = true
|
||||
}
|
||||
}
|
||||
|
||||
var conversations []ConversationView
|
||||
for otherID := range userMap {
|
||||
otherUser, err := s.userRepo.FindByID(otherID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
lastMsg, _ := s.messageRepo.FindLastMessage(userID, otherID)
|
||||
unread, _ := s.messageRepo.CountUnreadFromUser(otherID, userID)
|
||||
|
||||
avatar := ""
|
||||
if otherUser.Avatar != "" {
|
||||
avatar = "/" + otherUser.Avatar
|
||||
}
|
||||
|
||||
conversations = append(conversations, ConversationView{
|
||||
UserID: otherUser.ID,
|
||||
Username: otherUser.Username,
|
||||
Avatar: avatar,
|
||||
LastMsg: lastMsg.Content,
|
||||
Time: lastMsg.CreatedAt.Format("2006-01-02 15:04"),
|
||||
Unread: unread,
|
||||
})
|
||||
}
|
||||
|
||||
return conversations, nil
|
||||
}
|
||||
|
||||
func (s *MessageService) NotifyUserApproved(adminID, userID uint) error {
|
||||
return s.SendMessage(adminID, userID, "恭喜!您的账号已通过管理员审核,现在可以正常登录使用了。")
|
||||
}
|
||||
|
||||
func (s *MessageService) NotifyUserRejected(adminID, userID uint) error {
|
||||
return s.SendMessage(adminID, userID, "您的账号审核未通过。如有疑问,请联系管理员。")
|
||||
}
|
||||
|
||||
func (s *MessageService) NotifyUserBanned(adminID, userID uint) error {
|
||||
return s.SendMessage(adminID, userID, "您的账号已被管理员封禁。如有疑问,请联系管理员。")
|
||||
}
|
||||
94
internal/services/user.go
Normal file
94
internal/services/user.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"lv8girl/internal/models"
|
||||
"lv8girl/internal/repositories"
|
||||
)
|
||||
|
||||
type UserService struct {
|
||||
userRepo *repositories.UserRepository
|
||||
discussionRepo *repositories.DiscussionRepository
|
||||
commentRepo *repositories.CommentRepository
|
||||
messageRepo *repositories.MessageRepository
|
||||
}
|
||||
|
||||
func NewUserService() *UserService {
|
||||
return &UserService{
|
||||
userRepo: repositories.NewUserRepository(),
|
||||
discussionRepo: repositories.NewDiscussionRepository(),
|
||||
commentRepo: repositories.NewCommentRepository(),
|
||||
messageRepo: repositories.NewMessageRepository(),
|
||||
}
|
||||
}
|
||||
|
||||
type UserProfileView struct {
|
||||
User *models.User
|
||||
Posts []models.Discussion
|
||||
PostCount int64
|
||||
UnreadCount int64
|
||||
IsOwner bool
|
||||
}
|
||||
|
||||
func (s *UserService) GetUserProfile(viewerID, targetUserID uint) (*UserProfileView, error) {
|
||||
user, err := s.userRepo.FindByID(targetUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
posts, _ := s.discussionRepo.FindByUserID(targetUserID)
|
||||
postCount, _ := s.discussionRepo.CountByUserID(targetUserID)
|
||||
|
||||
var unreadCount int64
|
||||
if viewerID != 0 {
|
||||
unreadCount, _ = s.messageRepo.CountUnread(viewerID)
|
||||
}
|
||||
|
||||
return &UserProfileView{
|
||||
User: user,
|
||||
Posts: posts,
|
||||
PostCount: postCount,
|
||||
UnreadCount: unreadCount,
|
||||
IsOwner: viewerID == targetUserID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *UserService) UpdateAvatar(userID uint, avatarPath string) error {
|
||||
user, err := s.userRepo.FindByID(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.userRepo.UpdateField(user.ID, "avatar", avatarPath)
|
||||
}
|
||||
|
||||
func (s *UserService) GetUserByID(id uint) (*models.User, error) {
|
||||
return s.userRepo.FindByID(id)
|
||||
}
|
||||
|
||||
func (s *UserService) UpdateUserStatus(id uint, status string) error {
|
||||
return s.userRepo.UpdateField(id, "status", status)
|
||||
}
|
||||
|
||||
func (s *UserService) UpdateUserRole(id uint, role string) error {
|
||||
return s.userRepo.UpdateField(id, "role", role)
|
||||
}
|
||||
|
||||
func (s *UserService) DeleteUser(id uint) error {
|
||||
return s.userRepo.Delete(id)
|
||||
}
|
||||
|
||||
func (s *UserService) GetPendingUsers() ([]models.User, error) {
|
||||
return s.userRepo.FindPending()
|
||||
}
|
||||
|
||||
func (s *UserService) GetAllUsers() ([]models.User, error) {
|
||||
return s.userRepo.FindAll()
|
||||
}
|
||||
|
||||
func (s *UserService) GetUserStats() (int64, int64, error) {
|
||||
total, err := s.userRepo.Count()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
online, err := s.userRepo.CountOnline()
|
||||
return total, online, err
|
||||
}
|
||||
9
internal/utils/utils.go
Normal file
9
internal/utils/utils.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package utils
|
||||
|
||||
func Substring(s string, length int) string {
|
||||
runes := []rune(s)
|
||||
if len(runes) <= length {
|
||||
return s
|
||||
}
|
||||
return string(runes[:length])
|
||||
}
|
||||
Reference in New Issue
Block a user