2022-05-07 18:58:00 +00:00
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"
2022-05-07 19:31:37 +00:00
"git.handmade.network/hmn/hmn/src/hmndata"
2022-05-07 18:58:00 +00:00
"git.handmade.network/hmn/hmn/src/models"
2022-05-07 19:31:37 +00:00
"git.handmade.network/hmn/hmn/src/oops"
2022-05-07 18:58:00 +00:00
"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 - 01 T00 : 00 : 00 Z ' )
` ,
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\")..." )
2022-05-07 19:45:21 +00:00
admin := seedUser ( ctx , tx , models . User { Username : "admin" , Name : "Admin" , Email : "admin@handmade.network" , IsStaff : true } )
2022-05-07 18:58:00 +00:00
fmt . Println ( "Creating normal users (all with password \"password\")..." )
2022-05-07 19:45:21 +00:00
alice := seedUser ( ctx , tx , models . User { Username : "alice" , Name : "Alice" } )
bob := seedUser ( ctx , tx , models . User { Username : "bob" , Name : "Bob" } )
charlie := seedUser ( ctx , tx , models . User { Username : "charlie" , Name : "Charlie" } )
2022-05-07 18:58:00 +00:00
fmt . Println ( "Creating a spammer..." )
2022-05-07 19:45:21 +00:00
spammer := seedUser ( ctx , tx , models . User {
2022-05-07 19:31:37 +00:00
Username : "spam" ,
Status : models . UserStatusConfirmed ,
Name : "Hot singletons in your local area" ,
Bio : "Howdy, everybody I go by Jarva seesharpe from Bangalore. In this way, assuming you need to partake in a shared global instance with me then, at that poi" ,
} )
users := [ ] * models . User { alice , bob , charlie , spammer }
2022-05-07 18:58:00 +00:00
2022-05-07 19:45:21 +00:00
fmt . Println ( "Creating some forum threads..." )
2022-05-07 19:31:37 +00:00
for i := 0 ; i < 5 ; i ++ {
2022-05-07 19:45:21 +00:00
thread := seedThread ( ctx , tx , models . Thread { } )
populateThread ( ctx , tx , thread , users , rand . Intn ( 5 ) + 1 )
2022-05-07 19:31:37 +00:00
}
// spam-only thread
{
2022-05-07 19:45:21 +00:00
thread := seedThread ( ctx , tx , models . Thread { } )
populateThread ( ctx , tx , thread , [ ] * models . User { spammer } , 1 )
}
fmt . Println ( "Creating the news posts..." )
{
for i := 0 ; i < 3 ; i ++ {
thread := seedThread ( ctx , tx , models . Thread { Type : models . ThreadTypeProjectBlogPost } )
populateThread ( ctx , tx , thread , [ ] * models . User { admin , alice , bob , charlie } , rand . Intn ( 5 ) + 1 )
}
2022-05-07 19:31:37 +00:00
}
2022-05-07 18:58:00 +00:00
// 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 - 01 T00 : 00 : 00 Z ' , ' 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
}
2022-05-07 19:45:21 +00:00
func seedThread ( ctx context . Context , tx pgx . Tx , input models . Thread ) * models . Thread {
2022-05-07 19:31:37 +00:00
input . Type = utils . OrDefault ( input . Type , models . ThreadTypeForumPost )
var defaultSubforum * int
if input . Type == models . ThreadTypeForumPost {
id := 2
defaultSubforum = & id
}
2022-05-07 19:45:21 +00:00
thread , err := db . QueryOne [ models . Thread ] ( ctx , tx ,
2022-05-07 19:31:37 +00:00
`
INSERT INTO thread (
title ,
type , sticky ,
project_id , subforum_id ,
first_id , last_id
)
VALUES (
$ 1 ,
$ 2 , $ 3 ,
$ 4 , $ 5 ,
$ 6 , $ 7
)
RETURNING $ columns
` ,
utils . OrDefault ( input . Title , lorem . Sentence ( 3 , 8 ) ) ,
utils . OrDefault ( input . Type , models . ThreadTypeForumPost ) , false ,
utils . OrDefault ( input . ProjectID , models . HMNProjectID ) , utils . OrDefault ( input . SubforumID , defaultSubforum ) ,
- 1 , - 1 ,
)
if err != nil {
panic ( oops . New ( err , "failed to create thread" ) )
}
return thread
}
2022-05-07 19:45:21 +00:00
func populateThread ( ctx context . Context , tx pgx . Tx , thread * models . Thread , users [ ] * models . User , numPosts int ) {
2022-05-07 19:31:37 +00:00
var lastPostId int
for i := 0 ; i < numPosts ; i ++ {
user := users [ i % len ( users ) ]
var replyId * int
if lastPostId != 0 {
if rand . Intn ( 10 ) < 3 {
replyId = & lastPostId
}
}
2022-05-07 19:45:21 +00:00
hmndata . CreateNewPost ( ctx , tx , thread . ProjectID , thread . ID , thread . Type , user . ID , replyId , lorem . Paragraph ( 1 , 10 ) , "192.168.2.1" )
2022-05-07 19:31:37 +00:00
}
}
2022-05-07 18:58:00 +00:00
func randomName ( ) string {
return "John Doe" // chosen by fair dice roll. guaranteed to be random.
}
func randomBool ( ) bool {
return rand . Intn ( 2 ) == 1
}