Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c8f332f
direnv, flake, and deps stuff
benbot Apr 11, 2025
7ea2e54
Added backpacks and many_to_many relationships for all user_content
benbot Apr 15, 2025
c611145
Resolve merge conflicts: Adopt BackpackEntry approach with individual…
fire Jan 28, 2026
4b2c0f0
fixed field name in user_content
benbot Apr 15, 2025
f6a02e2
ran mix format
benbot Apr 15, 2025
f877844
Changed to stable nix channel
benbot Apr 15, 2025
a35750d
using one join table
benbot Apr 15, 2025
aa3bc0d
removed recode and added live_view deps
benbot Apr 19, 2025
4a0106f
added mishka components
benbot Apr 19, 2025
d570f9a
Resolve conflicts: Adopt BackpackEntry approach and update routing st…
fire Jan 28, 2026
f186733
Resolve docker configuration conflicts for release setup
fire Jan 28, 2026
086ebf2
more fixes and live reloading
benbot Apr 21, 2025
6cd40a5
Revert "added mishka components"
benbot May 14, 2025
fe172a6
more config fixes
benbot May 14, 2025
087bf13
minimal mishka
benbot May 14, 2025
b1a4963
Update Elixir version constraint to support ~> 1.17 or ~> 1.19
fire Jan 28, 2026
520391b
Update Elixir Docker image to version 1.17.3-otp-27
fire Jan 28, 2026
f9c4487
Update dependencies in mix.lock and remove unnecessary end statement …
fire Jan 28, 2026
bd98360
Remove .igniter file and remove mishka_chelekom dependency.
fire Jan 29, 2026
e55d646
Split into umbrella structure with separate API and web apps
fire Jan 29, 2026
2fc8275
Remove elixir_mvsqlite references from CI workflow
fire Jan 29, 2026
23105c6
Fix Dialyzer warnings and update .gitignore
fire Jan 29, 2026
50ab0ce
Refactor user JSON schema handling and improve readability
fire Jan 29, 2026
7e6c9bc
Update README and add QA tests documentation
fire Jan 29, 2026
d4e5a48
Remove duplicate root lib and priv/repo directories after umbrella co…
fire Jan 29, 2026
8476d44
Update all :uro application references to :uro_api for umbrella struc…
fire Jan 29, 2026
76fef0f
Add automated QA tests and Credo dependency
fire Jan 29, 2026
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
10 changes: 10 additions & 0 deletions .dialyzer_ignore.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Dialyzer ignore file for incomplete features and known dependency/opaque issues.
# Format: {file, warning_type} or {file, short_description} etc.
# See https://hexdocs.pm/dialyxir/readme.html#ignore-warnings

[
# Ecto.Multi opaque type - pipeline passes struct between new/insert/update
{"lib/uro/accounts.ex", :call_without_opaque},
{"lib/uro/shared_content.ex", :call_without_opaque},
{"lib/uro/user_content.ex", :call_without_opaque}
]
3 changes: 1 addition & 2 deletions .formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@
"priv/*/seeds.exs",
"{config,lib,test}/**/*.{ex,exs,eex}"
],
subdirectories: ["priv/*/migrations"],
plugins: [Recode.FormatterPlugin]
subdirectories: ["priv/*/migrations"]
]
10 changes: 2 additions & 8 deletions .github/workflows/uro_release_x86_64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest

container:
image: elixir:1.16.3-otp-26
image: elixir:1.17.3-otp-27

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -59,14 +59,8 @@ jobs:
id: test_step
run: |
set -e
chmod +x elixir_mvsqlite/rust_src/target/release/mvstore
export RUST_LOG=error
elixir_mvsqlite/rust_src/target/release/mvstore --data-plane 127.0.0.1:7000 --admin-api 127.0.0.1:7001 --metadata-prefix mvstore-test --raw-data-prefix m --auto-create-namespace --cluster /etc/foundationdb/fdb.cluster &
sleep 1
curl http://localhost:7001/api/create_namespace -d '{"key":"uro_dev.sqlite3","metadata":""}'
sleep 1
MIX_ENV=test mix ecto.setup
MIX_ENV=test mix run priv/repo/test_seeds.exs
MIX_ENV=test mix run apps/uro_api/priv/repo/test_seeds.exs
mix test | tee test_output.txt; test ${PIPESTATUS[0]} -eq 0

- name: Upload test results
Expand Down
27 changes: 27 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# The directory Mix will write compiled artifacts to.
/_build/
# Umbrella: ignore _build and deps in each app
**/_build/
apps/*/deps/
apps/*/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/
Expand Down Expand Up @@ -55,3 +59,26 @@ result
.envrc
.elixir_ls
.elixir-tools
cockroach
caddy

# Devenv
.devenv*
devenv.local.nix

# direnv
.direnv

# pre-commit
.pre-commit-config.yaml

# macOS
.DS_Store

# Dialyzer PLT (if using priv/plts)
/priv/plts/*.plt
/priv/plts/*.plt.hash

# CI test output
test_output.txt

2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ARG ELIXIR_VERSION=1.16
ARG ELIXIR_VERSION=1.17

# Elixir build environment.
FROM elixir:${ELIXIR_VERSION}-alpine as elixir-base
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
# Uro Server

## Docker Quick Setup

To run the entire stack locally with Docker, use these commands:

### Windows/Mac

Add these lines to your **hosts** file

```
0.0.0.0 vsekai.local
0.0.0.0 uro.v-sekai.cloud
```

then

```
mv frontend/.env.example frontend/.env
mv .env.example .env
docker compose up
```

### Linux

```
# From repository root
mv frontend/.env.example frontend/.env
Expand All @@ -43,6 +49,7 @@ Auto generated root CA will be in `./caddy/data/caddy/pki/authorities/local/root
### Setup

To run the entire stack locally with Docker in **development** mode, use the command:

```
docker compose -f docker-compose.development.yml up
```
Expand All @@ -52,18 +59,22 @@ By default, the stack uses [Caddy](https://caddyserver.com/) as a reverse proxy
If you want to configure **captcha** for registration, you need to set `TURNSTILE_SECRET_KEY` and `NEXT_PUBLIC_TURNSTILE_SITEKEY` ([Cloudflare Turnstile](https://developers.cloudflare.com/turnstile/get-started/))

Once configured, access the application at:

- http://vsekai.local/
- http://vsekai.local/api/v1/

### OpenAPI Specification

When making changes to Uro, update the OpenAPI specification by running:

```
mix uro.apigen
```

This command generates the OpenAPI specification in `frontend/src/__generated/openapi.json`. The Uro API serves this specification at http://vsekai.local/api/v1/openapi, with documentation available at http://vsekai.local/api/v1/docs.

Once you have updated the OpenAPI specification, to regenerate the client in the frontend (and your editor), run:

```
docker compose -f docker-compose.development.yml up nextjs --build
```
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ defmodule Uro.Accounts.User do

has_many :backpacks, Uro.Inventory.Backpack, foreign_key: :owner_id

has_many :avatars, through: [:backpacks, :avatars]
has_many :maps, through: [:backpacks, :maps]
has_many :props, through: [:backpacks, :props]
has_many :avatars, through: [:backpacks, :avatar]
has_many :maps, through: [:backpacks, :map]
has_many :props, through: [:backpacks, :prop]

######

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ defmodule Uro.Application do
Uro.Repo,
Uro.Endpoint,
Uro.VSekai.ShardJanitor,
{Redix, {Application.get_env(:uro, Redix)[:url], [name: :redix]}},
{Redix, {Application.get_env(:uro_api, Redix)[:url], [name: :redix]}},
{Phoenix.PubSub, [name: Uro.PubSub, adapter: Phoenix.PubSub.PG2]},
ExMarcel.TableWrapper,

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ defmodule Uro.AuthenticationController do

alias OpenApiSpex.Schema
alias Plug.Conn
alias PowAssent.Plug
alias Uro.Accounts
alias Uro.Accounts.User
alias Uro.Accounts.UserPrivilegeRuleset
Expand Down Expand Up @@ -71,7 +70,7 @@ defmodule Uro.AuthenticationController do

def login_with_provider(conn, %{"provider" => provider}) when is_binary(provider) do
redirect_url = redirect_uri(conn)
{:ok, url, conn} = Plug.authorize_url(conn, provider, redirect_url)
{:ok, url, conn} = PowAssent.Plug.authorize_url(conn, provider, redirect_url)

json(
conn,
Expand Down Expand Up @@ -119,7 +118,7 @@ defmodule Uro.AuthenticationController do

case conn
|> Conn.put_private(:pow_assent_session_params, params)
|> Plug.callback_upsert(provider, params, redirect_uri(conn)) do
|> PowAssent.Plug.callback_upsert(provider, params, redirect_uri(conn)) do
{:ok, conn} ->
login_success(conn, params)

Expand All @@ -140,7 +139,7 @@ defmodule Uro.AuthenticationController do
suffix = for(_ <- 1..4, into: "", do: <<Enum.random(~c"0123456789abcdef")>>)
user_params = %{user_params | "username" => "#{user_params["username"]}_#{suffix}"}

{:ok, _, conn} = Plug.create_user(conn, user_identity_params, user_params)
{:ok, _, conn} = PowAssent.Plug.create_user(conn, user_identity_params, user_params)
login_success(conn, params)

{:error,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ defmodule Uro.FallbackController do

See `Phoenix.Controller.action_fallback/1` for more details.
"""
@dialyzer {:nowarn_function, open_api_operation: 1}

use Uro, :controller

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
defmodule Uro.IdentityProofController do
@dialyzer {:nowarn_function, open_api_operation: 1}

use Uro, :controller

alias Uro.Error

@spec create(Conn.t(), map()) :: Conn.t()
@spec create(Plug.Conn.t(), map()) :: Plug.Conn.t()
def create(conn, %{"identity_proof" => identity_proof_params}) do
if !Map.has_key?(identity_proof_params, "user_to") do
conn
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
50 changes: 50 additions & 0 deletions apps/uro_api/lib/uro/endpoint.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
defmodule Uro.Endpoint do
use Phoenix.Endpoint, otp_app: :uro_api

def public_url(pathname \\ "") do
URI.to_string(Application.fetch_env!(:uro_api, :url)) <> pathname
end

if Mix.env() == :dev do
plug(Phoenix.CodeReloader)
end

# The session will be stored in the cookie and signed,
# this means its contents can be read but not tampered with.
# Set :encryption_salt if you would also like to encrypt it.
@session_options [
store: :cookie,
key: "_uro_api_key",
signing_salt: "JifcOOjX",
same_site: "Lax"
]

plug(Plug.Static,
at: "/uploads",
from: Path.expand("../../../../uploads"),
gzip: false
)

# Code reloading can be explicitly enabled under the
# :code_reloader configuration of your endpoint.
if code_reloading? do
plug Phoenix.CodeReloader
plug Phoenix.Ecto.CheckRepoStatus, otp_app: :uro_api
end

plug(Plug.RequestId, assign_as: :request_id)
plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint])

# Max upload size, 200mb
plug(Plug.Parsers,
parsers: [:urlencoded, {:multipart, length: 200_000_000}, :json],
query_string_length: 1_000_000,
pass: ["*/*"],
json_decoder: Phoenix.json_library()
)

plug(Plug.MethodOverride)
plug(Plug.Head)
plug Plug.Session, @session_options
plug Uro.Router
end
5 changes: 5 additions & 0 deletions apps/uro_api/lib/uro/error_json.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule Uro.ErrorJSON do
def render(template, _assigns) do
%{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}}
end
end
File renamed without changes.
File renamed without changes.
50 changes: 50 additions & 0 deletions apps/uro_api/lib/uro/gettext.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
defmodule Uro.Gettext do
@moduledoc """
A module providing Internationalization with a gettext-based API.

By using [Gettext](https://hexdocs.pm/gettext),
your module gains a set of macros for translations, for example:

import Uro.Gettext

# Simple translation
gettext("Here is the string to translate")

# Plural translation
ngettext("Here is the string to translate",
"Here are the strings to translate",
3)

# Domain-based translation
dgettext("errors", "Here is the error message to translate")

See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
"""
use Gettext.Backend, otp_app: :uro_api
end

# TODO(benbot) migrate Uro to UroWeb where needed
defmodule UroWeb.Gettext do
@moduledoc """
A module providing Internationalization with a gettext-based API.

By using [Gettext](https://hexdocs.pm/gettext),
your module gains a set of macros for translations, for example:

import Uro.Gettext

# Simple translation
gettext("Here is the string to translate")

# Plural translation
ngettext("Here is the string to translate",
"Here are the strings to translate",
3)

# Domain-based translation
dgettext("errors", "Here is the error message to translate")

See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
"""
use Gettext.Backend, otp_app: :uro_api
end
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,10 @@ defmodule Uro.Helpers.Validation do
end
end

@spec generate_file_sha256(Path.t()) :: String.t()
def generate_file_sha256(file_path) do
# 4KB chunks
file_stream = File.stream!(file_path, [], 4096)
# 4KB byte chunks (second arg is byte count for streaming)
file_stream = File.stream!(file_path, 4096)

hash =
Enum.reduce(file_stream, :crypto.hash_init(:sha256), fn chunk, acc ->
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,27 +1,19 @@
defmodule Uro.Inventory.Backpack do
alias Uro.Accounts.User
import Ecto.Changeset
use Ecto.Schema

defmodule Join do
use Ecto.Schema
import Ecto.Query

@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "backpack_join" do
belongs_to :backpack, Uro.Inventory.Backpack

belongs_to :map, Uro.UserContent.Map
belongs_to :prop, Uro.UserContent.Prop
belongs_to :avatar, Uro.UserContent.Avatar

timestamps()
end
end

@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "backpacks" do
belongs_to :owner, User, foreign_key: :owner_id, type: :binary_id

Expand Down
File renamed without changes.
Loading
Loading