diff --git a/internal/controllers/web_controller.go b/internal/controllers/web_controller.go index bf43657..786d16f 100644 --- a/internal/controllers/web_controller.go +++ b/internal/controllers/web_controller.go @@ -116,6 +116,13 @@ func (c *WebController) GitHubProfile(ctx *gin.Context) { currentPageProfile, err := c.getProfileByUsername(ctx, sessionProfile, uri.Username) if err == sql.ErrNoRows { + username, err := c.userService.GetUsernameByOldUsername(ctx, dbs.SocialProviderGithub, uri.Username) + if err == nil { + ctx.Redirect(http.StatusTemporaryRedirect, "/github/"+username) + + return + } + ctx.Data(http.StatusBadRequest, "text/html; charset=utf-8", []byte(fmt.Sprintf("User not found"))) return diff --git a/internal/services/user_service.go b/internal/services/user_service.go index 675f2bf..97e8958 100644 --- a/internal/services/user_service.go +++ b/internal/services/user_service.go @@ -33,6 +33,13 @@ func (s *UserService) GetBySocialProviderUsername(ctx context.Context, provider }) } +func (s *UserService) GetUsernameByOldUsername(ctx context.Context, provider dbs.SocialProvider, oldUsername string) (string, error) { + return s.repository.Queries().UsernameHistoryGetByOldUsername(ctx, dbs.UsernameHistoryGetByOldUsernameParams{ + Username: oldUsername, + SocialProvider: provider, + }) +} + func (s *UserService) GetByID(ctx context.Context, id int64) (dbs.UsersGetByIDRow, error) { return s.repository.Queries().UsersGetByID(ctx, id) } @@ -119,6 +126,17 @@ func (s *UserService) Upsert( return err } + err = queries.UsernameHistoryNew(ctx, dbs.UsernameHistoryNewParams{ + UserID: txID, + SocialProvider: provider, + Username: username, + CreatedAt: now, + UpdatedAt: now, + }) + if err != nil { + return err + } + err = queries.ProfileTotalViewsNew(ctx, txID) if err != nil { return err diff --git a/internal/storage/dbs/db.go b/internal/storage/dbs/db.go index 0ef5f0e..c04e8d5 100644 --- a/internal/storage/dbs/db.go +++ b/internal/storage/dbs/db.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.21.0 +// sqlc v1.20.0 package dbs @@ -48,6 +48,12 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.referralsNewStmt, err = db.PrepareContext(ctx, referralsNew); err != nil { return nil, fmt.Errorf("error preparing query ReferralsNew: %w", err) } + if q.usernameHistoryGetByOldUsernameStmt, err = db.PrepareContext(ctx, usernameHistoryGetByOldUsername); err != nil { + return nil, fmt.Errorf("error preparing query UsernameHistoryGetByOldUsername: %w", err) + } + if q.usernameHistoryNewStmt, err = db.PrepareContext(ctx, usernameHistoryNew); err != nil { + return nil, fmt.Errorf("error preparing query UsernameHistoryNew: %w", err) + } if q.usersCreatedAtStatsByDayStmt, err = db.PrepareContext(ctx, usersCreatedAtStatsByDay); err != nil { return nil, fmt.Errorf("error preparing query UsersCreatedAtStatsByDay: %w", err) } @@ -117,6 +123,16 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing referralsNewStmt: %w", cerr) } } + if q.usernameHistoryGetByOldUsernameStmt != nil { + if cerr := q.usernameHistoryGetByOldUsernameStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing usernameHistoryGetByOldUsernameStmt: %w", cerr) + } + } + if q.usernameHistoryNewStmt != nil { + if cerr := q.usernameHistoryNewStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing usernameHistoryNewStmt: %w", cerr) + } + } if q.usersCreatedAtStatsByDayStmt != nil { if cerr := q.usersCreatedAtStatsByDayStmt.Close(); cerr != nil { err = fmt.Errorf("error closing usersCreatedAtStatsByDayStmt: %w", cerr) @@ -204,6 +220,8 @@ type Queries struct { profileTotalViewsNewStmt *sql.Stmt referralsCreatedAtStatsByDayStmt *sql.Stmt referralsNewStmt *sql.Stmt + usernameHistoryGetByOldUsernameStmt *sql.Stmt + usernameHistoryNewStmt *sql.Stmt usersCreatedAtStatsByDayStmt *sql.Stmt usersGetStmt *sql.Stmt usersGetAllUsernamesStmt *sql.Stmt @@ -226,6 +244,8 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries { profileTotalViewsNewStmt: q.profileTotalViewsNewStmt, referralsCreatedAtStatsByDayStmt: q.referralsCreatedAtStatsByDayStmt, referralsNewStmt: q.referralsNewStmt, + usernameHistoryGetByOldUsernameStmt: q.usernameHistoryGetByOldUsernameStmt, + usernameHistoryNewStmt: q.usernameHistoryNewStmt, usersCreatedAtStatsByDayStmt: q.usersCreatedAtStatsByDayStmt, usersGetStmt: q.usersGetStmt, usersGetAllUsernamesStmt: q.usersGetAllUsernamesStmt, diff --git a/internal/storage/dbs/models.go b/internal/storage/dbs/models.go index 62279d8..3cc4d39 100644 --- a/internal/storage/dbs/models.go +++ b/internal/storage/dbs/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.21.0 +// sqlc v1.20.0 package dbs @@ -80,3 +80,12 @@ type User struct { UpdatedAt time.Time LastLoginAt time.Time } + +type UsernameHistory struct { + ID int64 + UserID int64 + SocialProvider SocialProvider + CanonicalUsername string + CreatedAt time.Time + UpdatedAt time.Time +} diff --git a/internal/storage/dbs/profile_hourly_views_stats.sql.go b/internal/storage/dbs/profile_hourly_views_stats.sql.go index 3a2997a..1ace51a 100644 --- a/internal/storage/dbs/profile_hourly_views_stats.sql.go +++ b/internal/storage/dbs/profile_hourly_views_stats.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.21.0 +// sqlc v1.20.0 // source: profile_hourly_views_stats.sql package dbs diff --git a/internal/storage/dbs/profile_total_views.sql.go b/internal/storage/dbs/profile_total_views.sql.go index aaa4ca9..9ad9658 100644 --- a/internal/storage/dbs/profile_total_views.sql.go +++ b/internal/storage/dbs/profile_total_views.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.21.0 +// sqlc v1.20.0 // source: profile_total_views.sql package dbs diff --git a/internal/storage/dbs/referrals.sql.go b/internal/storage/dbs/referrals.sql.go index 92f26f0..64af5b0 100644 --- a/internal/storage/dbs/referrals.sql.go +++ b/internal/storage/dbs/referrals.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.21.0 +// sqlc v1.20.0 // source: referrals.sql package dbs diff --git a/internal/storage/dbs/username_history.sql.go b/internal/storage/dbs/username_history.sql.go new file mode 100644 index 0000000..3d7adb8 --- /dev/null +++ b/internal/storage/dbs/username_history.sql.go @@ -0,0 +1,60 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.20.0 +// source: username_history.sql + +package dbs + +import ( + "context" + "time" +) + +const usernameHistoryGetByOldUsername = `-- name: UsernameHistoryGetByOldUsername :one +SELECT u.username +FROM username_history uh +JOIN users u + ON uh.user_id = u.id +WHERE uh.canonical_username = LOWER($1) + AND uh.social_provider = $2 +ORDER BY uh.updated_at DESC +LIMIT 1 +` + +type UsernameHistoryGetByOldUsernameParams struct { + Username string + SocialProvider SocialProvider +} + +func (q *Queries) UsernameHistoryGetByOldUsername(ctx context.Context, arg UsernameHistoryGetByOldUsernameParams) (string, error) { + row := q.queryRow(ctx, q.usernameHistoryGetByOldUsernameStmt, usernameHistoryGetByOldUsername, arg.Username, arg.SocialProvider) + var username string + err := row.Scan(&username) + return username, err +} + +const usernameHistoryNew = `-- name: UsernameHistoryNew :exec +INSERT INTO username_history (user_id, social_provider, canonical_username, created_at, updated_at) +VALUES ($1, $2, LOWER($3), $4, $5) +ON CONFLICT (canonical_username, social_provider, user_id) DO UPDATE + SET updated_at = excluded.updated_at +` + +type UsernameHistoryNewParams struct { + UserID int64 + SocialProvider SocialProvider + Username string + CreatedAt time.Time + UpdatedAt time.Time +} + +func (q *Queries) UsernameHistoryNew(ctx context.Context, arg UsernameHistoryNewParams) error { + _, err := q.exec(ctx, q.usernameHistoryNewStmt, usernameHistoryNew, + arg.UserID, + arg.SocialProvider, + arg.Username, + arg.CreatedAt, + arg.UpdatedAt, + ) + return err +} diff --git a/internal/storage/dbs/users.sql.go b/internal/storage/dbs/users.sql.go index 5450064..89e49a0 100644 --- a/internal/storage/dbs/users.sql.go +++ b/internal/storage/dbs/users.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.21.0 +// sqlc v1.20.0 // source: users.sql package dbs diff --git a/internal/storage/migrations/20240306163810_username_history.sql b/internal/storage/migrations/20240306163810_username_history.sql new file mode 100644 index 0000000..603ed24 --- /dev/null +++ b/internal/storage/migrations/20240306163810_username_history.sql @@ -0,0 +1,18 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE username_history +( + id BIGSERIAL NOT NULL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users (id), + social_provider SOCIAL_PROVIDER NOT NULL, + canonical_username VARCHAR NOT NULL, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, + UNIQUE (canonical_username, social_provider, user_id) +); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE username_history; +-- +goose StatementEnd \ No newline at end of file diff --git a/internal/storage/queries/username_history.sql b/internal/storage/queries/username_history.sql new file mode 100644 index 0000000..fb722a1 --- /dev/null +++ b/internal/storage/queries/username_history.sql @@ -0,0 +1,15 @@ +-- name: UsernameHistoryNew :exec +INSERT INTO username_history (user_id, social_provider, canonical_username, created_at, updated_at) +VALUES (@user_id, @social_provider, LOWER(@username), @created_at, @updated_at) +ON CONFLICT (canonical_username, social_provider, user_id) DO UPDATE + SET updated_at = excluded.updated_at; + +-- name: UsernameHistoryGetByOldUsername :one +SELECT u.username +FROM username_history uh +JOIN users u + ON uh.user_id = u.id +WHERE uh.canonical_username = LOWER(@username) + AND uh.social_provider = @social_provider +ORDER BY uh.updated_at DESC +LIMIT 1;