package human import ( "context" "errors" "strings" "time" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "go.jetify.com/typeid" ) var errNotFound = errors.New("not found") type humanIDPrefix struct{} func (humanIDPrefix) Prefix() string { return "human" } type humanID struct { typeid.TypeID[humanIDPrefix] } func newHumanID() (humanID, error) { return typeid.New[humanID]() } func emailPrefix(email string) string { return strings.Split(email, "@")[0] } type repository interface { getByEmail(ctx context.Context, email string) (*Human, error) getByID(ctx context.Context, id string) (*Human, error) create(ctx context.Context, email string) (*Human, error) exists(ctx context.Context, email string) (bool, error) listAll(ctx context.Context) ([]*Human, error) updateEmailNotificationsEnabled(ctx context.Context, id string, enabled bool) error updateLastEmailNotificationSentAt(ctx context.Context, id string, t time.Time) error updateAvatarObjectID(ctx context.Context, id string, objectID *string) error } type repositoryImpl struct { pool *pgxpool.Pool } func newRepository(pool *pgxpool.Pool) repository { return &repositoryImpl{pool: pool} } func (r *repositoryImpl) getByEmail(ctx context.Context, email string) (*Human, error) { var h Human err := r.pool.QueryRow(ctx, `SELECT id, email, email_notifications_enabled, last_email_notification_sent_at, created_at, avatar_object_id FROM humans WHERE email = $1`, email, ).Scan(&h.ID, &h.Email, &h.EmailNotificationsEnabled, &h.LastEmailNotificationSentAt, &h.CreatedAt, &h.AvatarObjectID) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, errNotFound } return nil, err } h.EmailPrefix = emailPrefix(h.Email) return &h, nil } func (r *repositoryImpl) getByID(ctx context.Context, id string) (*Human, error) { var h Human err := r.pool.QueryRow(ctx, `SELECT id, email, email_notifications_enabled, last_email_notification_sent_at, created_at, avatar_object_id FROM humans WHERE id = $1`, id, ).Scan(&h.ID, &h.Email, &h.EmailNotificationsEnabled, &h.LastEmailNotificationSentAt, &h.CreatedAt, &h.AvatarObjectID) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, errNotFound } return nil, err } h.EmailPrefix = emailPrefix(h.Email) return &h, nil } func (r *repositoryImpl) create(ctx context.Context, email string) (*Human, error) { id, err := newHumanID() if err != nil { return nil, err } var h Human err = r.pool.QueryRow(ctx, `INSERT INTO humans (id, email) VALUES ($1, $2) RETURNING id, email, email_notifications_enabled, last_email_notification_sent_at, created_at`, id.String(), email, ).Scan(&h.ID, &h.Email, &h.EmailNotificationsEnabled, &h.LastEmailNotificationSentAt, &h.CreatedAt) if err != nil { return nil, err } h.EmailPrefix = emailPrefix(h.Email) return &h, nil } func (r *repositoryImpl) exists(ctx context.Context, email string) (bool, error) { var exists bool err := r.pool.QueryRow(ctx, `SELECT EXISTS(SELECT 1 FROM humans WHERE email = $1)`, email, ).Scan(&exists) if err != nil { return false, err } return exists, nil } func (r *repositoryImpl) listAll(ctx context.Context) ([]*Human, error) { rows, err := r.pool.Query(ctx, `SELECT id, email, email_notifications_enabled, last_email_notification_sent_at, created_at, avatar_object_id FROM humans`, ) if err != nil { return nil, err } defer rows.Close() var humans []*Human for rows.Next() { var h Human if err := rows.Scan(&h.ID, &h.Email, &h.EmailNotificationsEnabled, &h.LastEmailNotificationSentAt, &h.CreatedAt, &h.AvatarObjectID); err != nil { return nil, err } h.EmailPrefix = emailPrefix(h.Email) humans = append(humans, &h) } return humans, rows.Err() } func (r *repositoryImpl) updateEmailNotificationsEnabled(ctx context.Context, id string, enabled bool) error { result, err := r.pool.Exec(ctx, `UPDATE humans SET email_notifications_enabled = $2 WHERE id = $1`, id, enabled, ) if err != nil { return err } if result.RowsAffected() == 0 { return errNotFound } return nil } func (r *repositoryImpl) updateLastEmailNotificationSentAt(ctx context.Context, id string, t time.Time) error { result, err := r.pool.Exec(ctx, `UPDATE humans SET last_email_notification_sent_at = $2 WHERE id = $1`, id, t, ) if err != nil { return err } if result.RowsAffected() == 0 { return errNotFound } return nil } func (r *repositoryImpl) updateAvatarObjectID(ctx context.Context, id string, objectID *string) error { result, err := r.pool.Exec(ctx, `UPDATE humans SET avatar_object_id = $2 WHERE id = $1`, id, objectID, ) if err != nil { return err } if result.RowsAffected() == 0 { return errNotFound } return nil }