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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getCurrentVersion(conn *pgx.Conn) (types.MigrationVersion, error) {
 | 
			
		||||
func getCurrentVersion(ctx context.Context, conn *pgx.Conn) (types.MigrationVersion, error) {
 | 
			
		||||
	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)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return types.MigrationVersion{}, err
 | 
			
		||||
| 
						 | 
				
			
			@ -115,10 +115,12 @@ func getCurrentVersion(conn *pgx.Conn) (types.MigrationVersion, error) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func ListMigrations() {
 | 
			
		||||
	conn := db.NewConn()
 | 
			
		||||
	defer conn.Close(context.Background())
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	currentVersion, _ := getCurrentVersion(conn)
 | 
			
		||||
	conn := db.NewConn()
 | 
			
		||||
	defer conn.Close(ctx)
 | 
			
		||||
 | 
			
		||||
	currentVersion, _ := getCurrentVersion(ctx, conn)
 | 
			
		||||
	for _, version := range getSortedMigrationVersions() {
 | 
			
		||||
		migration := migrations.All[version]
 | 
			
		||||
		indicator := "  "
 | 
			
		||||
| 
						 | 
				
			
			@ -130,11 +132,13 @@ func ListMigrations() {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func Migrate(targetVersion types.MigrationVersion) {
 | 
			
		||||
	ctx := context.Background() // In the future, this could actually do something cool.
 | 
			
		||||
 | 
			
		||||
	conn := db.NewConn()
 | 
			
		||||
	defer conn.Close(context.Background())
 | 
			
		||||
	defer conn.Close(ctx)
 | 
			
		||||
 | 
			
		||||
	// create migration table
 | 
			
		||||
	_, err := conn.Exec(context.Background(), `
 | 
			
		||||
	_, err := conn.Exec(ctx, `
 | 
			
		||||
		CREATE TABLE IF NOT EXISTS hmn_migration (
 | 
			
		||||
			version		TIMESTAMP WITH TIME ZONE
 | 
			
		||||
		)
 | 
			
		||||
| 
						 | 
				
			
			@ -144,21 +148,21 @@ func Migrate(targetVersion types.MigrationVersion) {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	// 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
 | 
			
		||||
	err = row.Scan(&numRows)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	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 {
 | 
			
		||||
			panic(fmt.Errorf("failed to insert initial migration row: %w", err))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// run migrations
 | 
			
		||||
	currentVersion, err := getCurrentVersion(conn)
 | 
			
		||||
	currentVersion, err := getCurrentVersion(ctx, conn)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(fmt.Errorf("failed to get current version: %w", err))
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -196,25 +200,25 @@ func Migrate(targetVersion types.MigrationVersion) {
 | 
			
		|||
			migration := migrations.All[version]
 | 
			
		||||
			fmt.Printf("Applying migration %v (%v)\n", version, migration.Name())
 | 
			
		||||
 | 
			
		||||
			tx, err := conn.Begin(context.Background())
 | 
			
		||||
			tx, err := conn.Begin(ctx)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				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 {
 | 
			
		||||
				fmt.Printf("MIGRATION FAILED for migration %v.\n", version)
 | 
			
		||||
				fmt.Printf("Error: %v\n", err)
 | 
			
		||||
				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 {
 | 
			
		||||
				panic(fmt.Errorf("failed to update version in migrations table: %w", err))
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			err = tx.Commit(context.Background())
 | 
			
		||||
			err = tx.Commit(ctx)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				panic(fmt.Errorf("failed to commit transaction: %w", err))
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -228,27 +232,27 @@ func Migrate(targetVersion types.MigrationVersion) {
 | 
			
		|||
				previousVersion = allVersions[i-1]
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			tx, err := conn.Begin(context.Background())
 | 
			
		||||
			tx, err := conn.Begin(ctx)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				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)
 | 
			
		||||
			migration := migrations.All[version]
 | 
			
		||||
			err = migration.Down(tx)
 | 
			
		||||
			err = migration.Down(ctx, tx)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				fmt.Printf("MIGRATION FAILED for migration %v.\n", version)
 | 
			
		||||
				fmt.Printf("Error: %v\n", err)
 | 
			
		||||
				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 {
 | 
			
		||||
				panic(fmt.Errorf("failed to update version in migrations table: %w", err))
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			err = tx.Commit(context.Background())
 | 
			
		||||
			err = tx.Commit(ctx)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				panic(fmt.Errorf("failed to commit transaction: %w", err))
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,10 +25,10 @@ func (m %NAME%) Description() string {
 | 
			
		|||
	return %DESCRIPTION%
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m %NAME%) Up(tx pgx.Tx) error {
 | 
			
		||||
func (m %NAME%) Up(ctx context.Context, tx pgx.Tx) error {
 | 
			
		||||
	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")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,8 +26,8 @@ func (m Initial) Description() string {
 | 
			
		|||
	return "Creates all the tables from the old site"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m Initial) Up(tx pgx.Tx) error {
 | 
			
		||||
	_, err := tx.Exec(context.Background(), `
 | 
			
		||||
func (m Initial) Up(ctx context.Context, tx pgx.Tx) error {
 | 
			
		||||
	_, err := tx.Exec(ctx, `
 | 
			
		||||
	--
 | 
			
		||||
	-- PostgreSQL database dump
 | 
			
		||||
	--
 | 
			
		||||
| 
						 | 
				
			
			@ -4648,6 +4648,6 @@ func (m Initial) Up(tx pgx.Tx) error {
 | 
			
		|||
	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")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,8 +26,8 @@ func (m AddSessionTable) Description() string {
 | 
			
		|||
	return "Adds a session table to replace the Django session table"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m AddSessionTable) Up(tx pgx.Tx) error {
 | 
			
		||||
	_, err := tx.Exec(context.Background(), `
 | 
			
		||||
func (m AddSessionTable) Up(ctx context.Context, tx pgx.Tx) error {
 | 
			
		||||
	_, err := tx.Exec(ctx, `
 | 
			
		||||
		CREATE TABLE sessions (
 | 
			
		||||
			id VARCHAR(40) PRIMARY KEY,
 | 
			
		||||
			username VARCHAR(150) NOT NULL,
 | 
			
		||||
| 
						 | 
				
			
			@ -37,8 +37,8 @@ func (m AddSessionTable) Up(tx pgx.Tx) error {
 | 
			
		|||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m AddSessionTable) Down(tx pgx.Tx) error {
 | 
			
		||||
	_, err := tx.Exec(context.Background(), `
 | 
			
		||||
func (m AddSessionTable) Down(ctx context.Context, tx pgx.Tx) error {
 | 
			
		||||
	_, err := tx.Exec(ctx, `
 | 
			
		||||
		DROP TABLE sessions;
 | 
			
		||||
	`)
 | 
			
		||||
	return err
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,16 +26,16 @@ func (m AllowLongerPasswordHashes) Description() string {
 | 
			
		|||
	return "Increase the storage size limit on hashed passwords"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m AllowLongerPasswordHashes) Up(tx pgx.Tx) error {
 | 
			
		||||
	_, err := tx.Exec(context.Background(), `
 | 
			
		||||
func (m AllowLongerPasswordHashes) Up(ctx context.Context, tx pgx.Tx) error {
 | 
			
		||||
	_, err := tx.Exec(ctx, `
 | 
			
		||||
		ALTER TABLE auth_user
 | 
			
		||||
			ALTER COLUMN password TYPE VARCHAR(256)
 | 
			
		||||
	`)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m AllowLongerPasswordHashes) Down(tx pgx.Tx) error {
 | 
			
		||||
	_, err := tx.Exec(context.Background(), `
 | 
			
		||||
func (m AllowLongerPasswordHashes) Down(ctx context.Context, tx pgx.Tx) error {
 | 
			
		||||
	_, err := tx.Exec(ctx, `
 | 
			
		||||
		ALTER TABLE auth_user
 | 
			
		||||
			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"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m DeleteOrphanedData) Up(tx pgx.Tx) error {
 | 
			
		||||
func (m DeleteOrphanedData) Up(ctx context.Context, tx pgx.Tx) error {
 | 
			
		||||
	// Delete orphaned users (no member)
 | 
			
		||||
	res, err := tx.Exec(context.Background(), `
 | 
			
		||||
	res, err := tx.Exec(ctx, `
 | 
			
		||||
		DELETE FROM auth_user
 | 
			
		||||
		WHERE
 | 
			
		||||
			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
 | 
			
		||||
	// (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
 | 
			
		||||
		WHERE
 | 
			
		||||
			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())
 | 
			
		||||
 | 
			
		||||
	// Delete orphaned memberextendeds (no member)
 | 
			
		||||
	res, err = tx.Exec(context.Background(), `
 | 
			
		||||
	res, err = tx.Exec(ctx, `
 | 
			
		||||
		DELETE FROM handmade_memberextended
 | 
			
		||||
		WHERE
 | 
			
		||||
			id IN (
 | 
			
		||||
| 
						 | 
				
			
			@ -84,7 +84,7 @@ func (m DeleteOrphanedData) Up(tx pgx.Tx) error {
 | 
			
		|||
	fmt.Printf("Deleted %v memberextendeds\n", res.RowsAffected())
 | 
			
		||||
 | 
			
		||||
	// Delete orphaned links (no member or project)
 | 
			
		||||
	res, err = tx.Exec(context.Background(), `
 | 
			
		||||
	res, err = tx.Exec(ctx, `
 | 
			
		||||
		DELETE FROM handmade_links
 | 
			
		||||
		WHERE
 | 
			
		||||
			id IN (
 | 
			
		||||
| 
						 | 
				
			
			@ -106,6 +106,6 @@ func (m DeleteOrphanedData) Up(tx pgx.Tx) error {
 | 
			
		|||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m DeleteOrphanedData) Down(tx pgx.Tx) error {
 | 
			
		||||
func (m DeleteOrphanedData) Down(ctx context.Context, tx pgx.Tx) error {
 | 
			
		||||
	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"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
	createUserColumn := func(ctx context.Context, tx pgx.Tx, table string, before string, notNull bool) {
 | 
			
		||||
		nullConstraint := ""
 | 
			
		||||
| 
						 | 
				
			
			@ -51,18 +51,18 @@ func (m RemoveMemberAndExtended) Up(tx pgx.Tx) error {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	// Migrate a lot of simple foreign keys
 | 
			
		||||
	createUserColumn(context.Background(), tx, "handmade_communicationchoice", "member_id", true)
 | 
			
		||||
	createUserColumn(context.Background(), tx, "handmade_communicationsubcategory", "member_id", true)
 | 
			
		||||
	createUserColumn(context.Background(), tx, "handmade_communicationsubthread", "member_id", true)
 | 
			
		||||
	createUserColumn(context.Background(), tx, "handmade_discord", "member_id", true)
 | 
			
		||||
	createUserColumn(context.Background(), tx, "handmade_categorylastreadinfo", "member_id", true)
 | 
			
		||||
	createUserColumn(context.Background(), tx, "handmade_threadlastreadinfo", "member_id", true)
 | 
			
		||||
	createUserColumn(context.Background(), tx, "handmade_posttextversion", "editor_id", false)
 | 
			
		||||
	createUserColumn(context.Background(), tx, "handmade_post", "author_id", false)
 | 
			
		||||
	createUserColumn(context.Background(), tx, "handmade_member_projects", "member_id", true)
 | 
			
		||||
	createUserColumn(ctx, tx, "handmade_communicationchoice", "member_id", true)
 | 
			
		||||
	createUserColumn(ctx, tx, "handmade_communicationsubcategory", "member_id", true)
 | 
			
		||||
	createUserColumn(ctx, tx, "handmade_communicationsubthread", "member_id", true)
 | 
			
		||||
	createUserColumn(ctx, tx, "handmade_discord", "member_id", true)
 | 
			
		||||
	createUserColumn(ctx, tx, "handmade_categorylastreadinfo", "member_id", true)
 | 
			
		||||
	createUserColumn(ctx, tx, "handmade_threadlastreadinfo", "member_id", true)
 | 
			
		||||
	createUserColumn(ctx, tx, "handmade_posttextversion", "editor_id", false)
 | 
			
		||||
	createUserColumn(ctx, tx, "handmade_post", "author_id", false)
 | 
			
		||||
	createUserColumn(ctx, tx, "handmade_member_projects", "member_id", true)
 | 
			
		||||
 | 
			
		||||
	// Directly associate links with members
 | 
			
		||||
	_, err := tx.Exec(context.Background(), `
 | 
			
		||||
	_, err := tx.Exec(ctx, `
 | 
			
		||||
		ALTER TABLE handmade_links
 | 
			
		||||
			ADD COLUMN user_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")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = tx.Exec(context.Background(), `
 | 
			
		||||
	_, err = tx.Exec(ctx, `
 | 
			
		||||
		ALTER TABLE auth_user
 | 
			
		||||
			-- From handmade_member --
 | 
			
		||||
			ADD blurb VARCHAR(140) NOT NULL DEFAULT '',
 | 
			
		||||
| 
						 | 
				
			
			@ -178,6 +178,6 @@ func (m RemoveMemberAndExtended) Up(tx pgx.Tx) error {
 | 
			
		|||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m RemoveMemberAndExtended) Down(tx pgx.Tx) error {
 | 
			
		||||
func (m RemoveMemberAndExtended) Down(ctx context.Context, tx pgx.Tx) error {
 | 
			
		||||
	panic("Implement me")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,7 +32,7 @@ func (m RemoveMemberAndExtended2) Description() string {
 | 
			
		|||
	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) {
 | 
			
		||||
		_, err := tx.Exec(ctx, `
 | 
			
		||||
			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(context.Background(), tx, "handmade_communicationsubcategory", "member_id", "user_id", "CASCADE")
 | 
			
		||||
	dropOldColumn(context.Background(), tx, "handmade_communicationsubthread", "member_id", "user_id", "CASCADE")
 | 
			
		||||
	dropOldColumn(context.Background(), tx, "handmade_discord", "member_id", "hmn_user_id", "CASCADE")
 | 
			
		||||
	dropOldColumn(context.Background(), tx, "handmade_categorylastreadinfo", "member_id", "user_id", "CASCADE")
 | 
			
		||||
	dropOldColumn(context.Background(), tx, "handmade_threadlastreadinfo", "member_id", "user_id", "CASCADE")
 | 
			
		||||
	dropOldColumn(context.Background(), tx, "handmade_posttextversion", "editor_id", "editor_id", "SET NULL")
 | 
			
		||||
	dropOldColumn(context.Background(), 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_communicationchoice", "member_id", "user_id", "CASCADE")
 | 
			
		||||
	dropOldColumn(ctx, tx, "handmade_communicationsubcategory", "member_id", "user_id", "CASCADE")
 | 
			
		||||
	dropOldColumn(ctx, tx, "handmade_communicationsubthread", "member_id", "user_id", "CASCADE")
 | 
			
		||||
	dropOldColumn(ctx, tx, "handmade_discord", "member_id", "hmn_user_id", "CASCADE")
 | 
			
		||||
	dropOldColumn(ctx, tx, "handmade_categorylastreadinfo", "member_id", "user_id", "CASCADE")
 | 
			
		||||
	dropOldColumn(ctx, tx, "handmade_threadlastreadinfo", "member_id", "user_id", "CASCADE")
 | 
			
		||||
	dropOldColumn(ctx, tx, "handmade_posttextversion", "editor_id", "editor_id", "SET NULL")
 | 
			
		||||
	dropOldColumn(ctx, tx, "handmade_post", "author_id", "author_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
 | 
			
		||||
			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")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = tx.Exec(context.Background(), `
 | 
			
		||||
	_, err = tx.Exec(ctx, `
 | 
			
		||||
		ALTER TABLE handmade_links
 | 
			
		||||
			ADD FOREIGN KEY (user_id) REFERENCES auth_user ON DELETE CASCADE,
 | 
			
		||||
			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:
 | 
			
		||||
	_, err = tx.Exec(context.Background(), `
 | 
			
		||||
	_, err = tx.Exec(ctx, `
 | 
			
		||||
		DROP TABLE handmade_member;
 | 
			
		||||
		DROP TABLE handmade_memberextended;
 | 
			
		||||
	`)
 | 
			
		||||
| 
						 | 
				
			
			@ -96,7 +96,7 @@ func (m RemoveMemberAndExtended2) Up(tx pgx.Tx) error {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	// 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 SEQUENCE handmade_member_projects_id_seq RENAME TO user_projects_id_seq;
 | 
			
		||||
		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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m RemoveMemberAndExtended2) Down(tx pgx.Tx) error {
 | 
			
		||||
func (m RemoveMemberAndExtended2) Down(ctx context.Context, tx pgx.Tx) error {
 | 
			
		||||
	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
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/jackc/pgx/v4"
 | 
			
		||||
| 
						 | 
				
			
			@ -10,8 +11,8 @@ type Migration interface {
 | 
			
		|||
	Version() MigrationVersion
 | 
			
		||||
	Name() string
 | 
			
		||||
	Description() string
 | 
			
		||||
	Up(conn pgx.Tx) error
 | 
			
		||||
	Down(conn pgx.Tx) error
 | 
			
		||||
	Up(ctx context.Context, conn pgx.Tx) error
 | 
			
		||||
	Down(ctx context.Context, conn pgx.Tx) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MigrationVersion time.Time
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,3 +29,25 @@ type Post struct {
 | 
			
		|||
	Preview  string `db:"preview"`
 | 
			
		||||
	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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func PostToTemplate(p *models.Post) Post {
 | 
			
		||||
func PostToTemplate(p *models.Post, author models.User) Post {
 | 
			
		||||
	return Post{
 | 
			
		||||
		Author:   UserToTemplate(&author),
 | 
			
		||||
		Preview:  p.Preview,
 | 
			
		||||
		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 {
 | 
			
		||||
	return Project{
 | 
			
		||||
		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.
 | 
			
		||||
*/}}
 | 
			
		||||
 | 
			
		||||
<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 }}">
 | 
			
		||||
    <div class="flex-grow-1 overflow-hidden">
 | 
			
		||||
        <div class="breadcrumbs">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,17 +54,22 @@
 | 
			
		|||
                            <h2 class="ph3">{{ $proj.Name }}</h2>
 | 
			
		||||
                        </a>
 | 
			
		||||
 | 
			
		||||
                        {{/* TODO: What is this?
 | 
			
		||||
                        {% if entry.featured and proj.slug != "hmn" %}
 | 
			
		||||
                            {% with post=entry.featured.0 has_read=entry.featured.1 %} 
 | 
			
		||||
                                {% if post.category.kind == 5 and post.parent == None %}
 | 
			
		||||
                                    {% include "thread_list_entry.html" with thread=post.thread %}
 | 
			
		||||
                                {% else %}
 | 
			
		||||
                                    {% include "blog_index_thread_list_entry.html" with align_top=True %}
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                            {% endwith %}
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                        */}}
 | 
			
		||||
                        {{ with $entry.FeaturedPost }}
 | 
			
		||||
                            <div class="flex items-start ph3 pv2">
 | 
			
		||||
                                <img class="avatar-icon mr2" src="{{ .User.AvatarUrl }}">
 | 
			
		||||
                                <div class="flex-grow-1">
 | 
			
		||||
                                    <div class="overflow-hidden">
 | 
			
		||||
                                        <div class="title nowrap truncate"><a href="{{ .Url }}">{{ .Title }}</a></div>
 | 
			
		||||
                                        <div class="details">
 | 
			
		||||
                                            <a class="user" href="{{ .User.ProfileUrl }}">{{ .User.Name }}</a> — <span class="datetime">{{ relativedate .Date }}</span>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div>
 | 
			
		||||
                                        {{ .Content }}
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        {{ end }}
 | 
			
		||||
 | 
			
		||||
                        {{ range $post := $posts }}
 | 
			
		||||
                            {{ template "post_list_item.html" $post }}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,6 +27,8 @@ type Post struct {
 | 
			
		|||
	Preview  string
 | 
			
		||||
	ReadOnly bool
 | 
			
		||||
 | 
			
		||||
	Content string
 | 
			
		||||
 | 
			
		||||
	IP string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,10 +19,19 @@ type LandingTemplateData struct {
 | 
			
		|||
 | 
			
		||||
type LandingPageProject struct {
 | 
			
		||||
	Project      templates.Project
 | 
			
		||||
	FeaturedPost *templates.PostListItem
 | 
			
		||||
	FeaturedPost *LandingPageFeaturedPost
 | 
			
		||||
	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 {
 | 
			
		||||
	const maxPosts = 5
 | 
			
		||||
	const numProjectsToGet = 7
 | 
			
		||||
| 
						 | 
				
			
			@ -116,15 +125,48 @@ func Index(c *RequestContext) ResponseData {
 | 
			
		|||
				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)
 | 
			
		||||
 | 
			
		||||
			landingPageProject.Posts = append(landingPageProject.Posts, templates.PostListItem{
 | 
			
		||||
				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,
 | 
			
		||||
			})
 | 
			
		||||
			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{
 | 
			
		||||
					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,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(projectPosts) > 0 {
 | 
			
		||||
| 
						 | 
				
			
			@ -158,13 +200,43 @@ func Index(c *RequestContext) ResponseData {
 | 
			
		|||
	newsThread := newsThreadRow.(*newsThreadQuery)
 | 
			
		||||
	_ = 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.BodyClasses = append(baseData.BodyClasses, "hmdev", "landing") // TODO: Is "hmdev" necessary any more?
 | 
			
		||||
 | 
			
		||||
	var res ResponseData
 | 
			
		||||
	err = res.WriteTemplate("index.html", LandingTemplateData{
 | 
			
		||||
		BaseData:    getBaseData(c),
 | 
			
		||||
		PostColumns: [][]LandingPageProject{pageProjects}, // TODO: NO
 | 
			
		||||
		PostColumns: cols,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
| 
						 | 
				
			
			@ -172,3 +244,17 @@ func Index(c *RequestContext) ResponseData {
 | 
			
		|||
 | 
			
		||||
	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