Skip to content

Commit

Permalink
Merge pull request #93 from Pomog/yurii_dev
Browse files Browse the repository at this point in the history
moderator
  • Loading branch information
TartuDen committed Jan 7, 2024
2 parents 9cee2cc + 2943d73 commit bb5c56e
Show file tree
Hide file tree
Showing 23 changed files with 573 additions and 133 deletions.
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

0 comments on commit bb5c56e

Please sign in to comment.