dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

admin.go (27966B)


      1 package handlers
      2 
      3 import (
      4 	dutils "dkforest/pkg/database/utils"
      5 	"dkforest/pkg/managers"
      6 	"dkforest/pkg/web/handlers/interceptors"
      7 	"dkforest/pkg/web/handlers/usersStreamsManager"
      8 	hutils "dkforest/pkg/web/handlers/utils"
      9 	"fmt"
     10 	wallet1 "github.com/monero-ecosystem/go-monero-rpc-client/wallet"
     11 	"gorm.io/gorm"
     12 	"io"
     13 	"math"
     14 	"net/http"
     15 	"regexp"
     16 	"strings"
     17 
     18 	"dkforest/pkg/config"
     19 	"dkforest/pkg/database"
     20 	"dkforest/pkg/utils"
     21 	"github.com/asaskevich/govalidator"
     22 	"github.com/google/uuid"
     23 	"github.com/labstack/echo"
     24 	"github.com/sirupsen/logrus"
     25 	"github.com/skratchdot/open-golang/open"
     26 )
     27 
     28 func AdminSpamFiltersHandler(c echo.Context) error {
     29 	db := c.Get("database").(*database.DkfDB)
     30 	var data adminSpamFiltersData
     31 	data.ActiveTab = "spamfilters"
     32 	data.SpamFilters, _ = db.GetSpamFilters()
     33 	data.SpamFiltersCount = int64(len(data.SpamFilters))
     34 
     35 	if c.Request().Method == http.MethodPost {
     36 		btnSubmit := c.Request().PostFormValue("btn_submit")
     37 		data.ID = utils.DoParseInt64(c.Request().PostFormValue("id"))
     38 		data.Filter = c.Request().PostFormValue("filter")
     39 		data.IsRegex = utils.DoParseBool(c.Request().PostFormValue("is_regex"))
     40 		data.Action = utils.Clamp(utils.DoParseInt64(c.Request().PostFormValue("action")), 0, 2)
     41 		data.NbMsg = utils.Clamp(utils.DoParseInt64(c.Request().PostFormValue("nb_msg")), 0, 100)
     42 		if !utils.ValidateRuneLength(data.Filter, 1, 255) {
     43 			data.Error = "filter must be within 1-255 characters"
     44 			return c.Render(http.StatusOK, "admin.spam-filter", data)
     45 		}
     46 		if data.ID == 0 || btnSubmit == "edit" {
     47 			if _, err := db.CreateOrEditSpamFilter(data.ID, data.Filter, data.IsRegex, data.Action, data.NbMsg); err != nil {
     48 				logrus.Error(err)
     49 			}
     50 			interceptors.LoadFilters(db)
     51 		} else if btnSubmit == "delete" {
     52 			if err := db.DeleteSpamFilterByID(data.ID); err != nil {
     53 				logrus.Error(err)
     54 			}
     55 			interceptors.LoadFilters(db)
     56 		}
     57 		return c.Redirect(http.StatusFound, "/admin/spam-filters")
     58 	}
     59 
     60 	return c.Render(http.StatusOK, "admin.spam-filters", data)
     61 }
     62 
     63 func AdminNewGistHandler(c echo.Context) error {
     64 	authUser := c.Get("authUser").(*database.User)
     65 	db := c.Get("database").(*database.DkfDB)
     66 	var data adminCreateGistData
     67 	data.ActiveTab = "gists"
     68 	if c.Request().Method == http.MethodPost {
     69 		data.Name = c.Request().PostFormValue("name")
     70 		data.Password = c.Request().PostFormValue("password")
     71 		data.Content = c.Request().PostFormValue("content")
     72 		if !govalidator.Matches(data.Name, "^[a-zA-Z0-9_.]{3,50}$") {
     73 			data.ErrorName = "invalid name"
     74 			return c.Render(http.StatusOK, "admin.gist-create", data)
     75 		}
     76 		passwordHash := ""
     77 		if data.Password != "" {
     78 			passwordHash = database.GetGistPasswordHash(data.Password)
     79 		}
     80 		gist := database.Gist{UUID: uuid.New().String(), Name: data.Name, Password: passwordHash, UserID: authUser.ID, Content: data.Content}
     81 		if err := db.DB().Create(&gist).Error; err != nil {
     82 			data.Error = err.Error()
     83 			return c.Render(http.StatusOK, "admin.gist-create", data)
     84 		}
     85 		return c.Redirect(http.StatusFound, "/gists/"+gist.UUID)
     86 	}
     87 	return c.Render(http.StatusOK, "admin.gist-create", data)
     88 }
     89 
     90 func AdminEditGistHandler(c echo.Context) error {
     91 	authUser := c.Get("authUser").(*database.User)
     92 	db := c.Get("database").(*database.DkfDB)
     93 	gistUUID := c.Param("gistUUID")
     94 	gist, err := db.GetGistByUUID(gistUUID)
     95 	if err != nil {
     96 		return c.Redirect(http.StatusFound, "/")
     97 	}
     98 	if gist.UserID != authUser.ID && !authUser.IsAdmin {
     99 		return c.Redirect(http.StatusFound, "/")
    100 	}
    101 	var data adminCreateGistData
    102 	data.ActiveTab = "gists"
    103 	data.IsEdit = true
    104 	data.Name = gist.Name
    105 	data.Content = gist.Content
    106 	if gist.Password != "" {
    107 		data.Password = "*****"
    108 	}
    109 
    110 	if c.Request().Method == http.MethodPost {
    111 		data.Name = c.Request().PostFormValue("name")
    112 		data.Password = c.Request().PostFormValue("password")
    113 		data.Content = c.Request().PostFormValue("content")
    114 		if !govalidator.Matches(data.Name, "^[a-zA-Z0-9_.]{3,50}$") {
    115 			data.ErrorName = "invalid name"
    116 			return c.Render(http.StatusOK, "admin.gist-create", data)
    117 		}
    118 		passwordHash := ""
    119 		if data.Password != "" && data.Password != "*****" {
    120 			passwordHash = database.GetGistPasswordHash(data.Password)
    121 			gist.Password = passwordHash
    122 		}
    123 		gist.Name = data.Name
    124 		gist.Content = data.Content
    125 		gist.DoSave(db)
    126 		return c.Redirect(http.StatusFound, "/gists/"+gist.UUID)
    127 	}
    128 	return c.Render(http.StatusOK, "admin.gist-create", data)
    129 }
    130 
    131 func AdminGistsHandler(c echo.Context) error {
    132 	db := c.Get("database").(*database.DkfDB)
    133 	var data adminGistsData
    134 	data.ActiveTab = "gists"
    135 
    136 	userQuery := c.QueryParam("u")
    137 
    138 	if err := db.DB().Table("gists").
    139 		Scopes(func(query *gorm.DB) *gorm.DB {
    140 			if userQuery != "" {
    141 				query = query.Where("user_id = ?", userQuery)
    142 			}
    143 			data.CurrentPage, data.MaxPage, data.GistsCount, query = NewPaginator().Paginate(c, query)
    144 			return query
    145 		}).
    146 		Preload("User").
    147 		Order("id DESC").
    148 		Find(&data.Gists).Error; err != nil {
    149 		logrus.Error(err)
    150 	}
    151 
    152 	return c.Render(http.StatusOK, "admin.gists", data)
    153 }
    154 
    155 func AdminUploadsHandler(c echo.Context) error {
    156 	db := c.Get("database").(*database.DkfDB)
    157 	if c.Request().Method == http.MethodPost {
    158 		formName := c.FormValue("formName")
    159 		if formName == "deleteUpload" {
    160 			fileName := c.Request().PostFormValue("file_name")
    161 			file, err := db.GetUploadByFileName(fileName)
    162 			if err != nil {
    163 				return c.Redirect(http.StatusFound, "/")
    164 			}
    165 			if err := file.Delete(db); err != nil {
    166 				logrus.Error(err)
    167 			}
    168 			return c.Redirect(http.StatusFound, "/admin/uploads")
    169 		}
    170 	}
    171 
    172 	var data adminUploadsData
    173 	data.ActiveTab = "uploads"
    174 	data.Uploads, _ = db.GetUploads()
    175 	for _, f := range data.Uploads {
    176 		data.TotalSize += f.FileSize
    177 	}
    178 	return c.Render(http.StatusOK, "admin.uploads", data)
    179 }
    180 
    181 func AdminFiledropsHandler(c echo.Context) error {
    182 	db := c.Get("database").(*database.DkfDB)
    183 	if c.Request().Method == http.MethodPost {
    184 		formName := c.FormValue("formName")
    185 		if formName == "createFiledrop" {
    186 			if _, err := db.CreateFiledrop(); err != nil {
    187 				logrus.Error(err)
    188 			}
    189 			return c.Redirect(http.StatusFound, "/admin/filedrops")
    190 
    191 		} else if formName == "deleteFiledrop" {
    192 			fileName := c.Request().PostFormValue("file_name")
    193 			file, err := db.GetFiledropByFileName(fileName)
    194 			if err != nil {
    195 				return c.Redirect(http.StatusFound, "/admin/filedrops")
    196 			}
    197 			if err := file.Delete(db); err != nil {
    198 				logrus.Error(err)
    199 			}
    200 			return c.Redirect(http.StatusFound, "/admin/filedrops")
    201 		}
    202 	}
    203 
    204 	var data adminFiledropsData
    205 	data.ActiveTab = "filedrops"
    206 	data.Filedrops, _ = db.GetFiledrops()
    207 	for _, f := range data.Filedrops {
    208 		data.TotalSize += f.FileSize
    209 	}
    210 	return c.Render(http.StatusOK, "admin.filedrops", data)
    211 }
    212 
    213 func AdminDownloadsHandler(c echo.Context) error {
    214 	db := c.Get("database").(*database.DkfDB)
    215 	var data adminDownloadsData
    216 	data.ActiveTab = "downloads"
    217 
    218 	userQuery := c.QueryParam("u")
    219 
    220 	db.DB().Model(&database.Download{}).
    221 		Scopes(func(query *gorm.DB) *gorm.DB {
    222 			if userQuery != "" {
    223 				query = query.Where("user_id = ?", userQuery)
    224 			}
    225 			data.CurrentPage, data.MaxPage, data.DownloadsCount, query = NewPaginator().Paginate(c, query)
    226 			return query
    227 		}).
    228 		Preload("User").
    229 		Order("id DESC").
    230 		Find(&data.Downloads)
    231 
    232 	return c.Render(http.StatusOK, "admin.downloads", data)
    233 }
    234 
    235 func AdminDeleteDownloadHandler(c echo.Context) error {
    236 	db := c.Get("database").(*database.DkfDB)
    237 	downloadID, err := utils.ParseInt64(c.Param("downloadID"))
    238 	if err != nil {
    239 		return c.Render(http.StatusOK, "flash",
    240 			FlashResponse{"download id not found", "/admin/downloads", "alert-danger"})
    241 	}
    242 
    243 	if err := db.DeleteDownloadByID(downloadID); err != nil {
    244 		logrus.Error(err)
    245 	}
    246 	return c.Redirect(http.StatusFound, "/admin/downloads")
    247 }
    248 
    249 // AdminSettingsHandler ...
    250 func AdminSettingsHandler(c echo.Context) error {
    251 	db := c.Get("database").(*database.DkfDB)
    252 	var data adminSettingsData
    253 	data.ActiveTab = "settings"
    254 	settings := db.GetSettings()
    255 	data.ProtectHome = settings.ProtectHome
    256 	data.HomeUsersList = settings.HomeUsersList
    257 	data.ForceLoginCaptcha = settings.ForceLoginCaptcha
    258 	data.SignupEnabled = settings.SignupEnabled
    259 	data.SignupFakeEnabled = settings.SignupFakeEnabled
    260 	data.DownloadsEnabled = settings.DownloadsEnabled
    261 	data.ForumEnabled = settings.ForumEnabled
    262 	data.MaybeAuthEnabled = settings.MaybeAuthEnabled
    263 	data.CaptchaDifficulty = settings.CaptchaDifficulty
    264 	data.PowEnabled = settings.PowEnabled
    265 	data.PokerWithdrawEnabled = settings.PokerWithdrawEnabled
    266 	data.MoneroPrice = settings.MoneroPrice
    267 
    268 	if c.Request().Method == http.MethodPost {
    269 		formName := c.Request().PostFormValue("formName")
    270 		if formName == "openProjectFolder" {
    271 			if err := open.Run(config.Global.ProjectPath.Get()); err != nil {
    272 				logrus.Error(err)
    273 			}
    274 			return c.Redirect(http.StatusFound, "/admin/settings")
    275 
    276 		} else if formName == "saveSettings" {
    277 			settings := db.GetSettings()
    278 			settings.ProtectHome = utils.DoParseBool(c.Request().PostFormValue("protectHome"))
    279 			settings.HomeUsersList = utils.DoParseBool(c.Request().PostFormValue("homeUsersList"))
    280 			settings.ForceLoginCaptcha = utils.DoParseBool(c.Request().PostFormValue("forceLoginCaptcha"))
    281 			settings.SignupEnabled = utils.DoParseBool(c.Request().PostFormValue("signupEnabled"))
    282 			settings.SignupFakeEnabled = utils.DoParseBool(c.Request().PostFormValue("signupFakeEnabled"))
    283 			settings.DownloadsEnabled = utils.DoParseBool(c.Request().PostFormValue("downloadsEnabled"))
    284 			settings.ForumEnabled = utils.DoParseBool(c.Request().PostFormValue("forumEnabled"))
    285 			settings.MaybeAuthEnabled = utils.DoParseBool(c.Request().PostFormValue("maybeAuthEnabled"))
    286 			settings.CaptchaDifficulty = utils.DoParseInt64(c.Request().PostFormValue("captchaDifficulty"))
    287 			settings.PowEnabled = utils.DoParseBool(c.Request().PostFormValue("powEnabled"))
    288 			settings.PokerWithdrawEnabled = utils.DoParseBool(c.Request().PostFormValue("pokerWithdrawEnabled"))
    289 			settings.MoneroPrice = math.Max(utils.DoParseF64(c.Request().PostFormValue("moneroPrice")), 1)
    290 			settings.DoSave(db)
    291 			config.ProtectHome.Store(settings.ProtectHome)
    292 			config.HomeUsersList.Store(settings.HomeUsersList)
    293 			config.ForceLoginCaptcha.Store(settings.ForceLoginCaptcha)
    294 			config.SignupEnabled.Store(settings.SignupEnabled)
    295 			config.CaptchaDifficulty.Store(settings.CaptchaDifficulty)
    296 			config.PowEnabled.Store(settings.PowEnabled)
    297 			config.PokerWithdrawEnabled.Store(settings.PokerWithdrawEnabled)
    298 			config.SignupFakeEnabled.Store(settings.SignupFakeEnabled)
    299 			config.DownloadsEnabled.Store(settings.DownloadsEnabled)
    300 			config.ForumEnabled.Store(settings.ForumEnabled)
    301 			config.MaybeAuthEnabled.Store(settings.MaybeAuthEnabled)
    302 			config.MoneroPrice.Store(settings.MoneroPrice)
    303 			return c.Redirect(http.StatusFound, "/admin/settings")
    304 		}
    305 
    306 	}
    307 
    308 	return c.Render(http.StatusOK, "admin.settings", data)
    309 }
    310 
    311 func AdminHandler(c echo.Context) error {
    312 	db := c.Get("database").(*database.DkfDB)
    313 	var data adminData
    314 	data.ActiveTab = "users"
    315 	data.Query = strings.TrimSpace(c.QueryParam("q"))
    316 	likeQuery := "%" + data.Query + "%"
    317 
    318 	if err := db.DB().
    319 		Table("users").
    320 		Scopes(func(query *gorm.DB) *gorm.DB {
    321 			if data.Query != "" {
    322 				query = query.Where("username LIKE ?", likeQuery, likeQuery, data.Query)
    323 			}
    324 			data.CurrentPage, data.MaxPage, data.UsersCount, query = NewPaginator().Paginate(c, query)
    325 			return query
    326 		}).
    327 		Order("id DESC").
    328 		Find(&data.Users).Error; err != nil {
    329 		logrus.Error(err)
    330 	}
    331 
    332 	return c.Render(http.StatusOK, "admin.users", data)
    333 }
    334 
    335 func SessionsHandler(c echo.Context) error {
    336 	db := c.Get("database").(*database.DkfDB)
    337 	var data adminSessionsData
    338 	data.ActiveTab = "sessions"
    339 	data.Query = c.QueryParam("q")
    340 	likeQuery := "%" + data.Query + "%"
    341 
    342 	if err := db.DB().
    343 		Table("sessions").
    344 		Where("deleted_at IS NULL").
    345 		Scopes(func(query *gorm.DB) *gorm.DB {
    346 			if data.Query != "" {
    347 				query = query.Where("token LIKE ?", likeQuery, likeQuery, data.Query)
    348 			}
    349 			data.CurrentPage, data.MaxPage, data.SessionsCount, query = NewPaginator().Paginate(c, query)
    350 			return query
    351 		}).Preload("User").
    352 		Order("created_at DESC").
    353 		Find(&data.Sessions).Error; err != nil {
    354 		logrus.Error(err)
    355 	}
    356 
    357 	return c.Render(http.StatusOK, "admin.sessions", data)
    358 }
    359 
    360 func IgnoredHandler(c echo.Context) error {
    361 	db := c.Get("database").(*database.DkfDB)
    362 	var data adminIgnoredData
    363 	data.ActiveTab = "ignored"
    364 	data.Query = c.QueryParam("q")
    365 	likeQuery := "%" + data.Query + "%"
    366 
    367 	if err := db.DB().
    368 		Table("ignored_users").
    369 		Scopes(func(query *gorm.DB) *gorm.DB {
    370 			if data.Query != "" {
    371 				query = query.Where("token LIKE ?", likeQuery, likeQuery, data.Query)
    372 			}
    373 			data.CurrentPage, data.MaxPage, data.IgnoredCount, query = NewPaginator().Paginate(c, query)
    374 			return query
    375 		}).
    376 		Preload("User").
    377 		Preload("IgnoredUser").
    378 		Order("created_at DESC").
    379 		Find(&data.Ignored).Error; err != nil {
    380 		logrus.Error(err)
    381 	}
    382 
    383 	return c.Render(http.StatusOK, "admin.ignored", data)
    384 }
    385 
    386 func DdosHandler(c echo.Context) error {
    387 	if c.Request().Method == http.MethodPost {
    388 		config.SignupPageLoad.Store(0)
    389 		config.SignupFailed.Store(0)
    390 		config.SignupSucceed.Store(0)
    391 		config.BHCCaptchaGenerated.Store(0)
    392 		config.BHCCaptchaSuccess.Store(0)
    393 		config.BHCCaptchaFailed.Store(0)
    394 		config.CaptchaRequiredGenerated.Store(0)
    395 		config.CaptchaRequiredSuccess.Store(0)
    396 		config.CaptchaRequiredFailed.Store(0)
    397 		return c.Redirect(http.StatusFound, "/admin/ddos")
    398 	}
    399 	var data adminDdosData
    400 	data.ActiveTab = "ddos"
    401 	data.RPS = config.RpsCounter.Rate()
    402 	data.RejectedReq = config.RejectedReqCounter.Rate()
    403 	data.SignupPageLoad = config.SignupPageLoad.Load()
    404 	data.SignupFailed = config.SignupFailed.Load()
    405 	data.SignupSucceed = config.SignupSucceed.Load()
    406 	data.BHCCaptchaGenerated = config.BHCCaptchaGenerated.Load()
    407 	data.BHCCaptchaSuccess = config.BHCCaptchaSuccess.Load()
    408 	data.BHCCaptchaFailed = config.BHCCaptchaFailed.Load()
    409 	data.CaptchaRequiredGenerated = config.CaptchaRequiredGenerated.Load()
    410 	data.CaptchaRequiredSuccess = config.CaptchaRequiredSuccess.Load()
    411 	data.CaptchaRequiredFailed = config.CaptchaRequiredFailed.Load()
    412 	return c.Render(http.StatusOK, "admin.ddos", data)
    413 }
    414 
    415 func BackupHandler(c echo.Context) error {
    416 	var data backupData
    417 	data.ActiveTab = "backup"
    418 	if c.Request().Method == http.MethodPost {
    419 		formName := c.Request().PostFormValue("formName")
    420 		if formName == "backup" {
    421 			if config.MaintenanceAtom.CAS(false, true) {
    422 				utils.SGo(func() {
    423 					defer config.MaintenanceAtom.SetFalse()
    424 					if err := database.Backup(); err != nil {
    425 						logrus.Error("Failed to backup database: ", err)
    426 					}
    427 				})
    428 			}
    429 			return c.Redirect(http.StatusFound, "/admin/backup")
    430 		} else if formName == "toggleMaintenance" {
    431 			if config.MaintenanceAtom.CAS(false, true) {
    432 				logrus.Info("maintenance mode turned on")
    433 			} else if config.MaintenanceAtom.CAS(true, false) {
    434 				logrus.Info("maintenance mode turned off")
    435 			}
    436 			return c.Redirect(http.StatusFound, "/admin/backup")
    437 		}
    438 	}
    439 
    440 	return c.Render(http.StatusOK, "admin.backup", data)
    441 }
    442 
    443 func AdminAuditsHandler(c echo.Context) error {
    444 	db := c.Get("database").(*database.DkfDB)
    445 	var data adminAuditsData
    446 	data.ActiveTab = "audits"
    447 
    448 	if err := db.DB().
    449 		Table("audit_logs").
    450 		Scopes(func(query *gorm.DB) *gorm.DB {
    451 			data.CurrentPage, data.MaxPage, data.AuditLogsCount, query = NewPaginator().Paginate(c, query)
    452 			return query
    453 		}).
    454 		Preload("User").
    455 		Order("id DESC").
    456 		Find(&data.AuditLogs).Error; err != nil {
    457 		logrus.Error(err)
    458 	}
    459 	return c.Render(http.StatusOK, "admin.audits", data)
    460 }
    461 
    462 func AdminRoomsHandler(c echo.Context) error {
    463 	db := c.Get("database").(*database.DkfDB)
    464 	var data adminRoomsData
    465 	data.ActiveTab = "rooms"
    466 	data.Query = c.QueryParam("q")
    467 	likeQuery := "%" + data.Query + "%"
    468 
    469 	if err := db.DB().
    470 		Table("chat_rooms").
    471 		Scopes(func(query *gorm.DB) *gorm.DB {
    472 			if data.Query != "" {
    473 				query = query.Where("username LIKE ?", likeQuery, likeQuery, data.Query)
    474 			}
    475 			data.CurrentPage, data.MaxPage, data.RoomsCount, query = NewPaginator().Paginate(c, query)
    476 			return query
    477 		}).
    478 		Order("id DESC").
    479 		Preload("OwnerUser").
    480 		Find(&data.Rooms).Error; err != nil {
    481 		logrus.Error(err)
    482 	}
    483 	return c.Render(http.StatusOK, "admin.rooms", data)
    484 }
    485 
    486 func AdminPokerAddressesHandler(c echo.Context) error {
    487 	db := c.Get("database").(*database.DkfDB)
    488 	var data adminPokerAddressesData
    489 	data.ActiveTab = "pokerAddresses"
    490 	res, err := config.Xmr().GetAddress(&wallet1.RequestGetAddress{})
    491 	if err != nil {
    492 		logrus.Error(err)
    493 		return hutils.RedirectReferer(c)
    494 	}
    495 	addrs := make([]string, 0)
    496 	for _, addr := range res.Addresses {
    497 		addrs = append(addrs, addr.Address)
    498 	}
    499 	var users []database.User
    500 	if err := db.DB().Where("poker_xmr_sub_address != '' AND poker_xmr_sub_address NOT IN (?)", addrs).Find(&users).Error; err != nil {
    501 		logrus.Error(err)
    502 	}
    503 	data.Addresses = addrs
    504 	data.Users = users
    505 	return c.Render(http.StatusOK, "admin.poker-addresses", data)
    506 }
    507 
    508 func AdminPokerTransactionsHandler(c echo.Context) error {
    509 	db := c.Get("database").(*database.DkfDB)
    510 	var data adminPokerTransactionsData
    511 	data.ActiveTab = "pokerTransactions"
    512 	data.PokerCasino = db.GetPokerCasino()
    513 	res, err := config.Xmr().GetBalance(&wallet1.RequestGetBalance{})
    514 	if err != nil {
    515 		logrus.Error(err)
    516 		return hutils.RedirectReferer(c)
    517 	}
    518 	data.Balance = database.Piconero(res.Balance)
    519 	data.UnlockedBalance = database.Piconero(res.UnlockedBalance)
    520 	data.SumIn, _ = db.GetPokerXmrTransactionsSumIn()
    521 	data.SumOut, _ = db.GetPokerXmrTransactionsSumOut()
    522 	data.DiffInOut = data.SumIn - data.SumOut
    523 	sumXmrBalance, _ := db.GetUsersXmrBalance()
    524 	data.UsersRakeBack, _ = db.GetUsersRakeBack()
    525 	sumTableAccounts, sumTableBets, _ := db.GetPokerTableAccountSums()
    526 	data.Discrepancy = (int64(data.SumIn) - int64(data.SumOut)) -
    527 		int64(sumXmrBalance) -
    528 		int64(data.UsersRakeBack.ToPiconero()) -
    529 		int64(data.PokerCasino.Rake.ToPiconero()) -
    530 		int64(sumTableAccounts.ToPiconero()) -
    531 		int64(sumTableBets.ToPiconero())
    532 	data.DiscrepancyPiconero = database.Piconero(uint64(math.Abs(float64(data.Discrepancy))))
    533 
    534 	if err := db.DB().
    535 		Table("poker_xmr_transactions").
    536 		Scopes(func(query *gorm.DB) *gorm.DB {
    537 			data.CurrentPage, data.MaxPage, data.TransactionsCount, query = NewPaginator().Paginate(c, query)
    538 			return query
    539 		}).
    540 		Order("id DESC").
    541 		Preload("User").
    542 		Find(&data.Transactions).Error; err != nil {
    543 		logrus.Error(err)
    544 	}
    545 
    546 	return c.Render(http.StatusOK, "admin.poker-transactions", data)
    547 }
    548 
    549 func AdminCaptchaHandler(c echo.Context) error {
    550 	db := c.Get("database").(*database.DkfDB)
    551 	var data adminCaptchaData
    552 	data.ActiveTab = "captcha"
    553 
    554 	if err := db.DB().Table("captcha_requests").
    555 		Scopes(func(query *gorm.DB) *gorm.DB {
    556 			data.CurrentPage, data.MaxPage, data.CaptchasCount, query = NewPaginator().Paginate(c, query)
    557 			return query
    558 		}).
    559 		Preload("User").
    560 		Order("id DESC").
    561 		Find(&data.Captchas).Error; err != nil {
    562 		logrus.Error(err)
    563 	}
    564 	return c.Render(http.StatusOK, "admin.captcha", data)
    565 }
    566 
    567 // AdminDeleteUserHandler ...
    568 func AdminDeleteUserHandler(c echo.Context) error {
    569 	db := c.Get("database").(*database.DkfDB)
    570 	userID, err := dutils.ParseUserID(c.Param("userID"))
    571 	if err != nil {
    572 		return c.Render(http.StatusOK, "flash",
    573 			FlashResponse{"user id not found", "/admin/users", "alert-danger"})
    574 	}
    575 	if userID == config.RootAdminID {
    576 		return c.Render(http.StatusOK, "flash",
    577 			FlashResponse{"Root admin cannot be deleted", "/admin/users", "alert-danger"})
    578 	}
    579 
    580 	if err := db.DeleteUserByID(userID); err != nil {
    581 		logrus.Error(err)
    582 	}
    583 	return hutils.RedirectReferer(c)
    584 }
    585 
    586 func IgnoredDeleteHandler(c echo.Context) error {
    587 	db := c.Get("database").(*database.DkfDB)
    588 	userID := dutils.DoParseUserID(c.Request().PostFormValue("user_id"))
    589 	ignoredUserID := dutils.DoParseUserID(c.Request().PostFormValue("ignored_user_id"))
    590 	db.UnIgnoreUser(userID, ignoredUserID)
    591 	return c.Redirect(http.StatusFound, "/admin/ignored")
    592 }
    593 
    594 // AdminDeleteRoomHandler ...
    595 func AdminDeleteRoomHandler(c echo.Context) error {
    596 	db := c.Get("database").(*database.DkfDB)
    597 	id := dutils.DoParseRoomID(c.Param("roomID"))
    598 	db.DeleteChatRoomByID(id)
    599 	return c.Redirect(http.StatusFound, "/admin/rooms")
    600 }
    601 
    602 func AdminUserSecurityLogsHandler(c echo.Context) error {
    603 	//authUser := c.Get("authUser").(*database.User)
    604 	db := c.Get("database").(*database.DkfDB)
    605 	userID, err := dutils.ParseUserID(c.Param("userID"))
    606 	if err != nil {
    607 		return c.Redirect(http.StatusFound, "/admin/users")
    608 	}
    609 	var data settingsSecurityData
    610 	data.ActiveTab = "security"
    611 	data.Logs, _ = db.GetSecurityLogs(userID)
    612 	return c.Render(http.StatusOK, "admin.user-security-logs", data)
    613 }
    614 
    615 // AdminEditUserHandler ...
    616 func AdminEditUserHandler(c echo.Context) error {
    617 	authUser := c.Get("authUser").(*database.User)
    618 	db := c.Get("database").(*database.DkfDB)
    619 	userID, err := dutils.ParseUserID(c.Param("userID"))
    620 	if err != nil {
    621 		return c.Redirect(http.StatusFound, "/admin/users")
    622 	}
    623 	// Only root admin can edit the root admin
    624 	if userID == config.RootAdminID && authUser.ID != config.RootAdminID {
    625 		return c.Redirect(http.StatusFound, "/admin/users")
    626 	}
    627 	user, err := db.GetUserByID(userID)
    628 	if err != nil {
    629 		return c.Redirect(http.StatusFound, "/admin/users")
    630 	}
    631 	var data adminEditUsereData
    632 	data.ActiveTab = "users"
    633 	data.User = user
    634 	data.ChatTutorial = user.ChatTutorial
    635 	data.AllFonts = utils.GetFonts()
    636 	data.IsEdit = true
    637 	data.Username = user.Username
    638 	data.ApiKey = user.ApiKey
    639 	data.IsAdmin = user.IsAdmin
    640 	data.IsHellbanned = user.IsHellbanned
    641 	data.Verified = user.Verified
    642 	data.IsClubMember = user.IsClubMember
    643 	data.CanUploadFile = user.CanUploadFile
    644 	data.CanUseForum = user.CanUseForum
    645 	data.CanUseMultiline = user.CanUseMultiline
    646 	data.CanUseChessAnalyze = user.CanUseChessAnalyze
    647 	data.CanSeeHellbanned = user.CanSeeHellbanned
    648 	data.IsIncognito = user.IsIncognito
    649 	data.CanChangeUsername = user.CanChangeUsername
    650 	data.CanUseUppercase = user.CanUseUppercase
    651 	data.CanChangeColor = user.CanChangeColor
    652 	data.Vetted = user.Vetted
    653 	data.Role = user.Role
    654 	data.ChatColor = user.ChatColor
    655 	data.ChatFont = user.ChatFont
    656 	data.SignupMetadata = user.SignupMetadata
    657 	data.CollectMetadata = user.CollectMetadata
    658 	if c.Request().Method == http.MethodGet {
    659 		return c.Render(http.StatusOK, "admin.user-edit", data)
    660 	}
    661 
    662 	formName := c.Request().PostFormValue("formName")
    663 	if formName == "reset_login_attempts" {
    664 		user.ResetLoginAttempts(db)
    665 		return c.Redirect(http.StatusFound, "/admin/users/"+userID.String()+"/edit")
    666 	} else if formName == "disable_2fa" {
    667 		user.DisableTotp2FA(db)
    668 		user.DisableGpg2FA(db)
    669 		return c.Redirect(http.StatusFound, "/admin/users/"+userID.String()+"/edit")
    670 	} else if formName == "reset_tutorial" {
    671 		user.ResetTutorial(db)
    672 		return c.Redirect(http.StatusFound, "/admin/users/"+userID.String()+"/edit")
    673 	}
    674 
    675 	data.Username = database.Username(c.FormValue("username"))
    676 	data.Role = c.Request().PostFormValue("role")
    677 	data.IsAdmin = utils.DoParseBool(c.FormValue("isAdmin"))
    678 	data.IsHellbanned = utils.DoParseBool(c.FormValue("isHellbanned"))
    679 	data.Verified = utils.DoParseBool(c.FormValue("verified"))
    680 	data.IsClubMember = utils.DoParseBool(c.FormValue("is_club_member"))
    681 	data.CanUploadFile = utils.DoParseBool(c.FormValue("can_upload_file"))
    682 	data.CanUseForum = utils.DoParseBool(c.FormValue("can_use_forum"))
    683 	data.CanUseMultiline = utils.DoParseBool(c.FormValue("can_use_multiline"))
    684 	data.CanUseChessAnalyze = utils.DoParseBool(c.FormValue("can_use_chess_analyze"))
    685 	data.CanSeeHellbanned = utils.DoParseBool(c.FormValue("can_see_hellbanned"))
    686 	data.IsIncognito = utils.DoParseBool(c.FormValue("is_incognito"))
    687 	data.CanChangeUsername = utils.DoParseBool(c.FormValue("can_change_username"))
    688 	data.CanUseUppercase = utils.DoParseBool(c.FormValue("can_use_uppercase"))
    689 	data.CanChangeColor = utils.DoParseBool(c.FormValue("can_change_color"))
    690 	data.Vetted = utils.DoParseBool(c.FormValue("vetted"))
    691 	data.CollectMetadata = utils.DoParseBool(c.FormValue("collect_metadata"))
    692 	data.ChatColor = c.FormValue("chat_color")
    693 	colorRgx := regexp.MustCompile(`^#[0-9a-fA-F]{6}$`)
    694 	if !colorRgx.MatchString(data.ChatColor) {
    695 		data.Errors.Username = "Invalid color format"
    696 	}
    697 	data.ChatFont = utils.DoParseInt64(c.FormValue("chat_font"))
    698 	if data.Username != user.Username {
    699 		if err := db.CanRenameTo(user.Username, data.Username); err != nil {
    700 			data.Errors.Username = err.Error()
    701 		}
    702 	}
    703 	// Edit password
    704 	var hashedPassword string
    705 	data.Password = c.Request().PostFormValue("password")
    706 	data.RePassword = c.Request().PostFormValue("repassword")
    707 	data.ApiKey = c.Request().PostFormValue("api_key")
    708 	if data.Password != "" || data.RePassword != "" {
    709 		hashedPassword, err = database.NewPasswordValidator(db, data.Password).CompareWith(data.RePassword).Hash()
    710 		if err != nil {
    711 			data.Errors.Password = err.Error()
    712 		}
    713 	}
    714 	if data.Errors.HasError() {
    715 		return c.Render(http.StatusOK, "admin.user-edit", data)
    716 	}
    717 
    718 	if hashedPassword != "" {
    719 		user.LoginAttempts = 0
    720 		if err := user.ChangePassword(db, hashedPassword); err != nil {
    721 			data.Errors.Password = err.Error()
    722 			return c.Render(http.StatusOK, "admin.user-edit", data)
    723 		}
    724 	}
    725 
    726 	user.Username = data.Username
    727 	user.IsAdmin = data.IsAdmin
    728 	if data.IsHellbanned {
    729 		user.HellBan(db)
    730 		managers.ActiveUsers.UpdateUserHBInRooms(managers.NewUserInfo(&user))
    731 	}
    732 	user.ApiKey = data.ApiKey
    733 	user.Verified = data.Verified
    734 	user.IsHellbanned = data.IsHellbanned
    735 	user.IsClubMember = data.IsClubMember
    736 	user.CanUploadFile = data.CanUploadFile
    737 	user.CanUseForum = data.CanUseForum
    738 	user.CanUseMultiline = data.CanUseMultiline
    739 	user.CanUseChessAnalyze = data.CanUseChessAnalyze
    740 	user.CanSeeHellbanned = data.CanSeeHellbanned
    741 	user.IsIncognito = data.IsIncognito
    742 	user.CanChangeUsername = data.CanChangeUsername
    743 	user.CanUseUppercase = data.CanUseUppercase
    744 	user.CanChangeColor = data.CanChangeColor
    745 	user.Vetted = data.Vetted
    746 	user.CollectMetadata = data.CollectMetadata
    747 	user.Role = data.Role
    748 	user.ChatColor = data.ChatColor
    749 	user.ChatFont = data.ChatFont
    750 	user.DoSave(db)
    751 
    752 	return c.Redirect(http.StatusFound, "/admin/users/"+userID.String()+"/edit")
    753 }
    754 
    755 // AdminEditRoomHandler ...
    756 func AdminEditRoomHandler(c echo.Context) error {
    757 	db := c.Get("database").(*database.DkfDB)
    758 	roomID, err := dutils.ParseRoomID(c.Param("roomID"))
    759 	if err != nil {
    760 		return c.Redirect(http.StatusFound, "/admin/rooms")
    761 	}
    762 	room, err := db.GetChatRoomByID(roomID)
    763 	if err != nil {
    764 		return c.Redirect(http.StatusFound, "/admin/rooms")
    765 	}
    766 	var data adminEditRoomData
    767 	data.ActiveTab = "rooms"
    768 	data.IsEdit = true
    769 	data.IsEphemeral = room.IsEphemeral
    770 	data.IsListed = room.IsListed
    771 	if c.Request().Method == http.MethodGet {
    772 		return c.Render(http.StatusOK, "admin.room-edit", data)
    773 	}
    774 
    775 	data.IsEphemeral = utils.DoParseBool(c.Request().PostFormValue("is_ephemeral"))
    776 	data.IsListed = utils.DoParseBool(c.Request().PostFormValue("is_listed"))
    777 
    778 	room.IsEphemeral = data.IsEphemeral
    779 	room.IsListed = data.IsListed
    780 	room.DoSave(db)
    781 
    782 	return c.Redirect(http.StatusFound, "/admin/rooms")
    783 }
    784 
    785 func StreamUsersHandler(c echo.Context) error {
    786 	db := c.Get("database").(*database.DkfDB)
    787 	usersIDs := usersStreamsManager.Inst.GetUsers()
    788 	users, _ := db.GetUsersByID(usersIDs)
    789 	out := ""
    790 	for _, user := range users {
    791 		out += string(user.Username) + ", "
    792 	}
    793 	return c.String(http.StatusOK, out)
    794 }
    795 
    796 func FiledropDownloadHandler(c echo.Context) error {
    797 	filename := c.Param("filename")
    798 	db := c.Get("database").(*database.DkfDB)
    799 	filedrop, err := db.GetFiledropByFileName(filename)
    800 	if err != nil {
    801 		return c.Redirect(http.StatusFound, "/")
    802 	}
    803 	if !filedrop.Exists() {
    804 		logrus.Error(filename + " does not exists")
    805 		return c.Redirect(http.StatusFound, "/")
    806 	}
    807 
    808 	osFile, decrypter, err := filedrop.GetContent()
    809 	if err != nil {
    810 		return echo.NotFoundHandler(c)
    811 	}
    812 	defer osFile.Close()
    813 
    814 	c.Response().Header().Set(echo.HeaderContentDisposition, fmt.Sprintf("%s; filename=%q", "attachment", filedrop.OrigFileName))
    815 
    816 	if _, err := io.Copy(c.Response().Writer, decrypter); err != nil {
    817 		logrus.Error(err)
    818 	}
    819 	c.Response().Flush()
    820 	return nil
    821 }