Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch ids to uuidv7 #64

Merged
merged 5 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
# Service containers to run with `runner-job`
services:
postgres:
image: postgis/postgis:16-3.5
image: renatolond/pg_uuidv7:v1.6.0
# Provide the password for postgres
env:
POSTGRES_PASSWORD: postgres
Expand All @@ -40,7 +40,7 @@ jobs:
- name: Create needed user
run: PGPASSWORD=postgres createuser -U postgres -h localhost postgres_password
- name: Create needed extensions
run: PGPASSWORD=postgres psql -h localhost -U postgres -c "CREATE EXTENSION citext" retromeet_test
run: PGPASSWORD=postgres psql -h localhost -U postgres -c "CREATE EXTENSION citext" retromeet_test && PGPASSWORD=postgres psql -h localhost -U postgres -c "CREATE EXTENSION pg_uuidv7" retromeet_test
# We don't need to create the extension because it's already in the image
# && PGPASSWORD=postgres psql -h localhost -U postgres -c "CREATE EXTENSION postgis" retromeet_test
- name: Setup the database
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ There's a [pronto](https://github.com/prontolabs/pronto) github action running o

### Setup

RetroMeet requires Postgresql >= 16.0 (it might work with a lower version than that, but it is not guaranteed) and PostGIS >= 3.4 (again, might work with a lower version, but not guaranteed).
RetroMeet requires Postgresql >= 16.0 (it might work with a lower version than that, but it is not guaranteed), PostGIS >= 3.4 (again, might work with a lower version, but not guaranteed) and the [pg_uuidv7](https://github.com/fboulnois/pg_uuidv7) extension.

First, we need to set up the database. RetroMeet uses [rodauth](https://github.com/jeremyevans/rodauth), the following instructions will create the needed users, database and extensions needed for roda.
1. Create two users:
Expand All @@ -36,6 +36,10 @@ psql -U postgres -c "CREATE EXTENSION citext" retromeet_dev
```sh
psql -U postgres -c "CREATE EXTENSION postgis" retromeet_dev
```
1. Load the pg_uuidv7 extension:
```sh
psql -U postgres -c "CREATE EXTENSION pg_uuidv7" retromeet_dev
```
1. Give the password user temporary rights to the schema:
```sh
psql -U postgres -c "GRANT CREATE ON SCHEMA public TO retromeet_password" retromeet_dev
Expand All @@ -56,6 +60,7 @@ The same setup needs to be done for the test database, replacing `retromeet_dev`
createdb -U postgres -O retromeet retromeet_test
psql -U postgres -c "CREATE EXTENSION citext" retromeet_test
psql -U postgres -c "CREATE EXTENSION postgis" retromeet_test
psql -U postgres -c "CREATE EXTENSION pg_uuidv7" retromeet_test
psql -U postgres -c "GRANT CREATE ON SCHEMA public TO retromeet_password" retromeet_test
RACK_ENV=test rake db:setup
psql -U postgres -c "REVOKE CREATE ON SCHEMA public FROM retromeet_password" retromeet_test
Expand Down
2 changes: 1 addition & 1 deletion app/api/authenticated/listing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class Listing < Grape::API
namespace :listing do
get "/" do
profiles = Persistence::Repository::Listing.nearby(account_id: rodauth.session[:account_id], max_distance_in_meters: params[:max_distance] * 1_000)
present profiles, with: Entities::OtherProfileInfos, only: [{ profiles: %i[display_name account_id age genders orientations relationship_status location_display_name location_distance] }]
present profiles, with: Entities::OtherProfileInfos, only: [{ profiles: %i[display_name id age genders orientations relationship_status location_display_name location_distance] }]
end
end
end
Expand Down
10 changes: 5 additions & 5 deletions app/api/authenticated/profile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Profile < Grape::API
produces: Authenticated::PRODUCES,
consumes: Authenticated::CONSUMES
get :complete do
profile_info = Persistence::Repository::Account.profile_info(account_id: rodauth.session[:account_id])
profile_info = Persistence::Repository::Account.profile_info(id: rodauth.session[:profile_id])
Entities::ProfileInfo.represent(profile_info)
end

Expand Down Expand Up @@ -58,7 +58,7 @@ class Profile < Grape::API
error!({ error: :AT_LEAST_ONE_PARAMETER_NEEDED, detail: "You need to provide at least one parameter to be changed, none given" }, :bad_request) if declared_params.empty?

Persistence::Repository::Account.update_profile_info(account_id: rodauth.session[:account_id], **declared_params)
profile_info = Persistence::Repository::Account.profile_info(account_id: rodauth.session[:account_id])
profile_info = Persistence::Repository::Account.profile_info(id: rodauth.session[:profile_id])
status :ok
Entities::ProfileInfo.represent(profile_info, only: declared_params.keys.map(&:to_sym))
end
Expand All @@ -78,13 +78,13 @@ class Profile < Grape::API
error!({ error: :UNEXPECTED_RESULTS_SIZE, detail: "Expected to have exactly one location with the given name, had #{results.size} instead" }, :unprocessable_content) if results.size != 1

Persistence::Repository::Account.update_profile_location(account_id: rodauth.session[:account_id], location_result: results.first)
profile_info = Persistence::Repository::Account.profile_info(account_id: rodauth.session[:account_id])
profile_info = Persistence::Repository::Account.profile_info(id: rodauth.session[:profile_id])
status :ok
Entities::ProfileInfo.represent(profile_info, only: %i[location_display_name])
end
namespace ":id" do
params do
requires :id, type: Integer
requires :id, type: String, regexp: /\A[0-9A-F]{8}-[0-9A-F]{4}-7[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\z/i
end

desc "Returns the complete profile information for the requested account id.",
Expand All @@ -93,7 +93,7 @@ class Profile < Grape::API
produces: Authenticated::PRODUCES,
consumes: Authenticated::CONSUMES
get :complete do
profile_info = Persistence::Repository::Account.profile_info(account_id: params[:id])
profile_info = Persistence::Repository::Account.profile_info(id: params[:id])
error!({ error: :PROFILE_NOT_FOUND, detail: "The requested profile does not exist or you don't have permission to see it" }, :not_found) unless profile_info

Entities::OtherProfileInfo.represent(profile_info)
Expand Down
2 changes: 1 addition & 1 deletion app/api/entities/other_profile_info.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class OtherProfileInfo < Grape::Entity
distance / 1_000
end
end
expose :account_id, documentation: { type: Integer }
expose :id, documentation: { type: String }
expose :display_name, documentation: { type: String }
expose :about_me, documentation: { type: String }
expose :genders, documentation: { type: String, is_array: true }
Expand Down
3 changes: 2 additions & 1 deletion app/api/rodauth_middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class RodauthMiddleware < Roda

after_create_account do
display_name = account[:email].split("@", 2).first
Database.connection[:account_informations].insert(account_id: account[:id], display_name:)
Persistence::Repository::Account.create_profile(account_id: account[:id], display_name:)
end
end

Expand All @@ -25,6 +25,7 @@ class RodauthMiddleware < Roda
r.rodauth
rodauth.require_authentication
rodauth.check_active_session
rodauth.session[:profile_id] = Persistence::Repository::Account.profile_id_from_account_id(account_id: rodauth.session[:account_id])
env["rodauth"] = rodauth
end
end
Expand Down
51 changes: 32 additions & 19 deletions app/persistence/repository/account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,60 +92,73 @@ module Account
].freeze

class << self
# @param account_id [Integer] An id for an account
def profile_id_from_account_id(account_id:)
profiles.where(account_id:).get(:id)
end

# Returns profile information for a given account
#
# @param account_id [Integer] An id for an account
# @param id [Integer] An id for a profile, it should be an UUID
# @return [Hash{Symbol => Object}] A record containing +account_id+, +created_at+ and +display_name+
def profile_info(account_id:)
account_informations.left_join(:locations, id: :location_id)
.where(account_id:)
.select_all(:account_informations)
# TODO: (renatolond, 2024-11-14) Filter the location display name for only the users' language and the fallback one
.select_append(Sequel[:locations][:display_name].as(:location_display_name))
.first
def profile_info(id:)
profiles.left_join(:locations, id: :location_id)
.where(Sequel[:profiles][:id] => id)
.select_all(:profiles)
# TODO: (renatolond, 2024-11-14) Filter the location display name for only the users' language and the fallback one
.select_append(Sequel[:locations][:display_name].as(:location_display_name))
.first
end

# Returns basic profile information for a given account
#
# @param account_id [Integer] An id for an account
# @param account_id (see .profile_id_from_account_id)
# @return [Hash{Symbol => Object}] A record containing +account_id+, +created_at+ and +display_name+
def basic_profile_info(account_id:)
account_informations.where(account_id:).select(:created_at, :display_name, :account_id).first
profiles.where(account_id:).select(:created_at, :display_name, :account_id).first
end

# Updates the profile information for a given account
# Does not validate argument names passed to +args+, so if not validated before-hand can raise an exception
# @param account_id (see .profile_info)
# @param account_id (see .profile_id_from_account_id)
# @param args [Hash{Symbol => Object}] A hash containing the fields to be updated. Will not be verified for validity.
# @return [void]
def update_profile_info(account_id:, **args)
args["languages"] = Sequel.pg_array(args["languages"], :languages) if args.key?("languages") && args["languages"]
args["genders"] = Sequel.pg_array(args["genders"], :genders) if args.key?("genders") && args["genders"]
args["orientations"] = Sequel.pg_array(args["orientations"], :orientations) if args.key?("orientations") && args["orientations"]
account_informations.where(account_id:).update(args)
profiles.where(account_id:).update(args)
end

# Updates the profile location for a given account
# @param account_id (see .profile_info)
# @param account_id (see .profile_id_from_account_id)
# @param location_result (see Persistence::Repository::Location.upsert_location)
# @return [void]
def update_profile_location(account_id:, location_result:)
location_id = Persistence::Repository::Location.upsert_location(location_result:)
account_informations.where(account_id:).update(location_id:)
profiles.where(account_id:).update(location_id:)
end

# @param account_id (see .profile_id_from_account_id)
# @return [String]
def profile_location(account_id:)
account_informations.inner_join(:locations, id: :location_id)
.where(account_id:)
.get(Sequel[:locations][:geom])
profiles.inner_join(:locations, id: :location_id)
.where(account_id:)
.get(Sequel[:locations][:geom])
end

# @param account_id (see .profile_id_from_account_id)
# @param display_name [String] the display name for the profile
# @return [void]
def create_profile(account_id:, display_name:)
profiles.insert(account_id:, display_name:)
end

private

# @return [Sequel::Postgres::Dataset]
def account_informations
Database.connection[:account_informations]
def profiles
Database.connection[:profiles]
end
end
end
Expand Down
10 changes: 5 additions & 5 deletions app/persistence/repository/listing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ def nearby(account_id:, min_account_id: nil, max_distance_in_meters: 5_000)
.select_append(:id, Sequel[:display_name].as(:location_display_name))
.where(Sequel.function(:ST_DWithin, Sequel.lit("geom::geography"), Sequel.lit("?::geography", profile_location), max_distance_in_meters))
.order(:dist)
accounts = account_informations.inner_join(location_subquery, id: :location_id)
.select(:display_name, :account_id, :birth_date, :genders, :orientations, :relationship_status, :location_display_name, Sequel[:dist].as(:location_distance))
.exclude(Sequel[:account_informations][:account_id] => account_id)
accounts = profiles.inner_join(location_subquery, id: :location_id)
.select(:display_name, Sequel[:profiles][:id], :birth_date, :genders, :orientations, :relationship_status, :location_display_name, Sequel[:dist].as(:location_distance))
.exclude(Sequel[:profiles][:account_id] => account_id)
accounts.where { account_id > min_account_id } if min_account_id
accounts.to_a
end
Expand All @@ -33,8 +33,8 @@ def locations
end

# @return [Sequel::Postgres::Dataset]
def account_informations
Database.connection[:account_informations]
def profiles
Database.connection[:profiles]
end
end
end
Expand Down
21 changes: 21 additions & 0 deletions db/migrations/20241119110138_change_table_name_and_add_id.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

Sequel.migration do
up do
rename_table(:account_informations, :profiles)
alter_table(:profiles) do
add_column :id, :uuid, null: false, default: Sequel.lit("uuid_generate_v7()")
drop_constraint(:account_informations_pkey)
add_primary_key(%i[id])
end
run "UPDATE profiles set id = uuid_timestamptz_to_v7(created_at)"
end
down do
rename_table(:profiles, :account_informations)
alter_table(:account_informations) do
drop_constraint(:profiles_pkey)
add_primary_key(%i[account_id])
drop_column :id
end
end
end
55 changes: 28 additions & 27 deletions db/schema.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# **Autogenerated file! Do not modify by hand!**
# Current migration: 20241115154326_fix_osm_id_type.rb
# Current migration: 20241119110138_change_table_name_and_add_id.rb

Sequel.migration do
change do
Expand Down Expand Up @@ -89,32 +89,6 @@
primary_key %i[id]
end

create_table(:account_informations) do
foreign_key :account_id, :accounts, type: "bigint", null: false, key: %i[id]
column :created_at, "timestamp without time zone", default: Sequel::CURRENT_TIMESTAMP, null: false
column :display_name, "text", null: false
column :about_me, "text"
column :birth_date, "date"
column :genders, "genders[]"
column :orientations, "orientations[]"
column :languages, "languages[]"
column :relationship_type, "relationship_type"
column :relationship_status, "relationship_status"
column :tobacco, "frequency"
column :marijuana, "frequency"
column :alcohol, "frequency"
column :other_recreational_drugs, "frequency"
column :pets, "haves_or_have_nots"
column :wants_pets, "wants"
column :kids, "haves_or_have_nots"
column :wants_kids, "wants"
column :religion, "religions"
column :religion_importance, "importance"
foreign_key :location_id, :locations, type: "bigint", key: %i[id]

primary_key %i[account_id]
end

create_table(:account_jwt_refresh_keys) do
primary_key :id, type: :Bignum
foreign_key :account_id, :accounts, type: "bigint", null: false, key: %i[id]
Expand Down Expand Up @@ -244,5 +218,32 @@

primary_key %i[id]
end

create_table(:profiles) do
foreign_key :account_id, :accounts, type: "bigint", null: false, key: %i[id]
column :created_at, "timestamp without time zone", default: Sequel::CURRENT_TIMESTAMP, null: false
column :display_name, "text", null: false
column :about_me, "text"
column :birth_date, "date"
column :genders, "genders[]"
column :orientations, "orientations[]"
column :languages, "languages[]"
column :relationship_type, "relationship_type"
column :relationship_status, "relationship_status"
column :tobacco, "frequency"
column :marijuana, "frequency"
column :alcohol, "frequency"
column :other_recreational_drugs, "frequency"
column :pets, "haves_or_have_nots"
column :wants_pets, "wants"
column :kids, "haves_or_have_nots"
column :wants_kids, "wants"
column :religion, "religions"
column :religion_importance, "importance"
foreign_key :location_id, :locations, type: "bigint", key: %i[id]
column :id, "uuid", default: Sequel::LiteralString.new("uuid_generate_v7()"), null: false

primary_key %i[id]
end
end
end
Loading