publapi/pages/users.go
2024-10-06 16:24:46 +02:00

218 lines
5.6 KiB
Go

package pages
import (
"os"
"os/exec"
"regexp"
"runtime"
"sort"
"strconv"
"strings"
log "log/slog"
"github.com/ProjectSegfault/publapi/utils"
"github.com/gofiber/fiber/v2"
"github.com/spf13/viper"
)
type UserStruct struct {
Status int `json:"status"`
Online int `json:"online"`
Total int `json:"total"`
Users []UserInfo `json:"users"`
}
type UserInfo struct {
Name string `json:"name"`
FullName string `json:"fullName"`
Desc string `json:"desc"`
Online bool `json:"online"`
Op bool `json:"op"`
Created int `json:"created"`
Email string `json:"email"`
Matrix string `json:"matrix"`
Fediverse string `json:"fediverse"`
Website string `json:"website"`
Capsule string `json:"capsule"`
Loc string `json:"loc"`
}
type ByAdminAndName []UserInfo
func (a ByAdminAndName) Len() int { return len(a) }
func (a ByAdminAndName) Less(i, j int) bool {
// If one person is an admin and the other is not, place the admin first
if a[i].Op && !a[j].Op {
return true
} else if !a[i].Op && a[j].Op {
return false
}
// If both persons are admins or both are not, sort by name
return a[i].Name < a[j].Name
}
func (a ByAdminAndName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func UserError(message string, username string, err error) {
log.Error("message", message, log.Any("err", err), "username", username)
}
func userData(username, usersonline, ops string) UserInfo {
regex := "(^| )" + username + "($| )"
isonline, err := regexp.MatchString(string(regex), string(usersonline))
if err != nil {
UserError("couldn't get online status for user", username, err)
}
isop, operr := regexp.MatchString(string(regex), string(ops))
if operr != nil {
UserError("couldn't get op status for user", username, operr)
}
cmd := "/usr/bin/stat -c %W /home/" + username
crd, crerr := exec.Command("bash", "-c", cmd).Output()
if crerr != nil {
UserError("couldn't stat user", username, crerr)
}
crdstr := strings.TrimSuffix(string(crd), "\n")
filename := "/home/" + username + "/meta-info.toml"
filestat, error := os.Stat(filename)
if error != nil {
if os.IsNotExist(error) {
UserError("user doesn't have a meta-info.toml", username, error)
var user UserInfo
user.Name = username
user.Created, _ = strconv.Atoi(crdstr)
if isonline {
user.Online = true
} else {
user.Online = false
}
if isop {
user.Op = true
} else {
user.Op = false
}
return user
}
}
// Check file size of meta-info.toml. If it's over 100 kB, bail
if filestat.Size() > 100_000 {
log.Error("user's meta-info.toml is above the 100 kB file size limit", "user", username)
user := UserInfo{
Name: username,
Online: false,
Op: false,
}
user.Created, _ = strconv.Atoi(crdstr)
if isonline {
user.Online = true
}
if isop {
user.Op = true
}
return user
}
viper.SetConfigFile(filename)
if err := viper.ReadInConfig(); err != nil {
log.Error("message", "couldn't read a users meta-info.toml file.", "error", log.Any("err", err), "user", username)
user := UserInfo{
Name: username,
}
user.Created, _ = strconv.Atoi(crdstr)
if isonline {
user.Online = true
} else {
user.Online = false
}
if isop {
user.Op = true
} else {
user.Op = false
}
return user
}
user := UserInfo{
Name: username,
FullName: viper.GetString("fullname"),
Capsule: viper.GetString("gemini"),
Website: viper.GetString("website"),
Desc: viper.GetString("description"),
Email: viper.GetString("email"),
Matrix: viper.GetString("matrix"),
Fediverse: viper.GetString("fediverse"),
Loc: viper.GetString("location"),
Op: false,
Online: false,
}
user.Created, _ = strconv.Atoi(crdstr)
if isop {
user.Op = true
}
if isonline {
user.Online = true
}
return user
}
func UsersPage(c *fiber.Ctx) error {
if runtime.GOOS == "windows" {
return c.JSON(fiber.Map{
"message": "/users is not supported on Windows",
"status": c.Response().StatusCode(),
})
}
// Get the number of users online
usersOnline, err := exec.Command("bash", "-c", "/usr/bin/users").Output()
if err != nil {
log.Error("error", log.Any("error", err))
return c.SendStatus(fiber.StatusInternalServerError)
}
usersOnlineStr := string(usersOnline)
usersOnlineDedup := utils.Dedup(usersOnlineStr)
var output int
if strings.Contains(usersOnlineDedup, " ") {
outputa := int(strings.Count(usersOnlineDedup, " "))
output = outputa + 1
} else if usersOnlineDedup == "" {
output = 0
} else {
output = 1
}
// Get OPs
ops, opserr := exec.Command("bash", "-c", "/usr/bin/members sudo").Output()
if opserr != nil {
log.Error("couldn't get ops", "error", log.Any("err", opserr))
return c.SendStatus(fiber.StatusInternalServerError)
}
opstr := string(ops)
// Get all users
users, err2 := exec.Command("bash", "-c", "/usr/bin/ls /home").Output()
if err2 != nil {
log.Error("couldn't get all users", "error", log.Any("err", err2))
return c.SendStatus(fiber.StatusInternalServerError)
}
userstr := strings.TrimSuffix(string(users), "\n")
usersarr := strings.Split(userstr, "\n")
// Fill user info
var userInfoArray []UserInfo
for i := 0; i < len(usersarr); i++ {
uname := string(usersarr[i])
userInfoArray = append(
userInfoArray,
userData(
uname,
strings.TrimSuffix(usersOnlineDedup, "\n"),
strings.TrimSuffix(opstr, "\n"),
),
)
}
sort.Sort(ByAdminAndName(userInfoArray))
data := UserStruct{
Status: c.Response().StatusCode(),
Online: output,
Users: userInfoArray,
Total: int(strings.Count(userstr, "\n")),
}
return c.JSON(data)
}