migration to chi
This commit is contained in:
@@ -3,3 +3,5 @@ build/
|
||||
cdn/
|
||||
.DS_Store
|
||||
private.config.json
|
||||
.env
|
||||
*.env
|
||||
Executable → Regular
+6
-6
@@ -1,4 +1,4 @@
|
||||
package v1
|
||||
package database
|
||||
|
||||
type Character struct {
|
||||
ID int32 `json:"id"`
|
||||
@@ -11,17 +11,17 @@ type Character struct {
|
||||
|
||||
func FetchCharacters() ([]*Character, error) {
|
||||
var characters = make([]*Character, 0)
|
||||
err := database.Select(&characters, `select * from characters;`)
|
||||
err := Database.Select(&characters, `select * from characters;`)
|
||||
return characters, err
|
||||
}
|
||||
|
||||
func FetchCharacter(id int) (*Character, error) {
|
||||
var char = &Character{}
|
||||
err := database.Get(char, `select * from characters where id=$1;`, id)
|
||||
err := Database.Get(char, `select * from characters where id=$1;`, id)
|
||||
return char, err
|
||||
}
|
||||
func UpdateCharacter(character *Character) (*Character, error) {
|
||||
rows, err := database.NamedQuery("update characters set original_name=:original_name, localized_name=:localized_name, description=:description, image=:image where id=:id returning *;", character)
|
||||
rows, err := Database.NamedQuery("update characters set original_name=:original_name, localized_name=:localized_name, description=:description, image=:image where id=:id returning *;", character)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -33,12 +33,12 @@ func UpdateCharacter(character *Character) (*Character, error) {
|
||||
return char, err
|
||||
}
|
||||
func DeleteCharacter(id int) error {
|
||||
_, err := database.Exec("delete from characters where id=$1;", id)
|
||||
_, err := Database.Exec("delete from characters where id=$1;", id)
|
||||
return err
|
||||
}
|
||||
|
||||
func FetchCharacterNovels(id int) ([]Novel, error) {
|
||||
var novels = make([]Novel, 0)
|
||||
err := database.Select(&novels, `select id, original_name, localized_name, coalesce(n.image, '') as image from character_novel join novels n on (n.id=character_novel.novel_id) where character_id=$1;`, id)
|
||||
err := Database.Select(&novels, `select id, original_name, localized_name, coalesce(n.image, '') as image from character_novel join novels n on (n.id=character_novel.novel_id) where character_id=$1;`, id)
|
||||
return novels, err
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package v1
|
||||
package database
|
||||
|
||||
type Club struct {
|
||||
ID uint16 `json:"id"`
|
||||
@@ -8,14 +8,14 @@ type Club struct {
|
||||
IsPrivate bool `json:"is_private" db:"is_private"`
|
||||
}
|
||||
|
||||
func GetClubs(userId uint64) ([]*Club, error) {
|
||||
func GetClubs(userId int) ([]*Club, error) {
|
||||
clubs := make([]*Club, 0)
|
||||
err := database.Select(&clubs, "select clubs.* from clubs join club_member cm on clubs.id = cm.club_id where not is_private or cm.user_id=$1;", userId)
|
||||
err := Database.Select(&clubs, "select clubs.* from clubs join club_member cm on clubs.id = cm.club_id where not is_private or cm.user_id=$1;", userId)
|
||||
return clubs, err
|
||||
}
|
||||
|
||||
func GetClub(id uint16, userId uint64) (*Club, error) {
|
||||
func GetClub(id int, userId uint64) (*Club, error) {
|
||||
club := new(Club)
|
||||
err := database.Get(club, "select clubs.* from clubs join club_member cm on clubs.id = cm.club_id where club_id=$1 and (not is_private or cm.user_id=$2);", id, userId)
|
||||
err := Database.Get(club, "select clubs.* from clubs join club_member cm on clubs.id = cm.club_id where club_id=$1 and (not is_private or cm.user_id=$2);", id, userId)
|
||||
return club, err
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/lib/pq"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var Database *sqlx.DB
|
||||
|
||||
func ConnectToDB() {
|
||||
var err error
|
||||
|
||||
host := os.Getenv("DB_HOST")
|
||||
user := os.Getenv("DB_USER")
|
||||
password := os.Getenv("DB_PASSWORD")
|
||||
name := os.Getenv("DB_NAME")
|
||||
|
||||
url := fmt.Sprintf("host=%s port=5432 user=%s password=%s dbname=%s sslmode=disable", host, user, password, name)
|
||||
Database, err = sqlx.Open("postgres", url)
|
||||
if err != nil {
|
||||
sentry.CaptureException(err)
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Printf("Connected to DB with name %v as %v on %v\n", name, user, host)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package database
|
||||
|
||||
import "time"
|
||||
|
||||
type Game struct {
|
||||
ID int32 `json:"id"`
|
||||
OriginalName string `json:"original_name" db:"original_name"`
|
||||
LocalizedName string `json:"localized_name" db:"localized_name"`
|
||||
Description string `json:"description"`
|
||||
Image string `json:"image"`
|
||||
ReleaseDate time.Time `json:"release_date" db:"release_date"`
|
||||
}
|
||||
|
||||
func FetchGame(id int32) (Game, error) {
|
||||
var game Game
|
||||
err := Database.Get(&game, "select * from games where id = $1 limit 1;", id)
|
||||
return game, err
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package v1
|
||||
package database
|
||||
|
||||
// TODO
|
||||
type Genre struct {
|
||||
@@ -9,18 +9,18 @@ type Genre struct {
|
||||
|
||||
func FetchGenres() ([]*Genre, error) {
|
||||
var genres = make([]*Genre, 0)
|
||||
err := database.Select(&genres, "select * from genres;")
|
||||
err := Database.Select(&genres, "select * from genres;")
|
||||
return genres, err
|
||||
}
|
||||
|
||||
func FetchGenre(id int) (*Genre, error) {
|
||||
var genre = &Genre{}
|
||||
err := database.Get(genre, "select * from genres where id = $1;", id)
|
||||
err := Database.Get(genre, "select * from genres where id = $1;", id)
|
||||
return genre, err
|
||||
}
|
||||
|
||||
func FetchGenreNovels(id int) ([]*Novel, error) {
|
||||
var novels = make([]*Novel, 0)
|
||||
err := database.Select(&novels, "select novels.* from novel_genre join novels on (novel_genre.novel_id = novels.id) where genre_id = $1;", id)
|
||||
err := Database.Select(&novels, "select novels.* from novel_genre join novels on (novel_genre.novel_id = novels.id) where genre_id = $1;", id)
|
||||
return novels, err
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package database
|
||||
|
||||
type Group struct {
|
||||
ID uint8 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Color string `json:"color"`
|
||||
MaxAvatarSize int `json:"max_avatar_size" db:"max_ava_size"`
|
||||
MaxAvaWeight int64 `json:"max_avatar_weight" db:"max_ava_weight"`
|
||||
IsSuperuser bool `json:"is_superuser" db:"is_superuser"`
|
||||
Permissions string `json:"permissions"`
|
||||
}
|
||||
|
||||
func GetGroups() ([]*Group, error) {
|
||||
var groups = make([]*Group, 0)
|
||||
err := Database.Select(&groups, "select * from groups;")
|
||||
return groups, err
|
||||
}
|
||||
|
||||
func GetGroup(id int) (*Group, error) {
|
||||
var group = &Group{}
|
||||
err := Database.Get(group, "select * from groups where id=$1;", id)
|
||||
return group, err
|
||||
}
|
||||
Executable → Regular
+7
-7
@@ -1,4 +1,4 @@
|
||||
package v1
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
@@ -16,17 +16,17 @@ type Post struct {
|
||||
|
||||
func FetchTotalNews() (int, error) {
|
||||
count := 0
|
||||
err := database.Get(&count, `select count("news") from news;`)
|
||||
err := Database.Get(&count, `select count("news") from news;`)
|
||||
return count, err
|
||||
}
|
||||
func FetchNews(offset, limit int) ([]Post, error) {
|
||||
news := make([]Post, 0)
|
||||
err := database.Select(&news, "select news.*, u.username as author from news inner join users u on news.author_id = u.id offset $1 limit $2;", offset, limit)
|
||||
err := Database.Select(&news, "select news.*, u.username as author from news inner join users u on news.author_id = u.id offset $1 limit $2;", offset, limit)
|
||||
return news, err
|
||||
}
|
||||
func CreatePost(post *Post) (*Post, error) {
|
||||
destPost := new(Post)
|
||||
r, err := database.NamedQuery("insert into news (title, short_post, full_post, author_id) values (:title, :short_post, :full_post, :author_id) returning *;", post)
|
||||
r, err := Database.NamedQuery("insert into news (title, short_post, full_post, author_id) values (:title, :short_post, :full_post, :author_id) returning *;", post)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -38,14 +38,14 @@ func CreatePost(post *Post) (*Post, error) {
|
||||
|
||||
func FetchPost(id int) (*Post, error) {
|
||||
post := &Post{}
|
||||
err := database.Get(post, "select news.*, u.username as author from news inner join users u on news.author_id = u.id where news.id = $1;", id)
|
||||
err := Database.Get(post, "select news.*, u.username as author from news inner join users u on news.author_id = u.id where news.id = $1;", id)
|
||||
return post, err
|
||||
}
|
||||
func UpdatePost(post *Post) error {
|
||||
_, err := database.NamedExec("update news set title=:title, short_post=:short_post, full_post=:full_post where id=:id;", post)
|
||||
_, err := Database.NamedExec("update news set title=:title, short_post=:short_post, full_post=:full_post where id=:id;", post)
|
||||
return err
|
||||
}
|
||||
func DeletePost(id int) error {
|
||||
_, err := database.Exec("delete from news where id=$1;", id)
|
||||
_, err := Database.Exec("delete from news where id=$1;", id)
|
||||
return err
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package v1
|
||||
package database
|
||||
|
||||
import (
|
||||
"backend/utils"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -23,6 +24,7 @@ type Novel struct {
|
||||
Walkthrough string `json:"walkthrough"`
|
||||
RatingAll []RatingAll `json:"rating_all" ignore:"1"`
|
||||
StatusAll []StatusAll `json:"status_all" ignore:"1"`
|
||||
IsNSFW bool `json:"is_nsfw" db:"is_nsfw"`
|
||||
}
|
||||
|
||||
type ShortNovel struct {
|
||||
@@ -41,6 +43,7 @@ type ShortNovel struct {
|
||||
Links []NovelLink `json:"-" ignore:"1"`
|
||||
Walkthrough string `json:"-"`
|
||||
RatingAll []RatingAll `json:"rating_all" ignore:"1"`
|
||||
IsNSFW bool `json:"is_nsfw" db:"is_nsfw"`
|
||||
}
|
||||
|
||||
type CommentNovel struct {
|
||||
@@ -69,12 +72,12 @@ type StatusAll struct {
|
||||
|
||||
func FetchNovels(orderBy string) ([]*ShortNovel, error) {
|
||||
var novels = make([]*ShortNovel, 0)
|
||||
err := database.Select(&novels, fmt.Sprintf("select id, original_name, localized_name, image, rating, release_date from novels order by %v desc;", orderBy))
|
||||
err := Database.Select(&novels, fmt.Sprintf("select id, original_name, localized_name, image, rating, release_date from novels order by %v desc;", orderBy))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx := database.MustBegin()
|
||||
tx := Database.MustBegin()
|
||||
for _, novel := range novels {
|
||||
novel.Genres = make([]Genre, 0)
|
||||
err = tx.Select(&novel.Genres, "select g.* from novel_genre join genres g on g.id = novel_genre.genre_id where novel_id=$1;", novel.ID)
|
||||
@@ -88,46 +91,46 @@ func FetchNovels(orderBy string) ([]*ShortNovel, error) {
|
||||
|
||||
func FetchNovel(id int) (*Novel, error) {
|
||||
novel := new(Novel)
|
||||
err := database.Get(novel, `select * from novels where id = $1;`, id)
|
||||
err := Database.Get(novel, `select * from novels where id = $1;`, id)
|
||||
if err != nil {
|
||||
return novel, err
|
||||
}
|
||||
|
||||
novel.Genres = make([]Genre, 0)
|
||||
err = database.Select(&novel.Genres, `select genres.* from novel_genre join genres on (novel_genre.genre_id = genres.id) where novel_id = $1`, id)
|
||||
err = Database.Select(&novel.Genres, `select genres.* from novel_genre join genres on (novel_genre.genre_id = genres.id) where novel_id = $1`, id)
|
||||
if err != nil {
|
||||
return novel, err
|
||||
}
|
||||
|
||||
novel.RatingAll = make([]RatingAll, 0)
|
||||
err = database.Select(&novel.RatingAll, `select mark, count(mark) from user_novel where novel_id=$1 and mark > 0 group by mark;`, id)
|
||||
err = Database.Select(&novel.RatingAll, `select mark, count(mark) from user_novel where novel_id=$1 and mark > 0 group by mark;`, id)
|
||||
if err != nil {
|
||||
return novel, err
|
||||
}
|
||||
|
||||
novel.StatusAll = make([]StatusAll, 0)
|
||||
err = database.Select(&novel.StatusAll, `select status, count(status) from user_novel where novel_id=$1 group by status;`, id)
|
||||
err = Database.Select(&novel.StatusAll, `select status, count(status) from user_novel where novel_id=$1 group by status;`, id)
|
||||
if err != nil {
|
||||
return novel, err
|
||||
}
|
||||
|
||||
novel.Links = make([]NovelLink, 0)
|
||||
err = database.Select(&novel.Links, "select * from novel_link where novel_id=$1;", novel.ID)
|
||||
err = Database.Select(&novel.Links, "select * from novel_link where novel_id=$1;", novel.ID)
|
||||
return novel, err
|
||||
}
|
||||
func UpdateNovel(novel *Novel) (*Novel, error) {
|
||||
var newNovel = new(Novel)
|
||||
setPart := ""
|
||||
setPartList := make([]string, 0)
|
||||
for _, i := range GetStructAndNamedForDatabase(newNovel) {
|
||||
for _, i := range utils.GetStructAndNamedForDatabase(newNovel) {
|
||||
setPartList = append(setPartList, i[0]+"="+i[1])
|
||||
}
|
||||
setPart = strings.Join(setPartList, ",")
|
||||
rows, err := database.NamedQuery(fmt.Sprintf(`update novels set %s where id=:id returning *;`, setPart), novel)
|
||||
rows, err := Database.NamedQuery(fmt.Sprintf(`update novels set %s where id=:id returning *;`, setPart), novel)
|
||||
if err != nil {
|
||||
return newNovel, err
|
||||
}
|
||||
err = UpdateNovelGenres(novel.ID, novel.Genres)
|
||||
err = UpdateNovelGenres(int(novel.ID), novel.Genres)
|
||||
if err != nil {
|
||||
return newNovel, err
|
||||
}
|
||||
@@ -136,18 +139,18 @@ func UpdateNovel(novel *Novel) (*Novel, error) {
|
||||
if err != nil {
|
||||
return newNovel, err
|
||||
}
|
||||
genres, err := FetchNovelGenres(novel.ID)
|
||||
genres, err := FetchNovelGenres(int(novel.ID))
|
||||
newNovel.Genres = genres
|
||||
|
||||
return newNovel, err
|
||||
}
|
||||
func UpdateNovelGenres(novelId int32, genres []Genre) error {
|
||||
_, err := database.Exec("delete from novel_genre where novel_id=$1;", novelId)
|
||||
func UpdateNovelGenres(novelId int, genres []Genre) error {
|
||||
_, err := Database.Exec("delete from novel_genre where novel_id=$1;", novelId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx, err := database.Begin()
|
||||
tx, err := Database.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -164,29 +167,29 @@ func UpdateNovelGenres(novelId int32, genres []Genre) error {
|
||||
return nil
|
||||
}
|
||||
func UpdateNovelCover(novelId int, url string) error {
|
||||
_, err := database.Exec("update novels set image=$1 where id=$2;", url, novelId)
|
||||
_, err := Database.Exec("update novels set image=$1 where id=$2;", url, novelId)
|
||||
return err
|
||||
}
|
||||
|
||||
func CreateNovel(novelData *Novel) (Novel, error) {
|
||||
var novel Novel
|
||||
rows, err := database.NamedQuery(`insert into novels (original_name, localized_name, image, description, is_demo) values (:original_name, :localized_name, :image, :description, :is_demo) returning *;`, novelData)
|
||||
rows, err := Database.NamedQuery(`insert into novels (original_name, localized_name, image, description, is_demo) values (:original_name, :localized_name, :image, :description, :is_demo) returning *;`, novelData)
|
||||
if rows != nil && rows.Next() {
|
||||
_ = rows.StructScan(&novel)
|
||||
}
|
||||
return novel, err
|
||||
}
|
||||
func DeleteNovel(id int) error {
|
||||
_, err := database.Exec("delete from novels where id = $1;", id)
|
||||
_, err := Database.Exec("delete from novels where id = $1;", id)
|
||||
return err
|
||||
}
|
||||
func SearchNovels(query string) ([]ShortNovel, error) {
|
||||
novels := make([]ShortNovel, 0)
|
||||
err := database.Select(&novels, "select * from search_novel($1) order by rating desc;", query)
|
||||
err := Database.Select(&novels, "select * from search_novel($1) order by rating desc;", query)
|
||||
if err != nil {
|
||||
return novels, err
|
||||
}
|
||||
tx := database.MustBegin()
|
||||
tx := Database.MustBegin()
|
||||
for _, novel := range novels {
|
||||
novel.Genres = make([]Genre, 0)
|
||||
err = tx.Select(&novel.Genres, "select g.* from novel_genre join genres g on g.id = novel_genre.genre_id where novel_id=$1;", novel.ID)
|
||||
@@ -200,12 +203,12 @@ func SearchNovels(query string) ([]ShortNovel, error) {
|
||||
|
||||
func FetchNovelComments(id int) ([]CommentNovel, error) {
|
||||
var comments = make([]CommentNovel, 0)
|
||||
err := database.Select(&comments, `select * from novel_comment where novel_id = $1;`, id)
|
||||
err := Database.Select(&comments, `select * from novel_comment where novel_id = $1;`, id)
|
||||
return comments, err
|
||||
}
|
||||
func AddNovelComment(commentData CommentNovel) (CommentNovel, error) {
|
||||
var comment CommentNovel
|
||||
rows, err := database.NamedQuery(`insert into novel_comment(user_id, novel_id, text) values (:user_id, :novel_id, :text) returning *;`, commentData)
|
||||
rows, err := Database.NamedQuery(`insert into novel_comment(user_id, novel_id, text) values (:user_id, :novel_id, :text) returning *;`, commentData)
|
||||
if rows != nil && rows.Next() {
|
||||
_ = rows.StructScan(&comment)
|
||||
}
|
||||
@@ -214,12 +217,12 @@ func AddNovelComment(commentData CommentNovel) (CommentNovel, error) {
|
||||
|
||||
func FetchNovelCharacters(id int) ([]Character, error) {
|
||||
var characters = make([]Character, 0)
|
||||
err := database.Select(&characters, `select characters.*, character_novel.main from character_novel join characters on (characters.id=character_novel.character_id) where novel_id=$1;`, id)
|
||||
err := Database.Select(&characters, `select characters.*, character_novel.main from character_novel join characters on (characters.id=character_novel.character_id) where novel_id=$1;`, id)
|
||||
return characters, err
|
||||
}
|
||||
|
||||
func FetchNovelGenres(id int32) ([]Genre, error) {
|
||||
func FetchNovelGenres(id int) ([]Genre, error) {
|
||||
var genres = make([]Genre, 0)
|
||||
err := database.Select(&genres, `select genres.* from novel_genre join genres on (novel_genre.genre_id = genres.id) where novel_id = $1`, id)
|
||||
err := Database.Select(&genres, `select genres.* from novel_genre join genres on (novel_genre.genre_id = genres.id) where novel_id = $1`, id)
|
||||
return genres, err
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package database
|
||||
|
||||
type App struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Secret string `json:"-"`
|
||||
IsVerified bool `json:"is_verified" db:"is_verified"`
|
||||
}
|
||||
|
||||
func FetchApp(id int) (*App, error) {
|
||||
var app = &App{}
|
||||
err := Database.Get(app, "select * from apps where id=$1;", id)
|
||||
return app, err
|
||||
}
|
||||
|
||||
func FetchApps() ([]*App, error) {
|
||||
var apps = make([]*App, 0)
|
||||
err := Database.Select(&apps, "select * from apps;")
|
||||
return apps, err
|
||||
}
|
||||
Executable → Regular
+32
-68
@@ -1,11 +1,9 @@
|
||||
package v1
|
||||
package database
|
||||
|
||||
import (
|
||||
"backend/utils"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
@@ -26,6 +24,12 @@ type User struct {
|
||||
IsBanned bool `json:"is_banned" db:"is_banned"`
|
||||
}
|
||||
|
||||
type UserSettings struct {
|
||||
UseGravatar bool `json:"use_gravatar" db:"use_gravatar"`
|
||||
Avatar string `json:"avatar"`
|
||||
Theme string `json:"theme"`
|
||||
}
|
||||
|
||||
type UserNovels struct {
|
||||
UserID int64 `json:"user_id" db:"user_id" sql:"REFERENCES users(id)"`
|
||||
NovelID int16 `json:"novel_id" db:"novel_id" sql:"REFERENCES novels(id)"`
|
||||
@@ -44,29 +48,30 @@ var (
|
||||
|
||||
func FetchUsers() ([]*User, error) {
|
||||
var users = make([]*User, 0)
|
||||
err := database.Select(&users, "select * from users limit 100;")
|
||||
err := Database.Select(&users, "select * from users limit 20;")
|
||||
for _, user := range users {
|
||||
if user.UseGravatar {
|
||||
user.Avatar = GetUserGravatar(user.Email)
|
||||
}
|
||||
err = Database.Get(&user.Group, `select * from groups where id = $1;`, user.GroupID)
|
||||
}
|
||||
return users, err
|
||||
}
|
||||
|
||||
func FetchUser(id int) (*User, error) {
|
||||
var user = &User{}
|
||||
err := database.Get(user, `select users.* from users where users.id = $1;`, id)
|
||||
user := new(User)
|
||||
err := Database.Get(user, `select users.* from users where users.id = $1;`, id)
|
||||
if err == nil {
|
||||
if user.UseGravatar {
|
||||
user.Avatar = GetUserGravatar(user.Email)
|
||||
}
|
||||
err = database.Get(&user.Group, `select * from groups where id = $1;`, user.GroupID)
|
||||
err = Database.Get(&user.Group, `select * from groups where id = $1;`, user.GroupID)
|
||||
}
|
||||
return user, err
|
||||
}
|
||||
func UpdateUser(user *User) (*User, error) {
|
||||
var u = &User{}
|
||||
rows, err := database.NamedQuery(`update users set avatar=:avatar, username=:username, is_activated=:is_activated, use_gravatar=:use_gravatar where id=:id returning *;`, user)
|
||||
rows, err := Database.NamedQuery(`update users set avatar=:avatar, username=:username, is_activated=:is_activated, use_gravatar=:use_gravatar where id=:id returning *;`, user)
|
||||
if rows != nil && rows.Next() {
|
||||
_ = rows.StructScan(u)
|
||||
if u.UseGravatar {
|
||||
@@ -76,72 +81,31 @@ func UpdateUser(user *User) (*User, error) {
|
||||
return u, err
|
||||
}
|
||||
func UpdatePassword(id int, password string) error {
|
||||
_, err := database.Exec("update users set password=$1 where id=$2;", password, id)
|
||||
_, err := Database.Exec("update users set password=$1 where id=$2;", password, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func FetchCurrentUser(r *http.Request) (*User, error) {
|
||||
cookie, err := r.Cookie("token")
|
||||
if err == http.ErrNoCookie {
|
||||
return nil, ErrNotAuthorized
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(cookie.Value) == 0 {
|
||||
return nil, ErrNotAuthorized
|
||||
}
|
||||
return FetchUserByToken(cookie.Value)
|
||||
}
|
||||
|
||||
func FetchUserByToken(tokenString string) (*User, error) {
|
||||
var user = &User{}
|
||||
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (i interface{}, e error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return []byte(privateConfig.AuthTokenSecret), nil
|
||||
})
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
if !token.Valid {
|
||||
return user, fmt.Errorf("token not valid")
|
||||
}
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok {
|
||||
err = database.Get(user, `select users.* from users where users.id = $1;`, claims["id"])
|
||||
if err == nil {
|
||||
if user.UseGravatar {
|
||||
user.Avatar = GetUserGravatar(user.Email)
|
||||
}
|
||||
err = database.Get(&user.Group, `select * from groups where id = $1;`, user.GroupID)
|
||||
}
|
||||
}
|
||||
return user, err
|
||||
}
|
||||
|
||||
func FetchUserByLogin(login string) (*User, error) {
|
||||
user := new(User)
|
||||
err := database.Get(user, `select * from users where username = $1 or email = $1;`, login)
|
||||
err := Database.Get(user, `select * from users where username = $1 or email = $1;`, login)
|
||||
if err == nil {
|
||||
if user.UseGravatar {
|
||||
user.Avatar = GetUserGravatar(user.Email)
|
||||
}
|
||||
err = database.Get(&user.Group, `select * from groups where id = $1;`, user.GroupID)
|
||||
err = Database.Get(&user.Group, `select * from groups where id = $1;`, user.GroupID)
|
||||
}
|
||||
return user, err
|
||||
}
|
||||
|
||||
func FetchUserNovels(id int) ([]*UserNovelWithInfo, error) {
|
||||
var novels = make([]*UserNovelWithInfo, 0)
|
||||
err := database.Select(&novels, `select novels.*, user_novel.* from user_novel inner join novels on (novels.id = user_novel.novel_id) where user_id = $1;`, id)
|
||||
err := Database.Select(&novels, `select novels.*, user_novel.* from user_novel inner join novels on (novels.id = user_novel.novel_id) where user_id = $1;`, id)
|
||||
return novels, err
|
||||
}
|
||||
|
||||
func CreateUserNovel(novelData *UserNovels) (*UserNovels, error) {
|
||||
var novel = &UserNovels{}
|
||||
rows, err := database.NamedQuery(`insert into user_novel(user_id, novel_id, mark, status) VALUES (:user_id, :novel_id, :mark, :status) returning *;`, novelData)
|
||||
rows, err := Database.NamedQuery(`insert into user_novel(user_id, novel_id, mark, status) VALUES (:user_id, :novel_id, :mark, :status) returning *;`, novelData)
|
||||
if rows != nil && rows.Next() {
|
||||
_ = rows.StructScan(novel)
|
||||
}
|
||||
@@ -149,54 +113,54 @@ func CreateUserNovel(novelData *UserNovels) (*UserNovels, error) {
|
||||
}
|
||||
func FetchUserNovel(userId int, novelId int) (*UserNovels, error) {
|
||||
var novel = &UserNovels{}
|
||||
err := database.Get(novel, `select user_novel.* from user_novel where user_id = $1 and novel_id = $2;`, userId, novelId)
|
||||
err := Database.Get(novel, `select user_novel.* from user_novel where user_id = $1 and novel_id = $2;`, userId, novelId)
|
||||
return novel, err
|
||||
}
|
||||
func UpdateUserNovel(novel *UserNovels) (*UserNovels, error) {
|
||||
var updatedNovel = &UserNovels{}
|
||||
rows, err := database.NamedQuery(`update user_novel set mark=:mark, status=:status where user_id=:user_id and novel_id=:novel_id returning *;`, novel)
|
||||
_, _ = database.Exec(`update novels set rating=avg_novel($1) where id=$1;`, novel.NovelID)
|
||||
rows, err := Database.NamedQuery(`update user_novel set mark=:mark, status=:status where user_id=:user_id and novel_id=:novel_id returning *;`, novel)
|
||||
_, _ = Database.Exec(`update novels set rating=avg_novel($1) where id=$1;`, novel.NovelID)
|
||||
if rows != nil && rows.Next() {
|
||||
_ = rows.StructScan(updatedNovel)
|
||||
}
|
||||
return updatedNovel, err
|
||||
}
|
||||
func DeleteUserNovel(userId int, novelId int) error {
|
||||
_, err := database.Exec("delete from user_novel where user_id = $1 and novel_id = $2;", userId, novelId)
|
||||
_, err := Database.Exec("delete from user_novel where user_id = $1 and novel_id = $2;", userId, novelId)
|
||||
return err
|
||||
}
|
||||
|
||||
func FetchUserNovelsComments(id int) ([]CommentNovel, error) {
|
||||
var comments = make([]CommentNovel, 0)
|
||||
err := database.Select(&comments, `select * from novel_comment where user_id = $1;`, id)
|
||||
err := Database.Select(&comments, `select * from novel_comment where user_id = $1;`, id)
|
||||
return comments, err
|
||||
}
|
||||
|
||||
func CreateUser(username string, password string, email string) (*User, error) {
|
||||
var user = &User{}
|
||||
var currentTime = time.Now()
|
||||
err := database.Get(user, "insert into users(username, password, email, reg_date, last_seen) VALUES ($1, $2, $3, $4, $4) returning *;", username, password, email, currentTime)
|
||||
err := Database.Get(user, "insert into users(username, password, email, reg_date, last_seen) VALUES ($1, $2, $3, $4, $4) returning *;", username, password, email, currentTime)
|
||||
return user, err
|
||||
}
|
||||
func ActivateUser(id int) (*User, error) {
|
||||
var user = &User{}
|
||||
err := database.Get(user, `update users set is_activated=true where id=$1 returning *;`, id)
|
||||
err := Database.Get(user, `update users set is_activated=true where id=$1 returning *;`, id)
|
||||
return user, err
|
||||
}
|
||||
|
||||
func GetUserGravatar(email string) string {
|
||||
return fmt.Sprintf("https://www.gravatar.com/avatar/%s", MD5(email))
|
||||
return fmt.Sprintf("https://www.gravatar.com/avatar/%s", utils.MD5(email))
|
||||
}
|
||||
|
||||
// Settings
|
||||
func FetchUserSettings(id uint64) (*Settings, error) {
|
||||
settings := new(Settings)
|
||||
err := database.Get(settings, "select use_gravatar, avatar, theme from users where id=$1;", id)
|
||||
func FetchUserSettings(id uint64) (*UserSettings, error) {
|
||||
settings := new(UserSettings)
|
||||
err := Database.Get(settings, "select use_gravatar, avatar, theme from users where id=$1;", id)
|
||||
return settings, err
|
||||
}
|
||||
|
||||
func UpdateUserSettings(id uint64, settings *Settings) (*User, error) {
|
||||
func UpdateUserSettings(id uint64, settings *UserSettings) (*User, error) {
|
||||
user := new(User)
|
||||
err := database.Get(user, "update users set use_gravatar=$2, avatar=$3, theme=$4 where id=$1 returning *;", id, settings.UseGravatar, settings.Avatar, settings.Theme)
|
||||
err := Database.Get(user, "update users set use_gravatar=$2, avatar=$3, theme=$4 where id=$1 returning *;", id, settings.UseGravatar, settings.Avatar, settings.Theme)
|
||||
return user, err
|
||||
}
|
||||
@@ -1,14 +1,19 @@
|
||||
module backend
|
||||
|
||||
go 1.14
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/getsentry/sentry-go v0.10.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/jmoiron/sqlx v1.3.3
|
||||
github.com/lib/pq v1.10.1
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
|
||||
google.golang.org/appengine v1.6.6 // indirect
|
||||
github.com/getsentry/sentry-go v0.31.1
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/lib/pq v1.10.9
|
||||
golang.org/x/crypto v0.36.0
|
||||
golang.org/x/time v0.11.0
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
|
||||
@@ -27,18 +28,25 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
|
||||
github.com/getsentry/sentry-go v0.10.0 h1:6gwY+66NHKqyZrdi6O2jGdo7wGdo9b3B69E01NFgT5g=
|
||||
github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
|
||||
github.com/getsentry/sentry-go v0.31.1 h1:ELVc0h7gwyhnXHDouXkhqTFSO5oslsRDk0++eyE0KJ4=
|
||||
github.com/getsentry/sentry-go v0.31.1/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY=
|
||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
||||
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
@@ -64,6 +72,10 @@ github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
|
||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||
github.com/jmoiron/sqlx v1.3.3 h1:j82X0bf7oQ27XeqxicSZsTU5suPwKElg3oyxNn43iTk=
|
||||
github.com/jmoiron/sqlx v1.3.3/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
|
||||
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
@@ -87,6 +99,8 @@ github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
|
||||
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.1 h1:6VXZrLU0jHBYyAqrSPa+MgPfnSvTPuMgK+k0o5kVFWo=
|
||||
github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
@@ -95,6 +109,7 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y
|
||||
github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
|
||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||
@@ -161,6 +176,8 @@ golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@@ -182,14 +199,24 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI=
|
||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
|
||||
@@ -1,52 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
v1 "backend/v1"
|
||||
"backend/database"
|
||||
"backend/routes"
|
||||
"backend/utils"
|
||||
"fmt"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/joho/godotenv"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var router *mux.Router
|
||||
var config *v1.Config
|
||||
var router chi.Router
|
||||
|
||||
func init() {
|
||||
v1.LoadPrivateConfig()
|
||||
log.Println("Private config was loaded")
|
||||
err := godotenv.Load(".env", ".private.env", ".db.env", ".email.env")
|
||||
if err != nil {
|
||||
log.Fatal("Error loading .env file")
|
||||
}
|
||||
|
||||
config = v1.LoadConfig()
|
||||
log.Println("Config was loaded")
|
||||
log.Println(fmt.Sprintf("Initizalized CDN path at %v", config.CDNPath))
|
||||
log.Println(fmt.Sprintf("Initizalized CDN URL at %v", config.CDNUrl))
|
||||
log.Println(".env files loaded")
|
||||
|
||||
err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: "https://b4bdbdbf01b344178b6fd69920ab898c@sentry.io/1817643",
|
||||
log.Println(fmt.Sprintf("Initizalized CDN path at %v", os.Getenv("CDN_PATH")))
|
||||
log.Println(fmt.Sprintf("Initizalized CDN URL at %v", os.Getenv("CDN_URL")))
|
||||
|
||||
err = sentry.Init(sentry.ClientOptions{
|
||||
Dsn: os.Getenv("SENTRY_DSN"),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Sentry initialization failed: %v\n", err)
|
||||
}
|
||||
|
||||
router = mux.NewRouter().StrictSlash(true)
|
||||
v1.RegisterRouters(router)
|
||||
router = chi.NewRouter()
|
||||
|
||||
v1.ConnectToDB()
|
||||
router.Use(middleware.RequestID)
|
||||
router.Use(middleware.RealIP)
|
||||
router.Use(middleware.Logger)
|
||||
router.Use(middleware.Recoverer)
|
||||
router.Route("/v1", routes.RegisterRouters)
|
||||
|
||||
database.ConnectToDB()
|
||||
}
|
||||
|
||||
func main() {
|
||||
server := http.Server{
|
||||
Addr: fmt.Sprintf("%v:%v", config.Address, config.Port),
|
||||
Addr: fmt.Sprintf("%v:%v", os.Getenv("ADDRESS"), os.Getenv("PORT")),
|
||||
Handler: router,
|
||||
IdleTimeout: 120 * time.Second,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
log.Println(fmt.Sprintf("Staring on %v:%v", config.Address, config.Port))
|
||||
log.Println(fmt.Sprintf("Staring on %v:%v", os.Getenv("ADDRESS"), utils.Getenv("PORT", "8080")))
|
||||
err := server.ListenAndServe()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
package v1
|
||||
package routes
|
||||
|
||||
import (
|
||||
"backend/database"
|
||||
"backend/utils"
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/gorilla/mux"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
@@ -36,17 +40,17 @@ type Activate struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func RegisterAuthRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/auth/login", resolveGenerateNewToken).Methods("OPTIONS", "POST")
|
||||
r.HandleFunc("/auth/logout", resolveDeleteToken).Methods("OPTIONS", "POST")
|
||||
r.HandleFunc("/auth/register", resolveRegisterUser).Methods("OPTIONS", "POST")
|
||||
r.HandleFunc("/auth/register/vk", resolveRegisterUserVK).Methods("OPTIONS", "POST")
|
||||
func RegisterAuthRoutes(r chi.Router) {
|
||||
r.Post("/login", resolveGenerateNewToken)
|
||||
r.Post("/logout", resolveDeleteToken)
|
||||
r.Post("/register", resolveRegisterUser)
|
||||
r.Post("/register/vk", resolveRegisterUserVK)
|
||||
|
||||
r.HandleFunc("/auth/activate", resolveActivateUser).Methods("OPTIONS", "POST")
|
||||
r.HandleFunc("/auth/activateToken", resolveGenerateActivateToken).Methods("OPTIONS", "POST")
|
||||
r.Post("/activate", resolveActivateUser)
|
||||
r.Post("/activateToken", resolveGenerateActivateToken)
|
||||
|
||||
r.HandleFunc("/auth/restore", resolveRestorePassword).Methods("OPTIONS", "POST")
|
||||
r.HandleFunc("/auth/restoreToken", resolveGenerateRestorePassword).Methods("OPTIONS", "POST")
|
||||
r.Post("/restore", resolveRestorePassword)
|
||||
r.Post("/restoreToken", resolveGenerateRestorePassword)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -71,13 +75,13 @@ func resolveRegisterUserVK(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.Get("https://oauth.vk.com/access_token?client_id=7836090&client_secret=HqlMicDyJyq4E3SvsPba&redirect_uri=http://localhost:3000/vk_auth&code="+body.Code)
|
||||
req, err := http.Get("https://oauth.vk.com/access_token?client_id=7836090&client_secret=HqlMicDyJyq4E3SvsPba&redirect_uri=http://localhost:3000/vk_auth&code=" + body.Code)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(req.Body)
|
||||
b, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
@@ -113,7 +117,7 @@ func resolveRestorePassword(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return []byte(privateConfig.ActivateTokenSecret), nil
|
||||
return []byte(os.Getenv("ACTIVATE_TOKEN_SECRET")), nil
|
||||
})
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
@@ -148,7 +152,7 @@ func resolveRestorePassword(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := FetchUser(int(id))
|
||||
user, err := database.FetchUser(int(id))
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
@@ -161,7 +165,7 @@ func resolveRestorePassword(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
hashedPassword := hashAndSaltPassword([]byte(restore.NewPassword))
|
||||
|
||||
err = UpdatePassword(int(id), hashedPassword)
|
||||
err = database.UpdatePassword(int(id), hashedPassword)
|
||||
Response(w, err, "ok")
|
||||
}
|
||||
|
||||
@@ -194,8 +198,8 @@ func resolveGenerateRestorePassword(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := FetchUserByLogin(restore.Email)
|
||||
if err == sql.ErrNoRows {
|
||||
user, err := database.FetchUserByLogin(restore.Email)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
NotFound.Ferror(w)
|
||||
return
|
||||
}
|
||||
@@ -218,8 +222,8 @@ func resolveGenerateRestorePassword(w http.ResponseWriter, r *http.Request) {
|
||||
var mailData = struct {
|
||||
Name string
|
||||
URL string
|
||||
}{Name: user.Username, URL: fmt.Sprintf("%s/restore/%s", config.FrontendUrl, restoreToken)}
|
||||
SendMailTemplate(user.Email, "Восстановление пароля", "restore_password.html", mailData)
|
||||
}{Name: user.Username, URL: fmt.Sprintf("%s/restore/%s", utils.GetFrontendURL(), restoreToken)}
|
||||
utils.SendMailTemplate(user.Email, "Восстановление пароля", "restore_password.html", mailData)
|
||||
Response(w, nil, "ok")
|
||||
}
|
||||
|
||||
@@ -246,7 +250,7 @@ func resolveActivateUser(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return []byte(privateConfig.ActivateTokenSecret), nil
|
||||
return []byte(os.Getenv("ACTIVATE_TOKEN_SECRET")), nil
|
||||
})
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
@@ -282,8 +286,8 @@ func resolveActivateUser(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := FetchUser(int(id))
|
||||
if err == sql.ErrNoRows {
|
||||
user, err := database.FetchUser(int(id))
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
NotFound.Ferror(w)
|
||||
return
|
||||
}
|
||||
@@ -295,7 +299,7 @@ func resolveActivateUser(w http.ResponseWriter, r *http.Request) {
|
||||
AlreadyActivated.Ferror(w)
|
||||
return
|
||||
}
|
||||
user, err = ActivateUser(int(id))
|
||||
user, err = database.ActivateUser(int(id))
|
||||
Response(w, err, user)
|
||||
}
|
||||
|
||||
@@ -314,8 +318,8 @@ func resolveRegisterUser(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = FetchUserByLogin(reg.Login)
|
||||
if err != sql.ErrNoRows {
|
||||
_, err = database.FetchUserByLogin(reg.Login)
|
||||
if !errors.Is(err, sql.ErrNoRows) {
|
||||
RawError(w, "User already register!")
|
||||
return
|
||||
}
|
||||
@@ -326,27 +330,27 @@ func resolveRegisterUser(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := CreateUser(reg.Login, hashAndSaltPassword([]byte(reg.Password)), reg.Email)
|
||||
user, err := database.CreateUser(reg.Login, hashAndSaltPassword([]byte(reg.Password)), reg.Email)
|
||||
Response(w, err, user)
|
||||
|
||||
activateToken := createActivateToken(user)
|
||||
var mailData = struct {
|
||||
Name string
|
||||
URL string
|
||||
}{Name: user.Username, URL: fmt.Sprintf("%s/activate/%s", config.FrontendUrl, activateToken)}
|
||||
SendMailTemplate(user.Email, "Активируйте аккаунт", "activate_account.html", mailData)
|
||||
}{Name: user.Username, URL: fmt.Sprintf("%s/activate/%s", utils.GetFrontendURL(), activateToken)}
|
||||
utils.SendMailTemplate(user.Email, "Активируйте аккаунт", "activate_account.html", mailData)
|
||||
|
||||
embeds := make([]Embed, 1)
|
||||
embeds = append(embeds, Embed{
|
||||
embeds := make([]utils.Embed, 1)
|
||||
embeds = append(embeds, utils.Embed{
|
||||
Title: "Nix13",
|
||||
Type: "rich",
|
||||
Description: "Был зарегестрирован новый пользователь!",
|
||||
Timestamp: time.Now(),
|
||||
URL: fmt.Sprintf("https://unmei.space/user/%d", 1),
|
||||
URL: fmt.Sprintf("%s/user/%d", utils.GetFrontendURL(), 1),
|
||||
})
|
||||
err = ExecuteWebhook(
|
||||
err = utils.ExecuteWebhook(
|
||||
"https://discord.com/api/webhooks/790593203862241320/vGyrLPzbmtKXHLaOpVrwNwn15tBK58CrsL3rQdAiUQSbi_jBvclpfUT1SoM75j98hBKY",
|
||||
WebhookContent{Embeds: embeds},
|
||||
utils.WebhookContent{Embeds: embeds},
|
||||
)
|
||||
if err != nil {
|
||||
sentry.CaptureException(err)
|
||||
@@ -409,9 +413,9 @@ func resolveGenerateNewToken(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := FetchUserByLogin(login.Login)
|
||||
user, err := database.FetchUserByLogin(login.Login)
|
||||
log.Println(login.Login)
|
||||
if err == sql.ErrNoRows || user == nil {
|
||||
if errors.Is(err, sql.ErrNoRows) || user == nil {
|
||||
WrongPassword.Ferror(w)
|
||||
return
|
||||
}
|
||||
@@ -427,7 +431,7 @@ func resolveGenerateNewToken(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString([]byte(privateConfig.AuthTokenSecret))
|
||||
tokenString, err := token.SignedString([]byte(os.Getenv("AUTH_TOKEN_SECRET")))
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
@@ -457,7 +461,7 @@ func resolveGenerateNewToken(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func resolveGenerateActivateToken(w http.ResponseWriter, r *http.Request) {
|
||||
tokenCookie, err := r.Cookie("token")
|
||||
if err == http.ErrNoCookie {
|
||||
if errors.Is(err, http.ErrNoCookie) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
@@ -470,7 +474,7 @@ func resolveGenerateActivateToken(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return []byte(privateConfig.AuthTokenSecret), nil
|
||||
return []byte(os.Getenv("AUTH_TOKEN_SECRET")), nil
|
||||
})
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
@@ -497,7 +501,7 @@ func resolveGenerateActivateToken(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
user, err := FetchUser(id)
|
||||
user, err := database.FetchUser(id)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
@@ -510,8 +514,8 @@ func resolveGenerateActivateToken(w http.ResponseWriter, r *http.Request) {
|
||||
var mailData = struct {
|
||||
Name string
|
||||
URL string
|
||||
}{Name: user.Username, URL: fmt.Sprintf("%s/activate/%s", config.FrontendUrl, activateToken)}
|
||||
SendMailTemplate(user.Email, "Активируйте аккаунт", "activate_account.html", mailData)
|
||||
}{Name: user.Username, URL: fmt.Sprintf("%s/activate/%s", utils.GetFrontendURL(), activateToken)}
|
||||
utils.SendMailTemplate(user.Email, "Активируйте аккаунт", "activate_account.html", mailData)
|
||||
Response(w, nil, "ok")
|
||||
}
|
||||
|
||||
@@ -528,19 +532,19 @@ func comparePasswords(password, hashedPassword string) bool {
|
||||
return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) == nil
|
||||
}
|
||||
|
||||
func createActivateToken(user *User) string {
|
||||
func createActivateToken(user *database.User) string {
|
||||
claims := jwt.MapClaims{
|
||||
"id": user.ID,
|
||||
"time": time.Now().Unix(),
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, _ := token.SignedString([]byte(privateConfig.ActivateTokenSecret))
|
||||
tokenString, _ := token.SignedString([]byte(os.Getenv("ACTIVATE_TOKEN_SECRET")))
|
||||
hashed := base64.StdEncoding.EncodeToString([]byte(tokenString))
|
||||
return hashed
|
||||
}
|
||||
|
||||
func hasPermission(user *User, permission string) bool {
|
||||
func hasPermission(user *database.User, permission string) bool {
|
||||
if user == nil {
|
||||
return false
|
||||
}
|
||||
@@ -557,7 +561,7 @@ func hasPermission(user *User, permission string) bool {
|
||||
}
|
||||
|
||||
func validateRecaptcha(token string, ip string) (bool, error) {
|
||||
url := fmt.Sprintf("https://www.google.com/recaptcha/api/siteverify?secret=%s&response=%s&remoteip=%s", privateConfig.RecaptchaSecret, token, ip)
|
||||
url := fmt.Sprintf("https://www.google.com/recaptcha/api/siteverify?secret=%s&response=%s&remoteip=%s", os.Getenv("RECAPTCHA_SECRET"), token, ip)
|
||||
|
||||
resp, err := http.Post(url, "application/json", bytes.NewBuffer([]byte("")))
|
||||
if err != nil {
|
||||
@@ -591,7 +595,7 @@ func JWTMiddleware(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
tokenCookie, err := r.Cookie("token")
|
||||
if err == http.ErrNoCookie {
|
||||
if errors.Is(err, http.ErrNoCookie) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
@@ -611,7 +615,7 @@ func JWTMiddleware(next http.Handler) http.Handler {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return []byte(privateConfig.AuthTokenSecret), nil
|
||||
return []byte(os.Getenv("AUTH_TOKEN_SECRET")), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -633,15 +637,14 @@ func JWTMiddleware(next http.Handler) http.Handler {
|
||||
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
|
||||
user, err := FetchCurrentUser(r)
|
||||
user, err := GetCurrentUser(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/v1/users") {
|
||||
vars := mux.Vars(r)
|
||||
id, _ := strconv.Atoi(vars["userId"])
|
||||
id := IURLParam(r, "userId")
|
||||
|
||||
if !strings.HasPrefix(r.URL.Path, "/v1/users/me") && id != int(claims["id"].(float64)) && !hasPermission(user, "users") {
|
||||
NoAccess.Ferror(w)
|
||||
@@ -0,0 +1,93 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"backend/database"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func RegisterCharactersRoutes(r chi.Router) {
|
||||
r.Route("/", func(s chi.Router) {
|
||||
s.Get("/", getCharacters)
|
||||
s.Post("/", createCharacter)
|
||||
})
|
||||
r.Route("/{id:[0-9]+}", func(s chi.Router) {
|
||||
s.Get("/", getCharacter)
|
||||
s.Put("/", updateCharacter)
|
||||
s.Delete("/", deleteCharacter)
|
||||
})
|
||||
r.Get("/characters/{id:[0-9]+}/novels", getCharacterNovels)
|
||||
}
|
||||
|
||||
func getCharacters(w http.ResponseWriter, r *http.Request) {
|
||||
characters, err := database.FetchCharacters()
|
||||
Response(w, err, characters)
|
||||
}
|
||||
func createCharacter(w http.ResponseWriter, r *http.Request) {
|
||||
Response(w, nil, "todo")
|
||||
}
|
||||
|
||||
func getCharacter(w http.ResponseWriter, r *http.Request) {
|
||||
id := IURLParam(r, "id")
|
||||
char, err := database.FetchCharacter(id)
|
||||
Response(w, err, char)
|
||||
}
|
||||
func updateCharacter(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := GetCurrentUser(r)
|
||||
if err != nil {
|
||||
NoAccess.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
hasPerm := hasPermission(user, "characters")
|
||||
if !hasPerm {
|
||||
NoAccess.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
character := new(database.Character)
|
||||
err = json.Unmarshal(body, character)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
character, err = database.UpdateCharacter(character)
|
||||
Response(w, err, character)
|
||||
}
|
||||
func deleteCharacter(w http.ResponseWriter, r *http.Request) {
|
||||
id := IURLParam(r, "id")
|
||||
|
||||
user, err := GetCurrentUser(r)
|
||||
if err != nil {
|
||||
NoAccess.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
hasPerm := hasPermission(user, "characters")
|
||||
if !hasPerm {
|
||||
NoAccess.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
err = database.DeleteCharacter(id)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
NotFound.Ferror(w)
|
||||
return
|
||||
}
|
||||
Response(w, err, "ok")
|
||||
}
|
||||
|
||||
func getCharacterNovels(w http.ResponseWriter, r *http.Request) {
|
||||
id := IURLParam(r, "id")
|
||||
novels, err := database.FetchCharacterNovels(id)
|
||||
Response(w, err, novels)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"backend/database"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func RegisterClubRoutes(r chi.Router) {
|
||||
r.Route("/", func(s chi.Router) {
|
||||
s.Get("/", getClubs)
|
||||
})
|
||||
r.Route("/{id:[0-9]+}", func(s chi.Router) {
|
||||
s.Get("/", getClub)
|
||||
})
|
||||
}
|
||||
|
||||
func getClubs(w http.ResponseWriter, r *http.Request) {
|
||||
userId, err := GetCurrentUserID(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
clubs, err := database.GetClubs(int(userId))
|
||||
Response(w, err, clubs)
|
||||
}
|
||||
|
||||
func getClub(w http.ResponseWriter, r *http.Request) {
|
||||
id := IURLParam(r, "id")
|
||||
|
||||
userId, err := GetCurrentUserID(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
club, err := database.GetClub(id, userId)
|
||||
Response(w, err, club)
|
||||
}
|
||||
Executable → Regular
+1
-1
@@ -1,4 +1,4 @@
|
||||
package v1
|
||||
package routes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -0,0 +1,17 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"backend/database"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func RegisterGamesRoutes(r chi.Router) {
|
||||
r.Get("/{id:[0-9]+}", getGame)
|
||||
}
|
||||
|
||||
func getGame(w http.ResponseWriter, r *http.Request) {
|
||||
id := IURLParam(r, "id")
|
||||
game, err := database.FetchGame(int32(id))
|
||||
Response(w, err, game)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"backend/database"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func RegisterGenresRoutes(r chi.Router) {
|
||||
r.Route("/", func(s chi.Router) {
|
||||
s.Get("/", getGenres)
|
||||
s.Post("/", addGenre)
|
||||
})
|
||||
r.Route("/{id:[0-9]+}", func(s chi.Router) {
|
||||
s.Get("/", getGenre)
|
||||
})
|
||||
r.Route("/{id:[0-9]+}/novels", func(s chi.Router) {
|
||||
s.Get("/", getGenreNovels)
|
||||
})
|
||||
}
|
||||
|
||||
func getGenres(w http.ResponseWriter, r *http.Request) {
|
||||
genres, err := database.FetchGenres()
|
||||
Response(w, err, genres)
|
||||
}
|
||||
func addGenre(w http.ResponseWriter, r *http.Request) {
|
||||
RawError(w, "method not implemented")
|
||||
}
|
||||
|
||||
func getGenre(w http.ResponseWriter, r *http.Request) {
|
||||
id := IURLParam(r, "id")
|
||||
|
||||
genre, err := database.FetchGenre(id)
|
||||
Response(w, err, genre)
|
||||
}
|
||||
|
||||
func getGenreNovels(w http.ResponseWriter, r *http.Request) {
|
||||
id := IURLParam(r, "id")
|
||||
|
||||
novels, err := database.FetchGenreNovels(id)
|
||||
Response(w, err, novels)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"backend/database"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func RegisterGroupsRoutes(r chi.Router) {
|
||||
r.Route("/groups", func(s chi.Router) {
|
||||
s.Get("/", getGroups)
|
||||
})
|
||||
r.Route("/groups/{id:[0-9]+}", func(s chi.Router) {
|
||||
s.Get("/", getGroup)
|
||||
})
|
||||
}
|
||||
|
||||
func getGroups(w http.ResponseWriter, r *http.Request) {
|
||||
groups, err := database.GetGroups()
|
||||
Response(w, err, groups)
|
||||
}
|
||||
|
||||
func getGroup(w http.ResponseWriter, r *http.Request) {
|
||||
id := IURLParam(r, "id")
|
||||
group, err := database.GetGroup(id)
|
||||
Response(w, err, group)
|
||||
}
|
||||
Executable → Regular
+8
-7
@@ -1,24 +1,25 @@
|
||||
package v1
|
||||
package routes
|
||||
|
||||
import (
|
||||
"backend/database"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func RegisterLongPollRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/lp", resolveLongPoll).Methods("GET", "OPTIONS", "POST")
|
||||
func RegisterLongPollRoutes(r chi.Router) {
|
||||
r.Get("/", resolveLongPoll)
|
||||
}
|
||||
|
||||
func resolveLongPoll(w http.ResponseWriter, r *http.Request) {
|
||||
userId, err := GetCurrentUserID(r)
|
||||
if err == ErrNotAuthorized {
|
||||
if errors.Is(err, database.ErrNotAuthorized) {
|
||||
NoToken.Ferror(w)
|
||||
return
|
||||
}
|
||||
if err == http.ErrNoCookie {
|
||||
if errors.Is(err, http.ErrNoCookie) {
|
||||
NoToken.Ferror(w)
|
||||
return
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"backend/database"
|
||||
"encoding/json"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func RegisterNewsRoutes(r chi.Router) {
|
||||
r.Route("/", func(s chi.Router) {
|
||||
s.Get("/", getNews)
|
||||
s.Post("/", addPost)
|
||||
})
|
||||
r.Route("/{id:[0-9]+}", func(s chi.Router) {
|
||||
s.Get("/", getPost)
|
||||
s.Put("/", updatePost)
|
||||
s.Delete("/", deletePost)
|
||||
})
|
||||
}
|
||||
|
||||
func getNews(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
|
||||
offset, _ := strconv.Atoi(q.Get("offset"))
|
||||
limit, _ := strconv.Atoi(q.Get("limit"))
|
||||
if limit == 0 {
|
||||
limit = 20
|
||||
}
|
||||
|
||||
total, err := database.FetchTotalNews()
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
pagination := map[string]interface{}{
|
||||
"offset": offset,
|
||||
"limit": limit,
|
||||
"total": total,
|
||||
}
|
||||
|
||||
news, err := database.FetchNews(offset, limit)
|
||||
ResponsePagination(w, err, news, pagination)
|
||||
}
|
||||
|
||||
func addPost(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
post := new(database.Post)
|
||||
err = json.Unmarshal(body, post)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
uid, err := GetCurrentUserID(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
post.AuthorID = uid
|
||||
|
||||
createdPost, err := database.CreatePost(post)
|
||||
Response(w, err, createdPost)
|
||||
}
|
||||
|
||||
func getPost(w http.ResponseWriter, r *http.Request) {
|
||||
id := IURLParam(r, "id")
|
||||
post, err := database.FetchPost(id)
|
||||
Response(w, err, post)
|
||||
}
|
||||
func updatePost(w http.ResponseWriter, r *http.Request) {
|
||||
id := IURLParam(r, "id")
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
post, err := database.FetchPost(id)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, post)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = database.UpdatePost(post)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
func deletePost(w http.ResponseWriter, r *http.Request) {
|
||||
id := IURLParam(r, "id")
|
||||
err := database.DeletePost(id)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"backend/database"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func RegisterNovelsRoutes(r chi.Router) {
|
||||
r.Route("/", func(s chi.Router) {
|
||||
s.Get("/", getNovels)
|
||||
s.Post("/", createNovel)
|
||||
})
|
||||
r.Route("/{novelId:[0-9]+}", func(s chi.Router) {
|
||||
s.Get("/", getNovel)
|
||||
s.Put("/", updateNovel)
|
||||
s.Delete("/", deleteNovel)
|
||||
})
|
||||
r.Post("/{novelId:[0-9]+}/cover", uploadNovelCover)
|
||||
r.Route("/{novelId:[0-9]+}/comments", func(s chi.Router) {
|
||||
s.Get("/", getNovelComments)
|
||||
s.Post("/", createNovelComment)
|
||||
})
|
||||
r.Get("/{novelId:[0-9]+}/characters", resolveNovelCharacters)
|
||||
r.Get("/{novelId:[0-9]+}/genres", resolveNovelGenres)
|
||||
}
|
||||
|
||||
func getNovels(w http.ResponseWriter, r *http.Request) {
|
||||
sortBy, ok := r.URL.Query()["sort"]
|
||||
|
||||
if !ok || len(sortBy) <= 0 {
|
||||
sortBy = append(sortBy, "rating")
|
||||
}
|
||||
|
||||
query, ok := r.URL.Query()["q"]
|
||||
if ok {
|
||||
novels, err := database.SearchNovels(query[0])
|
||||
Response(w, err, novels)
|
||||
return
|
||||
}
|
||||
|
||||
novels, err := database.FetchNovels(sortBy[0])
|
||||
Response(w, err, &novels)
|
||||
}
|
||||
func createNovel(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var novel database.Novel
|
||||
err = json.Unmarshal(body, &novel)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
novel, err = database.CreateNovel(&novel)
|
||||
if err == nil {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Header().Add("Location", fmt.Sprintf("https://%v%v/%v", r.Host, r.URL, novel.ID))
|
||||
}
|
||||
Response(w, err, &novel)
|
||||
}
|
||||
|
||||
func getNovel(w http.ResponseWriter, r *http.Request) {
|
||||
novelId := IURLParam(r, "novelId")
|
||||
novel, err := database.FetchNovel(novelId)
|
||||
Response(w, err, novel)
|
||||
}
|
||||
func updateNovel(w http.ResponseWriter, r *http.Request) {
|
||||
novelId := IURLParam(r, "novelId")
|
||||
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
novel, err := database.FetchNovel(novelId)
|
||||
if err != nil {
|
||||
Response(w, err, nil)
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(body, novel)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
novel, err = database.UpdateNovel(novel)
|
||||
Response(w, err, novel)
|
||||
}
|
||||
func deleteNovel(w http.ResponseWriter, r *http.Request) {
|
||||
novelId := IURLParam(r, "novelId")
|
||||
_, err := database.FetchNovel(novelId)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
NotFound.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
err = database.DeleteNovel(novelId)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
JSON(w, "")
|
||||
}
|
||||
|
||||
func uploadNovelCover(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := GetCurrentUser(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !hasPermission(user, "novel") {
|
||||
NoAccess.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
novelId := IURLParam(r, "novelId")
|
||||
|
||||
err = r.ParseMultipartForm(1024 * 1024 * 2)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
file, handler, err := r.FormFile("cover")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(handler.Header["Content-Type"][0], "image") {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
RawError(w, "Wrong document type!")
|
||||
return
|
||||
}
|
||||
|
||||
fileBytes, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
ext := strings.Split(handler.Header["Content-Type"][0], "/")[1]
|
||||
path := filepath.Join(os.Getenv("CDN_PATH"), "novels", "logo", fmt.Sprintf("%v.%v", novelId, ext))
|
||||
|
||||
err = os.WriteFile(path, fileBytes, 0644)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
coverPath := fmt.Sprintf("%v/novels/logo/%v.%v", os.Getenv("CDN_URL"), novelId, ext)
|
||||
err = database.UpdateNovelCover(novelId, coverPath)
|
||||
Response(w, err, coverPath)
|
||||
}
|
||||
|
||||
func getNovelComments(w http.ResponseWriter, r *http.Request) {
|
||||
novelId := IURLParam(r, "novelId")
|
||||
offsetParam, ok := r.URL.Query()["offset"]
|
||||
if !ok || len(offsetParam) <= 0 {
|
||||
offsetParam = append(offsetParam, "0")
|
||||
}
|
||||
|
||||
countParam, ok := r.URL.Query()["count"]
|
||||
if !ok || len(countParam) <= 0 {
|
||||
countParam = append(countParam, "5")
|
||||
}
|
||||
|
||||
offset, _ := strconv.Atoi(offsetParam[0])
|
||||
count, _ := strconv.Atoi(countParam[0])
|
||||
|
||||
var response = make(map[string]interface{}, 2)
|
||||
comments, err := database.FetchNovelComments(novelId)
|
||||
response["count"] = len(comments)
|
||||
|
||||
if offset+count > len(comments) {
|
||||
comments = comments[offset:]
|
||||
} else {
|
||||
comments = comments[offset : offset+count]
|
||||
}
|
||||
response["comments"] = comments
|
||||
Response(w, err, response)
|
||||
}
|
||||
|
||||
func createNovelComment(w http.ResponseWriter, r *http.Request) {
|
||||
novelId := IURLParam(r, "novelId")
|
||||
user, err := GetCurrentUser(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
commentData := database.CommentNovel{NovelID: int16(novelId), UserID: user.ID}
|
||||
err = json.Unmarshal(body, &commentData)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
comment, err := database.AddNovelComment(commentData)
|
||||
Response(w, err, comment)
|
||||
}
|
||||
|
||||
func resolveNovelGenres(w http.ResponseWriter, r *http.Request) {
|
||||
novelId := IURLParam(r, "novelId")
|
||||
genres, err := database.FetchNovelGenres(novelId)
|
||||
Response(w, err, genres)
|
||||
}
|
||||
|
||||
func resolveNovelCharacters(w http.ResponseWriter, r *http.Request) {
|
||||
novelId := IURLParam(r, "novelId")
|
||||
characters, err := database.FetchNovelCharacters(novelId)
|
||||
Response(w, err, characters)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package v1
|
||||
package routes
|
||||
|
||||
import (
|
||||
"net"
|
||||
@@ -38,7 +38,7 @@ func getVisitor(ip string) *rate.Limiter {
|
||||
v, exists := visitors[ip]
|
||||
if !exists {
|
||||
limiter := rate.NewLimiter(1, 20)
|
||||
visitors[ip] = &visitor{ limiter, time.Now() }
|
||||
visitors[ip] = &visitor{limiter, time.Now()}
|
||||
return limiter
|
||||
}
|
||||
|
||||
@@ -1,63 +1,57 @@
|
||||
package v1
|
||||
package routes
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"backend/database"
|
||||
"backend/utils"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var lpServer *LongPollServer
|
||||
|
||||
func RegisterRouters(r *mux.Router) {
|
||||
sub := r.PathPrefix("/v1").Subrouter()
|
||||
|
||||
func RegisterRouters(r chi.Router) {
|
||||
lpServer = InitLongPollServer()
|
||||
|
||||
sub.Use(apiMiddleware)
|
||||
sub.Use(LimitMiddleware)
|
||||
r.Use(apiMiddleware)
|
||||
r.Use(LimitMiddleware)
|
||||
go CleanupVisitors()
|
||||
sub.Use(JWTMiddleware)
|
||||
r.Use(JWTMiddleware)
|
||||
|
||||
sub.HandleFunc("/version", func(w http.ResponseWriter, _ *http.Request) {
|
||||
r.Get("/version", func(w http.ResponseWriter, _ *http.Request) {
|
||||
Response(w, nil, map[string]string{
|
||||
"version": config.Version,
|
||||
"build": strconv.Itoa(int(config.Build)),
|
||||
"version": os.Getenv("VERSION"),
|
||||
"build": os.Getenv("BUILD"),
|
||||
})
|
||||
})
|
||||
|
||||
RegisterAuthRoutes(sub)
|
||||
RegisterUsersRoutes(sub)
|
||||
RegisterNovelsRoutes(sub)
|
||||
RegisterCharactersRoutes(sub)
|
||||
RegisterGenresRoutes(sub)
|
||||
RegisterGamesRoutes(sub)
|
||||
RegisterClubRoutes(sub)
|
||||
RegisterGroupsRoutes(sub)
|
||||
RegisterNewsRoutes(sub)
|
||||
RegisterTokensRouter(sub)
|
||||
RegisterLongPollRoutes(sub)
|
||||
r.Route("/auth", RegisterAuthRoutes)
|
||||
r.Route("/users", RegisterUsersRoutes)
|
||||
r.Route("/novels", RegisterNovelsRoutes)
|
||||
r.Route("/characters", RegisterCharactersRoutes)
|
||||
r.Route("/genres", RegisterGenresRoutes)
|
||||
r.Route("/games", RegisterGamesRoutes)
|
||||
r.Route("/clubs", RegisterClubRoutes)
|
||||
r.Route("/groups", RegisterGroupsRoutes)
|
||||
r.Route("/news", RegisterNewsRoutes)
|
||||
r.Route("/tokens", RegisterTokensRouter)
|
||||
r.Route("/lp", RegisterLongPollRoutes)
|
||||
|
||||
if config.Debug {
|
||||
err := sub.Walk(func(route *mux.Route, _ *mux.Router, _ []*mux.Route) error {
|
||||
path, err := route.GetPathTemplate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("loading path %s\n", path)
|
||||
if utils.IsDevelopment() && utils.IsDebug() {
|
||||
err := chi.Walk(r, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
|
||||
log.Printf("[%s]: %s\n", method, route)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
@@ -68,7 +62,7 @@ func Response(w http.ResponseWriter, err error, data interface{}) {
|
||||
const baseResponse = `{"error":false,"data":%v}`
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
NotFound.Ferror(w)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
@@ -94,7 +88,7 @@ func ResponsePagination(w http.ResponseWriter, err error, data interface{}, pagi
|
||||
const baseResponse = `{"error":false,"pagination":%v,"data":%v}`
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
NotFound.Ferror(w)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
@@ -152,7 +146,7 @@ func JSON(w http.ResponseWriter, data string) {
|
||||
}
|
||||
|
||||
func ReadBody(r *http.Request) ([]byte, error) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -161,10 +155,14 @@ func ReadBody(r *http.Request) ([]byte, error) {
|
||||
return body, err
|
||||
}
|
||||
|
||||
func MD5(s string) string {
|
||||
hash := md5.New()
|
||||
hash.Write([]byte(s))
|
||||
return hex.EncodeToString(hash.Sum(nil))
|
||||
func IURLParam(r *http.Request, key string) int {
|
||||
par := chi.URLParam(r, key)
|
||||
i, err := strconv.Atoi(par)
|
||||
if err != nil {
|
||||
return 0
|
||||
} else {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
func GetCurrentUserID(r *http.Request) (uint64, error) {
|
||||
@@ -176,7 +174,7 @@ func GetCurrentUserID(r *http.Request) (uint64, error) {
|
||||
if token.Method != jwt.SigningMethodHS256 {
|
||||
return nil, fmt.Errorf("invalid singning method: %s", token.Method)
|
||||
}
|
||||
return []byte(privateConfig.AuthTokenSecret), nil
|
||||
return []byte(os.Getenv("AUTH_TOKEN_SECRET")), nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -186,13 +184,39 @@ func GetCurrentUserID(r *http.Request) (uint64, error) {
|
||||
return uint64(id), nil
|
||||
}
|
||||
|
||||
func GetCurrentUser(r *http.Request) (*database.User, error) {
|
||||
cookie, err := r.Cookie("token")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(cookie.Value) == 0 {
|
||||
return nil, fmt.Errorf("token cookie is empty")
|
||||
}
|
||||
|
||||
user := new(database.User)
|
||||
tokenString := cookie.Value
|
||||
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (i interface{}, e error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return []byte(os.Getenv("AUTH_TOKEN_SECRET")), nil
|
||||
})
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
if !token.Valid {
|
||||
return user, fmt.Errorf("token not valid")
|
||||
}
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok {
|
||||
user, err = database.FetchUser(int(claims["id"].(float64)))
|
||||
}
|
||||
return user, err
|
||||
}
|
||||
|
||||
func apiMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("Dev") == "1" {
|
||||
w.Header().Add("Access-Control-Allow-Origin", config.DevFrontendUrl)
|
||||
} else {
|
||||
w.Header().Add("Access-Control-Allow-Origin", config.FrontendUrl)
|
||||
}
|
||||
w.Header().Add("Access-Control-Allow-Origin", utils.GetFrontendURL())
|
||||
w.Header().Add("Access-Control-Allow-Credentials", "true")
|
||||
w.Header().Add("Content-Type", "application/json; charset=utf-8")
|
||||
//w.Header().Add("Cache-Control", "max-age=60")
|
||||
@@ -203,13 +227,13 @@ func apiMiddleware(next http.Handler) http.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
if database == nil {
|
||||
if database.Database == nil {
|
||||
log.Println("db not initialized")
|
||||
return
|
||||
}
|
||||
err := database.Ping()
|
||||
err := database.Database.Ping()
|
||||
if err != nil && strings.Contains(err.Error(), "broken pipe") {
|
||||
ConnectToDB()
|
||||
database.ConnectToDB()
|
||||
log.Println("connection to db reopened")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"backend/database"
|
||||
"errors"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func RegisterTokensRouter(r chi.Router) {
|
||||
r.Post("/auth", auth)
|
||||
r.Route("/apps", func(s chi.Router) {
|
||||
s.Get("/", getApps)
|
||||
s.Get("/{id:[0-9]+}", getApp)
|
||||
})
|
||||
}
|
||||
|
||||
func auth(w http.ResponseWriter, r *http.Request) {
|
||||
tokenCookie, err := r.Cookie("token")
|
||||
if errors.Is(err, http.ErrNoCookie) || len(tokenCookie.Value) == 0 {
|
||||
Unauthorized.Ferror(w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func getApps(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := GetCurrentUser(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
if !hasPermission(user, "apps") {
|
||||
NoAccess.Ferror(w)
|
||||
return
|
||||
}
|
||||
apps, err := database.FetchApps()
|
||||
Response(w, err, apps)
|
||||
}
|
||||
|
||||
func getApp(w http.ResponseWriter, r *http.Request) {}
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"backend/database"
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"image"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func RegisterUsersRoutes(r chi.Router) {
|
||||
r.Get("/", getUsers)
|
||||
r.Get("/me", getCurrentUser)
|
||||
r.Post("/me/avatar", uploadAvatar)
|
||||
r.Route("/me/settings", func(s chi.Router) {
|
||||
s.Get("/", getSettings)
|
||||
s.Post("/", updateSettings)
|
||||
})
|
||||
r.Route("/{userId:[0-9]+}", func(s chi.Router) {
|
||||
s.Get("/", getUser)
|
||||
s.Put("/", updateUser)
|
||||
})
|
||||
r.Route("/{userId:[0-9]+}/novels", func(s chi.Router) {
|
||||
s.Get("/", getUserNovels)
|
||||
s.Post("/", addUserNovel)
|
||||
})
|
||||
r.Route("/{userId:[0-9]+}/novels/{novelId:[0-9]+}", func(s chi.Router) {
|
||||
s.Get("/", getUserNovel)
|
||||
s.Put("/", updateUserNovel)
|
||||
s.Delete("/", deleteUserNovel)
|
||||
})
|
||||
r.Get("/{userId:[0-9]+}/novels/comments", resolveUserNovelsComments)
|
||||
}
|
||||
|
||||
func getUsers(w http.ResponseWriter, r *http.Request) {
|
||||
users, err := database.FetchUsers()
|
||||
Response(w, err, users)
|
||||
}
|
||||
|
||||
func getCurrentUser(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := GetCurrentUser(r)
|
||||
if errors.Is(err, database.ErrNotAuthorized) {
|
||||
NoToken.Ferror(w)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, http.ErrNoCookie) {
|
||||
NoToken.Ferror(w)
|
||||
return
|
||||
}
|
||||
Response(w, err, user)
|
||||
}
|
||||
|
||||
func uploadAvatar(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := GetCurrentUser(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = r.ParseMultipartForm(user.Group.MaxAvaWeight)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
file, handler, err := r.FormFile("avatar")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(handler.Header["Content-Type"][0], "image") {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
RawError(w, "Wrong document type!")
|
||||
return
|
||||
}
|
||||
|
||||
fileBytes, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
img, _, err := image.DecodeConfig(bytes.NewReader(fileBytes))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
width, height := img.Width, img.Width
|
||||
if width > user.Group.MaxAvatarSize && height > user.Group.MaxAvatarSize {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
RawError(w, "Image too big!")
|
||||
return
|
||||
}
|
||||
|
||||
ext := strings.Split(handler.Header["Content-Type"][0], "/")[1]
|
||||
path := filepath.Join(os.Getenv("CDN_PATH"), "users", "avatars", fmt.Sprintf("%v.%v", user.ID, ext))
|
||||
|
||||
err = os.WriteFile(path, fileBytes, 0644)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
avatarPath := fmt.Sprintf("%v/avatars/%v.%v", os.Getenv("CDN_URL"), user.ID, ext)
|
||||
Response(w, nil, avatarPath)
|
||||
}
|
||||
|
||||
func getSettings(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := GetCurrentUserID(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
settings, err := database.FetchUserSettings(id)
|
||||
Response(w, err, settings)
|
||||
}
|
||||
func updateSettings(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := GetCurrentUserID(r)
|
||||
settings, err := database.FetchUserSettings(id)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, settings)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := database.UpdateUserSettings(id, settings)
|
||||
Response(w, err, user)
|
||||
}
|
||||
|
||||
func getUser(w http.ResponseWriter, r *http.Request) {
|
||||
id, _ := strconv.Atoi(chi.URLParam(r, "userId"))
|
||||
user, err := database.FetchUser(id)
|
||||
Response(w, err, user)
|
||||
}
|
||||
func updateUser(w http.ResponseWriter, r *http.Request) {
|
||||
id, _ := strconv.Atoi(chi.URLParam(r, "userId"))
|
||||
|
||||
currentUser, err := GetCurrentUser(r)
|
||||
if err != nil && !errors.Is(err, database.ErrNotAuthorized) {
|
||||
Error(w, err)
|
||||
return
|
||||
} else if errors.Is(err, database.ErrNotAuthorized) || currentUser == nil {
|
||||
currentUser = &database.User{ID: -1}
|
||||
}
|
||||
if currentUser.ID != int64(id) && !hasPermission(currentUser, "users") {
|
||||
NoAccess.Ferror(w)
|
||||
return
|
||||
}
|
||||
_, err = database.FetchUser(id)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var u *database.User
|
||||
err = json.Unmarshal(body, u)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
user, err := database.UpdateUser(u)
|
||||
Response(w, err, user)
|
||||
}
|
||||
|
||||
func getUserNovels(w http.ResponseWriter, r *http.Request) {
|
||||
userId, _ := strconv.Atoi(chi.URLParam(r, "userId"))
|
||||
|
||||
novels, err := database.FetchUserNovels(userId)
|
||||
Response(w, err, novels)
|
||||
}
|
||||
func addUserNovel(w http.ResponseWriter, r *http.Request) {
|
||||
userId, _ := strconv.Atoi(chi.URLParam(r, "userId"))
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
var novel = &database.UserNovels{}
|
||||
err = json.Unmarshal(body, novel)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
novel.UserID = int64(userId)
|
||||
|
||||
novel, err = database.CreateUserNovel(novel)
|
||||
if err == nil {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Header().Add("Location", fmt.Sprintf("https://%v%v/%v", r.Host, r.URL, novel.NovelID))
|
||||
}
|
||||
Response(w, err, novel)
|
||||
}
|
||||
|
||||
func getUserNovel(w http.ResponseWriter, r *http.Request) {
|
||||
userId := IURLParam(r, "userId")
|
||||
novelId := IURLParam(r, "novelId")
|
||||
novel, err := database.FetchUserNovel(userId, novelId)
|
||||
Response(w, err, novel)
|
||||
|
||||
}
|
||||
func updateUserNovel(w http.ResponseWriter, r *http.Request) {
|
||||
userId := IURLParam(r, "userId")
|
||||
novelId := IURLParam(r, "novelId")
|
||||
|
||||
_, err := database.FetchNovel(novelId)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
NotFound.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = database.FetchUser(userId)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
NotFound.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
novel, _ := database.FetchUserNovel(userId, novelId)
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if novel == nil {
|
||||
NotFound.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &novel)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
updatedNovel, err := database.UpdateUserNovel(novel)
|
||||
Response(w, err, updatedNovel)
|
||||
}
|
||||
func deleteUserNovel(w http.ResponseWriter, r *http.Request) {
|
||||
userId := IURLParam(r, "userId")
|
||||
novelId := IURLParam(r, "novelId")
|
||||
_, err := database.FetchUserNovel(userId, novelId)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
NotFound.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
err = database.DeleteUserNovel(userId, novelId)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func resolveUserNovelsComments(w http.ResponseWriter, r *http.Request) {
|
||||
id := IURLParam(r, "userId")
|
||||
|
||||
comments, err := database.FetchUserNovelsComments(id)
|
||||
Response(w, err, comments)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package v1
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -0,0 +1,27 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func GetFrontendURL() string {
|
||||
if IsDevelopment() {
|
||||
return os.Getenv("DEV_FRONTEND_URL")
|
||||
}
|
||||
return os.Getenv("FRONTEND_URL")
|
||||
}
|
||||
|
||||
func IsDevelopment() bool {
|
||||
return os.Getenv("DEVELOPMENT") == "true"
|
||||
}
|
||||
func IsDebug() bool {
|
||||
return os.Getenv("DEBUG") == "true"
|
||||
}
|
||||
|
||||
func Getenv(key, def string) string {
|
||||
value := os.Getenv(key)
|
||||
if value == "" {
|
||||
return def
|
||||
}
|
||||
return value
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package v1
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -18,9 +18,11 @@ const (
|
||||
"%v\r\n"
|
||||
)
|
||||
|
||||
var mailAuth = smtp.PlainAuth("", os.Getenv("MAIL_USERNAME"), os.Getenv("MAIL_PASSWORD"), os.Getenv("MAIL_HOST"))
|
||||
|
||||
func SendMail(to string, subject string, body string) {
|
||||
msg := []byte(fmt.Sprintf(format, privateConfig.Email.Username, to, subject, body))
|
||||
err := smtp.SendMail(privateConfig.Email.Host+":587", mailAuth, privateConfig.Email.Username, []string{to}, msg)
|
||||
msg := []byte(fmt.Sprintf(format, os.Getenv("MAIL_USERNAME"), to, subject, body))
|
||||
err := smtp.SendMail(os.Getenv("MAIL_HOST")+":587", mailAuth, os.Getenv("MAIL_USERNAME"), []string{to}, msg)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package v1
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
@@ -61,5 +63,10 @@ func GetStructAndNamedForDatabase(s interface{}) [][]string {
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func MD5(s string) string {
|
||||
hash := md5.New()
|
||||
hash.Write([]byte(s))
|
||||
return hex.EncodeToString(hash.Sum(nil))
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func RegisterCharactersRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/characters", resolveCharacters).Methods("GET", "OPTIONS", "POST")
|
||||
r.HandleFunc("/characters/{id:[0-9]+}", resolveCharacter).Methods("GET", "OPTIONS", "PUT", "DELETE")
|
||||
r.HandleFunc("/characters/{id:[0-9]+}/novels", resolveCharacterNovels).Methods("GET", "OPTIONS")
|
||||
}
|
||||
|
||||
func resolveCharacters(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
characters, err := FetchCharacters()
|
||||
Response(w, err, characters)
|
||||
} else if r.Method == http.MethodPost {
|
||||
Response(w, nil, "todo")
|
||||
}
|
||||
}
|
||||
|
||||
func resolveCharacter(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id, _ := strconv.Atoi(vars["id"])
|
||||
|
||||
if r.Method == http.MethodGet {
|
||||
char, err := FetchCharacter(id)
|
||||
Response(w, err, char)
|
||||
} else if r.Method == http.MethodPut {
|
||||
user, err := FetchCurrentUser(r)
|
||||
if err != nil {
|
||||
NoAccess.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
hasPerm := hasPermission(user, "characters")
|
||||
if !hasPerm {
|
||||
NoAccess.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
character := new(Character)
|
||||
err = json.Unmarshal(body, character)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
character, err = UpdateCharacter(character)
|
||||
Response(w, err, character)
|
||||
} else if r.Method == http.MethodDelete {
|
||||
user, err := FetchCurrentUser(r)
|
||||
if err != nil {
|
||||
NoAccess.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
hasPerm := hasPermission(user, "characters")
|
||||
if !hasPerm {
|
||||
NoAccess.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
err = DeleteCharacter(id)
|
||||
if err == sql.ErrNoRows {
|
||||
NotFound.Ferror(w)
|
||||
return
|
||||
}
|
||||
Response(w, err, "ok")
|
||||
}
|
||||
}
|
||||
|
||||
func resolveCharacterNovels(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id, _ := strconv.Atoi(vars["id"])
|
||||
novels, err := FetchCharacterNovels(id)
|
||||
Response(w, err, novels)
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func RegisterClubRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/clubs", resolveClubs).Methods("GET", "OPTIONS", "POST")
|
||||
r.HandleFunc("/clubs/{id:[0-9]+}", resolveClub).Methods("GET", "OPTIONS")
|
||||
}
|
||||
|
||||
func resolveClubs(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" {
|
||||
userId, err := GetCurrentUserID(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
clubs, err := GetClubs(userId)
|
||||
Response(w, err, clubs)
|
||||
}
|
||||
}
|
||||
|
||||
func resolveClub(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
rawId, _ := strconv.Atoi(vars["id"])
|
||||
id := uint16(rawId)
|
||||
|
||||
if r.Method == http.MethodGet {
|
||||
userId, err := GetCurrentUserID(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
club, err := GetClub(id, userId)
|
||||
Response(w, err, club)
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/smtp"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Address string `json:"address"`
|
||||
Port string `json:"port"`
|
||||
DevFrontendUrl string `json:"dev_frontend_url"`
|
||||
FrontendUrl string `json:"frontend_url"`
|
||||
CDNPath string `json:"cdn_path"`
|
||||
CDNUrl string `json:"cdn_url"`
|
||||
Version string `json:"version"`
|
||||
Build uint32 `json:"build"`
|
||||
Debug bool `json:"debug"`
|
||||
}
|
||||
|
||||
type PrivateConfig struct {
|
||||
AuthTokenSecret string `json:"auth_token"`
|
||||
ActivateTokenSecret string `json:"activate_token"`
|
||||
RecaptchaSecret string `json:"recaptcha"`
|
||||
Database struct {
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
URL string `json:"url"`
|
||||
Name string `json:"name"`
|
||||
} `json:"database"`
|
||||
Email struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Host string `json:"host"`
|
||||
} `json:"email"`
|
||||
}
|
||||
|
||||
var (
|
||||
config *Config
|
||||
privateConfig *PrivateConfig
|
||||
mailAuth smtp.Auth
|
||||
)
|
||||
|
||||
func LoadConfig() *Config {
|
||||
data, err := ioutil.ReadFile("config.json")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
err = json.Unmarshal(data, &config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func LoadPrivateConfig() {
|
||||
data, err := ioutil.ReadFile("private.config.json")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
err = json.Unmarshal(data, &privateConfig)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
mailAuth = smtp.PlainAuth("", privateConfig.Email.Username, privateConfig.Email.Password, privateConfig.Email.Host)
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func RegisterGamesRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/games/{id:[0-9]+}", resolveGame)
|
||||
}
|
||||
|
||||
func resolveGame(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id, _ := strconv.Atoi(vars["id"])
|
||||
game, err := FetchGame(int32(id))
|
||||
Response(w, err, game)
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package v1
|
||||
|
||||
import "time"
|
||||
|
||||
type Game struct {
|
||||
ID int32 `json:"id"`
|
||||
OriginalName string `json:"original_name" db:"original_name"`
|
||||
LocalizedName string `json:"localized_name" db:"localized_name"`
|
||||
Description string `json:"description"`
|
||||
Image string `json:"image"`
|
||||
ReleaseDate time.Time `json:"release_date" db:"release_date"`
|
||||
}
|
||||
|
||||
func FetchGame(id int32) (Game, error) {
|
||||
var game Game
|
||||
err := database.Get(&game, "select * from games where id = $1 limit 1;", id)
|
||||
return game, err
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/lib/pq"
|
||||
"log"
|
||||
)
|
||||
|
||||
var database *sqlx.DB
|
||||
|
||||
func ConnectToDB() {
|
||||
var err error
|
||||
conf := privateConfig.Database
|
||||
url := fmt.Sprintf("host=%s port=5432 user=%s password=%s dbname=%s", conf.URL, conf.User, conf.Password, conf.Name)
|
||||
database, err = sqlx.Open("postgres", url)
|
||||
if err != nil {
|
||||
sentry.CaptureException(err)
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Printf("Connected to DB with name %v as %v on %v\n", conf.Name, conf.User, conf.URL)
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func RegisterGenresRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/genres", resolveGenres).Methods("GET", "OPTIONS", "POST") // GET, POST
|
||||
r.HandleFunc("/genres/{id:[0-9]+}", resolveGenre).Methods("GET", "OPTIONS", "PUT", "DELETE") // GET, PUT, DELETE
|
||||
r.HandleFunc("/genres/{id:[0-9]+}/novels", resolveGenreNovels).Methods("GET", "OPTIONS") // GET
|
||||
}
|
||||
|
||||
func resolveGenres(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
genres, err := FetchGenres()
|
||||
Response(w, err, genres)
|
||||
} else if r.Method == http.MethodPost {
|
||||
RawError(w, "method not implemented")
|
||||
}
|
||||
}
|
||||
|
||||
func resolveGenre(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id, _ := strconv.Atoi(vars["id"])
|
||||
|
||||
genre, err := FetchGenre(id)
|
||||
Response(w, err, genre)
|
||||
}
|
||||
|
||||
func resolveGenreNovels(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id, _ := strconv.Atoi(vars["id"])
|
||||
|
||||
novels, err := FetchGenreNovels(id)
|
||||
Response(w, err, novels)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func RegisterGroupsRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/groups", resolveGroups).Methods("GET", "OPTIONS", "POST")
|
||||
r.HandleFunc("/groups/{id:[0-9]+}", resolveGroup).Methods("GET", "OPTIONS", "PUT", "DELETE")
|
||||
}
|
||||
|
||||
func resolveGroups(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
groups, err := GetGroups()
|
||||
Response(w, err, groups)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func resolveGroup(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id, _ := strconv.Atoi(vars["id"])
|
||||
|
||||
if r.Method == http.MethodGet {
|
||||
group, err := GetGroup(id)
|
||||
Response(w, err, group)
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package v1
|
||||
|
||||
type Group struct {
|
||||
ID uint8 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Color string `json:"color"`
|
||||
MaxAvatarSize int `json:"max_avatar_size" db:"max_ava_size"`
|
||||
MaxAvaWeight int64 `json:"max_avatar_weight" db:"max_ava_weight"`
|
||||
IsSuperuser bool `json:"is_superuser" db:"is_superuser"`
|
||||
Permissions string `json:"permissions"`
|
||||
}
|
||||
|
||||
func GetGroups() ([]*Group, error) {
|
||||
var groups = make([]*Group, 0)
|
||||
err := database.Select(&groups, "select * from groups;")
|
||||
return groups, err
|
||||
}
|
||||
|
||||
func GetGroup(id int) (*Group, error) {
|
||||
var group = &Group{}
|
||||
err := database.Get(group, "select * from groups where id=$1;", id)
|
||||
return group, err
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gorilla/mux"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func RegisterNewsRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/news", resolveNews).Methods("GET", "OPTIONS", "POST")
|
||||
r.HandleFunc("/news/{id:[0-9]+}", resolvePost).Methods("GET", "OPTIONS", "PUT", "DELETE")
|
||||
}
|
||||
|
||||
func resolveNews(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
q := r.URL.Query()
|
||||
|
||||
offset, _ := strconv.Atoi(q.Get("offset"))
|
||||
limit, _ := strconv.Atoi(q.Get("limit"))
|
||||
if limit == 0 {
|
||||
limit = 20
|
||||
}
|
||||
|
||||
total, err := FetchTotalNews()
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
pagination := map[string]interface{}{
|
||||
"offset": offset,
|
||||
"limit": limit,
|
||||
"total": total,
|
||||
}
|
||||
|
||||
news, err := FetchNews(offset, limit)
|
||||
ResponsePagination(w, err, news, pagination)
|
||||
} else if r.Method == http.MethodPost {
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
post := new(Post)
|
||||
err = json.Unmarshal(body, post)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
uid, err := GetCurrentUserID(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
post.AuthorID = uid
|
||||
|
||||
createdPost, err := CreatePost(post)
|
||||
Response(w, err, createdPost)
|
||||
}
|
||||
}
|
||||
|
||||
func resolvePost(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id, _ := strconv.Atoi(vars["id"])
|
||||
|
||||
if r.Method == http.MethodGet {
|
||||
post, err := FetchPost(id)
|
||||
Response(w, err, post)
|
||||
} else if r.Method == http.MethodPut {
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
post, err := FetchPost(id)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, post)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = UpdatePost(post)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
} else if r.Method == http.MethodDelete {
|
||||
err := DeletePost(id)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func RegisterNovelsRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/novels", resolveNovels).Methods("GET", "OPTIONS", "POST")
|
||||
r.HandleFunc("/novels/{novelId:[0-9]+}", resolveNovel).Methods("GET", "OPTIONS", "PUT", "DELETE")
|
||||
r.HandleFunc("/novels/{novelId:[0-9]+}/cover", uploadNovelCover).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/novels/{novelId:[0-9]+}/comments", resolveNovelComments).Methods("GET", "OPTIONS", "POST")
|
||||
r.HandleFunc("/novels/{novelId:[0-9]+}/characters", resolveNovelCharacters).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/novels/{novelId:[0-9]+}/genres", resolveNovelGenres).Methods("GET", "OPTIONS")
|
||||
}
|
||||
|
||||
func resolveNovels(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
sortBy, ok := r.URL.Query()["sort"]
|
||||
|
||||
if !ok || len(sortBy) <= 0 {
|
||||
sortBy = append(sortBy, "rating")
|
||||
}
|
||||
|
||||
query, ok := r.URL.Query()["q"]
|
||||
if ok {
|
||||
novels, err := SearchNovels(query[0])
|
||||
Response(w, err, novels)
|
||||
return
|
||||
}
|
||||
|
||||
novels, err := FetchNovels(sortBy[0])
|
||||
Response(w, err, &novels)
|
||||
} else if r.Method == http.MethodPost {
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var novel Novel
|
||||
err = json.Unmarshal(body, &novel)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
novel, err = CreateNovel(&novel)
|
||||
if err == nil {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Header().Add("Location", fmt.Sprintf("https://%v%v/%v", r.Host, r.URL, novel.ID))
|
||||
}
|
||||
Response(w, err, &novel)
|
||||
}
|
||||
}
|
||||
|
||||
func resolveNovelGenres(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id, _ := strconv.Atoi(vars["novelId"])
|
||||
|
||||
genres, err := FetchNovelGenres(int32(id))
|
||||
Response(w, err, genres)
|
||||
}
|
||||
|
||||
func resolveNovelCharacters(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
novelId, _ := strconv.Atoi(vars["novelId"])
|
||||
|
||||
characters, err := FetchNovelCharacters(novelId)
|
||||
Response(w, err, characters)
|
||||
}
|
||||
|
||||
func resolveNovel(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
novelId, _ := strconv.Atoi(vars["novelId"])
|
||||
|
||||
if r.Method == http.MethodGet {
|
||||
novel, err := FetchNovel(novelId)
|
||||
Response(w, err, novel)
|
||||
} else if r.Method == http.MethodPut {
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
novel, err := FetchNovel(novelId)
|
||||
if err != nil {
|
||||
Response(w, err, nil)
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(body, novel)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
novel, err = UpdateNovel(novel)
|
||||
Response(w, err, novel)
|
||||
} else if r.Method == http.MethodDelete {
|
||||
_, err := FetchNovel(novelId)
|
||||
if err == sql.ErrNoRows {
|
||||
NotFound.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
err = DeleteNovel(novelId)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
JSON(w, "")
|
||||
}
|
||||
}
|
||||
|
||||
func uploadNovelCover(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := FetchCurrentUser(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !hasPermission(user, "novel") {
|
||||
NoAccess.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
novelId, _ := strconv.Atoi(vars["novelId"])
|
||||
|
||||
err = r.ParseMultipartForm(1024 * 1024 * 2)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
file, handler, err := r.FormFile("cover")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(handler.Header["Content-Type"][0], "image") {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
RawError(w, "Wrong document type!")
|
||||
return
|
||||
}
|
||||
|
||||
fileBytes, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
ext := strings.Split(handler.Header["Content-Type"][0], "/")[1]
|
||||
path := filepath.Join(config.CDNPath, "novels", "logo", fmt.Sprintf("%v.%v", novelId, ext))
|
||||
|
||||
err = ioutil.WriteFile(path, fileBytes, 0644)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
coverPath := fmt.Sprintf("%v/novels/logo/%v.%v", config.CDNUrl, novelId, ext)
|
||||
err = UpdateNovelCover(novelId, coverPath)
|
||||
Response(w, err, coverPath)
|
||||
}
|
||||
|
||||
func resolveNovelComments(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
novelId, _ := strconv.Atoi(vars["novelId"])
|
||||
|
||||
if r.Method == http.MethodGet {
|
||||
offsetParam, ok := r.URL.Query()["offset"]
|
||||
if !ok || len(offsetParam) <= 0 {
|
||||
offsetParam = append(offsetParam, "0")
|
||||
}
|
||||
|
||||
countParam, ok := r.URL.Query()["count"]
|
||||
if !ok || len(countParam) <= 0 {
|
||||
countParam = append(countParam, "5")
|
||||
}
|
||||
|
||||
offset, _ := strconv.Atoi(offsetParam[0])
|
||||
count, _ := strconv.Atoi(countParam[0])
|
||||
|
||||
var response = make(map[string]interface{}, 2)
|
||||
comments, err := FetchNovelComments(novelId)
|
||||
response["count"] = len(comments)
|
||||
|
||||
if offset+count > len(comments) {
|
||||
comments = comments[offset:]
|
||||
} else {
|
||||
comments = comments[offset : offset+count]
|
||||
}
|
||||
response["comments"] = comments
|
||||
Response(w, err, response)
|
||||
} else if r.Method == http.MethodPost {
|
||||
user, err := FetchCurrentUser(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
commentData := CommentNovel{NovelID: int16(novelId), UserID: user.ID}
|
||||
err = json.Unmarshal(body, &commentData)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
comment, err := AddNovelComment(commentData)
|
||||
Response(w, err, comment)
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func RegisterTokensRouter(r *mux.Router) {
|
||||
sub := r.PathPrefix("/tokens").Subrouter()
|
||||
|
||||
sub.HandleFunc("/auth", auth)
|
||||
sub.HandleFunc("/apps", resolveApps)
|
||||
sub.HandleFunc("/apps/{id:[0-9]+}", auth)
|
||||
}
|
||||
|
||||
func auth(w http.ResponseWriter, r *http.Request) {
|
||||
tokenCookie, err := r.Cookie("token")
|
||||
if err == http.ErrNoCookie || len(tokenCookie.Value) == 0 {
|
||||
Unauthorized.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
func resolveApps(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := FetchCurrentUser(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
if !hasPermission(user, "apps") {
|
||||
NoAccess.Ferror(w)
|
||||
return
|
||||
}
|
||||
apps, err := FetchApps()
|
||||
Response(w, err, apps)
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package v1
|
||||
|
||||
type App struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Secret string `json:"-"`
|
||||
IsVerified bool `json:"is_verified" db:"is_verified"`
|
||||
}
|
||||
|
||||
func FetchApp(id int) (*App, error) {
|
||||
var app = &App{}
|
||||
err := database.Get(app, "select * from apps where id=$1;", id)
|
||||
return app, err
|
||||
}
|
||||
|
||||
func FetchApps() ([]*App, error) {
|
||||
var apps = make([]*App, 0)
|
||||
err := database.Select(&apps, "select * from apps;")
|
||||
return apps, err
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"image"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func RegisterUsersRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/users", resolveUsers).Methods("GET", "OPTIONS", "POST")
|
||||
r.HandleFunc("/users/me", resolveCurrentUser).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/users/me/avatar", uploadAvatar).Methods("OPTIONS", "POST")
|
||||
r.HandleFunc("/users/me/settings", resolveSettings).Methods("GET", "OPTIONS", "POST")
|
||||
r.HandleFunc("/users/{userId:[0-9]+}", resolveUser).Methods("GET", "OPTIONS", "PUT")
|
||||
r.HandleFunc("/users/{userId:[0-9]+}/novels", resolveUserNovels).Methods("GET", "OPTIONS", "POST")
|
||||
r.HandleFunc("/users/{userId:[0-9]+}/novels/{novelId:[0-9]+}", resolveUserNovel).Methods("GET", "OPTIONS", "PUT", "DELETE")
|
||||
r.HandleFunc("/users/{userId:[0-9]+}/novels/comments", resolveUserNovelsComments).Methods("GET", "OPTIONS", "POST")
|
||||
}
|
||||
|
||||
type Settings struct {
|
||||
UseGravatar bool `json:"use_gravatar" db:"use_gravatar"`
|
||||
Avatar string `json:"avatar"`
|
||||
Theme string `json:"theme"`
|
||||
}
|
||||
|
||||
func resolveSettings(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := GetCurrentUserID(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == "GET" {
|
||||
settings, err := FetchUserSettings(id)
|
||||
Response(w, err, settings)
|
||||
} else if r.Method == "POST" {
|
||||
settings, err := FetchUserSettings(id)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, settings)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := UpdateUserSettings(id, settings)
|
||||
Response(w, err, user)
|
||||
}
|
||||
}
|
||||
|
||||
func resolveUsers(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
users, err := FetchUsers()
|
||||
Response(w, err, users)
|
||||
} else {
|
||||
RawError(w, "Work in progress...")
|
||||
}
|
||||
}
|
||||
|
||||
func resolveCurrentUser(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := FetchCurrentUser(r)
|
||||
if err == ErrNotAuthorized {
|
||||
NoToken.Ferror(w)
|
||||
return
|
||||
}
|
||||
if err == http.ErrNoCookie {
|
||||
NoToken.Ferror(w)
|
||||
return
|
||||
}
|
||||
Response(w, err, user)
|
||||
}
|
||||
|
||||
func resolveUser(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id, _ := strconv.Atoi(vars["userId"])
|
||||
|
||||
if r.Method == http.MethodGet {
|
||||
user, err := FetchUser(id)
|
||||
Response(w, err, user)
|
||||
return
|
||||
}
|
||||
|
||||
currentUser, err := FetchCurrentUser(r)
|
||||
if err != nil && err != ErrNotAuthorized {
|
||||
Error(w, err)
|
||||
return
|
||||
} else if err == ErrNotAuthorized || currentUser == nil {
|
||||
currentUser = &User{ID: -1}
|
||||
}
|
||||
|
||||
if r.Method == http.MethodPut {
|
||||
if currentUser.ID != int64(id) && !hasPermission(currentUser, "users") {
|
||||
NoAccess.Ferror(w)
|
||||
return
|
||||
}
|
||||
_, err := FetchUser(id)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var u *User
|
||||
err = json.Unmarshal(body, u)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
user, err := UpdateUser(u)
|
||||
Response(w, err, user)
|
||||
}
|
||||
}
|
||||
|
||||
func uploadAvatar(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := FetchCurrentUser(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = r.ParseMultipartForm(user.Group.MaxAvaWeight)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
file, handler, err := r.FormFile("avatar")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(handler.Header["Content-Type"][0], "image") {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
RawError(w, "Wrong document type!")
|
||||
return
|
||||
}
|
||||
|
||||
fileBytes, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
img, _, err := image.DecodeConfig(bytes.NewReader(fileBytes))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
width, height := img.Width, img.Width
|
||||
if width > user.Group.MaxAvatarSize && height > user.Group.MaxAvatarSize {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
RawError(w, "Image too big!")
|
||||
return
|
||||
}
|
||||
|
||||
ext := strings.Split(handler.Header["Content-Type"][0], "/")[1]
|
||||
path := filepath.Join(config.CDNPath, "users", "avatars", fmt.Sprintf("%v.%v", user.ID, ext))
|
||||
|
||||
err = ioutil.WriteFile(path, fileBytes, 0644)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
avatarPath := fmt.Sprintf("%v/users/avatars/%v.%v", config.CDNUrl, user.ID, ext)
|
||||
Response(w, nil, avatarPath)
|
||||
}
|
||||
|
||||
func resolveUserNovels(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
userId, _ := strconv.Atoi(vars["userId"])
|
||||
|
||||
if r.Method == http.MethodGet {
|
||||
novels, err := FetchUserNovels(userId)
|
||||
Response(w, err, novels)
|
||||
} else if r.Method == http.MethodPost {
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
var novel = &UserNovels{}
|
||||
err = json.Unmarshal(body, novel)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
novel.UserID = int64(userId)
|
||||
|
||||
novel, err = CreateUserNovel(novel)
|
||||
if err == nil {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Header().Add("Location", fmt.Sprintf("https://%v%v/%v", r.Host, r.URL, novel.NovelID))
|
||||
}
|
||||
Response(w, err, novel)
|
||||
}
|
||||
}
|
||||
|
||||
func resolveUserNovel(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
userId, _ := strconv.Atoi(vars["userId"])
|
||||
novelId, _ := strconv.Atoi(vars["novelId"])
|
||||
|
||||
if r.Method == http.MethodGet {
|
||||
novel, err := FetchUserNovel(userId, novelId)
|
||||
Response(w, err, novel)
|
||||
} else if r.Method == http.MethodPut {
|
||||
_, err := FetchNovel(novelId)
|
||||
if err == sql.ErrNoRows {
|
||||
NotFound.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = FetchUser(userId)
|
||||
if err == sql.ErrNoRows {
|
||||
NotFound.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
novel, _ := FetchUserNovel(userId, novelId)
|
||||
body, err := ReadBody(r)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if novel == nil {
|
||||
NotFound.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &novel)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
updatedNovel, err := UpdateUserNovel(novel)
|
||||
lpServer.PushEvent(updatedNovel, uint64(userId), "update-user-novel")
|
||||
Response(w, err, updatedNovel)
|
||||
} else if r.Method == http.MethodDelete {
|
||||
_, err := FetchUserNovel(userId, novelId)
|
||||
if err == sql.ErrNoRows {
|
||||
NotFound.Ferror(w)
|
||||
return
|
||||
}
|
||||
|
||||
err = DeleteUserNovel(userId, novelId)
|
||||
if err != nil {
|
||||
Error(w, err)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
func resolveUserNovelsComments(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id, _ := strconv.Atoi(vars["userId"])
|
||||
|
||||
comments, err := FetchUserNovelsComments(id)
|
||||
Response(w, err, comments)
|
||||
}
|
||||
Reference in New Issue
Block a user