Release v0.1.4 with terrain rescanning and owner-published map updates
All checks were successful
Deploy MineGeo to Dokploy / deploy (push) Successful in 6m26s

This commit is contained in:
2026-10-28 19:40:53 +05:00
parent 5898ef35a5
commit 658c8974ac
53 changed files with 608 additions and 83 deletions

View File

@@ -16,7 +16,7 @@ try {
await db.exec(`create role anon; create role authenticated; create role service_role bypassrls; create role supabase_auth_admin bypassrls;
create schema auth; create table auth.users(id uuid primary key);
create function auth.uid() returns uuid language sql as $$select null::uuid$$;
create schema storage; create table storage.buckets(id text primary key,name text,public boolean,file_size_limit bigint);`);
create schema storage; create table storage.buckets(id text primary key,name text,public boolean,file_size_limit bigint,allowed_mime_types text[]); create table storage.objects(id uuid primary key default gen_random_uuid(),bucket_id text,name text); create function storage.foldername(name text) returns text[] language sql as $$ select string_to_array(name, '/') $$;`);
for(const file of (await readdir("supabase/migrations")).filter(f=>f.endsWith(".sql")).sort())
await db.exec(await readFile(`supabase/migrations/${file}`,"utf8"));
await db.exec("grant select,insert,update,delete on all tables in schema public to service_role;");
@@ -61,8 +61,8 @@ try {
await db.query("insert into public.contributor_tokens(owner_id,token_hash,label) values($1,'uploadfixture','fixture')",[owner]);
await db.query("insert into public.pairing_codes(device_hash,user_code,owner_id,expires_at,token_value) values('device','PAIR',$1,clock_timestamp()+interval '1 day','secret')",[owner]);
const key=await value("select public.developer_key_create($1,'Test',array['maps:read'],$2,'mg_dev_fixture',1) value",[owner,hash]);
await db.query("insert into public.game_sessions(id,revision_id,owner_id,access_hash,difficulty) values($1,$2,$3,'old-solo-secret','easy')",[game,revision,owner]);
await db.query("insert into public.multiplayer_rooms(id,revision_id,host_player_id,difficulty,player_limit) values($1,$2,$3,'easy',4)",[room,revision,player]);
await db.query("insert into public.game_sessions(id,revision_id,owner_id,access_hash,difficulty,difficulty_settings) values($1,$2,$3,'old-solo-secret','easy',public.resolve_difficulty_preset('easy'))",[game,revision,owner]);
await db.query("insert into public.multiplayer_rooms(id,revision_id,host_player_id,difficulty,player_limit,difficulty_settings) values($1,$2,$3,'easy',4,public.resolve_difficulty_preset('easy'))",[room,revision,player]);
await db.query("insert into public.multiplayer_players(id,room_id,access_hash,display_name,account_id) values($1,$2,'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd','Fixture',$3)",[player,room,owner]);
assert.deepEqual(await action(owner,"ban"),{id:owner,status:"banned",statusVersion:1});
assert.equal((await profile()).account_reason,reason);

View File

@@ -7,7 +7,7 @@ try {
await db.exec(`create role anon; create role authenticated; create role service_role;
create schema auth; create table auth.users(id uuid primary key);
create function auth.uid() returns uuid language sql as $$ select null::uuid $$;
create schema storage; create table storage.buckets(id text primary key,name text,public boolean,file_size_limit bigint);`);
create schema storage; create table storage.buckets(id text primary key,name text,public boolean,file_size_limit bigint,allowed_mime_types text[]); create table storage.objects(id uuid primary key default gen_random_uuid(),bucket_id text,name text); create function storage.foldername(name text) returns text[] language sql as $$ select string_to_array(name, '/') $$;`);
for (const file of (await readdir("supabase/migrations"))
.filter((f) => f.endsWith(".sql"))
.sort())

View File

@@ -7,7 +7,7 @@ try {
await db.exec(`create role anon;create role authenticated;create role service_role;
create schema auth;create table auth.users(id uuid primary key);
create function auth.uid() returns uuid language sql as $$select null::uuid$$;
create schema storage;create table storage.buckets(id text primary key,name text,public boolean,file_size_limit bigint);`);
create schema storage;create table storage.buckets(id text primary key,name text,public boolean,file_size_limit bigint,allowed_mime_types text[]); create table storage.objects(id uuid primary key default gen_random_uuid(),bucket_id text,name text); create function storage.foldername(name text) returns text[] language sql as $$ select string_to_array(name, '/') $$;`);
for (const file of (await readdir("supabase/migrations"))
.filter((file) => file.endsWith(".sql"))
.sort())

View File

@@ -5,7 +5,10 @@ try {
await db.exec(`create role anon; create role authenticated; create role service_role;
create schema auth; create table auth.users(id uuid primary key);
create function auth.uid() returns uuid language sql as $$ select null::uuid $$;
create schema storage; create table storage.buckets(id text primary key,name text,public boolean,file_size_limit bigint);`);
create schema storage;
create table storage.buckets(id text primary key,name text,public boolean,file_size_limit bigint,allowed_mime_types text[]);
create table storage.objects(id uuid primary key default gen_random_uuid(),bucket_id text,name text);
create function storage.foldername(name text) returns text[] language sql as $$ select string_to_array(name, '/') $$;`);
for (const file of (await readdir("supabase/migrations"))
.filter((f) => f.endsWith(".sql"))
.sort())

View File

@@ -0,0 +1,56 @@
import { readFile, readdir } from "node:fs/promises";
import { PGlite } from "../.data/sql-verification/node_modules/@electric-sql/pglite/dist/index.js";
const db = new PGlite();
try {
await db.exec(`create role anon; create role authenticated; create role service_role bypassrls;
create schema auth; create table auth.users(id uuid primary key);
create function auth.uid() returns uuid language sql as $$ select null::uuid $$;
create schema storage;
create table storage.buckets(id text primary key,name text,public boolean,file_size_limit bigint,allowed_mime_types text[]);
create table storage.objects(id uuid primary key default gen_random_uuid(),bucket_id text,name text);
create function storage.foldername(name text) returns text[] language sql as $$ select string_to_array(name, '/') $$;`);
for (const file of (await readdir("supabase/migrations"))
.filter((f) => f.endsWith(".sql"))
.sort())
await db.exec(await readFile(`supabase/migrations/${file}`, "utf8"));
await db.exec(`do $$ declare owner uuid:=gen_random_uuid(); other uuid:=gen_random_uuid(); root uuid; child uuid; m jsonb; old_revision uuid; begin
insert into auth.users(id) values(owner),(other);
insert into public.profiles(id,display_name) values(owner,'Owner'),(other,'Other') on conflict(id) do nothing;
m:=jsonb_build_object('name','Test map','description','test','source','plugin','serverName','Test server','serverAddress','one.example',
'bounds',jsonb_build_object('minX',0,'minZ',0,'maxX',64,'maxZ',64),
'viewpoints',(select jsonb_agg(jsonb_build_object('id','p'||n,'x',n,'y',65,'z',n)) from generate_series(1,5) n));
root:=public.create_upload(owner,m);
perform public.set_contribution_server(root,owner,'ONE.example:25565, two.example:25566');
perform public.assert_capture_server((select contribution_server_address from public.datasets where id=root),'two.example:25566');
begin perform public.assert_capture_server((select contribution_server_address from public.datasets where id=root),'wrong.example'); raise exception 'Mismatch accepted';
exception when others then if sqlerrm not like 'Wrong capture server:%' then raise; end if; end;
update public.dataset_inputs set validated=true where dataset_id=root;
insert into public.revisions(dataset_id,manifest,asset_root) values(root,m,root::text) returning id into old_revision;
update public.datasets set state='published',revision_id=old_revision,update_state='draft' where id=root;
begin perform public.publish_owner_update(root,other); raise exception 'Non-owner accepted';
exception when others then if sqlerrm <> 'Map owner permission required' then raise; end if; end;
perform public.publish_owner_update(root,owner);
if (select revision_id=old_revision or update_state is not null from public.datasets where id=root) then raise exception 'Owner update failed'; end if;
old_revision:=(select revision_id from public.datasets where id=root);
child:=public.create_upload(owner,m||jsonb_build_object('contributionTo',root,'serverAddress','two.example:25566','viewpoints',jsonb_build_array(jsonb_build_object('id','new','x',10,'y',65,'z',10))));
update public.datasets set state='draft' where id=child;
update public.dataset_inputs set validated=true where dataset_id=child;
update public.datasets set update_state='draft' where id=root;
begin perform public.publish_own_contribution(child,owner); raise exception 'Unpublished changes were overwritten';
exception when others then if sqlerrm <> 'Publish or discard the existing map draft first' then raise; end if; end;
update public.datasets set update_state=null where id=root;
perform public.publish_own_contribution(child,owner);
if (select revision_id=old_revision or panorama_count<>6 from public.datasets where id=root) then raise exception 'One-click contribution failed'; end if;
if (select contribution_status from public.datasets where id=child)<>'accepted' then raise exception 'Owner contribution was not accepted'; end if;
if has_function_privilege('authenticated','public.publish_owner_update(uuid,uuid)','execute') then raise exception 'Public RPC exposed'; end if;
end $$;`);
console.log(
"Map update SQL passed: aliases, mismatch rejection, owner authorization and immutable revision.",
);
} catch (error) {
console.error(error.message);
process.exitCode = 1;
} finally {
await db.close();
}

View File

@@ -27,7 +27,7 @@ async function serverJar(version) {
`https://fill.papermc.io/v3/projects/paper/versions/${version}/builds`,
{
headers: {
"User-Agent": "MineGeo/0.1.3 local compatibility verification",
"User-Agent": "MineGeo/0.1.4 local compatibility verification",
},
},
);
@@ -120,7 +120,7 @@ for (const version of matrix) {
await mkdir(`${directory}/plugins`, { recursive: true });
await copyFile(await serverJar(version), `${directory}/paper.jar`);
await copyFile(
"public/tools/plugin/minegeo-plugin-0.1.3.jar",
"public/tools/plugin/minegeo-plugin-0.1.4.jar",
`${directory}/plugins/minegeo.jar`,
);
await writeFile(`${directory}/eula.txt`, "eula=true\n");

View File

@@ -7,7 +7,7 @@ try {
await db.exec(`create role anon; create role authenticated; create role service_role;
create schema auth; create table auth.users(id uuid primary key);
create function auth.uid() returns uuid language sql as $$ select null::uuid $$;
create schema storage; create table storage.buckets(id text primary key,name text,public boolean,file_size_limit bigint);`);
create schema storage; create table storage.buckets(id text primary key,name text,public boolean,file_size_limit bigint,allowed_mime_types text[]); create table storage.objects(id uuid primary key default gen_random_uuid(),bucket_id text,name text); create function storage.foldername(name text) returns text[] language sql as $$ select string_to_array(name, '/') $$;`);
for (const file of (await readdir("supabase/migrations"))
.filter((f) => f.endsWith(".sql"))
.sort()) {

View File

@@ -110,7 +110,7 @@ await writeFile(
// The deployed plugin always uses its hardcoded production endpoint.
const execute = promisify(execFile);
let exportDir: string;
const pluginJar = path.resolve("public/tools/plugin/minegeo-plugin-0.1.3.jar");
const pluginJar = path.resolve("public/tools/plugin/minegeo-plugin-0.1.4.jar");
await execute("javac", [
"-cp",
pluginJar,

View File

@@ -11,7 +11,9 @@ try {
await db.exec(`create role anon; create role authenticated; create role service_role bypassrls;
create schema auth; create table auth.users(id uuid primary key);
create function auth.uid() returns uuid language sql as $$ select null::uuid $$;
create schema storage; create table storage.buckets(id text primary key,name text,public boolean,file_size_limit bigint);`);
create schema storage; create table storage.buckets(id text primary key,name text,public boolean,file_size_limit bigint,allowed_mime_types text[]);
create table storage.objects(id uuid primary key default gen_random_uuid(),bucket_id text,name text);
create function storage.foldername(name text) returns text[] language sql as $$ select string_to_array(name, '/') $$;`);
for (const file of (await readdir("supabase/migrations"))
.filter((f) => f.endsWith(".sql"))
.sort())
@@ -44,8 +46,8 @@ try {
)
).rows[0].id;
await db.query(
`insert into public.game_sessions(revision_id,access_hash,difficulty,current_round,created_at)
values($1,'test','easy',5,now()),($1,'test','hard',0,now()-interval '2 days')`,
`insert into public.game_sessions(revision_id,access_hash,difficulty,difficulty_settings,current_round,created_at)
values($1,'test','easy',public.resolve_difficulty_preset('easy'),5,now()),($1,'test','hard',public.resolve_difficulty_preset('hard'),0,now()-interval '2 days')`,
[revision],
);
const rpc = async (sql, args = []) =>
@@ -88,7 +90,7 @@ try {
"select public.read_prometheus_metrics($1) value",
[oldHash],
);
assert.equal(snapshot.length, 43);
assert.equal(snapshot.length, 45); // Includes complete/incomplete Void games.
assert.equal(snapshot.find((s) => s.metric === "minegeo_accounts").value, 3);
assert.equal(
snapshot.find(
@@ -183,6 +185,9 @@ try {
await db.query("update public.profiles set role='admin' where id=$1", [
owner,
]);
await db.query("update public.profiles set account_status='deactivated' where id=$1", [owner]);
assert.equal(await rpc("select public.read_prometheus_metrics($1) value", [newHash]), null);
await db.query("update public.profiles set account_status='active' where id=$1", [owner]);
await rpc("select public.configure_prometheus($1,null) value", [owner]);
assert.equal(
await rpc("select public.read_prometheus_metrics($1) value", [newHash]),

View File

@@ -22,6 +22,7 @@ try {
"insert into auth.users values(gen_random_uuid()) returning id",
)
).rows[0].id;
await db.query("insert into public.profiles(id,display_name) values($1,'Host')", [owner]);
const map = (
await db.query(
`insert into public.datasets(owner_id,name,source,bounds,state,panorama_count)