Seed users (and rework a lot of user access to use new helpers)
This commit is contained in:
parent
b46f5d8637
commit
3a93aa93e9
1
go.mod
1
go.mod
|
@ -3,6 +3,7 @@ module git.handmade.network/hmn/hmn
|
||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/HandmadeNetwork/golorem v0.0.0-20220507185207-414965a3a817
|
||||||
github.com/Masterminds/sprig v2.22.0+incompatible
|
github.com/Masterminds/sprig v2.22.0+incompatible
|
||||||
github.com/alecthomas/chroma v0.9.2
|
github.com/alecthomas/chroma v0.9.2
|
||||||
github.com/aws/aws-sdk-go-v2 v1.8.1
|
github.com/aws/aws-sdk-go-v2 v1.8.1
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -17,6 +17,8 @@ github.com/HandmadeNetwork/bbcode v0.0.0-20210623031351-ec0e2e2e39d9 h1:5WhEr56C
|
||||||
github.com/HandmadeNetwork/bbcode v0.0.0-20210623031351-ec0e2e2e39d9/go.mod h1:vMiNHD8absjmnO60Do5KCaJBwdbaiI/AzhMmSipMme4=
|
github.com/HandmadeNetwork/bbcode v0.0.0-20210623031351-ec0e2e2e39d9/go.mod h1:vMiNHD8absjmnO60Do5KCaJBwdbaiI/AzhMmSipMme4=
|
||||||
github.com/HandmadeNetwork/goldmark v1.4.1-0.20210707024600-f7e596e26b5e h1:z0GlF2OMmy852mrcMVpjZIzEHYCbUweS8RaWRCPfL1g=
|
github.com/HandmadeNetwork/goldmark v1.4.1-0.20210707024600-f7e596e26b5e h1:z0GlF2OMmy852mrcMVpjZIzEHYCbUweS8RaWRCPfL1g=
|
||||||
github.com/HandmadeNetwork/goldmark v1.4.1-0.20210707024600-f7e596e26b5e/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/HandmadeNetwork/goldmark v1.4.1-0.20210707024600-f7e596e26b5e/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
github.com/HandmadeNetwork/golorem v0.0.0-20220507185207-414965a3a817 h1:cBqVP/sLiK7DPay7Aac1PRUwu3fCVyL5Wc+xLXzqwkE=
|
||||||
|
github.com/HandmadeNetwork/golorem v0.0.0-20220507185207-414965a3a817/go.mod h1:doKbGBIdiM1nkEfvAeP5hvUmERah9H6StTVfCverqdE=
|
||||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||||
|
|
|
@ -37,8 +37,8 @@ func addCreateProjectCommand(projectCommand *cobra.Command) {
|
||||||
descParsed := parsing.ParseMarkdown(description, parsing.ForumRealMarkdown)
|
descParsed := parsing.ParseMarkdown(description, parsing.ForumRealMarkdown)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
conn := db.NewConnPool(1, 1)
|
conn := db.NewConn()
|
||||||
defer conn.Close()
|
defer conn.Close(ctx)
|
||||||
|
|
||||||
tx, err := conn.Begin(ctx)
|
tx, err := conn.Begin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -160,8 +160,8 @@ func addProjectTagCommand(projectCommand *cobra.Command) {
|
||||||
tag, _ := cmd.Flags().GetString("tag")
|
tag, _ := cmd.Flags().GetString("tag")
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
conn := db.NewConnPool(1, 1)
|
conn := db.NewConn()
|
||||||
defer conn.Close()
|
defer conn.Close(ctx)
|
||||||
|
|
||||||
resultTag, err := hmndata.SetProjectTag(ctx, conn, nil, projectID, tag)
|
resultTag, err := hmndata.SetProjectTag(ctx, conn, nil, projectID, tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -42,8 +42,8 @@ func init() {
|
||||||
password := args[1]
|
password := args[1]
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
conn := db.NewConnPool(1, 1)
|
conn := db.NewConn()
|
||||||
defer conn.Close()
|
defer conn.Close(ctx)
|
||||||
|
|
||||||
row := conn.QueryRow(ctx, "SELECT id, username FROM hmn_user WHERE lower(username) = lower($1)", username)
|
row := conn.QueryRow(ctx, "SELECT id, username FROM hmn_user WHERE lower(username) = lower($1)", username)
|
||||||
var id int
|
var id int
|
||||||
|
@ -83,8 +83,8 @@ func init() {
|
||||||
username := args[0]
|
username := args[0]
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
conn := db.NewConnPool(1, 1)
|
conn := db.NewConn()
|
||||||
defer conn.Close()
|
defer conn.Close(ctx)
|
||||||
|
|
||||||
res, err := conn.Exec(ctx, "UPDATE hmn_user SET status = $1 WHERE LOWER(username) = LOWER($2);", models.UserStatusConfirmed, username)
|
res, err := conn.Exec(ctx, "UPDATE hmn_user SET status = $1 WHERE LOWER(username) = LOWER($2);", models.UserStatusConfirmed, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -138,8 +138,8 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
conn := db.NewConnPool(1, 1)
|
conn := db.NewConn()
|
||||||
defer conn.Close()
|
defer conn.Close(ctx)
|
||||||
|
|
||||||
res, err := conn.Exec(ctx, "UPDATE hmn_user SET status = $1 WHERE LOWER(username) = LOWER($2);", status, username)
|
res, err := conn.Exec(ctx, "UPDATE hmn_user SET status = $1 WHERE LOWER(username) = LOWER($2);", status, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -201,8 +201,8 @@ func init() {
|
||||||
projectSlug, _ := cmd.Flags().GetString("project_slug")
|
projectSlug, _ := cmd.Flags().GetString("project_slug")
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
conn := db.NewConnPool(1, 1)
|
conn := db.NewConn()
|
||||||
defer conn.Close()
|
defer conn.Close(ctx)
|
||||||
|
|
||||||
tx, err := conn.Begin(ctx)
|
tx, err := conn.Begin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -280,8 +280,8 @@ func init() {
|
||||||
subforumSlug, _ := cmd.Flags().GetString("subforum_slug")
|
subforumSlug, _ := cmd.Flags().GetString("subforum_slug")
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
conn := db.NewConnPool(1, 1)
|
conn := db.NewConn()
|
||||||
defer conn.Close()
|
defer conn.Close(ctx)
|
||||||
|
|
||||||
tx, err := conn.Begin(ctx)
|
tx, err := conn.Begin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -190,6 +190,11 @@ func UpdatePassword(ctx context.Context, conn db.ConnOrTx, username string, hp H
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetPassword(ctx context.Context, conn db.ConnOrTx, username string, password string) error {
|
||||||
|
hp := HashPassword(password)
|
||||||
|
return UpdatePassword(ctx, conn, username, hp)
|
||||||
|
}
|
||||||
|
|
||||||
func DeleteInactiveUsers(ctx context.Context, conn *pgxpool.Pool) (int64, error) {
|
func DeleteInactiveUsers(ctx context.Context, conn *pgxpool.Pool) (int64, error) {
|
||||||
tag, err := conn.Exec(ctx,
|
tag, err := conn.Exec(ctx,
|
||||||
`
|
`
|
||||||
|
|
47
src/db/db.go
47
src/db/db.go
|
@ -11,6 +11,7 @@ import (
|
||||||
"git.handmade.network/hmn/hmn/src/config"
|
"git.handmade.network/hmn/hmn/src/config"
|
||||||
"git.handmade.network/hmn/hmn/src/logging"
|
"git.handmade.network/hmn/hmn/src/logging"
|
||||||
"git.handmade.network/hmn/hmn/src/oops"
|
"git.handmade.network/hmn/hmn/src/oops"
|
||||||
|
"git.handmade.network/hmn/hmn/src/utils"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgconn"
|
"github.com/jackc/pgconn"
|
||||||
"github.com/jackc/pgtype"
|
"github.com/jackc/pgtype"
|
||||||
|
@ -45,7 +46,18 @@ var connInfo = pgtype.NewConnInfo()
|
||||||
// Creates a new connection to the HMN database.
|
// Creates a new connection to the HMN database.
|
||||||
// This connection is not safe for concurrent use.
|
// This connection is not safe for concurrent use.
|
||||||
func NewConn() *pgx.Conn {
|
func NewConn() *pgx.Conn {
|
||||||
conn, err := pgx.Connect(context.Background(), config.Config.Postgres.DSN())
|
return NewConnWithConfig(config.PostgresConfig{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConnWithConfig(cfg config.PostgresConfig) *pgx.Conn {
|
||||||
|
cfg = overrideDefaultConfig(cfg)
|
||||||
|
|
||||||
|
pgcfg, err := pgx.ParseConfig(cfg.DSN())
|
||||||
|
|
||||||
|
pgcfg.Logger = zerologadapter.NewLogger(log.Logger)
|
||||||
|
pgcfg.LogLevel = cfg.LogLevel
|
||||||
|
|
||||||
|
conn, err := pgx.ConnectConfig(context.Background(), pgcfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(oops.New(err, "failed to connect to database"))
|
panic(oops.New(err, "failed to connect to database"))
|
||||||
}
|
}
|
||||||
|
@ -55,15 +67,21 @@ func NewConn() *pgx.Conn {
|
||||||
|
|
||||||
// Creates a connection pool for the HMN database.
|
// Creates a connection pool for the HMN database.
|
||||||
// The resulting pool is safe for concurrent use.
|
// The resulting pool is safe for concurrent use.
|
||||||
func NewConnPool(minConns, maxConns int32) *pgxpool.Pool {
|
func NewConnPool() *pgxpool.Pool {
|
||||||
cfg, err := pgxpool.ParseConfig(config.Config.Postgres.DSN())
|
return NewConnPoolWithConfig(config.PostgresConfig{})
|
||||||
|
}
|
||||||
|
|
||||||
cfg.MinConns = minConns
|
func NewConnPoolWithConfig(cfg config.PostgresConfig) *pgxpool.Pool {
|
||||||
cfg.MaxConns = maxConns
|
cfg = overrideDefaultConfig(cfg)
|
||||||
cfg.ConnConfig.Logger = zerologadapter.NewLogger(log.Logger)
|
|
||||||
cfg.ConnConfig.LogLevel = config.Config.Postgres.LogLevel
|
|
||||||
|
|
||||||
conn, err := pgxpool.ConnectConfig(context.Background(), cfg)
|
pgcfg, err := pgxpool.ParseConfig(cfg.DSN())
|
||||||
|
|
||||||
|
pgcfg.MinConns = cfg.MinConn
|
||||||
|
pgcfg.MaxConns = cfg.MaxConn
|
||||||
|
pgcfg.ConnConfig.Logger = zerologadapter.NewLogger(log.Logger)
|
||||||
|
pgcfg.ConnConfig.LogLevel = cfg.LogLevel
|
||||||
|
|
||||||
|
conn, err := pgxpool.ConnectConfig(context.Background(), pgcfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(oops.New(err, "failed to create database connection pool"))
|
panic(oops.New(err, "failed to create database connection pool"))
|
||||||
}
|
}
|
||||||
|
@ -71,6 +89,19 @@ func NewConnPool(minConns, maxConns int32) *pgxpool.Pool {
|
||||||
return conn
|
return conn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func overrideDefaultConfig(cfg config.PostgresConfig) config.PostgresConfig {
|
||||||
|
return config.PostgresConfig{
|
||||||
|
User: utils.OrDefault(cfg.User, config.Config.Postgres.User),
|
||||||
|
Password: utils.OrDefault(cfg.Password, config.Config.Postgres.Password),
|
||||||
|
Hostname: utils.OrDefault(cfg.Hostname, config.Config.Postgres.Hostname),
|
||||||
|
Port: utils.OrDefault(cfg.Port, config.Config.Postgres.Port),
|
||||||
|
DbName: utils.OrDefault(cfg.DbName, config.Config.Postgres.DbName),
|
||||||
|
LogLevel: utils.OrDefault(cfg.LogLevel, config.Config.Postgres.LogLevel),
|
||||||
|
MinConn: utils.OrDefault(cfg.MinConn, config.Config.Postgres.MinConn),
|
||||||
|
MaxConn: utils.OrDefault(cfg.MaxConn, config.Config.Postgres.MaxConn),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Performs a SQL query and returns a slice of all the result rows. The query is just plain SQL, but make sure to read the package documentation for details. You must explicitly provide the type argument - this is how it knows what Go type to map the results to, and it cannot be inferred.
|
Performs a SQL query and returns a slice of all the result rows. The query is just plain SQL, but make sure to read the package documentation for details. You must explicitly provide the type argument - this is how it knows what Go type to map the results to, and it cannot be inferred.
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ func init() {
|
||||||
Long: "Scrape the entire history of Discord channels, saving message content (but not creating snippets)",
|
Long: "Scrape the entire history of Discord channels, saving message content (but not creating snippets)",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
conn := db.NewConnPool(1, 1)
|
conn := db.NewConnPool()
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
for _, channelID := range args {
|
for _, channelID := range args {
|
||||||
|
@ -47,8 +47,8 @@ func init() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
conn := db.NewConnPool(1, 1)
|
conn := db.NewConn()
|
||||||
defer conn.Close()
|
defer conn.Close(ctx)
|
||||||
|
|
||||||
chanID := args[0]
|
chanID := args[0]
|
||||||
|
|
||||||
|
|
|
@ -96,11 +96,12 @@ func (bot *botInstance) handleProfileCommand(ctx context.Context, i *Interaction
|
||||||
FROM
|
FROM
|
||||||
discord_user AS duser
|
discord_user AS duser
|
||||||
JOIN hmn_user ON duser.hmn_user_id = hmn_user.id
|
JOIN hmn_user ON duser.hmn_user_id = hmn_user.id
|
||||||
LEFT JOIN asset AS hmn_user_avatar ON hmn_user_avatar.id = hmn_user.avatar_asset_id
|
|
||||||
WHERE
|
WHERE
|
||||||
duser.userid = $1
|
duser.userid = $1
|
||||||
|
AND hmn_user.status = $2
|
||||||
`,
|
`,
|
||||||
userID,
|
userID,
|
||||||
|
models.UserStatusApproved,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, db.NotFound) {
|
if errors.Is(err, db.NotFound) {
|
||||||
|
|
|
@ -150,7 +150,7 @@ func FetchProjects(
|
||||||
for i, p := range projectRows {
|
for i, p := range projectRows {
|
||||||
projectIds[i] = p.Project.ID
|
projectIds[i] = p.Project.ID
|
||||||
}
|
}
|
||||||
projectOwners, err := FetchMultipleProjectsOwners(ctx, tx, projectIds)
|
projectOwners, err := FetchMultipleProjectsOwners(ctx, tx, currentUser, projectIds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -316,6 +316,7 @@ type ProjectOwners struct {
|
||||||
func FetchMultipleProjectsOwners(
|
func FetchMultipleProjectsOwners(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
dbConn db.ConnOrTx,
|
dbConn db.ConnOrTx,
|
||||||
|
currentUser *models.User,
|
||||||
projectIds []int,
|
projectIds []int,
|
||||||
) ([]ProjectOwners, error) {
|
) ([]ProjectOwners, error) {
|
||||||
perf := perf.ExtractPerf(ctx)
|
perf := perf.ExtractPerf(ctx)
|
||||||
|
@ -358,17 +359,9 @@ func FetchMultipleProjectsOwners(
|
||||||
userIds = append(userIds, userProject.UserID)
|
userIds = append(userIds, userProject.UserID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
users, err := db.Query[models.User](ctx, tx,
|
users, err := FetchUsers(ctx, tx, currentUser, UsersQuery{
|
||||||
`
|
UserIDs: userIds,
|
||||||
SELECT $columns{hmn_user}
|
})
|
||||||
FROM
|
|
||||||
hmn_user
|
|
||||||
LEFT JOIN asset AS hmn_user_avatar ON hmn_user_avatar.id = hmn_user.avatar_asset_id
|
|
||||||
WHERE
|
|
||||||
hmn_user.id = ANY($1)
|
|
||||||
`,
|
|
||||||
userIds,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, oops.New(err, "failed to fetch users for projects")
|
return nil, oops.New(err, "failed to fetch users for projects")
|
||||||
}
|
}
|
||||||
|
@ -415,13 +408,14 @@ func FetchMultipleProjectsOwners(
|
||||||
func FetchProjectOwners(
|
func FetchProjectOwners(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
dbConn db.ConnOrTx,
|
dbConn db.ConnOrTx,
|
||||||
|
currentUser *models.User,
|
||||||
projectId int,
|
projectId int,
|
||||||
) ([]*models.User, error) {
|
) ([]*models.User, error) {
|
||||||
perf := perf.ExtractPerf(ctx)
|
perf := perf.ExtractPerf(ctx)
|
||||||
perf.StartBlock("SQL", "Fetch owners for project")
|
perf.StartBlock("SQL", "Fetch owners for project")
|
||||||
defer perf.EndBlock()
|
defer perf.EndBlock()
|
||||||
|
|
||||||
projectOwners, err := FetchMultipleProjectsOwners(ctx, dbConn, []int{projectId})
|
projectOwners, err := FetchMultipleProjectsOwners(ctx, dbConn, currentUser, []int{projectId})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
package hmndata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.handmade.network/hmn/hmn/src/db"
|
||||||
|
"git.handmade.network/hmn/hmn/src/models"
|
||||||
|
"git.handmade.network/hmn/hmn/src/oops"
|
||||||
|
"git.handmade.network/hmn/hmn/src/perf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UsersQuery struct {
|
||||||
|
// Ignored when using FetchUser
|
||||||
|
UserIDs []int // if empty, all users
|
||||||
|
Usernames []string // if empty, all users
|
||||||
|
|
||||||
|
// Flags to modify behavior
|
||||||
|
AnyStatus bool // Bypasses shadowban system
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Fetches users and related models from the database according to all the given
|
||||||
|
query params. For the most correct results, provide as much information as you have
|
||||||
|
on hand.
|
||||||
|
*/
|
||||||
|
func FetchUsers(
|
||||||
|
ctx context.Context,
|
||||||
|
dbConn db.ConnOrTx,
|
||||||
|
currentUser *models.User,
|
||||||
|
q UsersQuery,
|
||||||
|
) ([]*models.User, error) {
|
||||||
|
perf := perf.ExtractPerf(ctx)
|
||||||
|
perf.StartBlock("SQL", "Fetch users")
|
||||||
|
defer perf.EndBlock()
|
||||||
|
|
||||||
|
var currentUserID *int
|
||||||
|
if currentUser != nil {
|
||||||
|
currentUserID = ¤tUser.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range q.Usernames {
|
||||||
|
q.Usernames[i] = strings.ToLower(q.Usernames[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
type userRow struct {
|
||||||
|
User models.User `db:"hmn_user"`
|
||||||
|
AvatarAsset *models.Asset `db:"avatar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var qb db.QueryBuilder
|
||||||
|
qb.Add(`
|
||||||
|
SELECT $columns
|
||||||
|
FROM
|
||||||
|
hmn_user
|
||||||
|
LEFT JOIN asset AS avatar ON avatar.id = hmn_user.avatar_asset_id
|
||||||
|
WHERE
|
||||||
|
TRUE
|
||||||
|
`)
|
||||||
|
if len(q.UserIDs) > 0 {
|
||||||
|
qb.Add(`AND hmn_user.id = ANY($?)`, q.UserIDs)
|
||||||
|
}
|
||||||
|
if len(q.Usernames) > 0 {
|
||||||
|
qb.Add(`AND LOWER(hmn_user.username) = ANY($?)`, q.Usernames)
|
||||||
|
}
|
||||||
|
if !q.AnyStatus {
|
||||||
|
if currentUser == nil {
|
||||||
|
qb.Add(`AND hmn_user.status = $?`, models.UserStatusApproved)
|
||||||
|
} else if !currentUser.IsStaff {
|
||||||
|
qb.Add(
|
||||||
|
`
|
||||||
|
AND (
|
||||||
|
hmn_user.status = $? -- user is Approved
|
||||||
|
OR hmn_user.id = $? -- getting self
|
||||||
|
)
|
||||||
|
`,
|
||||||
|
models.UserStatusApproved,
|
||||||
|
currentUserID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userRows, err := db.Query[userRow](ctx, dbConn, qb.String(), qb.Args()...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, oops.New(err, "failed to fetch users")
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]*models.User, len(userRows))
|
||||||
|
for i, row := range userRows {
|
||||||
|
user := row.User
|
||||||
|
user.AvatarAsset = row.AvatarAsset
|
||||||
|
result[i] = &user
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Fetches a single user and related data. A wrapper around FetchUsers.
|
||||||
|
As with FetchUsers, provide as much information as you know to get the
|
||||||
|
most correct results.
|
||||||
|
|
||||||
|
Returns db.NotFound if no result is found.
|
||||||
|
*/
|
||||||
|
func FetchUser(
|
||||||
|
ctx context.Context,
|
||||||
|
dbConn db.ConnOrTx,
|
||||||
|
currentUser *models.User,
|
||||||
|
userID int,
|
||||||
|
q UsersQuery,
|
||||||
|
) (*models.User, error) {
|
||||||
|
q.UserIDs = []int{userID}
|
||||||
|
|
||||||
|
res, err := FetchUsers(ctx, dbConn, currentUser, q)
|
||||||
|
if err != nil {
|
||||||
|
return nil, oops.New(err, "failed to fetch user")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res) == 0 {
|
||||||
|
return nil, db.NotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return res[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Fetches a single user and related data. A wrapper around FetchUsers.
|
||||||
|
As with FetchUsers, provide as much information as you know to get the
|
||||||
|
most correct results.
|
||||||
|
|
||||||
|
Returns db.NotFound if no result is found.
|
||||||
|
*/
|
||||||
|
func FetchUserByUsername(
|
||||||
|
ctx context.Context,
|
||||||
|
dbConn db.ConnOrTx,
|
||||||
|
currentUser *models.User,
|
||||||
|
username string,
|
||||||
|
q UsersQuery,
|
||||||
|
) (*models.User, error) {
|
||||||
|
q.Usernames = []string{username}
|
||||||
|
|
||||||
|
res, err := FetchUsers(ctx, dbConn, currentUser, q)
|
||||||
|
if err != nil {
|
||||||
|
return nil, oops.New(err, "failed to fetch user")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res) == 0 {
|
||||||
|
return nil, db.NotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return res[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(ben): Someday we can add CountUsers...I don't have a need for it right now.
|
|
@ -5,7 +5,6 @@ import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -15,7 +14,6 @@ import (
|
||||||
"git.handmade.network/hmn/hmn/src/db"
|
"git.handmade.network/hmn/hmn/src/db"
|
||||||
"git.handmade.network/hmn/hmn/src/migration/migrations"
|
"git.handmade.network/hmn/hmn/src/migration/migrations"
|
||||||
"git.handmade.network/hmn/hmn/src/migration/types"
|
"git.handmade.network/hmn/hmn/src/migration/types"
|
||||||
"git.handmade.network/hmn/hmn/src/models"
|
|
||||||
"git.handmade.network/hmn/hmn/src/website"
|
"git.handmade.network/hmn/hmn/src/website"
|
||||||
"github.com/jackc/pgconn"
|
"github.com/jackc/pgconn"
|
||||||
"github.com/jackc/pgx/v4"
|
"github.com/jackc/pgx/v4"
|
||||||
|
@ -162,7 +160,9 @@ func LatestVersion() types.MigrationVersion {
|
||||||
func Migrate(targetVersion types.MigrationVersion) {
|
func Migrate(targetVersion types.MigrationVersion) {
|
||||||
ctx := context.Background() // In the future, this could actually do something cool.
|
ctx := context.Background() // In the future, this could actually do something cool.
|
||||||
|
|
||||||
conn := db.NewConn()
|
conn := db.NewConnWithConfig(config.PostgresConfig{
|
||||||
|
LogLevel: pgx.LogLevelWarn,
|
||||||
|
})
|
||||||
defer conn.Close(ctx)
|
defer conn.Close(ctx)
|
||||||
|
|
||||||
// create migration table
|
// create migration table
|
||||||
|
@ -368,113 +368,3 @@ func ResetDB() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Applies a cloned db to the local db.
|
|
||||||
// Applies the seed after the migration specified in `afterMigration`.
|
|
||||||
// NOTE(asaf): The db role specified in the config must have the CREATEDB attribute! `ALTER ROLE hmn WITH CREATEDB;`
|
|
||||||
func SeedFromFile(seedFile string) {
|
|
||||||
file, err := os.Open(seedFile)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("couldn't open seed file %s: %w", seedFile, err))
|
|
||||||
}
|
|
||||||
file.Close()
|
|
||||||
|
|
||||||
fmt.Println("Executing seed...")
|
|
||||||
cmd := exec.Command("pg_restore",
|
|
||||||
"--single-transaction",
|
|
||||||
"--dbname", config.Config.Postgres.DSN(),
|
|
||||||
seedFile,
|
|
||||||
)
|
|
||||||
fmt.Println("Running command:", cmd)
|
|
||||||
if output, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
fmt.Print(string(output))
|
|
||||||
panic(fmt.Errorf("failed to execute seed: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Done! You may want to migrate forward from here.")
|
|
||||||
ListMigrations()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE(asaf): This will be useful for open-sourcing the website, but is not yet necessary.
|
|
||||||
// Creates only what's necessary for a fresh deployment with no data
|
|
||||||
// TODO(opensource)
|
|
||||||
func BareMinimumSeed() {
|
|
||||||
Migrate(LatestVersion())
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
conn := db.NewConnPool(1, 1)
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
tx, err := conn.Begin(ctx)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer tx.Rollback(ctx)
|
|
||||||
|
|
||||||
// Create the HMN project
|
|
||||||
_, err = tx.Exec(ctx,
|
|
||||||
`
|
|
||||||
INSERT INTO project (id, slug, name, blurb, description, personal, lifecycle, color_1, color_2, forum_enabled, blog_enabled, date_created)
|
|
||||||
VALUES (1, 'hmn', 'Handmade Network', '', '', FALSE, $1, 'ab4c47', 'a5467d', TRUE, TRUE, '2017-01-01T00:00:00Z')
|
|
||||||
`,
|
|
||||||
models.ProjectLifecycleActive,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the base forum
|
|
||||||
_, err = tx.Exec(ctx, `
|
|
||||||
INSERT INTO subforum (id, slug, name, parent_id, project_id)
|
|
||||||
VALUES (2, '', 'Handmade Network', null, 1)
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Associate the forum with the HMN project
|
|
||||||
_, err = tx.Exec(ctx, `
|
|
||||||
UPDATE project SET forum_id = 2 WHERE slug = 'hmn'
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tx.Commit(ctx)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE(asaf): This will be useful for open-sourcing the website, but is not yet necessary.
|
|
||||||
// Creates enough data for development
|
|
||||||
// TODO(opensource)
|
|
||||||
func SampleSeed() {
|
|
||||||
BareMinimumSeed()
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
conn := db.NewConnPool(1, 1)
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
tx, err := conn.Begin(ctx)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer tx.Rollback(ctx)
|
|
||||||
|
|
||||||
// admin := CreateAdminUser("admin", "12345678")
|
|
||||||
// user := CreateUser("regular_user", "12345678")
|
|
||||||
// hmnProject := CreateProject("hmn", "Handmade Network")
|
|
||||||
// Create category
|
|
||||||
// Create thread
|
|
||||||
// Create accepted user project
|
|
||||||
// Create pending user project
|
|
||||||
// Create showcase items
|
|
||||||
// Create codelanguages
|
|
||||||
// Create library and library resources
|
|
||||||
|
|
||||||
err = tx.Commit(ctx)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,187 @@
|
||||||
|
package migration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"git.handmade.network/hmn/hmn/src/auth"
|
||||||
|
"git.handmade.network/hmn/hmn/src/config"
|
||||||
|
"git.handmade.network/hmn/hmn/src/db"
|
||||||
|
"git.handmade.network/hmn/hmn/src/models"
|
||||||
|
"git.handmade.network/hmn/hmn/src/utils"
|
||||||
|
lorem "github.com/HandmadeNetwork/golorem"
|
||||||
|
"github.com/jackc/pgx/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Applies a cloned db to the local db.
|
||||||
|
// Applies the seed after the migration specified in `afterMigration`.
|
||||||
|
// NOTE(asaf): The db role specified in the config must have the CREATEDB attribute! `ALTER ROLE hmn WITH CREATEDB;`
|
||||||
|
func SeedFromFile(seedFile string) {
|
||||||
|
file, err := os.Open(seedFile)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("couldn't open seed file %s: %w", seedFile, err))
|
||||||
|
}
|
||||||
|
file.Close()
|
||||||
|
|
||||||
|
fmt.Println("Executing seed...")
|
||||||
|
cmd := exec.Command("pg_restore",
|
||||||
|
"--single-transaction",
|
||||||
|
"--dbname", config.Config.Postgres.DSN(),
|
||||||
|
seedFile,
|
||||||
|
)
|
||||||
|
fmt.Println("Running command:", cmd)
|
||||||
|
if output, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
fmt.Print(string(output))
|
||||||
|
panic(fmt.Errorf("failed to execute seed: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Done! You may want to migrate forward from here.")
|
||||||
|
ListMigrations()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates only what's necessary to get the site running. Not really very useful for
|
||||||
|
// local dev on its own; sample data makes things a lot better.
|
||||||
|
func BareMinimumSeed() {
|
||||||
|
Migrate(LatestVersion())
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
conn := db.NewConnWithConfig(config.PostgresConfig{
|
||||||
|
LogLevel: pgx.LogLevelWarn,
|
||||||
|
})
|
||||||
|
defer conn.Close(ctx)
|
||||||
|
|
||||||
|
tx, err := conn.Begin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback(ctx)
|
||||||
|
|
||||||
|
fmt.Println("Creating HMN project...")
|
||||||
|
_, err = tx.Exec(ctx,
|
||||||
|
`
|
||||||
|
INSERT INTO project (id, slug, name, blurb, description, personal, lifecycle, color_1, color_2, forum_enabled, blog_enabled, date_created)
|
||||||
|
VALUES (1, 'hmn', 'Handmade Network', '', '', FALSE, $1, 'ab4c47', 'a5467d', TRUE, TRUE, '2017-01-01T00:00:00Z')
|
||||||
|
`,
|
||||||
|
models.ProjectLifecycleActive,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Creating main forum...")
|
||||||
|
_, err = tx.Exec(ctx, `
|
||||||
|
INSERT INTO subforum (id, slug, name, parent_id, project_id)
|
||||||
|
VALUES (2, '', 'Handmade Network', NULL, 1)
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_, err = tx.Exec(ctx, `
|
||||||
|
UPDATE project SET forum_id = 2 WHERE slug = 'hmn'
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Commit(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seeds the database with sample data for local dev.
|
||||||
|
func SampleSeed() {
|
||||||
|
BareMinimumSeed()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
conn := db.NewConnWithConfig(config.PostgresConfig{
|
||||||
|
LogLevel: pgx.LogLevelWarn,
|
||||||
|
})
|
||||||
|
defer conn.Close(ctx)
|
||||||
|
|
||||||
|
tx, err := conn.Begin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback(ctx)
|
||||||
|
|
||||||
|
fmt.Println("Creating admin user (\"admin\"/\"password\")...")
|
||||||
|
seedUser(ctx, conn, models.User{Username: "admin", Email: "admin@handmade.network", IsStaff: true})
|
||||||
|
|
||||||
|
fmt.Println("Creating normal users (all with password \"password\")...")
|
||||||
|
alice := seedUser(ctx, conn, models.User{Username: "alice", Name: "Alice"})
|
||||||
|
bob := seedUser(ctx, conn, models.User{Username: "bob", Name: "Bob"})
|
||||||
|
charlie := seedUser(ctx, conn, models.User{Username: "charlie", Name: "Charlie"})
|
||||||
|
|
||||||
|
fmt.Println("Creating a spammer...")
|
||||||
|
seedUser(ctx, conn, models.User{Username: "spam", Name: "Hot singletons in your local area", Status: models.UserStatusConfirmed})
|
||||||
|
|
||||||
|
_ = []*models.User{alice, bob, charlie}
|
||||||
|
|
||||||
|
// admin := CreateAdminUser("admin", "12345678")
|
||||||
|
// user := CreateUser("regular_user", "12345678")
|
||||||
|
// hmnProject := CreateProject("hmn", "Handmade Network")
|
||||||
|
// Create category
|
||||||
|
// Create thread
|
||||||
|
// Create accepted user project
|
||||||
|
// Create pending user project
|
||||||
|
// Create showcase items
|
||||||
|
// Create codelanguages
|
||||||
|
// Create library and library resources
|
||||||
|
|
||||||
|
err = tx.Commit(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func seedUser(ctx context.Context, conn db.ConnOrTx, input models.User) *models.User {
|
||||||
|
user, err := db.QueryOne[models.User](ctx, conn,
|
||||||
|
`
|
||||||
|
INSERT INTO hmn_user (
|
||||||
|
username, password, email,
|
||||||
|
is_staff,
|
||||||
|
status,
|
||||||
|
name, bio, blurb, signature,
|
||||||
|
darktheme,
|
||||||
|
showemail, edit_library,
|
||||||
|
date_joined, registration_ip, avatar_asset_id
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, $2, $3,
|
||||||
|
$4,
|
||||||
|
$5,
|
||||||
|
$6, $7, $8, $9,
|
||||||
|
TRUE,
|
||||||
|
$10, FALSE,
|
||||||
|
'2017-01-01T00:00:00Z', '192.168.2.1', null
|
||||||
|
)
|
||||||
|
RETURNING $columns
|
||||||
|
`,
|
||||||
|
input.Username, "", utils.OrDefault(input.Email, fmt.Sprintf("%s@example.com", input.Username)),
|
||||||
|
input.IsStaff,
|
||||||
|
utils.OrDefault(input.Status, models.UserStatusApproved),
|
||||||
|
utils.OrDefault(input.Name, randomName()), utils.OrDefault(input.Bio, lorem.Paragraph(0, 2)), utils.OrDefault(input.Blurb, lorem.Sentence(0, 14)), utils.OrDefault(input.Signature, lorem.Sentence(0, 16)),
|
||||||
|
input.ShowEmail,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = auth.SetPassword(ctx, conn, input.Username, "password")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomName() string {
|
||||||
|
return "John Doe" // chosen by fair dice roll. guaranteed to be random.
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomBool() bool {
|
||||||
|
return rand.Intn(2) == 1
|
||||||
|
}
|
|
@ -36,7 +36,6 @@ type User struct {
|
||||||
Blurb string `db:"blurb"`
|
Blurb string `db:"blurb"`
|
||||||
Signature string `db:"signature"`
|
Signature string `db:"signature"`
|
||||||
AvatarAssetID *uuid.UUID `db:"avatar_asset_id"`
|
AvatarAssetID *uuid.UUID `db:"avatar_asset_id"`
|
||||||
AvatarAsset *Asset `db:"avatar"`
|
|
||||||
|
|
||||||
DarkTheme bool `db:"darktheme"`
|
DarkTheme bool `db:"darktheme"`
|
||||||
Timezone string `db:"timezone"`
|
Timezone string `db:"timezone"`
|
||||||
|
@ -48,6 +47,9 @@ type User struct {
|
||||||
DiscordDeleteSnippetOnMessageDelete bool `db:"discord_delete_snippet_on_message_delete"`
|
DiscordDeleteSnippetOnMessageDelete bool `db:"discord_delete_snippet_on_message_delete"`
|
||||||
|
|
||||||
MarkedAllReadAt time.Time `db:"marked_all_read_at"`
|
MarkedAllReadAt time.Time `db:"marked_all_read_at"`
|
||||||
|
|
||||||
|
// Non-db fields, to be filled in by fetch helpers
|
||||||
|
AvatarAsset *Asset
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) BestName() string {
|
func (u *User) BestName() string {
|
||||||
|
|
|
@ -10,6 +10,16 @@ import (
|
||||||
"git.handmade.network/hmn/hmn/src/oops"
|
"git.handmade.network/hmn/hmn/src/oops"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Returns the provided value, or a default value if the input was zero.
|
||||||
|
func OrDefault[T comparable](v T, def T) T {
|
||||||
|
var zero T
|
||||||
|
if v == zero {
|
||||||
|
return def
|
||||||
|
} else {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func IntMin(a, b int) int {
|
func IntMin(a, b int) int {
|
||||||
if a < b {
|
if a < b {
|
||||||
return a
|
return a
|
||||||
|
|
|
@ -256,15 +256,7 @@ func AdminApprovalQueueSubmit(c *RequestContext) ResponseData {
|
||||||
return RejectRequest(c, "User id can't be parsed")
|
return RejectRequest(c, "User id can't be parsed")
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := db.QueryOne[models.User](c.Context(), c.Conn,
|
user, err := hmndata.FetchUser(c.Context(), c.Conn, c.CurrentUser, userId, hmndata.UsersQuery{})
|
||||||
`
|
|
||||||
SELECT $columns{hmn_user}
|
|
||||||
FROM hmn_user
|
|
||||||
LEFT JOIN asset AS hmn_user_avatar ON hmn_user_avatar.id = hmn_user.avatar_asset_id
|
|
||||||
WHERE hmn_user.id = $1
|
|
||||||
`,
|
|
||||||
userId,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, db.NotFound) {
|
if errors.Is(err, db.NotFound) {
|
||||||
return RejectRequest(c, "User not found")
|
return RejectRequest(c, "User not found")
|
||||||
|
|
|
@ -21,10 +21,9 @@ func APICheckUsername(c *RequestContext) ResponseData {
|
||||||
c.Perf.StartBlock("SQL", "Fetch user")
|
c.Perf.StartBlock("SQL", "Fetch user")
|
||||||
user, err := db.QueryOne[models.User](c.Context(), c.Conn,
|
user, err := db.QueryOne[models.User](c.Context(), c.Conn,
|
||||||
`
|
`
|
||||||
SELECT $columns{hmn_user}
|
SELECT $columns
|
||||||
FROM
|
FROM
|
||||||
hmn_user
|
hmn_user
|
||||||
LEFT JOIN asset AS hmn_user_avatar ON hmn_user_avatar.id = hmn_user.avatar_asset_id
|
|
||||||
WHERE
|
WHERE
|
||||||
LOWER(hmn_user.username) = LOWER($1)
|
LOWER(hmn_user.username) = LOWER($1)
|
||||||
AND status = ANY ($2)
|
AND status = ANY ($2)
|
||||||
|
|
|
@ -77,10 +77,8 @@ func Login(c *RequestContext) ResponseData {
|
||||||
|
|
||||||
user, err := db.QueryOne[models.User](c.Context(), c.Conn,
|
user, err := db.QueryOne[models.User](c.Context(), c.Conn,
|
||||||
`
|
`
|
||||||
SELECT $columns{hmn_user}
|
SELECT $columns
|
||||||
FROM
|
FROM hmn_user
|
||||||
hmn_user
|
|
||||||
LEFT JOIN asset AS hmn_user_avatar ON hmn_user_avatar.id = hmn_user.avatar_asset_id
|
|
||||||
WHERE LOWER(username) = LOWER($1)
|
WHERE LOWER(username) = LOWER($1)
|
||||||
`,
|
`,
|
||||||
username,
|
username,
|
||||||
|
@ -457,10 +455,8 @@ func RequestPasswordResetSubmit(c *RequestContext) ResponseData {
|
||||||
}
|
}
|
||||||
user, err := db.QueryOne[models.User](c.Context(), c.Conn,
|
user, err := db.QueryOne[models.User](c.Context(), c.Conn,
|
||||||
`
|
`
|
||||||
SELECT $columns{hmn_user}
|
SELECT $columns
|
||||||
FROM
|
FROM hmn_user
|
||||||
hmn_user
|
|
||||||
LEFT JOIN asset AS hmn_user_avatar ON hmn_user_avatar.id = hmn_user.avatar_asset_id
|
|
||||||
WHERE
|
WHERE
|
||||||
LOWER(username) = LOWER($1)
|
LOWER(username) = LOWER($1)
|
||||||
AND LOWER(email) = LOWER($2)
|
AND LOWER(email) = LOWER($2)
|
||||||
|
|
|
@ -77,7 +77,7 @@ func BlogIndex(c *RequestContext) ResponseData {
|
||||||
canCreate := false
|
canCreate := false
|
||||||
if c.CurrentProject.HasBlog() && c.CurrentUser != nil {
|
if c.CurrentProject.HasBlog() && c.CurrentUser != nil {
|
||||||
isProjectOwner := false
|
isProjectOwner := false
|
||||||
owners, err := hmndata.FetchProjectOwners(c.Context(), c.Conn, c.CurrentProject.ID)
|
owners, err := hmndata.FetchProjectOwners(c.Context(), c.Conn, c.CurrentUser, c.CurrentProject.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch project owners"))
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch project owners"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,7 @@ func ProjectHomepage(c *RequestContext) ResponseData {
|
||||||
// There are no further permission checks to do, because permissions are
|
// There are no further permission checks to do, because permissions are
|
||||||
// checked whatever way we fetch the project.
|
// checked whatever way we fetch the project.
|
||||||
|
|
||||||
owners, err := hmndata.FetchProjectOwners(c.Context(), c.Conn, c.CurrentProject.ID)
|
owners, err := hmndata.FetchProjectOwners(c.Context(), c.Conn, c.CurrentUser, c.CurrentProject.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.ErrorResponse(http.StatusInternalServerError, err)
|
return c.ErrorResponse(http.StatusInternalServerError, err)
|
||||||
}
|
}
|
||||||
|
@ -818,10 +818,8 @@ func updateProject(ctx context.Context, tx pgx.Tx, user *models.User, payload *P
|
||||||
|
|
||||||
owners, err := db.Query[models.User](ctx, tx,
|
owners, err := db.Query[models.User](ctx, tx,
|
||||||
`
|
`
|
||||||
SELECT $columns{hmn_user}
|
SELECT $columns
|
||||||
FROM
|
FROM hmn_user
|
||||||
hmn_user
|
|
||||||
LEFT JOIN asset AS hmn_user_avatar ON hmn_user_avatar.id = hmn_user.avatar_asset_id
|
|
||||||
WHERE LOWER(username) = ANY ($1)
|
WHERE LOWER(username) = ANY ($1)
|
||||||
`,
|
`,
|
||||||
payload.OwnerUsernames,
|
payload.OwnerUsernames,
|
||||||
|
|
|
@ -548,16 +548,9 @@ func getCurrentUserAndSession(c *RequestContext, sessionId string) (*models.User
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := db.QueryOne[models.User](c.Context(), c.Conn,
|
user, err := hmndata.FetchUserByUsername(c.Context(), c.Conn, nil, session.Username, hmndata.UsersQuery{
|
||||||
`
|
AnyStatus: true,
|
||||||
SELECT $columns{hmn_user}
|
})
|
||||||
FROM
|
|
||||||
hmn_user
|
|
||||||
LEFT JOIN asset AS hmn_user_avatar ON hmn_user_avatar.id = hmn_user.avatar_asset_id
|
|
||||||
WHERE username = $1
|
|
||||||
`,
|
|
||||||
session.Username,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, db.NotFound) {
|
if errors.Is(err, db.NotFound) {
|
||||||
logging.Debug().Str("username", session.Username).Msg("returning no current user for this request because the user for the session couldn't be found")
|
logging.Debug().Str("username", session.Username).Msg("returning no current user for this request because the user for the session couldn't be found")
|
||||||
|
|
|
@ -52,19 +52,7 @@ func UserProfile(c *RequestContext) ResponseData {
|
||||||
if c.CurrentUser != nil && strings.ToLower(c.CurrentUser.Username) == username {
|
if c.CurrentUser != nil && strings.ToLower(c.CurrentUser.Username) == username {
|
||||||
profileUser = c.CurrentUser
|
profileUser = c.CurrentUser
|
||||||
} else {
|
} else {
|
||||||
c.Perf.StartBlock("SQL", "Fetch user")
|
user, err := hmndata.FetchUserByUsername(c.Context(), c.Conn, c.CurrentUser, username, hmndata.UsersQuery{})
|
||||||
user, err := db.QueryOne[models.User](c.Context(), c.Conn,
|
|
||||||
`
|
|
||||||
SELECT $columns{hmn_user}
|
|
||||||
FROM
|
|
||||||
hmn_user
|
|
||||||
LEFT JOIN asset AS hmn_user_avatar ON hmn_user_avatar.id = hmn_user.avatar_asset_id
|
|
||||||
WHERE
|
|
||||||
LOWER(hmn_user.username) = $1
|
|
||||||
`,
|
|
||||||
username,
|
|
||||||
)
|
|
||||||
c.Perf.EndBlock()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, db.NotFound) {
|
if errors.Is(err, db.NotFound) {
|
||||||
return FourOhFour(c)
|
return FourOhFour(c)
|
||||||
|
|
|
@ -33,7 +33,7 @@ var WebsiteCommand = &cobra.Command{
|
||||||
backgroundJobContext, cancelBackgroundJobs := context.WithCancel(context.Background())
|
backgroundJobContext, cancelBackgroundJobs := context.WithCancel(context.Background())
|
||||||
longRequestContext, cancelLongRequests := context.WithCancel(context.Background())
|
longRequestContext, cancelLongRequests := context.WithCancel(context.Background())
|
||||||
|
|
||||||
conn := db.NewConnPool(config.Config.Postgres.MinConn, config.Config.Postgres.MaxConn)
|
conn := db.NewConnPool()
|
||||||
perfCollector := perf.RunPerfCollector(backgroundJobContext)
|
perfCollector := perf.RunPerfCollector(backgroundJobContext)
|
||||||
|
|
||||||
server := http.Server{
|
server := http.Server{
|
||||||
|
|
Loading…
Reference in New Issue