Add snowflake IDs
This commit is contained in:
parent
4a890a39f4
commit
44b484de21
24 changed files with 116 additions and 14 deletions
|
@ -22,6 +22,10 @@ config :nulla, NullaWeb.Endpoint,
|
||||||
pubsub_server: Nulla.PubSub,
|
pubsub_server: Nulla.PubSub,
|
||||||
live_view: [signing_salt: "jcAt5/U+"]
|
live_view: [signing_salt: "jcAt5/U+"]
|
||||||
|
|
||||||
|
# Snowflake configuration
|
||||||
|
config :nulla, :snowflake,
|
||||||
|
worker_id: 1
|
||||||
|
|
||||||
# Configures the mailer
|
# Configures the mailer
|
||||||
#
|
#
|
||||||
# By default it uses the "Local" adapter which stores the emails
|
# By default it uses the "Local" adapter which stores the emails
|
||||||
|
|
|
@ -7,6 +7,8 @@ defmodule Nulla.Application do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def start(_type, _args) do
|
def start(_type, _args) do
|
||||||
|
worker_id = Application.fetch_env!(:nulla, :snowflake)[:worker_id]
|
||||||
|
|
||||||
children = [
|
children = [
|
||||||
NullaWeb.Telemetry,
|
NullaWeb.Telemetry,
|
||||||
Nulla.Repo,
|
Nulla.Repo,
|
||||||
|
@ -17,7 +19,8 @@ defmodule Nulla.Application do
|
||||||
# Start a worker by calling: Nulla.Worker.start_link(arg)
|
# Start a worker by calling: Nulla.Worker.start_link(arg)
|
||||||
# {Nulla.Worker, arg},
|
# {Nulla.Worker, arg},
|
||||||
# Start to serve requests, typically the last entry
|
# Start to serve requests, typically the last entry
|
||||||
NullaWeb.Endpoint
|
NullaWeb.Endpoint,
|
||||||
|
{Nulla.Snowflake, worker_id}
|
||||||
]
|
]
|
||||||
|
|
||||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||||
|
|
|
@ -2,6 +2,7 @@ defmodule Nulla.Models.Activity do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@primary_key {:id, :integer, autogenerate: false}
|
||||||
schema "activities" do
|
schema "activities" do
|
||||||
field :type, :string
|
field :type, :string
|
||||||
field :actor, :string
|
field :actor, :string
|
||||||
|
|
|
@ -5,6 +5,7 @@ defmodule Nulla.Models.Bookmark do
|
||||||
alias Nulla.Repo
|
alias Nulla.Repo
|
||||||
alias Nulla.Models.Bookmark
|
alias Nulla.Models.Bookmark
|
||||||
|
|
||||||
|
@primary_key {:id, :integer, autogenerate: false}
|
||||||
schema "bookmarks" do
|
schema "bookmarks" do
|
||||||
field :url, :string
|
field :url, :string
|
||||||
field :user_id, :id
|
field :user_id, :id
|
||||||
|
|
|
@ -2,6 +2,7 @@ defmodule Nulla.Models.Follow do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@primary_key {:id, :integer, autogenerate: false}
|
||||||
schema "follows" do
|
schema "follows" do
|
||||||
belongs_to :user, Nulla.Models.User
|
belongs_to :user, Nulla.Models.User
|
||||||
belongs_to :target, Nulla.Models.User
|
belongs_to :target, Nulla.Models.User
|
||||||
|
|
|
@ -2,6 +2,7 @@ defmodule Nulla.Models.Hashtag do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@primary_key {:id, :integer, autogenerate: false}
|
||||||
schema "hashtags" do
|
schema "hashtags" do
|
||||||
field :tag, :string
|
field :tag, :string
|
||||||
field :usage_count, :integer, default: 0
|
field :usage_count, :integer, default: 0
|
||||||
|
|
|
@ -2,6 +2,7 @@ defmodule Nulla.Models.MediaAttachment do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@primary_key {:id, :integer, autogenerate: false}
|
||||||
schema "media_attachments" do
|
schema "media_attachments" do
|
||||||
field :file, :string
|
field :file, :string
|
||||||
field :mime_type, :string
|
field :mime_type, :string
|
||||||
|
|
|
@ -2,6 +2,7 @@ defmodule Nulla.Models.ModerationLog do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@primary_key {:id, :integer, autogenerate: false}
|
||||||
schema "moderation_logs" do
|
schema "moderation_logs" do
|
||||||
field :target_type, :string
|
field :target_type, :string
|
||||||
field :target_id, :string
|
field :target_id, :string
|
||||||
|
|
|
@ -5,6 +5,7 @@ defmodule Nulla.Models.Note do
|
||||||
alias Nulla.Repo
|
alias Nulla.Repo
|
||||||
alias Nulla.Models.Note
|
alias Nulla.Models.Note
|
||||||
|
|
||||||
|
@primary_key {:id, :integer, autogenerate: false}
|
||||||
schema "notes" do
|
schema "notes" do
|
||||||
field :content, :string
|
field :content, :string
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ defmodule Nulla.Models.Notification do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@primary_key {:id, :integer, autogenerate: false}
|
||||||
schema "notifications" do
|
schema "notifications" do
|
||||||
field :type, :string
|
field :type, :string
|
||||||
field :data, :map
|
field :data, :map
|
||||||
|
|
|
@ -2,6 +2,7 @@ defmodule Nulla.Models.Session do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@primary_key {:id, :integer, autogenerate: false}
|
||||||
schema "sessions" do
|
schema "sessions" do
|
||||||
field :token, :string
|
field :token, :string
|
||||||
field :user_agent, :string
|
field :user_agent, :string
|
||||||
|
|
|
@ -2,8 +2,10 @@ defmodule Nulla.Models.User do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
alias Nulla.Repo
|
alias Nulla.Repo
|
||||||
|
alias Nulla.Snowflake
|
||||||
alias Nulla.Models.User
|
alias Nulla.Models.User
|
||||||
|
|
||||||
|
@primary_key {:id, :integer, autogenerate: false}
|
||||||
schema "users" do
|
schema "users" do
|
||||||
field :username, :string
|
field :username, :string
|
||||||
field :email, :string
|
field :email, :string
|
||||||
|
@ -77,6 +79,15 @@ defmodule Nulla.Models.User do
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_user(attrs) do
|
||||||
|
id = Snowflake.next_id()
|
||||||
|
|
||||||
|
%User{}
|
||||||
|
|> User.changeset(attrs)
|
||||||
|
|> Ecto.Changeset.put_change(:id, id)
|
||||||
|
|> Repo.insert()
|
||||||
|
end
|
||||||
|
|
||||||
def get_user_by_username(username), do: Repo.get_by(User, username: username)
|
def get_user_by_username(username), do: Repo.get_by(User, username: username)
|
||||||
|
|
||||||
def get_user_by_username!(username), do: Repo.get_by!(User, username: username)
|
def get_user_by_username!(username), do: Repo.get_by!(User, username: username)
|
||||||
|
|
62
lib/nulla/snowflake.ex
Normal file
62
lib/nulla/snowflake.ex
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
defmodule Nulla.Snowflake do
|
||||||
|
use GenServer
|
||||||
|
import Bitwise
|
||||||
|
|
||||||
|
@epoch :calendar.datetime_to_gregorian_seconds({{2020, 1, 1}, {0, 0, 0}}) * 1000
|
||||||
|
@max_sequence 4095
|
||||||
|
@time_shift 22
|
||||||
|
@worker_shift 12
|
||||||
|
|
||||||
|
def start_link(worker_id) when worker_id in 0..1023 do
|
||||||
|
GenServer.start_link(__MODULE__, worker_id, name: __MODULE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_id do
|
||||||
|
GenServer.call(__MODULE__, :next_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def init(worker_id) do
|
||||||
|
{:ok, %{last_timestamp: -1, sequence: 0, worker_id: worker_id}}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_call(:next_id, _from, %{worker_id: worker_id} = state) do
|
||||||
|
timestamp = current_time()
|
||||||
|
|
||||||
|
{timestamp, sequence, state} =
|
||||||
|
cond do
|
||||||
|
timestamp < state.last_timestamp ->
|
||||||
|
raise "Clock moved backwards"
|
||||||
|
|
||||||
|
timestamp == state.last_timestamp and state.sequence < @max_sequence ->
|
||||||
|
{timestamp, state.sequence + 1, %{state | sequence: state.sequence + 1}}
|
||||||
|
|
||||||
|
timestamp == state.last_timestamp ->
|
||||||
|
wait_for_next_millisecond(timestamp)
|
||||||
|
new_timestamp = current_time()
|
||||||
|
{new_timestamp, 0, %{state | last_timestamp: new_timestamp, sequence: 0}}
|
||||||
|
|
||||||
|
true ->
|
||||||
|
{timestamp, 0, %{state | last_timestamp: timestamp, sequence: 0}}
|
||||||
|
end
|
||||||
|
|
||||||
|
raw_id =
|
||||||
|
((timestamp - @epoch) <<< @time_shift)
|
||||||
|
|> bor(worker_id <<< @worker_shift)
|
||||||
|
|> bor(sequence)
|
||||||
|
|
||||||
|
id = Bitwise.band(raw_id, 0x7FFFFFFFFFFFFFFF)
|
||||||
|
|
||||||
|
{:reply, id, %{state | last_timestamp: timestamp, sequence: sequence}}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp current_time do
|
||||||
|
System.system_time(:millisecond)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp wait_for_next_millisecond(last_ts) do
|
||||||
|
:timer.sleep(1)
|
||||||
|
if current_time() <= last_ts, do: wait_for_next_millisecond(last_ts), else: :ok
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateInstanceSettings do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
def change do
|
def change do
|
||||||
create table(:instance_settings) do
|
create table(:instance_settings, primary_key: false) do
|
||||||
|
add :id, :integer, primary_key: true
|
||||||
add :name, :string, default: "Nulla", null: false
|
add :name, :string, default: "Nulla", null: false
|
||||||
add :description, :text, default: "Freedom Social Network", null: false
|
add :description, :text, default: "Freedom Social Network", null: false
|
||||||
add :domain, :string, default: "localhost", null: false
|
add :domain, :string, default: "localhost", null: false
|
||||||
|
@ -16,6 +17,8 @@ defmodule Nulla.Repo.Migrations.CreateInstanceSettings do
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
execute "ALTER TABLE instance_settings ADD CONSTRAINT single_row CHECK (id = 1);"
|
||||||
|
|
||||||
flush()
|
flush()
|
||||||
|
|
||||||
execute(fn ->
|
execute(fn ->
|
||||||
|
@ -31,11 +34,11 @@ defmodule Nulla.Repo.Migrations.CreateInstanceSettings do
|
||||||
|
|
||||||
sql = """
|
sql = """
|
||||||
INSERT INTO instance_settings (
|
INSERT INTO instance_settings (
|
||||||
name, description, domain, registration,
|
id, name, description, domain, registration,
|
||||||
max_characters, max_upload_size, api_offset,
|
max_characters, max_upload_size, api_offset,
|
||||||
public_key, private_key, inserted_at, updated_at
|
public_key, private_key, inserted_at, updated_at
|
||||||
) VALUES (
|
) VALUES (
|
||||||
'Nulla', 'Freedom Social Network', '#{domain}', false,
|
1, 'Nulla', 'Freedom Social Network', '#{domain}', false,
|
||||||
5000, 50, 100,
|
5000, 50, 100,
|
||||||
#{esc.(public_key)}, #{esc.(private_key)},
|
#{esc.(public_key)}, #{esc.(private_key)},
|
||||||
'#{now}', '#{now}'
|
'#{now}', '#{now}'
|
||||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateUsers do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
def change do
|
def change do
|
||||||
create table(:users) do
|
create table(:users, primary_key: false) do
|
||||||
|
add :id, :bigint, primary_key: true
|
||||||
add :username, :string, null: false, unique: true
|
add :username, :string, null: false, unique: true
|
||||||
add :email, :string
|
add :email, :string
|
||||||
add :password, :string
|
add :password, :string
|
||||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateNotes do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
def change do
|
def change do
|
||||||
create table(:notes) do
|
create table(:notes, primary_key: false) do
|
||||||
|
add :id, :bigint, primary_key: true
|
||||||
add :content, :text
|
add :content, :text
|
||||||
add :visibility, :string, default: "public"
|
add :visibility, :string, default: "public"
|
||||||
add :sensitive, :boolean, default: false
|
add :sensitive, :boolean, default: false
|
||||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateBookmarks do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
def change do
|
def change do
|
||||||
create table(:bookmarks) do
|
create table(:bookmarks, primary_key: false) do
|
||||||
|
add :id, :bigint, primary_key: true
|
||||||
add :url, :string
|
add :url, :string
|
||||||
add :user_id, references(:users, on_delete: :delete_all)
|
add :user_id, references(:users, on_delete: :delete_all)
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateNotifications do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
def change do
|
def change do
|
||||||
create table(:notifications) do
|
create table(:notifications, primary_key: false) do
|
||||||
|
add :id, :bigint, primary_key: true
|
||||||
add :user_id, references(:users, on_delete: :delete_all), null: false
|
add :user_id, references(:users, on_delete: :delete_all), null: false
|
||||||
add :actor_id, references(:users, on_delete: :nilify_all)
|
add :actor_id, references(:users, on_delete: :nilify_all)
|
||||||
add :type, :string, null: false
|
add :type, :string, null: false
|
||||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateModerationLogs do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
def change do
|
def change do
|
||||||
create table(:moderation_logs) do
|
create table(:moderation_logs, primary_key: false) do
|
||||||
|
add :id, :bigint, primary_key: true
|
||||||
add :moderator_id, references(:users, on_delete: :nilify_all), null: false
|
add :moderator_id, references(:users, on_delete: :nilify_all), null: false
|
||||||
add :target_type, :string, null: false
|
add :target_type, :string, null: false
|
||||||
add :target_id, :string, null: false
|
add :target_id, :string, null: false
|
||||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateHashtags do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
def change do
|
def change do
|
||||||
create table(:hashtags) do
|
create table(:hashtags, primary_key: false) do
|
||||||
|
add :id, :bigint, primary_key: true
|
||||||
add :tag, :string, null: false
|
add :tag, :string, null: false
|
||||||
add :usage_count, :integer, default: 0, null: false
|
add :usage_count, :integer, default: 0, null: false
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateFollows do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
def change do
|
def change do
|
||||||
create table(:follows) do
|
create table(:follows, primary_key: false) do
|
||||||
|
add :id, :bigint, primary_key: true
|
||||||
add :user_id, references(:users, on_delete: :delete_all), null: false
|
add :user_id, references(:users, on_delete: :delete_all), null: false
|
||||||
add :target_id, references(:users, on_delete: :delete_all), null: false
|
add :target_id, references(:users, on_delete: :delete_all), null: false
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateSessions do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
def change do
|
def change do
|
||||||
create table(:sessions) do
|
create table(:sessions, primary_key: false) do
|
||||||
|
add :id, :bigint, primary_key: true
|
||||||
add :user_id, references(:users, on_delete: :delete_all), null: false
|
add :user_id, references(:users, on_delete: :delete_all), null: false
|
||||||
add :token, :string, null: false
|
add :token, :string, null: false
|
||||||
add :user_agent, :string
|
add :user_agent, :string
|
||||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateMediaAttachments do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
def change do
|
def change do
|
||||||
create table(:media_attachments) do
|
create table(:media_attachments, primary_key: false) do
|
||||||
|
add :id, :bigint, primary_key: true
|
||||||
add :note_id, references(:notes, on_delete: :delete_all), null: false
|
add :note_id, references(:notes, on_delete: :delete_all), null: false
|
||||||
add :file, :string, null: false
|
add :file, :string, null: false
|
||||||
add :mime_type, :string
|
add :mime_type, :string
|
||||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateActivities do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
def change do
|
def change do
|
||||||
create table(:activities) do
|
create table(:activities, primary_key: false) do
|
||||||
|
add :id, :bigint, primary_key: true
|
||||||
add :type, :string, null: false
|
add :type, :string, null: false
|
||||||
add :actor, :string, null: false
|
add :actor, :string, null: false
|
||||||
add :object, :map, null: false
|
add :object, :map, null: false
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue