Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

moderator #93

Merged
merged 5 commits into from
Jan 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# FFForum
Online forum for MMORPG fans and friends!
![Forum Home Page](static/readme_images/image.png)
![Forum Home Page](static/readme_images/homePageScreen.png)

## Access the Web Application

Expand All @@ -18,15 +18,23 @@ This is a Web Application written in Golang, utilizing only standard [Go librari
- [How to use Docker](docs/howToUsewDocker.md)

### User Management:
- User Registration: Users can create new accounts.
- Guest Registration: Allow guest users to access limited features or explore the platform without registration.
- Login System: Users can log in using their email and password credentials.
- **User Registration:** Users can create new accounts.
- **Guest Registration:** Allows guest users to access limited features or explore the platform without registration.
- **Login System:** Users can log in using their email and password credentials.
- The registered user can have types: user, moderator.
- To change the user type, registered users should use a Special Code in their personal cabinet.
#### User Types
- **Moderator:** Has special rights, including the ability to see all messages and change the classification for messages.

### Content Creation:
- Topic Creation: Users can create new discussion topics.
- Topic Creation with Image: Option to include images when creating topics.
- Post Creation: Users can contribute to discussions by creating posts.
- Post Creation with Image: Ability to attach images to posts.
- **Topic Creation:** Users can create new discussion topics.
- **Topic Creation with Image:** Option to include images when creating topics.
- **Post Creation:** Users can contribute to discussions by creating posts.
- **Post Creation with Image:** Ability to attach images to posts.
- Forum messages have classifications: unsorted, obscene, illegal, insulting, approved.
- Users without moderator rights can create unsorted messages.
- Users without moderator rights can see all their own messages and approved messages from other users.


### Interaction Features:
- Like/Dislike System: Users can like or dislike posts.
Expand Down Expand Up @@ -64,7 +72,7 @@ The session feature in this project is implemented using the `github.com/google/
- Initializes the database and performs checks to ensure connectivity.

### SQL schema
<img src="static/readme_images/shema.png.jpg?raw=true" alt="example" style="width:30%;">
<img src="static/readme_images/schema.jpg?raw=true" alt="example" style="width:30%;">

### Template Rendering
- We are using in-memory cache to parse HTML templates
Expand Down
4 changes: 4 additions & 0 deletions cmd/web/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ func routes(a *config.AppConfig) http.Handler {
mux.HandleFunc("/login-google", handler.Repo.LoginWithGoogleHandler)
mux.HandleFunc("/google-callback", handler.Repo.CallbackGoogleHandler)

mux.HandleFunc("/moder_panel", handler.Repo.ModerPanelHandler)



return mux
}

Expand Down
4 changes: 4 additions & 0 deletions internal/handler/homeHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ func processThreadInfo(m *Repository, thread models.Thread) (models.ThreadDataFo
info.Subject = thread.Subject
info.Created = thread.Created.Format("2006-01-02 15:04:05")
info.Category = thread.Category
info.Classification = thread.Classification
info.UserID = user.ID


info.PictureUserWhoCreatedThread = user.Picture
info.UserNameWhoCreatedThread = user.UserName
Expand Down Expand Up @@ -141,6 +144,7 @@ func prepareDataForTemplate(w http.ResponseWriter, r *http.Request, m *Repositor
data["threads"] = threadsInfo
data["loggedAs"] = loggedUser.UserName
data["loggedAsID"] = loggedUser.ID
data["loggedUserType"] = loggedUser.Type

return data, nil
}
Expand Down
125 changes: 125 additions & 0 deletions internal/handler/moderPanelHanlder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package handler

import (
"net/http"
"strconv"

"github.com/Pomog/ForumFFF/internal/models"
"github.com/Pomog/ForumFFF/internal/renderer"
)

func (m *Repository) ModerPanelHandler(w http.ResponseWriter, r *http.Request) {
sessionUserID := m.GetLoggedUser(w, r)
if sessionUserID == 0 {
setErrorAndRedirect(w, r, "unautorized", "/error-page")
return
}
user, err := m.DB.GetUserByID(sessionUserID)
if err != nil {
setErrorAndRedirect(w, r, "Could not get user from: GetUserByID(sessionUserID) "+err.Error(), "/error-page")
return
}
if user.Type != "moder" {
setErrorAndRedirect(w, r, "Unauthorized access, only Moderator can visit this page -"+err.Error(), "/error-page")
return
}

if r.Method == http.MethodGet {
handleGetRequestModerPage(w, r, m, sessionUserID)
} else if r.Method == http.MethodPost {
handlePostRequestModerPage(w, r, m, sessionUserID)
}
}

func handlePostRequestModerPage(w http.ResponseWriter, r *http.Request, m *Repository, sessionUserID int) {
err := r.ParseForm()
if err != nil {
setErrorAndRedirect(w, r, "Could not parse form "+err.Error(), "/error-page")
return
}

selectedCategory := r.FormValue("btnradio")

if r.FormValue("postID") != "" {
postID, err := strconv.Atoi(r.FormValue("postID"))
if err != nil {
setErrorAndRedirect(w, r, "Could not convert string into int "+err.Error(), "/error-page")
return
}
post, err := m.DB.GetPostByID(postID)
if err != nil {
setErrorAndRedirect(w, r, "Could not get post by id "+err.Error(), "/error-page")
return
}
cat := models.TextClassification(selectedCategory)

err = m.DB.EditPostClassification(post, cat)
if err != nil {
setErrorAndRedirect(w, r, "Could not edit post classification "+err.Error(), "/error-page")
return
}
}

if r.FormValue("topicID") != "" {
topicID, err := strconv.Atoi(r.FormValue("topicID"))
if err != nil {
setErrorAndRedirect(w, r, "Could not convert string into int "+err.Error(), "/error-page")
return
}
topic, err := m.DB.GetThreadByID(topicID)
if err != nil {
setErrorAndRedirect(w, r, "Could not get topic by id "+err.Error(), "/error-page")
return
}
cat := models.TextClassification(selectedCategory)

err = m.DB.EditTopicClassification(topic, cat)
if err != nil {
setErrorAndRedirect(w, r, "Could not edit topic classification "+err.Error(), "/error-page")
return
}
}

// Redirect back to the previous page (referer)
http.Redirect(w, r, r.Header.Get("Referer"), http.StatusFound)
}

// handleGetRequest handles GET requests for the home page.
func handleGetRequestModerPage(w http.ResponseWriter, r *http.Request, m *Repository, sessionUserID int) {
topicCat := r.URL.Query().Get("topic")
postCat := r.URL.Query().Get("post")
var topics []models.Thread
var posts []models.Post
var err error
if topicCat != "" && postCat == "" {
topics, err = m.DB.GetAllThreadsByClassification(models.TextClassification(topicCat))
if err != nil {
setErrorAndRedirect(w, r, "Could not get topics by category "+err.Error(), "/error-page")
return
}
} else if topicCat == "" && postCat != "" {
posts, err = m.DB.GetAllPostsByClassification(models.TextClassification(postCat))
if err != nil {
setErrorAndRedirect(w, r, "Could not get posts by category "+err.Error(), "/error-page")
return
}
}

data := make(map[string]interface{})
loggedUser, err := m.DB.GetUserByID(sessionUserID)
if err != nil {
setErrorAndRedirect(w, r, "Could not get logged user from: GetUserByID(sessionUserID) -"+err.Error(), "/error-page")
return
}

data["loggedAs"] = loggedUser.UserName
data["loggedAsID"] = loggedUser.ID
data["loggedUserType"] = loggedUser.Type
data["categories"] = models.Classifications
data["posts"] = posts
data["topics"] = topics

renderer.RendererTemplate(w, "moderMain.page.html", &models.TemplateData{
Data: data,
})
}
4 changes: 4 additions & 0 deletions internal/handler/themeHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ func (m *Repository) ThemeHandler(w http.ResponseWriter, r *http.Request) {
return
}

data["loggedAsID"] = visitorID

renderer.RendererTemplate(w, "theme.page.html", &models.TemplateData{
Data: data,
})
Expand Down Expand Up @@ -250,6 +252,7 @@ func getPostsInfo(m *Repository, w http.ResponseWriter, r *http.Request, threadI
info.UserPostsAmmount = userPostsAmount
info.Likes = likes
info.Dislikes = dislikes
info.Classification = post.Classification
postsInfo = append(postsInfo, info)
}

Expand All @@ -275,6 +278,7 @@ func prepareDataForThemePage(m *Repository, w http.ResponseWriter, r *http.Reque

data["loggedAs"] = loggedUser.UserName
data["loggedAsID"] = loggedUser.ID
data["loggedUserType"] = loggedUser.Type
//__________________________________
creatorPostsAmount, err := m.DB.GetTotalPostsAmmountByUserID(mainThread.UserID)
if err != nil {
Expand Down
12 changes: 12 additions & 0 deletions internal/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type ThreadDataForMainPage struct {
Image string
Category string
Classification string
UserID int
}

type PostDataForThemePage struct {
Expand Down Expand Up @@ -74,3 +75,14 @@ type Votes struct {
DownCount int
PostId int
}

// CommentType represents the type of a comment
type TextClassification string

var Classifications = []TextClassification{"irrelevant",
"obscene",
"illegal",
"insulting",
"unsorted",
"approved",
}
3 changes: 3 additions & 0 deletions internal/renderer/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ var functions = template.FuncMap{
"convertTime": func(post models.Post) string {
return post.Created.Format("2006-01-02 15:04:05")
},
"convertTimeTopic": func(topic models.Thread) string {
return topic.Created.Format("2006-01-02 15:04:05")
},
"shortenPost": func(allPosts []models.Post) string {
var latestPost2 models.Post
latestPost2.Created, _ = time.Parse("2006-01-02 15:04:05", "2006-01-02 15:04:05")
Expand Down
102 changes: 101 additions & 1 deletion internal/repository/dbrepo/sqllite.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ func (m *SqliteBDRepo) DeletePost(post models.Post) error {
return nil
}

// EditTopic updates topic
func (m *SqliteBDRepo) EditTopic(topic models.Thread) error {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
Expand Down Expand Up @@ -689,7 +690,7 @@ func (m *SqliteBDRepo) GetSearchedThreadsByCategory(search string) ([]models.Thr
return threads, nil
}

//EditUserType changes type of user from "user" to "moder"
// EditUserType changes type of user from "user" to "moder"
func (m *SqliteBDRepo) EditUserType(user models.User) error {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
Expand Down Expand Up @@ -725,3 +726,102 @@ func (m *SqliteBDRepo) DelSessionByUserID(userID int) error {
}
return nil
}

// GetAllThreadsByClassification returns all Threads of given classification type, nil if there are no threads in DB
func (m *SqliteBDRepo) GetAllThreadsByClassification(classification models.TextClassification) ([]models.Thread, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

query := `select *
from thread
where classification=$1
`
rows, err := m.DB.QueryContext(ctx, query, classification)
if err != nil {
return nil, err
}
defer rows.Close()

var threads []models.Thread

for rows.Next() {
var thread models.Thread
err := rows.Scan(&thread.ID, &thread.Subject, &thread.Created, &thread.UserID, &thread.Image, &thread.Category, &thread.Classification)
if err != nil {
return nil, err
}
threads = append(threads, thread)
}

return threads, nil
}

// GetAllPostsByClassification returns all slice of all Posts with given classification type, nil if there are no Posts
func (m *SqliteBDRepo) GetAllPostsByClassification(classification models.TextClassification) ([]models.Post, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

query := `select *
from post
where classification = $1
`
rows, err := m.DB.QueryContext(ctx, query, classification)
if err != nil {
return nil, err
}
defer rows.Close()

var posts []models.Post

for rows.Next() {
var post models.Post
err := rows.Scan(&post.ID, &post.Subject, &post.Content, &post.Created, &post.ThreadId, &post.UserID, &post.Image, &post.Classification)
if err != nil {
return nil, err
}
posts = append(posts, post)
}

return posts, nil
}

// EditTopicClassification updates classification of the topic by moderator
func (m *SqliteBDRepo) EditTopicClassification(topic models.Thread, classification models.TextClassification) error {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

stmt := `UPDATE thread
SET classification = $1
WHERE id = $2;
`
_, err := m.DB.ExecContext(ctx, stmt,
classification,
topic.ID,
)

if err != nil {
return err
}
return nil
}

// EditTopicClassification updates classification of the topic by moderator
func (m *SqliteBDRepo) EditPostClassification(post models.Post, classification models.TextClassification) error {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

stmt := `UPDATE post
SET classification = $1
WHERE id = $2;
`

_, err := m.DB.ExecContext(ctx, stmt,
classification,
post.ID,
)

if err != nil {
return err
}
return nil
}
4 changes: 4 additions & 0 deletions internal/repository/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ type DatabaseInt interface {
GetAllLikedPostsByUserID(userID int) ([]models.Post, error)
EditUserType(user models.User) error
DelSessionByUserID(userID int) error
EditPostClassification(post models.Post, classification models.TextClassification) error
EditTopicClassification(topic models.Thread, classification models.TextClassification) error
GetAllPostsByClassification(classification models.TextClassification) ([]models.Post, error)
GetAllThreadsByClassification(classification models.TextClassification) ([]models.Thread, error)
}
Loading