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,
|
||||
live_view: [signing_salt: "jcAt5/U+"]
|
||||
|
||||
# Snowflake configuration
|
||||
config :nulla, :snowflake,
|
||||
worker_id: 1
|
||||
|
||||
# Configures the mailer
|
||||
#
|
||||
# By default it uses the "Local" adapter which stores the emails
|
||||
|
|
|
@ -7,6 +7,8 @@ defmodule Nulla.Application do
|
|||
|
||||
@impl true
|
||||
def start(_type, _args) do
|
||||
worker_id = Application.fetch_env!(:nulla, :snowflake)[:worker_id]
|
||||
|
||||
children = [
|
||||
NullaWeb.Telemetry,
|
||||
Nulla.Repo,
|
||||
|
@ -17,7 +19,8 @@ defmodule Nulla.Application do
|
|||
# Start a worker by calling: Nulla.Worker.start_link(arg)
|
||||
# {Nulla.Worker, arg},
|
||||
# Start to serve requests, typically the last entry
|
||||
NullaWeb.Endpoint
|
||||
NullaWeb.Endpoint,
|
||||
{Nulla.Snowflake, worker_id}
|
||||
]
|
||||
|
||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||
|
|
|
@ -2,6 +2,7 @@ defmodule Nulla.Models.Activity do
|
|||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :integer, autogenerate: false}
|
||||
schema "activities" do
|
||||
field :type, :string
|
||||
field :actor, :string
|
||||
|
|
|
@ -5,6 +5,7 @@ defmodule Nulla.Models.Bookmark do
|
|||
alias Nulla.Repo
|
||||
alias Nulla.Models.Bookmark
|
||||
|
||||
@primary_key {:id, :integer, autogenerate: false}
|
||||
schema "bookmarks" do
|
||||
field :url, :string
|
||||
field :user_id, :id
|
||||
|
|
|
@ -2,6 +2,7 @@ defmodule Nulla.Models.Follow do
|
|||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :integer, autogenerate: false}
|
||||
schema "follows" do
|
||||
belongs_to :user, Nulla.Models.User
|
||||
belongs_to :target, Nulla.Models.User
|
||||
|
|
|
@ -2,6 +2,7 @@ defmodule Nulla.Models.Hashtag do
|
|||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :integer, autogenerate: false}
|
||||
schema "hashtags" do
|
||||
field :tag, :string
|
||||
field :usage_count, :integer, default: 0
|
||||
|
|
|
@ -2,6 +2,7 @@ defmodule Nulla.Models.MediaAttachment do
|
|||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :integer, autogenerate: false}
|
||||
schema "media_attachments" do
|
||||
field :file, :string
|
||||
field :mime_type, :string
|
||||
|
|
|
@ -2,6 +2,7 @@ defmodule Nulla.Models.ModerationLog do
|
|||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :integer, autogenerate: false}
|
||||
schema "moderation_logs" do
|
||||
field :target_type, :string
|
||||
field :target_id, :string
|
||||
|
|
|
@ -5,6 +5,7 @@ defmodule Nulla.Models.Note do
|
|||
alias Nulla.Repo
|
||||
alias Nulla.Models.Note
|
||||
|
||||
@primary_key {:id, :integer, autogenerate: false}
|
||||
schema "notes" do
|
||||
field :content, :string
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ defmodule Nulla.Models.Notification do
|
|||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :integer, autogenerate: false}
|
||||
schema "notifications" do
|
||||
field :type, :string
|
||||
field :data, :map
|
||||
|
|
|
@ -2,6 +2,7 @@ defmodule Nulla.Models.Session do
|
|||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :integer, autogenerate: false}
|
||||
schema "sessions" do
|
||||
field :token, :string
|
||||
field :user_agent, :string
|
||||
|
|
|
@ -2,8 +2,10 @@ defmodule Nulla.Models.User do
|
|||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Nulla.Repo
|
||||
alias Nulla.Snowflake
|
||||
alias Nulla.Models.User
|
||||
|
||||
@primary_key {:id, :integer, autogenerate: false}
|
||||
schema "users" do
|
||||
field :username, :string
|
||||
field :email, :string
|
||||
|
@ -77,6 +79,15 @@ defmodule Nulla.Models.User do
|
|||
])
|
||||
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)
|
||||
|
|
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
|
||||
|
||||
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 :description, :text, default: "Freedom Social Network", null: false
|
||||
add :domain, :string, default: "localhost", null: false
|
||||
|
@ -16,6 +17,8 @@ defmodule Nulla.Repo.Migrations.CreateInstanceSettings do
|
|||
timestamps()
|
||||
end
|
||||
|
||||
execute "ALTER TABLE instance_settings ADD CONSTRAINT single_row CHECK (id = 1);"
|
||||
|
||||
flush()
|
||||
|
||||
execute(fn ->
|
||||
|
@ -31,11 +34,11 @@ defmodule Nulla.Repo.Migrations.CreateInstanceSettings do
|
|||
|
||||
sql = """
|
||||
INSERT INTO instance_settings (
|
||||
name, description, domain, registration,
|
||||
id, name, description, domain, registration,
|
||||
max_characters, max_upload_size, api_offset,
|
||||
public_key, private_key, inserted_at, updated_at
|
||||
) VALUES (
|
||||
'Nulla', 'Freedom Social Network', '#{domain}', false,
|
||||
1, 'Nulla', 'Freedom Social Network', '#{domain}', false,
|
||||
5000, 50, 100,
|
||||
#{esc.(public_key)}, #{esc.(private_key)},
|
||||
'#{now}', '#{now}'
|
||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateUsers do
|
|||
use Ecto.Migration
|
||||
|
||||
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 :email, :string
|
||||
add :password, :string
|
||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateNotes do
|
|||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:notes) do
|
||||
create table(:notes, primary_key: false) do
|
||||
add :id, :bigint, primary_key: true
|
||||
add :content, :text
|
||||
add :visibility, :string, default: "public"
|
||||
add :sensitive, :boolean, default: false
|
||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateBookmarks do
|
|||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:bookmarks) do
|
||||
create table(:bookmarks, primary_key: false) do
|
||||
add :id, :bigint, primary_key: true
|
||||
add :url, :string
|
||||
add :user_id, references(:users, on_delete: :delete_all)
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateNotifications do
|
|||
use Ecto.Migration
|
||||
|
||||
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 :actor_id, references(:users, on_delete: :nilify_all)
|
||||
add :type, :string, null: false
|
||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateModerationLogs do
|
|||
use Ecto.Migration
|
||||
|
||||
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 :target_type, :string, null: false
|
||||
add :target_id, :string, null: false
|
||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateHashtags do
|
|||
use Ecto.Migration
|
||||
|
||||
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 :usage_count, :integer, default: 0, null: false
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateFollows do
|
|||
use Ecto.Migration
|
||||
|
||||
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 :target_id, references(:users, on_delete: :delete_all), null: false
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateSessions do
|
|||
use Ecto.Migration
|
||||
|
||||
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 :token, :string, null: false
|
||||
add :user_agent, :string
|
||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateMediaAttachments do
|
|||
use Ecto.Migration
|
||||
|
||||
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 :file, :string, null: false
|
||||
add :mime_type, :string
|
||||
|
|
|
@ -2,7 +2,8 @@ defmodule Nulla.Repo.Migrations.CreateActivities do
|
|||
use Ecto.Migration
|
||||
|
||||
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 :actor, :string, null: false
|
||||
add :object, :map, null: false
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue