Skip to content

Commit 34d62bd

Browse files
Merge pull request #811 from decentraland/revert-810-feat/change-how-settings-are-shown-for-worlds
Revert "feat: Change how settings are shown for worlds"
2 parents f3ebe8e + 58482e1 commit 34d62bd

14 files changed

+68
-676
lines changed

src/entities/CheckScenes/task/handleWorldScenesUndeployment.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { WorldScenesUndeploymentEvent } from "@dcl/schemas/dist/platform/events/world"
22
import logger from "decentraland-gatsby/dist/entities/Development/logger"
33

4+
import PlaceModel from "../../Place/model"
45
import { notifyError } from "../../Slack/utils"
5-
import WorldModel from "../../World/model"
66

77
/**
88
* Handles WorldScenesUndeploymentEvent from the worlds content server.
9-
* Atomically deletes place records for undeployed scenes and refreshes
10-
* the world's deployment-derived fields from the next-latest remaining scene.
9+
* Deletes the place records corresponding to the undeployed scenes,
10+
* identified by world name and each scene's base parcel.
1111
*/
1212
export async function handleWorldScenesUndeployment(
1313
event: WorldScenesUndeploymentEvent
@@ -41,14 +41,14 @@ export async function handleWorldScenesUndeployment(
4141
)}`
4242
)
4343

44-
await WorldModel.deleteWorldScenesAndRefresh(
44+
await PlaceModel.deleteByWorldIdAndPositions(
4545
worldName,
4646
basePositions,
4747
event.timestamp
4848
)
4949

5050
loggerExtended.log(
51-
`Deleted place records and refreshed world: ${worldName} at positions: ${basePositions.join(
51+
`Deleted place records for world: ${worldName} at positions: ${basePositions.join(
5252
", "
5353
)}`
5454
)

src/entities/CheckScenes/task/taskRunnerSqs.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import CategoryModel from "../../Category/model"
66
import { DecentralandCategories } from "../../Category/types"
77
import PlaceModel from "../../Place/model"
88
import { PlaceAttributes } from "../../Place/types"
9-
import { getThumbnailFromContentDeployment } from "../../Place/utils"
109
import PlaceCategories from "../../PlaceCategories/model"
1110
import PlaceContentRatingModel from "../../PlaceContentRating/model"
1211
import PlacePositionModel from "../../PlacePosition/model"
@@ -84,17 +83,15 @@ export async function taskRunnerSqs(job: DeploymentToSqs) {
8483
const isOptOut =
8584
!!contentEntityScene?.metadata?.worldConfiguration?.placesConfig?.optOut
8685

87-
const worldImage = getThumbnailFromContentDeployment(contentEntityScene, {
88-
url: job.contentServerUrls![0],
89-
})
90-
86+
// Insert the world only if it doesn't already exist.
87+
// If it already exists (configured via settings or a previous deployment),
88+
// its data is left untouched.
9189
const worldId = await WorldModel.insertWorldIfNotExists({
9290
world_name: worldName,
9391
title:
9492
contentEntityScene?.metadata?.display?.title?.slice(0, 50) || undefined,
9593
description:
9694
contentEntityScene?.metadata?.display?.description || undefined,
97-
image: worldImage,
9895
content_rating:
9996
(contentEntityScene?.metadata?.policy
10097
?.contentRating as SceneContentRating) || undefined,

src/entities/Destination/model.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
} from "./types"
1313
import PlaceModel from "../Place/model"
1414
import { HotScene, PlaceListOrderBy } from "../Place/types"
15-
import { DEFAULT_WORLD_IMAGE } from "../shared/constants"
1615
import WorldModel from "../World/model"
1716

1817
/**
@@ -37,7 +36,7 @@ const PLACES_DESTINATION_SELECT = SQL`
3736
* using the lateral join (lp) for place-derived fields.
3837
*/
3938
const WORLDS_DESTINATION_SELECT = SQL`
40-
w.id, w.title, w.description, COALESCE(w.image, ${DEFAULT_WORLD_IMAGE}) as image,
39+
w.id, w.title, w.description, COALESCE(w.image, lp.image) as image,
4140
w.owner, w.world_name,
4241
w.content_rating, w.categories, w.likes, w.dislikes, w.favorites,
4342
w.like_rate, w.like_score, w.disabled, w.disabled_at,

src/entities/Place/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
} from "./types"
1111
import { SceneStats, SceneStatsMap } from "../../api/DataTeam"
1212
import toCanonicalPosition from "../../utils/position/toCanonicalPosition"
13-
import { DEFAULT_WORLD_IMAGE } from "../shared/constants"
1413
import { AnyEntityAttributes } from "../shared/entityTypes"
1514

1615
const DECENTRALAND_URL =
@@ -128,7 +127,8 @@ export function getThumbnailFromContentDeployment(
128127
}
129128

130129
if (!thumbnail && deployment?.metadata?.worldConfiguration) {
131-
thumbnail = DEFAULT_WORLD_IMAGE
130+
thumbnail =
131+
"https://peer.decentraland.org/content/contents/bafkreidj26s7aenyxfthfdibnqonzqm5ptc4iamml744gmcyuokewkr76y"
132132
} else if (!thumbnail) {
133133
thumbnail = Land.getInstance().getMapImage({
134134
selected: positions,

src/entities/World/model.ts

Lines changed: 32 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
limit,
88
offset,
99
table,
10-
tsquery,
1110
values,
1211
} from "decentraland-gatsby/dist/entities/Database/utils"
1312
import { oneOf } from "decentraland-gatsby/dist/entities/Schema/utils"
@@ -20,13 +19,13 @@ import {
2019
WorldAttributes,
2120
WorldListOrderBy,
2221
} from "./types"
23-
import { DEFAULT_WORLD_IMAGE } from "../shared/constants"
2422
import {
2523
buildTextsearch,
2624
buildUpdateFavoritesQuery,
2725
buildUpdateLikesQuery,
2826
buildUserInteractionColumns,
2927
buildUserInteractionJoins,
28+
buildWorldTextSearchRank,
3029
} from "../shared/entityInteractions"
3130
import UserFavoriteModel from "../UserFavorite/model"
3231
import UserLikesModel from "../UserLikes/model"
@@ -47,7 +46,7 @@ export default class WorldModel extends Model<WorldAttributes> {
4746
static buildLatestPlaceLateralJoin(worldAlias: string): SQLStatement {
4847
const a = SQL.raw(worldAlias)
4948
return SQL`LEFT JOIN LATERAL (
50-
SELECT p.contact_name, p.contact_email,
49+
SELECT p.image, p.contact_name, p.contact_email,
5150
p.creator_address, p.sdk, p.deployed_at
5251
FROM places p
5352
WHERE p.world_id = ${a}.id AND p.disabled IS FALSE
@@ -98,9 +97,7 @@ export default class WorldModel extends Model<WorldAttributes> {
9897
)}
9998
${conditional(
10099
!!options.search,
101-
SQL`AND ${a}.textsearch @@ to_tsquery(${tsquery(
102-
options.search || ""
103-
)})`
100+
SQL`AND ${buildWorldTextSearchRank(alias, options.search || "")} > 0`
104101
)}
105102
${conditional(
106103
(options.world_names?.length ?? 0) > 0,
@@ -178,7 +175,7 @@ export default class WorldModel extends Model<WorldAttributes> {
178175
): SQLStatement {
179176
const forCount = opts?.forCount ?? false
180177
const defaultSelectColumns = SQL`w.*
181-
, COALESCE(w.image, ${DEFAULT_WORLD_IMAGE}) as image
178+
, COALESCE(w.image, lp.image) as image
182179
, lp.contact_name
183180
, '0,0' as base_position
184181
, true as world
@@ -196,9 +193,7 @@ export default class WorldModel extends Model<WorldAttributes> {
196193
)}
197194
${conditional(
198195
!forCount && !!options.search,
199-
SQL`, ts_rank_cd(w.textsearch, to_tsquery(${tsquery(
200-
options.search || ""
201-
)})) as rank`
196+
SQL`, ${buildWorldTextSearchRank("w", options.search || "")} as rank`
202197
)}
203198
FROM ${table(this)} w
204199
${this.buildLatestPlaceLateralJoin("w")}
@@ -257,7 +252,7 @@ export default class WorldModel extends Model<WorldAttributes> {
257252
): Promise<AggregateWorldAttributes | null> {
258253
const sql = SQL`
259254
SELECT w.*
260-
, COALESCE(w.image, ${DEFAULT_WORLD_IMAGE}) as image
255+
, COALESCE(w.image, lp.image) as image
261256
, lp.contact_name
262257
, '0,0' as base_position
263258
${conditional(
@@ -279,7 +274,13 @@ export default class WorldModel extends Model<WorldAttributes> {
279274
, 0 as user_visits
280275
, lp.deployed_at
281276
FROM ${table(this)} w
282-
${this.buildLatestPlaceLateralJoin("w")}
277+
LEFT JOIN LATERAL (
278+
SELECT p.image, p.contact_name, p.deployed_at
279+
FROM places p
280+
WHERE p.world_id = w.id AND p.disabled IS FALSE
281+
ORDER BY p.deployed_at DESC
282+
LIMIT 1
283+
) lp ON true
283284
${conditional(
284285
!!options.user,
285286
SQL`LEFT JOIN ${table(
@@ -438,8 +439,6 @@ export default class WorldModel extends Model<WorldAttributes> {
438439
highlighted: world.highlighted ?? false,
439440
highlighted_image: world.highlighted_image ?? null,
440441
ranking: world.ranking ?? 0,
441-
settings_configured: world.settings_configured ?? false,
442-
textsearch: undefined,
443442
likes: world.likes ?? 0,
444443
dislikes: world.dislikes ?? 0,
445444
favorites: world.favorites ?? 0,
@@ -453,24 +452,22 @@ export default class WorldModel extends Model<WorldAttributes> {
453452
}
454453

455454
/**
456-
* Insert a world or update its deployment-derived fields if settings haven't been
457-
* explicitly configured. Uses INSERT ... ON CONFLICT (id) DO UPDATE ... WHERE
458-
* settings_configured = false so that user-configured worlds are never overwritten
459-
* by deployments.
455+
* Insert a world only if it doesn't already exist.
456+
* Uses INSERT ... ON CONFLICT (id) DO NOTHING for atomicity.
457+
* Returns the world ID (lowercased world_name) regardless of whether
458+
* the insert was performed.
460459
*/
461460
static async insertWorldIfNotExists(
462461
world: Partial<WorldAttributes> & { world_name: string }
463462
): Promise<string> {
464463
const worldData = this.buildWorldData(world)
465-
const textsearch = this.textsearch(worldData)
466464

467465
const sql = SQL`
468466
INSERT INTO ${table(this)} (
469467
"id", "world_name", "title", "description", "image",
470468
"content_rating", "categories", "owner", "show_in_places",
471469
"single_player", "skybox_time", "is_private",
472470
"highlighted", "highlighted_image", "ranking",
473-
"settings_configured", "textsearch",
474471
"likes", "dislikes", "favorites",
475472
"like_rate", "like_score", "disabled", "disabled_at",
476473
"created_at", "updated_at"
@@ -490,8 +487,6 @@ export default class WorldModel extends Model<WorldAttributes> {
490487
${worldData.highlighted},
491488
${worldData.highlighted_image},
492489
${worldData.ranking},
493-
${worldData.settings_configured},
494-
${textsearch},
495490
${worldData.likes},
496491
${worldData.dislikes},
497492
${worldData.favorites},
@@ -502,15 +497,7 @@ export default class WorldModel extends Model<WorldAttributes> {
502497
${worldData.created_at},
503498
${worldData.updated_at}
504499
)
505-
ON CONFLICT (id) DO UPDATE SET
506-
title = EXCLUDED.title,
507-
description = EXCLUDED.description,
508-
image = EXCLUDED.image,
509-
content_rating = EXCLUDED.content_rating,
510-
categories = EXCLUDED.categories,
511-
textsearch = EXCLUDED.textsearch,
512-
updated_at = EXCLUDED.updated_at
513-
WHERE worlds.settings_configured = false
500+
ON CONFLICT (id) DO NOTHING
514501
`
515502

516503
await this.namedQuery("insert_world_if_not_exists", sql)
@@ -520,15 +507,13 @@ export default class WorldModel extends Model<WorldAttributes> {
520507
static async upsertWorld(
521508
world: Partial<WorldAttributes> & { world_name: string }
522509
): Promise<WorldAttributes> {
523-
const worldData = this.buildWorldData({
524-
...world,
525-
settings_configured: true,
526-
})
527-
const textsearch = this.textsearch(worldData)
510+
const worldData = this.buildWorldData(world)
528511

512+
// Fields that can be updated on conflict (excludes id, world_name, likes, etc.)
529513
const updatableFields: (keyof WorldAttributes)[] = [
530514
"title",
531515
"description",
516+
"image",
532517
"content_rating",
533518
"categories",
534519
"owner",
@@ -538,120 +523,22 @@ export default class WorldModel extends Model<WorldAttributes> {
538523
"is_private",
539524
]
540525

541-
const setClauses: SQLStatement[] = [
542-
SQL`"settings_configured" = true`,
543-
SQL`"textsearch" = ${textsearch}`,
544-
SQL`"image" = EXCLUDED."image"`,
545-
SQL`"updated_at" = EXCLUDED."updated_at"`,
546-
]
547-
526+
// Build changes object with only explicitly provided fields
527+
// This ensures we don't overwrite existing values with defaults on conflict
528+
const changes: Partial<WorldAttributes> = {
529+
updated_at: worldData.updated_at,
530+
}
548531
for (const field of updatableFields) {
549532
if (world[field] !== undefined) {
550-
setClauses.push(SQL`"${SQL.raw(field)}" = EXCLUDED."${SQL.raw(field)}"`)
533+
;(changes as Record<string, unknown>)[field] = world[field]
551534
}
552535
}
553536

554-
const sql = SQL`
555-
INSERT INTO ${table(this)} (
556-
"id", "world_name", "title", "description", "image",
557-
"content_rating", "categories", "owner", "show_in_places",
558-
"single_player", "skybox_time", "is_private",
559-
"highlighted", "highlighted_image", "ranking",
560-
"settings_configured", "textsearch",
561-
"likes", "dislikes", "favorites",
562-
"like_rate", "like_score", "disabled", "disabled_at",
563-
"created_at", "updated_at"
564-
) VALUES (
565-
${worldData.id},
566-
${worldData.world_name},
567-
${worldData.title},
568-
${worldData.description},
569-
${worldData.image},
570-
${worldData.content_rating},
571-
${worldData.categories},
572-
${worldData.owner},
573-
${worldData.show_in_places},
574-
${worldData.single_player},
575-
${worldData.skybox_time},
576-
${worldData.is_private},
577-
${worldData.highlighted},
578-
${worldData.highlighted_image},
579-
${worldData.ranking},
580-
${worldData.settings_configured},
581-
${textsearch},
582-
${worldData.likes},
583-
${worldData.dislikes},
584-
${worldData.favorites},
585-
${worldData.like_rate},
586-
${worldData.like_score},
587-
${worldData.disabled},
588-
${worldData.disabled_at},
589-
${worldData.created_at},
590-
${worldData.updated_at}
591-
)
592-
ON CONFLICT (id) DO UPDATE SET
593-
${join(setClauses, SQL`, `)}
594-
RETURNING *
595-
`
596-
597-
const results = await this.namedQuery<WorldAttributes>("upsert_world", sql)
598-
return results[0]
599-
}
600-
601-
/**
602-
* Atomically delete place records for undeployed scenes and refresh the world's
603-
* deployment-derived fields from the next-latest remaining scene. All operations
604-
* happen in a single SQL statement (CTE-based) to prevent inconsistent state.
605-
*
606-
* If settings_configured = true, the world update is skipped (user settings preserved).
607-
* If no remaining places exist, the UPDATE produces no rows (world stays as-is but
608-
* won't appear in queries since they check EXISTS(places)).
609-
*/
610-
static async deleteWorldScenesAndRefresh(
611-
worldId: string,
612-
basePositions: string[],
613-
eventTimestamp: number
614-
): Promise<void> {
615-
const normalizedWorldId = worldId.toLowerCase()
616-
const eventDate = new Date(eventTimestamp)
617-
618-
const sql = SQL`
619-
WITH deleted AS (
620-
DELETE FROM places
621-
WHERE world_id = ${normalizedWorldId}
622-
AND base_position = ANY(${basePositions})
623-
AND deployed_at < ${eventDate}
624-
RETURNING world_id
625-
),
626-
latest_remaining AS (
627-
SELECT p.title, p.description, p.image, p.content_rating, p.categories
628-
FROM places p
629-
WHERE p.world_id = ${normalizedWorldId}
630-
AND p.disabled IS FALSE
631-
AND p.base_position != ALL(${basePositions})
632-
ORDER BY p.deployed_at DESC
633-
LIMIT 1
634-
)
635-
UPDATE worlds SET
636-
title = lr.title,
637-
description = lr.description,
638-
image = lr.image,
639-
content_rating = COALESCE(lr.content_rating, worlds.content_rating),
640-
categories = COALESCE(lr.categories, worlds.categories),
641-
textsearch = (
642-
setweight(to_tsvector(coalesce(lr.title, '')), 'A') ||
643-
setweight(to_tsvector(coalesce(worlds.world_name, '')), 'A') ||
644-
setweight(to_tsvector(coalesce(lr.description, '')), 'B') ||
645-
setweight(to_tsvector(coalesce(worlds.owner, '')), 'C')
646-
),
647-
updated_at = now()
648-
FROM latest_remaining lr
649-
WHERE worlds.id = ${normalizedWorldId}
650-
AND worlds.settings_configured = false
651-
AND EXISTS (SELECT 1 FROM deleted)
652-
`
653-
654-
await this.namedQuery("delete_world_scenes_and_refresh", sql)
537+
// Upsert on id (lowercased world_name) as the conflict target
538+
return this.upsert(worldData, {
539+
target: ["id"],
540+
changes,
541+
})
655542
}
656543

657544
static async disableWorld(worldName: string): Promise<void> {

0 commit comments

Comments
 (0)