Get post content migrated and loading
This commit is contained in:
parent
d7c512f1c8
commit
a04b00c0a7
|
@ -102,9 +102,9 @@ func getSortedMigrationVersions() []types.MigrationVersion {
|
||||||
return allVersions
|
return allVersions
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCurrentVersion(conn *pgx.Conn) (types.MigrationVersion, error) {
|
func getCurrentVersion(ctx context.Context, conn *pgx.Conn) (types.MigrationVersion, error) {
|
||||||
var currentVersion time.Time
|
var currentVersion time.Time
|
||||||
row := conn.QueryRow(context.Background(), "SELECT version FROM hmn_migration")
|
row := conn.QueryRow(ctx, "SELECT version FROM hmn_migration")
|
||||||
err := row.Scan(¤tVersion)
|
err := row.Scan(¤tVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.MigrationVersion{}, err
|
return types.MigrationVersion{}, err
|
||||||
|
@ -115,10 +115,12 @@ func getCurrentVersion(conn *pgx.Conn) (types.MigrationVersion, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListMigrations() {
|
func ListMigrations() {
|
||||||
conn := db.NewConn()
|
ctx := context.Background()
|
||||||
defer conn.Close(context.Background())
|
|
||||||
|
|
||||||
currentVersion, _ := getCurrentVersion(conn)
|
conn := db.NewConn()
|
||||||
|
defer conn.Close(ctx)
|
||||||
|
|
||||||
|
currentVersion, _ := getCurrentVersion(ctx, conn)
|
||||||
for _, version := range getSortedMigrationVersions() {
|
for _, version := range getSortedMigrationVersions() {
|
||||||
migration := migrations.All[version]
|
migration := migrations.All[version]
|
||||||
indicator := " "
|
indicator := " "
|
||||||
|
@ -130,11 +132,13 @@ func ListMigrations() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Migrate(targetVersion types.MigrationVersion) {
|
func Migrate(targetVersion types.MigrationVersion) {
|
||||||
|
ctx := context.Background() // In the future, this could actually do something cool.
|
||||||
|
|
||||||
conn := db.NewConn()
|
conn := db.NewConn()
|
||||||
defer conn.Close(context.Background())
|
defer conn.Close(ctx)
|
||||||
|
|
||||||
// create migration table
|
// create migration table
|
||||||
_, err := conn.Exec(context.Background(), `
|
_, err := conn.Exec(ctx, `
|
||||||
CREATE TABLE IF NOT EXISTS hmn_migration (
|
CREATE TABLE IF NOT EXISTS hmn_migration (
|
||||||
version TIMESTAMP WITH TIME ZONE
|
version TIMESTAMP WITH TIME ZONE
|
||||||
)
|
)
|
||||||
|
@ -144,21 +148,21 @@ func Migrate(targetVersion types.MigrationVersion) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure there is a row
|
// ensure there is a row
|
||||||
row := conn.QueryRow(context.Background(), "SELECT COUNT(*) FROM hmn_migration")
|
row := conn.QueryRow(ctx, "SELECT COUNT(*) FROM hmn_migration")
|
||||||
var numRows int
|
var numRows int
|
||||||
err = row.Scan(&numRows)
|
err = row.Scan(&numRows)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if numRows < 1 {
|
if numRows < 1 {
|
||||||
_, err := conn.Exec(context.Background(), "INSERT INTO hmn_migration (version) VALUES ($1)", time.Time{})
|
_, err := conn.Exec(ctx, "INSERT INTO hmn_migration (version) VALUES ($1)", time.Time{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("failed to insert initial migration row: %w", err))
|
panic(fmt.Errorf("failed to insert initial migration row: %w", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// run migrations
|
// run migrations
|
||||||
currentVersion, err := getCurrentVersion(conn)
|
currentVersion, err := getCurrentVersion(ctx, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("failed to get current version: %w", err))
|
panic(fmt.Errorf("failed to get current version: %w", err))
|
||||||
}
|
}
|
||||||
|
@ -196,25 +200,25 @@ func Migrate(targetVersion types.MigrationVersion) {
|
||||||
migration := migrations.All[version]
|
migration := migrations.All[version]
|
||||||
fmt.Printf("Applying migration %v (%v)\n", version, migration.Name())
|
fmt.Printf("Applying migration %v (%v)\n", version, migration.Name())
|
||||||
|
|
||||||
tx, err := conn.Begin(context.Background())
|
tx, err := conn.Begin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("failed to start transaction: %w", err))
|
panic(fmt.Errorf("failed to start transaction: %w", err))
|
||||||
}
|
}
|
||||||
defer tx.Rollback(context.Background())
|
defer tx.Rollback(ctx)
|
||||||
|
|
||||||
err = migration.Up(tx)
|
err = migration.Up(ctx, tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("MIGRATION FAILED for migration %v.\n", version)
|
fmt.Printf("MIGRATION FAILED for migration %v.\n", version)
|
||||||
fmt.Printf("Error: %v\n", err)
|
fmt.Printf("Error: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.Exec(context.Background(), "UPDATE hmn_migration SET version = $1", version)
|
_, err = tx.Exec(ctx, "UPDATE hmn_migration SET version = $1", version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("failed to update version in migrations table: %w", err))
|
panic(fmt.Errorf("failed to update version in migrations table: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.Commit(context.Background())
|
err = tx.Commit(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("failed to commit transaction: %w", err))
|
panic(fmt.Errorf("failed to commit transaction: %w", err))
|
||||||
}
|
}
|
||||||
|
@ -228,27 +232,27 @@ func Migrate(targetVersion types.MigrationVersion) {
|
||||||
previousVersion = allVersions[i-1]
|
previousVersion = allVersions[i-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
tx, err := conn.Begin(context.Background())
|
tx, err := conn.Begin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("failed to start transaction: %w", err))
|
panic(fmt.Errorf("failed to start transaction: %w", err))
|
||||||
}
|
}
|
||||||
defer tx.Rollback(context.Background())
|
defer tx.Rollback(ctx)
|
||||||
|
|
||||||
fmt.Printf("Rolling back migration %v\n", version)
|
fmt.Printf("Rolling back migration %v\n", version)
|
||||||
migration := migrations.All[version]
|
migration := migrations.All[version]
|
||||||
err = migration.Down(tx)
|
err = migration.Down(ctx, tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("MIGRATION FAILED for migration %v.\n", version)
|
fmt.Printf("MIGRATION FAILED for migration %v.\n", version)
|
||||||
fmt.Printf("Error: %v\n", err)
|
fmt.Printf("Error: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.Exec(context.Background(), "UPDATE hmn_migration SET version = $1", previousVersion)
|
_, err = tx.Exec(ctx, "UPDATE hmn_migration SET version = $1", previousVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("failed to update version in migrations table: %w", err))
|
panic(fmt.Errorf("failed to update version in migrations table: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.Commit(context.Background())
|
err = tx.Commit(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("failed to commit transaction: %w", err))
|
panic(fmt.Errorf("failed to commit transaction: %w", err))
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,10 +25,10 @@ func (m %NAME%) Description() string {
|
||||||
return %DESCRIPTION%
|
return %DESCRIPTION%
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m %NAME%) Up(tx pgx.Tx) error {
|
func (m %NAME%) Up(ctx context.Context, tx pgx.Tx) error {
|
||||||
panic("Implement me")
|
panic("Implement me")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m %NAME%) Down(tx pgx.Tx) error {
|
func (m %NAME%) Down(ctx context.Context, tx pgx.Tx) error {
|
||||||
panic("Implement me")
|
panic("Implement me")
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,8 @@ func (m Initial) Description() string {
|
||||||
return "Creates all the tables from the old site"
|
return "Creates all the tables from the old site"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Initial) Up(tx pgx.Tx) error {
|
func (m Initial) Up(ctx context.Context, tx pgx.Tx) error {
|
||||||
_, err := tx.Exec(context.Background(), `
|
_, err := tx.Exec(ctx, `
|
||||||
--
|
--
|
||||||
-- PostgreSQL database dump
|
-- PostgreSQL database dump
|
||||||
--
|
--
|
||||||
|
@ -4648,6 +4648,6 @@ func (m Initial) Up(tx pgx.Tx) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Initial) Down(tx pgx.Tx) error {
|
func (m Initial) Down(ctx context.Context, tx pgx.Tx) error {
|
||||||
panic("nope, ha ha, I'm the initial migration. how did you even run this function anyway")
|
panic("nope, ha ha, I'm the initial migration. how did you even run this function anyway")
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,8 @@ func (m AddSessionTable) Description() string {
|
||||||
return "Adds a session table to replace the Django session table"
|
return "Adds a session table to replace the Django session table"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m AddSessionTable) Up(tx pgx.Tx) error {
|
func (m AddSessionTable) Up(ctx context.Context, tx pgx.Tx) error {
|
||||||
_, err := tx.Exec(context.Background(), `
|
_, err := tx.Exec(ctx, `
|
||||||
CREATE TABLE sessions (
|
CREATE TABLE sessions (
|
||||||
id VARCHAR(40) PRIMARY KEY,
|
id VARCHAR(40) PRIMARY KEY,
|
||||||
username VARCHAR(150) NOT NULL,
|
username VARCHAR(150) NOT NULL,
|
||||||
|
@ -37,8 +37,8 @@ func (m AddSessionTable) Up(tx pgx.Tx) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m AddSessionTable) Down(tx pgx.Tx) error {
|
func (m AddSessionTable) Down(ctx context.Context, tx pgx.Tx) error {
|
||||||
_, err := tx.Exec(context.Background(), `
|
_, err := tx.Exec(ctx, `
|
||||||
DROP TABLE sessions;
|
DROP TABLE sessions;
|
||||||
`)
|
`)
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -26,16 +26,16 @@ func (m AllowLongerPasswordHashes) Description() string {
|
||||||
return "Increase the storage size limit on hashed passwords"
|
return "Increase the storage size limit on hashed passwords"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m AllowLongerPasswordHashes) Up(tx pgx.Tx) error {
|
func (m AllowLongerPasswordHashes) Up(ctx context.Context, tx pgx.Tx) error {
|
||||||
_, err := tx.Exec(context.Background(), `
|
_, err := tx.Exec(ctx, `
|
||||||
ALTER TABLE auth_user
|
ALTER TABLE auth_user
|
||||||
ALTER COLUMN password TYPE VARCHAR(256)
|
ALTER COLUMN password TYPE VARCHAR(256)
|
||||||
`)
|
`)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m AllowLongerPasswordHashes) Down(tx pgx.Tx) error {
|
func (m AllowLongerPasswordHashes) Down(ctx context.Context, tx pgx.Tx) error {
|
||||||
_, err := tx.Exec(context.Background(), `
|
_, err := tx.Exec(ctx, `
|
||||||
ALTER TABLE auth_user
|
ALTER TABLE auth_user
|
||||||
ALTER COLUMN password TYPE VARCHAR(128)
|
ALTER COLUMN password TYPE VARCHAR(128)
|
||||||
`)
|
`)
|
||||||
|
|
|
@ -28,9 +28,9 @@ func (m DeleteOrphanedData) Description() string {
|
||||||
return "Delete data that doesn't have other important associated records"
|
return "Delete data that doesn't have other important associated records"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m DeleteOrphanedData) Up(tx pgx.Tx) error {
|
func (m DeleteOrphanedData) Up(ctx context.Context, tx pgx.Tx) error {
|
||||||
// Delete orphaned users (no member)
|
// Delete orphaned users (no member)
|
||||||
res, err := tx.Exec(context.Background(), `
|
res, err := tx.Exec(ctx, `
|
||||||
DELETE FROM auth_user
|
DELETE FROM auth_user
|
||||||
WHERE
|
WHERE
|
||||||
id IN (
|
id IN (
|
||||||
|
@ -58,7 +58,7 @@ func (m DeleteOrphanedData) Up(tx pgx.Tx) error {
|
||||||
|
|
||||||
// Delete memberextended<->links joins for memberextendeds that are about to die
|
// Delete memberextended<->links joins for memberextendeds that are about to die
|
||||||
// (my kingdom for ON DELETE CASCADE, I mean come on)
|
// (my kingdom for ON DELETE CASCADE, I mean come on)
|
||||||
res, err = tx.Exec(context.Background(), `
|
res, err = tx.Exec(ctx, `
|
||||||
DELETE FROM handmade_memberextended_links
|
DELETE FROM handmade_memberextended_links
|
||||||
WHERE
|
WHERE
|
||||||
memberextended_id IN (
|
memberextended_id IN (
|
||||||
|
@ -71,7 +71,7 @@ func (m DeleteOrphanedData) Up(tx pgx.Tx) error {
|
||||||
fmt.Printf("Deleted %v memberextended<->links joins\n", res.RowsAffected())
|
fmt.Printf("Deleted %v memberextended<->links joins\n", res.RowsAffected())
|
||||||
|
|
||||||
// Delete orphaned memberextendeds (no member)
|
// Delete orphaned memberextendeds (no member)
|
||||||
res, err = tx.Exec(context.Background(), `
|
res, err = tx.Exec(ctx, `
|
||||||
DELETE FROM handmade_memberextended
|
DELETE FROM handmade_memberextended
|
||||||
WHERE
|
WHERE
|
||||||
id IN (
|
id IN (
|
||||||
|
@ -84,7 +84,7 @@ func (m DeleteOrphanedData) Up(tx pgx.Tx) error {
|
||||||
fmt.Printf("Deleted %v memberextendeds\n", res.RowsAffected())
|
fmt.Printf("Deleted %v memberextendeds\n", res.RowsAffected())
|
||||||
|
|
||||||
// Delete orphaned links (no member or project)
|
// Delete orphaned links (no member or project)
|
||||||
res, err = tx.Exec(context.Background(), `
|
res, err = tx.Exec(ctx, `
|
||||||
DELETE FROM handmade_links
|
DELETE FROM handmade_links
|
||||||
WHERE
|
WHERE
|
||||||
id IN (
|
id IN (
|
||||||
|
@ -106,6 +106,6 @@ func (m DeleteOrphanedData) Up(tx pgx.Tx) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m DeleteOrphanedData) Down(tx pgx.Tx) error {
|
func (m DeleteOrphanedData) Down(ctx context.Context, tx pgx.Tx) error {
|
||||||
panic("Implement me")
|
panic("Implement me")
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ func (m RemoveMemberAndExtended) Description() string {
|
||||||
return "Remove the member and member extended records, collapsing their data into users"
|
return "Remove the member and member extended records, collapsing their data into users"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m RemoveMemberAndExtended) Up(tx pgx.Tx) error {
|
func (m RemoveMemberAndExtended) Up(ctx context.Context, tx pgx.Tx) error {
|
||||||
// Creates a column that will eventually be a foreign key to auth_user.
|
// Creates a column that will eventually be a foreign key to auth_user.
|
||||||
createUserColumn := func(ctx context.Context, tx pgx.Tx, table string, before string, notNull bool) {
|
createUserColumn := func(ctx context.Context, tx pgx.Tx, table string, before string, notNull bool) {
|
||||||
nullConstraint := ""
|
nullConstraint := ""
|
||||||
|
@ -51,18 +51,18 @@ func (m RemoveMemberAndExtended) Up(tx pgx.Tx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate a lot of simple foreign keys
|
// Migrate a lot of simple foreign keys
|
||||||
createUserColumn(context.Background(), tx, "handmade_communicationchoice", "member_id", true)
|
createUserColumn(ctx, tx, "handmade_communicationchoice", "member_id", true)
|
||||||
createUserColumn(context.Background(), tx, "handmade_communicationsubcategory", "member_id", true)
|
createUserColumn(ctx, tx, "handmade_communicationsubcategory", "member_id", true)
|
||||||
createUserColumn(context.Background(), tx, "handmade_communicationsubthread", "member_id", true)
|
createUserColumn(ctx, tx, "handmade_communicationsubthread", "member_id", true)
|
||||||
createUserColumn(context.Background(), tx, "handmade_discord", "member_id", true)
|
createUserColumn(ctx, tx, "handmade_discord", "member_id", true)
|
||||||
createUserColumn(context.Background(), tx, "handmade_categorylastreadinfo", "member_id", true)
|
createUserColumn(ctx, tx, "handmade_categorylastreadinfo", "member_id", true)
|
||||||
createUserColumn(context.Background(), tx, "handmade_threadlastreadinfo", "member_id", true)
|
createUserColumn(ctx, tx, "handmade_threadlastreadinfo", "member_id", true)
|
||||||
createUserColumn(context.Background(), tx, "handmade_posttextversion", "editor_id", false)
|
createUserColumn(ctx, tx, "handmade_posttextversion", "editor_id", false)
|
||||||
createUserColumn(context.Background(), tx, "handmade_post", "author_id", false)
|
createUserColumn(ctx, tx, "handmade_post", "author_id", false)
|
||||||
createUserColumn(context.Background(), tx, "handmade_member_projects", "member_id", true)
|
createUserColumn(ctx, tx, "handmade_member_projects", "member_id", true)
|
||||||
|
|
||||||
// Directly associate links with members
|
// Directly associate links with members
|
||||||
_, err := tx.Exec(context.Background(), `
|
_, err := tx.Exec(ctx, `
|
||||||
ALTER TABLE handmade_links
|
ALTER TABLE handmade_links
|
||||||
ADD COLUMN user_id INTEGER DEFAULT 99999,
|
ADD COLUMN user_id INTEGER DEFAULT 99999,
|
||||||
ADD COLUMN project_id INTEGER DEFAULT 99999;
|
ADD COLUMN project_id INTEGER DEFAULT 99999;
|
||||||
|
@ -92,7 +92,7 @@ func (m RemoveMemberAndExtended) Up(tx pgx.Tx) error {
|
||||||
return oops.New(err, "failed to associate links with members and projects")
|
return oops.New(err, "failed to associate links with members and projects")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.Exec(context.Background(), `
|
_, err = tx.Exec(ctx, `
|
||||||
ALTER TABLE auth_user
|
ALTER TABLE auth_user
|
||||||
-- From handmade_member --
|
-- From handmade_member --
|
||||||
ADD blurb VARCHAR(140) NOT NULL DEFAULT '',
|
ADD blurb VARCHAR(140) NOT NULL DEFAULT '',
|
||||||
|
@ -178,6 +178,6 @@ func (m RemoveMemberAndExtended) Up(tx pgx.Tx) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m RemoveMemberAndExtended) Down(tx pgx.Tx) error {
|
func (m RemoveMemberAndExtended) Down(ctx context.Context, tx pgx.Tx) error {
|
||||||
panic("Implement me")
|
panic("Implement me")
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ func (m RemoveMemberAndExtended2) Description() string {
|
||||||
return "Phase 2 of the above"
|
return "Phase 2 of the above"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m RemoveMemberAndExtended2) Up(tx pgx.Tx) error {
|
func (m RemoveMemberAndExtended2) Up(ctx context.Context, tx pgx.Tx) error {
|
||||||
dropOldColumn := func(ctx context.Context, tx pgx.Tx, table string, before, after string, onDelete string) {
|
dropOldColumn := func(ctx context.Context, tx pgx.Tx, table string, before, after string, onDelete string) {
|
||||||
_, err := tx.Exec(ctx, `
|
_, err := tx.Exec(ctx, `
|
||||||
ALTER TABLE `+table+`
|
ALTER TABLE `+table+`
|
||||||
|
@ -48,17 +48,17 @@ func (m RemoveMemberAndExtended2) Up(tx pgx.Tx) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dropOldColumn(context.Background(), tx, "handmade_communicationchoice", "member_id", "user_id", "CASCADE")
|
dropOldColumn(ctx, tx, "handmade_communicationchoice", "member_id", "user_id", "CASCADE")
|
||||||
dropOldColumn(context.Background(), tx, "handmade_communicationsubcategory", "member_id", "user_id", "CASCADE")
|
dropOldColumn(ctx, tx, "handmade_communicationsubcategory", "member_id", "user_id", "CASCADE")
|
||||||
dropOldColumn(context.Background(), tx, "handmade_communicationsubthread", "member_id", "user_id", "CASCADE")
|
dropOldColumn(ctx, tx, "handmade_communicationsubthread", "member_id", "user_id", "CASCADE")
|
||||||
dropOldColumn(context.Background(), tx, "handmade_discord", "member_id", "hmn_user_id", "CASCADE")
|
dropOldColumn(ctx, tx, "handmade_discord", "member_id", "hmn_user_id", "CASCADE")
|
||||||
dropOldColumn(context.Background(), tx, "handmade_categorylastreadinfo", "member_id", "user_id", "CASCADE")
|
dropOldColumn(ctx, tx, "handmade_categorylastreadinfo", "member_id", "user_id", "CASCADE")
|
||||||
dropOldColumn(context.Background(), tx, "handmade_threadlastreadinfo", "member_id", "user_id", "CASCADE")
|
dropOldColumn(ctx, tx, "handmade_threadlastreadinfo", "member_id", "user_id", "CASCADE")
|
||||||
dropOldColumn(context.Background(), tx, "handmade_posttextversion", "editor_id", "editor_id", "SET NULL")
|
dropOldColumn(ctx, tx, "handmade_posttextversion", "editor_id", "editor_id", "SET NULL")
|
||||||
dropOldColumn(context.Background(), tx, "handmade_post", "author_id", "author_id", "SET NULL")
|
dropOldColumn(ctx, tx, "handmade_post", "author_id", "author_id", "SET NULL")
|
||||||
dropOldColumn(context.Background(), tx, "handmade_member_projects", "member_id", "user_id", "SET NULL")
|
dropOldColumn(ctx, tx, "handmade_member_projects", "member_id", "user_id", "SET NULL")
|
||||||
|
|
||||||
_, err := tx.Exec(context.Background(), `
|
_, err := tx.Exec(ctx, `
|
||||||
ALTER TABLE handmade_member_projects
|
ALTER TABLE handmade_member_projects
|
||||||
RENAME TO handmade_user_projects;
|
RENAME TO handmade_user_projects;
|
||||||
`)
|
`)
|
||||||
|
@ -66,7 +66,7 @@ func (m RemoveMemberAndExtended2) Up(tx pgx.Tx) error {
|
||||||
return oops.New(err, "failed to rename member projects table")
|
return oops.New(err, "failed to rename member projects table")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.Exec(context.Background(), `
|
_, err = tx.Exec(ctx, `
|
||||||
ALTER TABLE handmade_links
|
ALTER TABLE handmade_links
|
||||||
ADD FOREIGN KEY (user_id) REFERENCES auth_user ON DELETE CASCADE,
|
ADD FOREIGN KEY (user_id) REFERENCES auth_user ON DELETE CASCADE,
|
||||||
ALTER user_id DROP DEFAULT,
|
ALTER user_id DROP DEFAULT,
|
||||||
|
@ -87,7 +87,7 @@ func (m RemoveMemberAndExtended2) Up(tx pgx.Tx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// And now, the moment you've all been waiting for:
|
// And now, the moment you've all been waiting for:
|
||||||
_, err = tx.Exec(context.Background(), `
|
_, err = tx.Exec(ctx, `
|
||||||
DROP TABLE handmade_member;
|
DROP TABLE handmade_member;
|
||||||
DROP TABLE handmade_memberextended;
|
DROP TABLE handmade_memberextended;
|
||||||
`)
|
`)
|
||||||
|
@ -96,7 +96,7 @@ func (m RemoveMemberAndExtended2) Up(tx pgx.Tx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// And finally, a little cleanup.
|
// And finally, a little cleanup.
|
||||||
_, err = tx.Exec(context.Background(), `
|
_, err = tx.Exec(ctx, `
|
||||||
ALTER INDEX handmade_member_projects_b098ad43 RENAME TO user_projects_btree;
|
ALTER INDEX handmade_member_projects_b098ad43 RENAME TO user_projects_btree;
|
||||||
ALTER SEQUENCE handmade_member_projects_id_seq RENAME TO user_projects_id_seq;
|
ALTER SEQUENCE handmade_member_projects_id_seq RENAME TO user_projects_id_seq;
|
||||||
ALTER INDEX handmade_member_projects_pkey RENAME TO user_projects_pkey;
|
ALTER INDEX handmade_member_projects_pkey RENAME TO user_projects_pkey;
|
||||||
|
@ -108,6 +108,6 @@ func (m RemoveMemberAndExtended2) Up(tx pgx.Tx) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m RemoveMemberAndExtended2) Down(tx pgx.Tx) error {
|
func (m RemoveMemberAndExtended2) Down(ctx context.Context, tx pgx.Tx) error {
|
||||||
panic("Implement me")
|
panic("Implement me")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.handmade.network/hmn/hmn/src/migration/types"
|
||||||
|
"git.handmade.network/hmn/hmn/src/oops"
|
||||||
|
"github.com/jackc/pgx/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registerMigration(FixOrphanedPostTextVersions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type FixOrphanedPostTextVersions struct{}
|
||||||
|
|
||||||
|
func (m FixOrphanedPostTextVersions) Version() types.MigrationVersion {
|
||||||
|
return types.MigrationVersion(time.Date(2021, 4, 23, 0, 0, 0, 0, time.UTC))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m FixOrphanedPostTextVersions) Name() string {
|
||||||
|
return "FixOrphanedPostTextVersions"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m FixOrphanedPostTextVersions) Description() string {
|
||||||
|
return "Set the post_id on posttextversions that lost track of their parents"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m FixOrphanedPostTextVersions) Up(ctx context.Context, tx pgx.Tx) error {
|
||||||
|
_, err := tx.Exec(ctx, `
|
||||||
|
UPDATE handmade_posttextversion AS tver
|
||||||
|
SET
|
||||||
|
post_id = (
|
||||||
|
SELECT id
|
||||||
|
FROM handmade_post AS post
|
||||||
|
WHERE
|
||||||
|
post.current_id = tver.id
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
tver.post_id IS NULL
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return oops.New(err, "failed to fix up half-orphaned posttextversions")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m FixOrphanedPostTextVersions) Down(ctx context.Context, tx pgx.Tx) error {
|
||||||
|
panic("Implement me")
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.handmade.network/hmn/hmn/src/migration/types"
|
||||||
|
"git.handmade.network/hmn/hmn/src/oops"
|
||||||
|
"github.com/jackc/pgx/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registerMigration(RemovePostText{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemovePostText struct{}
|
||||||
|
|
||||||
|
func (m RemovePostText) Version() types.MigrationVersion {
|
||||||
|
return types.MigrationVersion(time.Date(2021, 4, 23, 2, 11, 47, 0, time.UTC))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m RemovePostText) Name() string {
|
||||||
|
return "RemovePostText"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m RemovePostText) Description() string {
|
||||||
|
return "Collapse handmade_posttext and handmade_posttextversion into one table"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m RemovePostText) Up(ctx context.Context, tx pgx.Tx) error {
|
||||||
|
_, err := tx.Exec(ctx, `
|
||||||
|
CREATE TABLE handmade_postversion (
|
||||||
|
id INT PRIMARY KEY,
|
||||||
|
post_id INT NOT NULL REFERENCES handmade_post(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
text_raw TEXT NOT NULL,
|
||||||
|
text_parsed TEXT NOT NULL,
|
||||||
|
parser INT NOT NULL,
|
||||||
|
|
||||||
|
edit_ip INET,
|
||||||
|
edit_date TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
|
edit_reason VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
editor_id INT REFERENCES auth_user(id) ON DELETE SET NULL
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return oops.New(err, "failed to create new postversion table")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tx.Exec(ctx, `
|
||||||
|
INSERT INTO handmade_postversion
|
||||||
|
SELECT
|
||||||
|
tver.id,
|
||||||
|
tver.post_id,
|
||||||
|
|
||||||
|
t.text,
|
||||||
|
t.textparsed,
|
||||||
|
t.parser,
|
||||||
|
|
||||||
|
tver.editip,
|
||||||
|
COALESCE(tver.editdate, p.postdate),
|
||||||
|
COALESCE(tver.editreason, ''),
|
||||||
|
tver.editor_id
|
||||||
|
FROM
|
||||||
|
handmade_posttextversion AS tver
|
||||||
|
JOIN handmade_posttext AS t ON tver.text_id = t.id
|
||||||
|
JOIN handmade_post AS p ON tver.post_id = p.id
|
||||||
|
WHERE
|
||||||
|
tver.post_id IS NOT NULL
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return oops.New(err, "failed to create postversions")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tx.Exec(ctx, `
|
||||||
|
ALTER TABLE handmade_post
|
||||||
|
DROP CONSTRAINT handmade_post_current_id_762211b7_fk_handmade_,
|
||||||
|
ADD FOREIGN KEY (current_id) REFERENCES handmade_postversion ON DELETE RESTRICT;
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return oops.New(err, "failed to drop old current constraint")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tx.Exec(ctx, `
|
||||||
|
DROP TABLE handmade_posttextversion;
|
||||||
|
DROP TABLE handmade_posttext;
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return oops.New(err, "failed to drop tables")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m RemovePostText) Down(ctx context.Context, tx pgx.Tx) error {
|
||||||
|
panic("Implement me")
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v4"
|
"github.com/jackc/pgx/v4"
|
||||||
|
@ -10,8 +11,8 @@ type Migration interface {
|
||||||
Version() MigrationVersion
|
Version() MigrationVersion
|
||||||
Name() string
|
Name() string
|
||||||
Description() string
|
Description() string
|
||||||
Up(conn pgx.Tx) error
|
Up(ctx context.Context, conn pgx.Tx) error
|
||||||
Down(conn pgx.Tx) error
|
Down(ctx context.Context, conn pgx.Tx) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type MigrationVersion time.Time
|
type MigrationVersion time.Time
|
||||||
|
|
|
@ -29,3 +29,25 @@ type Post struct {
|
||||||
Preview string `db:"preview"`
|
Preview string `db:"preview"`
|
||||||
ReadOnly bool `db:"readonly"`
|
ReadOnly bool `db:"readonly"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Parser int
|
||||||
|
|
||||||
|
const (
|
||||||
|
ParserBBCode Parser = 1
|
||||||
|
ParserCleanHTML = 2
|
||||||
|
ParserMarkdown = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
type PostVersion struct {
|
||||||
|
ID int `db:"id"`
|
||||||
|
PostID int `db:"post_id"`
|
||||||
|
|
||||||
|
TextRaw string `db:"text_raw"`
|
||||||
|
TextParsed string `db:"text_parsed"`
|
||||||
|
Parser Parser `db:"parser"`
|
||||||
|
|
||||||
|
EditIP *net.IPNet `db:"edit_ip"`
|
||||||
|
EditDate time.Time `db:"edit_date"`
|
||||||
|
EditReason string `db:"edit_reason"`
|
||||||
|
EditorID *int `db:"editor_id"`
|
||||||
|
}
|
||||||
|
|
|
@ -5,13 +5,25 @@ import (
|
||||||
"git.handmade.network/hmn/hmn/src/models"
|
"git.handmade.network/hmn/hmn/src/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PostToTemplate(p *models.Post) Post {
|
func PostToTemplate(p *models.Post, author models.User) Post {
|
||||||
return Post{
|
return Post{
|
||||||
|
Author: UserToTemplate(&author),
|
||||||
Preview: p.Preview,
|
Preview: p.Preview,
|
||||||
ReadOnly: p.ReadOnly,
|
ReadOnly: p.ReadOnly,
|
||||||
|
|
||||||
|
// No content. Do it yourself if you care.
|
||||||
|
|
||||||
|
IP: p.IP.String(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func PostToTemplateWithContent(p *models.Post, author models.User, content string) Post {
|
||||||
|
post := PostToTemplate(p, author)
|
||||||
|
post.Content = content
|
||||||
|
|
||||||
|
return post
|
||||||
|
}
|
||||||
|
|
||||||
func ProjectToTemplate(p *models.Project) Project {
|
func ProjectToTemplate(p *models.Project) Project {
|
||||||
return Project{
|
return Project{
|
||||||
Name: maybeString(p.Name),
|
Name: maybeString(p.Name),
|
||||||
|
|
|
@ -4,7 +4,7 @@ This template is intended to display a single post or thread in the context of a
|
||||||
It should be called with PostListItemData.
|
It should be called with PostListItemData.
|
||||||
*/}}
|
*/}}
|
||||||
|
|
||||||
<div data-tmpl="container" class="flex items-center ph3 pv2">
|
<div class="flex items-center ph3 pv2">
|
||||||
<img class="avatar-icon mr2" src="{{ .User.AvatarUrl }}">
|
<img class="avatar-icon mr2" src="{{ .User.AvatarUrl }}">
|
||||||
<div class="flex-grow-1 overflow-hidden">
|
<div class="flex-grow-1 overflow-hidden">
|
||||||
<div class="breadcrumbs">
|
<div class="breadcrumbs">
|
||||||
|
|
|
@ -54,17 +54,22 @@
|
||||||
<h2 class="ph3">{{ $proj.Name }}</h2>
|
<h2 class="ph3">{{ $proj.Name }}</h2>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{{/* TODO: What is this?
|
{{ with $entry.FeaturedPost }}
|
||||||
{% if entry.featured and proj.slug != "hmn" %}
|
<div class="flex items-start ph3 pv2">
|
||||||
{% with post=entry.featured.0 has_read=entry.featured.1 %}
|
<img class="avatar-icon mr2" src="{{ .User.AvatarUrl }}">
|
||||||
{% if post.category.kind == 5 and post.parent == None %}
|
<div class="flex-grow-1">
|
||||||
{% include "thread_list_entry.html" with thread=post.thread %}
|
<div class="overflow-hidden">
|
||||||
{% else %}
|
<div class="title nowrap truncate"><a href="{{ .Url }}">{{ .Title }}</a></div>
|
||||||
{% include "blog_index_thread_list_entry.html" with align_top=True %}
|
<div class="details">
|
||||||
{% endif %}
|
<a class="user" href="{{ .User.ProfileUrl }}">{{ .User.Name }}</a> — <span class="datetime">{{ relativedate .Date }}</span>
|
||||||
{% endwith %}
|
</div>
|
||||||
{% endif %}
|
</div>
|
||||||
*/}}
|
<div>
|
||||||
|
{{ .Content }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
{{ range $post := $posts }}
|
{{ range $post := $posts }}
|
||||||
{{ template "post_list_item.html" $post }}
|
{{ template "post_list_item.html" $post }}
|
||||||
|
|
|
@ -27,6 +27,8 @@ type Post struct {
|
||||||
Preview string
|
Preview string
|
||||||
ReadOnly bool
|
ReadOnly bool
|
||||||
|
|
||||||
|
Content string
|
||||||
|
|
||||||
IP string
|
IP string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,10 +19,19 @@ type LandingTemplateData struct {
|
||||||
|
|
||||||
type LandingPageProject struct {
|
type LandingPageProject struct {
|
||||||
Project templates.Project
|
Project templates.Project
|
||||||
FeaturedPost *templates.PostListItem
|
FeaturedPost *LandingPageFeaturedPost
|
||||||
Posts []templates.PostListItem
|
Posts []templates.PostListItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LandingPageFeaturedPost struct {
|
||||||
|
Title string
|
||||||
|
Url string
|
||||||
|
User templates.User
|
||||||
|
Date time.Time
|
||||||
|
Unread bool
|
||||||
|
Content string
|
||||||
|
}
|
||||||
|
|
||||||
func Index(c *RequestContext) ResponseData {
|
func Index(c *RequestContext) ResponseData {
|
||||||
const maxPosts = 5
|
const maxPosts = 5
|
||||||
const numProjectsToGet = 7
|
const numProjectsToGet = 7
|
||||||
|
@ -116,8 +125,40 @@ func Index(c *RequestContext) ResponseData {
|
||||||
hasRead = true
|
hasRead = true
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Logger.Debug().Time("post date", projectPost.Post.PostDate).Msg("")
|
featurable := (!proj.IsHMN() &&
|
||||||
|
projectPost.Cat.Kind == models.CatTypeBlog &&
|
||||||
|
projectPost.Post.ParentID == nil &&
|
||||||
|
landingPageProject.FeaturedPost == nil)
|
||||||
|
|
||||||
|
if featurable {
|
||||||
|
type featuredContentResult struct {
|
||||||
|
Content string `db:"ver.text_parsed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
contentResult, err := db.QueryOne(c.Context(), c.Conn, featuredContentResult{}, `
|
||||||
|
SELECT $columns
|
||||||
|
FROM
|
||||||
|
handmade_post AS post
|
||||||
|
JOIN handmade_postversion AS ver ON post.current_id = ver.id
|
||||||
|
WHERE
|
||||||
|
post.id = $1
|
||||||
|
`, projectPost.Post.ID)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
content := contentResult.(*featuredContentResult).Content
|
||||||
|
|
||||||
|
// c.Logger.Debug().Str("content", content).Msg("")
|
||||||
|
|
||||||
|
landingPageProject.FeaturedPost = &LandingPageFeaturedPost{
|
||||||
|
Title: projectPost.Thread.Title,
|
||||||
|
Url: templates.PostUrl(projectPost.Post, projectPost.Cat.Kind, proj.Subdomain()), // TODO
|
||||||
|
User: templates.UserToTemplate(&projectPost.User),
|
||||||
|
Date: projectPost.Post.PostDate,
|
||||||
|
Unread: !hasRead,
|
||||||
|
Content: content,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
landingPageProject.Posts = append(landingPageProject.Posts, templates.PostListItem{
|
landingPageProject.Posts = append(landingPageProject.Posts, templates.PostListItem{
|
||||||
Title: projectPost.Thread.Title,
|
Title: projectPost.Thread.Title,
|
||||||
Url: templates.PostUrl(projectPost.Post, projectPost.Cat.Kind, proj.Subdomain()), // TODO
|
Url: templates.PostUrl(projectPost.Post, projectPost.Cat.Kind, proj.Subdomain()), // TODO
|
||||||
|
@ -126,6 +167,7 @@ func Index(c *RequestContext) ResponseData {
|
||||||
Unread: !hasRead,
|
Unread: !hasRead,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(projectPosts) > 0 {
|
if len(projectPosts) > 0 {
|
||||||
pageProjects = append(pageProjects, landingPageProject)
|
pageProjects = append(pageProjects, landingPageProject)
|
||||||
|
@ -158,13 +200,43 @@ func Index(c *RequestContext) ResponseData {
|
||||||
newsThread := newsThreadRow.(*newsThreadQuery)
|
newsThread := newsThreadRow.(*newsThreadQuery)
|
||||||
_ = newsThread // TODO: NO
|
_ = newsThread // TODO: NO
|
||||||
|
|
||||||
|
/*
|
||||||
|
Columns are filled by placing projects into the least full column.
|
||||||
|
The fill array tracks the estimated sizes.
|
||||||
|
|
||||||
|
This is all hardcoded for two columns; deal with it.
|
||||||
|
*/
|
||||||
|
cols := [][]LandingPageProject{nil, nil}
|
||||||
|
fill := []int{4, 0}
|
||||||
|
featuredIndex := []int{0, 0}
|
||||||
|
for _, pageProject := range pageProjects {
|
||||||
|
leastFullColumnIndex := indexOfSmallestInt(fill)
|
||||||
|
|
||||||
|
numNewPosts := len(pageProject.Posts)
|
||||||
|
if numNewPosts > maxPosts {
|
||||||
|
numNewPosts = maxPosts
|
||||||
|
}
|
||||||
|
|
||||||
|
fill[leastFullColumnIndex] += numNewPosts
|
||||||
|
|
||||||
|
if pageProject.FeaturedPost != nil {
|
||||||
|
fill[leastFullColumnIndex] += 2 // featured posts add more to height
|
||||||
|
|
||||||
|
// projects with featured posts go at the top of the column
|
||||||
|
cols[leastFullColumnIndex] = append(cols[leastFullColumnIndex], pageProject)
|
||||||
|
featuredIndex[leastFullColumnIndex] += 1
|
||||||
|
} else {
|
||||||
|
cols[leastFullColumnIndex] = append(cols[leastFullColumnIndex], pageProject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
baseData := getBaseData(c)
|
baseData := getBaseData(c)
|
||||||
baseData.BodyClasses = append(baseData.BodyClasses, "hmdev", "landing") // TODO: Is "hmdev" necessary any more?
|
baseData.BodyClasses = append(baseData.BodyClasses, "hmdev", "landing") // TODO: Is "hmdev" necessary any more?
|
||||||
|
|
||||||
var res ResponseData
|
var res ResponseData
|
||||||
err = res.WriteTemplate("index.html", LandingTemplateData{
|
err = res.WriteTemplate("index.html", LandingTemplateData{
|
||||||
BaseData: getBaseData(c),
|
BaseData: getBaseData(c),
|
||||||
PostColumns: [][]LandingPageProject{pageProjects}, // TODO: NO
|
PostColumns: cols,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -172,3 +244,17 @@ func Index(c *RequestContext) ResponseData {
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func indexOfSmallestInt(s []int) int {
|
||||||
|
result := 0
|
||||||
|
min := s[result]
|
||||||
|
|
||||||
|
for i, val := range s {
|
||||||
|
if val < min {
|
||||||
|
result = i
|
||||||
|
min = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue