@playcademy/vite-plugin 0.2.31 → 0.2.32-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +149 -92
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -25370,7 +25370,7 @@ var package_default;
25370
25370
  var init_package = __esm(() => {
25371
25371
  package_default = {
25372
25372
  name: "@playcademy/sandbox",
25373
- version: "0.3.16",
25373
+ version: "0.3.17-beta.37",
25374
25374
  description: "Local development server for Playcademy game development",
25375
25375
  type: "module",
25376
25376
  exports: {
@@ -35349,6 +35349,9 @@ var gamePlatformEnum;
35349
35349
  var gameTypeEnum;
35350
35350
  var gameVisibilityEnum;
35351
35351
  var games;
35352
+ var gameMemberRoleEnum;
35353
+ var gameMembers;
35354
+ var gameMembersRelations;
35352
35355
  var gameSessions;
35353
35356
  var gameStates;
35354
35357
  var deploymentProviderEnum;
@@ -35360,6 +35363,7 @@ var customHostnameSslStatusEnum;
35360
35363
  var customHostnameEnvironmentEnum;
35361
35364
  var gameCustomHostnames;
35362
35365
  var init_table3 = __esm(() => {
35366
+ init_drizzle_orm();
35363
35367
  init_pg_core();
35364
35368
  init_table5();
35365
35369
  init_table6();
@@ -35368,9 +35372,6 @@ var init_table3 = __esm(() => {
35368
35372
  gameVisibilityEnum = pgEnum("game_visibility", ["visible", "unlisted", "internal"]);
35369
35373
  games = pgTable("games", {
35370
35374
  id: uuid("id").primaryKey().defaultRandom(),
35371
- developerId: text("developer_id").references(() => users.id, {
35372
- onDelete: "set null"
35373
- }),
35374
35375
  slug: varchar("slug", { length: 255 }).notNull().unique(),
35375
35376
  displayName: varchar("display_name", { length: 255 }).notNull(),
35376
35377
  version: varchar("version", { length: 50 }).notNull(),
@@ -35386,6 +35387,24 @@ var init_table3 = __esm(() => {
35386
35387
  createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
35387
35388
  updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow()
35388
35389
  });
35390
+ gameMemberRoleEnum = pgEnum("game_member_role", ["owner", "collaborator"]);
35391
+ gameMembers = pgTable("game_members", {
35392
+ id: uuid("id").primaryKey().defaultRandom(),
35393
+ gameId: uuid("game_id").notNull().references(() => games.id, { onDelete: "cascade" }),
35394
+ userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
35395
+ role: gameMemberRoleEnum("role").notNull().default("collaborator"),
35396
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
35397
+ }, (table3) => [uniqueIndex("game_members_game_user_idx").on(table3.gameId, table3.userId)]);
35398
+ gameMembersRelations = relations(gameMembers, ({ one }) => ({
35399
+ game: one(games, {
35400
+ fields: [gameMembers.gameId],
35401
+ references: [games.id]
35402
+ }),
35403
+ user: one(users, {
35404
+ fields: [gameMembers.userId],
35405
+ references: [users.id]
35406
+ })
35407
+ }));
35389
35408
  gameSessions = pgTable("game_sessions", {
35390
35409
  id: uuid("id").primaryKey().defaultRandom(),
35391
35410
  userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
@@ -36223,6 +36242,9 @@ __export(exports_tables_index, {
36223
36242
  gameScoresRelations: () => gameScoresRelations,
36224
36243
  gameScores: () => gameScores,
36225
36244
  gamePlatformEnum: () => gamePlatformEnum,
36245
+ gameMembersRelations: () => gameMembersRelations,
36246
+ gameMembers: () => gameMembers,
36247
+ gameMemberRoleEnum: () => gameMemberRoleEnum,
36226
36248
  gameDeployments: () => gameDeployments,
36227
36249
  gameDeployJobs: () => gameDeployJobs,
36228
36250
  gameCustomHostnames: () => gameCustomHostnames,
@@ -50643,29 +50665,33 @@ var init_game_service = __esm(() => {
50643
50665
  const db2 = this.deps.db;
50644
50666
  const isAdmin = caller?.role === "admin";
50645
50667
  const isDeveloper = caller?.role === "developer";
50646
- let whereClause;
50647
50668
  if (isAdmin) {
50648
- whereClause = undefined;
50649
- } else if (isDeveloper && caller?.id) {
50650
- whereClause = or(ne(games.visibility, "internal"), eq(games.developerId, caller.id));
50651
- } else {
50652
- whereClause = ne(games.visibility, "internal");
50669
+ return db2.query.games.findMany({
50670
+ orderBy: [desc(games.createdAt)]
50671
+ });
50672
+ }
50673
+ if (isDeveloper && caller?.id) {
50674
+ const rows = await db2.select().from(games).where(or(ne(games.visibility, "internal"), exists(db2.select({ one: sql`1` }).from(gameMembers).where(and(eq(gameMembers.gameId, games.id), eq(gameMembers.userId, caller.id)))))).orderBy(desc(games.createdAt));
50675
+ return rows;
50653
50676
  }
50654
50677
  return db2.query.games.findMany({
50655
- where: whereClause,
50678
+ where: ne(games.visibility, "internal"),
50656
50679
  orderBy: [desc(games.createdAt)]
50657
50680
  });
50658
50681
  }
50659
- async listManageable(user) {
50682
+ async listAccessible(user) {
50660
50683
  const seesAllGames = user.role === "admin" || user.role === "teacher";
50661
50684
  if (!seesAllGames) {
50662
50685
  this.validateDeveloperStatus(user);
50663
50686
  }
50664
50687
  const db2 = this.deps.db;
50665
- return db2.query.games.findMany({
50666
- where: seesAllGames ? undefined : eq(games.developerId, user.id),
50667
- orderBy: [desc(games.createdAt)]
50668
- });
50688
+ if (seesAllGames) {
50689
+ return db2.query.games.findMany({
50690
+ orderBy: [desc(games.createdAt)]
50691
+ });
50692
+ }
50693
+ const rows = await db2.select({ games }).from(games).innerJoin(gameMembers, eq(gameMembers.gameId, games.id)).where(eq(gameMembers.userId, user.id)).orderBy(desc(games.createdAt));
50694
+ return rows.map((r) => r.games);
50669
50695
  }
50670
50696
  async getSubjects() {
50671
50697
  const db2 = this.deps.db;
@@ -50689,7 +50715,7 @@ var init_game_service = __esm(() => {
50689
50715
  if (!game) {
50690
50716
  throw new NotFoundError("Game", gameId);
50691
50717
  }
50692
- this.enforceVisibility(game, caller, gameId);
50718
+ await this.enforceVisibility(game, caller, gameId);
50693
50719
  return game;
50694
50720
  }
50695
50721
  async getBySlug(slug, caller) {
@@ -50700,7 +50726,7 @@ var init_game_service = __esm(() => {
50700
50726
  if (!game) {
50701
50727
  throw new NotFoundError("Game", slug);
50702
50728
  }
50703
- this.enforceVisibility(game, caller, slug);
50729
+ await this.enforceVisibility(game, caller, slug);
50704
50730
  return game;
50705
50731
  }
50706
50732
  async getManifest(gameId, caller) {
@@ -50866,15 +50892,20 @@ var init_game_service = __esm(() => {
50866
50892
  };
50867
50893
  }
50868
50894
  }
50869
- enforceVisibility(game, caller, lookupIdentifier) {
50895
+ async enforceVisibility(game, caller, lookupIdentifier) {
50870
50896
  if (game.visibility !== "internal") {
50871
50897
  return;
50872
50898
  }
50873
- const isAdmin = caller?.role === "admin";
50874
- const isOwner = caller?.id != null && caller.id === game.developerId;
50875
- if (!isAdmin && !isOwner) {
50899
+ if (!caller) {
50876
50900
  throw new NotFoundError("Game", lookupIdentifier);
50877
50901
  }
50902
+ if (caller.role === "admin") {
50903
+ return;
50904
+ }
50905
+ if (await this.hasGameMembership(game.id, caller.id)) {
50906
+ return;
50907
+ }
50908
+ throw new NotFoundError("Game", lookupIdentifier);
50878
50909
  }
50879
50910
  async upsertBySlug(slug, data, user) {
50880
50911
  const db2 = this.deps.db;
@@ -50911,17 +50942,24 @@ var init_game_service = __esm(() => {
50911
50942
  ...gameDataForDb,
50912
50943
  id: gameId,
50913
50944
  slug,
50914
- developerId: user.id,
50915
50945
  metadata: data.metadata || {},
50916
50946
  version: data.gameType === "external" ? "external" : "",
50917
50947
  deploymentUrl: null,
50918
50948
  createdAt: new Date
50919
50949
  };
50920
- const [createdGame] = await db2.insert(games).values(insertData).returning();
50921
- if (!createdGame) {
50922
- logger5.error("Game insert returned no rows", { slug, developerId: user.id });
50923
- throw new InternalError("DB insert failed to return result for new game");
50924
- }
50950
+ const createdGame = await db2.transaction(async (tx) => {
50951
+ const [game] = await tx.insert(games).values(insertData).returning();
50952
+ if (!game) {
50953
+ logger5.error("Game insert returned no rows", { slug, userId: user.id });
50954
+ throw new InternalError("DB insert failed to return result for new game");
50955
+ }
50956
+ await tx.insert(gameMembers).values({
50957
+ gameId: game.id,
50958
+ userId: user.id,
50959
+ role: "owner"
50960
+ });
50961
+ return game;
50962
+ });
50925
50963
  gameResponse = createdGame;
50926
50964
  }
50927
50965
  if (data.mapElementId) {
@@ -51019,51 +51057,43 @@ var init_game_service = __esm(() => {
51019
51057
  displayName: gameToDelete.displayName
51020
51058
  };
51021
51059
  }
51060
+ async hasGameMembership(gameId, userId) {
51061
+ const membership = await this.deps.db.query.gameMembers.findFirst({
51062
+ where: and(eq(gameMembers.gameId, gameId), eq(gameMembers.userId, userId)),
51063
+ columns: { id: true }
51064
+ });
51065
+ return Boolean(membership);
51066
+ }
51022
51067
  async validateOwnership(user, gameId) {
51023
- if (user.role === "admin") {
51024
- const gameExists = await this.deps.db.query.games.findFirst({
51025
- where: eq(games.id, gameId),
51026
- columns: { id: true }
51027
- });
51028
- if (!gameExists) {
51029
- throw new NotFoundError("Game", gameId);
51030
- }
51031
- return;
51032
- }
51033
51068
  const db2 = this.deps.db;
51034
- const gameOwnership = await db2.query.games.findFirst({
51035
- where: and(eq(games.id, gameId), eq(games.developerId, user.id)),
51069
+ const gameExists = await db2.query.games.findFirst({
51070
+ where: eq(games.id, gameId),
51036
51071
  columns: { id: true }
51037
51072
  });
51038
- if (!gameOwnership) {
51039
- const gameExists = await db2.query.games.findFirst({
51040
- where: eq(games.id, gameId),
51041
- columns: { id: true }
51042
- });
51043
- if (!gameExists) {
51044
- throw new NotFoundError("Game", gameId);
51045
- }
51073
+ if (!gameExists) {
51074
+ throw new NotFoundError("Game", gameId);
51075
+ }
51076
+ if (user.role === "admin") {
51077
+ return;
51078
+ }
51079
+ if (!await this.hasGameMembership(gameId, user.id)) {
51046
51080
  throw new AccessDeniedError("You do not own this game");
51047
51081
  }
51048
51082
  }
51049
51083
  async validateDeveloperAccess(user, gameId) {
51050
51084
  this.validateDeveloperStatus(user);
51051
- if (user.role === "admin") {
51052
- const gameExists = await this.deps.db.query.games.findFirst({
51053
- where: eq(games.id, gameId),
51054
- columns: { id: true }
51055
- });
51056
- if (!gameExists) {
51057
- throw new NotFoundError("Game", gameId);
51058
- }
51059
- return;
51060
- }
51061
51085
  const db2 = this.deps.db;
51062
- const existingGame = await db2.query.games.findFirst({
51063
- where: and(eq(games.id, gameId), eq(games.developerId, user.id)),
51086
+ const gameExists = await db2.query.games.findFirst({
51087
+ where: eq(games.id, gameId),
51064
51088
  columns: { id: true }
51065
51089
  });
51066
- if (!existingGame) {
51090
+ if (!gameExists) {
51091
+ throw new NotFoundError("Game", gameId);
51092
+ }
51093
+ if (user.role === "admin") {
51094
+ return;
51095
+ }
51096
+ if (!await this.hasGameMembership(gameId, user.id)) {
51067
51097
  throw new NotFoundError("Game", gameId);
51068
51098
  }
51069
51099
  }
@@ -51083,21 +51113,18 @@ var init_game_service = __esm(() => {
51083
51113
  async validateDeveloperAccessBySlug(user, slug) {
51084
51114
  this.validateDeveloperStatus(user);
51085
51115
  const db2 = this.deps.db;
51086
- if (user.role === "admin") {
51087
- const game2 = await db2.query.games.findFirst({
51088
- where: eq(games.slug, slug)
51089
- });
51090
- if (!game2) {
51091
- throw new NotFoundError("Game", slug);
51092
- }
51093
- return game2;
51094
- }
51095
51116
  const game = await db2.query.games.findFirst({
51096
- where: and(eq(games.slug, slug), eq(games.developerId, user.id))
51117
+ where: eq(games.slug, slug)
51097
51118
  });
51098
51119
  if (!game) {
51099
51120
  throw new NotFoundError("Game", slug);
51100
51121
  }
51122
+ if (user.role === "admin") {
51123
+ return game;
51124
+ }
51125
+ if (!await this.hasGameMembership(game.id, user.id)) {
51126
+ throw new NotFoundError("Game", slug);
51127
+ }
51101
51128
  return game;
51102
51129
  }
51103
51130
  validateDeveloperStatus(user) {
@@ -58779,10 +58806,17 @@ class LogsService {
58779
58806
  throw new AccessDeniedError("Must be an approved developer");
58780
58807
  }
58781
58808
  const game = await db2.query.games.findFirst({
58782
- where: and(eq(games.slug, slug2), eq(games.developerId, user.id)),
58809
+ where: eq(games.slug, slug2),
58783
58810
  columns: { id: true }
58784
58811
  });
58785
58812
  if (!game) {
58813
+ throw new NotFoundError("Game", slug2);
58814
+ }
58815
+ const membership = await db2.query.gameMembers.findFirst({
58816
+ where: and(eq(gameMembers.gameId, game.id), eq(gameMembers.userId, user.id)),
58817
+ columns: { id: true }
58818
+ });
58819
+ if (!membership) {
58786
58820
  logger28.warn("Developer attempted access to unowned game logs", {
58787
58821
  userId: user.id,
58788
58822
  slug: slug2
@@ -120320,7 +120354,6 @@ async function seedCoreGames(db2) {
120320
120354
  const coreGames = [
120321
120355
  {
120322
120356
  id: CORE_GAME_UUIDS.PLAYGROUND,
120323
- developerId: DEMO_USERS.developer.id,
120324
120357
  slug: "playground",
120325
120358
  displayName: "Playground",
120326
120359
  version: "local",
@@ -120337,6 +120370,11 @@ async function seedCoreGames(db2) {
120337
120370
  for (const gameData of coreGames) {
120338
120371
  try {
120339
120372
  await db2.insert(games).values(gameData).onConflictDoNothing();
120373
+ await db2.insert(gameMembers).values({
120374
+ gameId: gameData.id,
120375
+ userId: DEMO_USERS.developer.id,
120376
+ role: "owner"
120377
+ }).onConflictDoNothing();
120340
120378
  } catch (error2) {
120341
120379
  logger37.error(`Error seeding core game '${gameData.slug}': ${error2}`);
120342
120380
  }
@@ -120361,7 +120399,6 @@ async function seedCurrentProjectGame(db2, project) {
120361
120399
  }
120362
120400
  const gameRecord = {
120363
120401
  id: desiredGameId ?? crypto.randomUUID(),
120364
- developerId: DEMO_USERS.developer.id,
120365
120402
  slug: project.slug,
120366
120403
  displayName: project.displayName,
120367
120404
  version: project.version,
@@ -120379,6 +120416,11 @@ async function seedCurrentProjectGame(db2, project) {
120379
120416
  if (!newGame) {
120380
120417
  throw new Error("Failed to create game record");
120381
120418
  }
120419
+ await db2.insert(gameMembers).values({
120420
+ gameId: newGame.id,
120421
+ userId: DEMO_USERS.developer.id,
120422
+ role: "owner"
120423
+ }).onConflictDoNothing();
120382
120424
  if (project.timebackCourses && project.timebackCourses.length > 0) {
120383
120425
  await seedTimebackIntegrations(db2, newGame.id, project.timebackCourses);
120384
120426
  }
@@ -121371,7 +121413,6 @@ var init_schemas2 = __esm(() => {
121371
121413
  id: true,
121372
121414
  slug: true,
121373
121415
  createdAt: true,
121374
- developerId: true,
121375
121416
  version: true
121376
121417
  }).refine((data) => {
121377
121418
  if (data.gameType === "hosted" && data.deploymentUrl === null) {
@@ -122806,7 +122847,7 @@ var init_domain_controller = __esm(() => {
122806
122847
  });
122807
122848
  var logger48;
122808
122849
  var list3;
122809
- var listManageable;
122850
+ var listAccessible;
122810
122851
  var getSubjects;
122811
122852
  var getById2;
122812
122853
  var getBySlug;
@@ -122826,9 +122867,9 @@ var init_game_controller = __esm(() => {
122826
122867
  logger48.debug("Listing games", { userId: ctx.user.id });
122827
122868
  return ctx.services.game.list(ctx.user);
122828
122869
  });
122829
- listManageable = requireNonAnonymous(async (ctx) => {
122830
- logger48.debug("Listing manageable games", { userId: ctx.user.id });
122831
- return ctx.services.game.listManageable(ctx.user);
122870
+ listAccessible = requireNonAnonymous(async (ctx) => {
122871
+ logger48.debug("Listing accessible games", { userId: ctx.user.id });
122872
+ return ctx.services.game.listAccessible(ctx.user);
122832
122873
  });
122833
122874
  getSubjects = requireNonAnonymous(async (ctx) => {
122834
122875
  logger48.debug("Getting game subjects", { userId: ctx.user.id });
@@ -122901,7 +122942,7 @@ var init_game_controller = __esm(() => {
122901
122942
  });
122902
122943
  games2 = {
122903
122944
  list: list3,
122904
- listManageable,
122945
+ listAccessible,
122905
122946
  getSubjects,
122906
122947
  getById: getById2,
122907
122948
  getManifest,
@@ -125090,6 +125131,7 @@ var init_crud = __esm(() => {
125090
125131
  init_api();
125091
125132
  gameCrudRouter = new Hono2;
125092
125133
  gameCrudRouter.get("/", handle2(games2.list));
125134
+ gameCrudRouter.get("/accessible", handle2(games2.listAccessible));
125093
125135
  gameCrudRouter.get("/:gameId{[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}}/manifest", handle2(games2.getManifest));
125094
125136
  gameCrudRouter.get("/:gameId{[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}}", handle2(games2.getById));
125095
125137
  gameCrudRouter.get("/:slug", handle2(games2.getBySlug));
@@ -125229,10 +125271,12 @@ var init_deploy = __esm(() => {
125229
125271
  const db2 = ctx.db;
125230
125272
  const existingGames = await db2.select().from(games).where(eq(games.slug, slug2));
125231
125273
  const existingGame = existingGames[0];
125232
- if (existingGame) {
125233
- const isAdmin = user.role === "admin";
125234
- const isOwner = existingGame.developerId === user.id;
125235
- if (!isAdmin && !isOwner) {
125274
+ if (existingGame && user.role !== "admin") {
125275
+ const membership = await db2.query.gameMembers.findFirst({
125276
+ where: and(eq(gameMembers.gameId, existingGame.id), eq(gameMembers.userId, user.id)),
125277
+ columns: { id: true }
125278
+ });
125279
+ if (!membership) {
125236
125280
  return c2.json({
125237
125281
  error: {
125238
125282
  code: "FORBIDDEN",
@@ -125256,17 +125300,24 @@ var init_deploy = __esm(() => {
125256
125300
  if (!body2.metadata?.displayName) {
125257
125301
  return c2.json({ error: { code: "BAD_REQUEST", message: "Display name required for new game" } }, 400);
125258
125302
  }
125259
- const inserted = await db2.insert(games).values({
125303
+ const gameValues = {
125260
125304
  slug: slug2,
125261
125305
  displayName: body2.metadata.displayName,
125262
125306
  version: "1.0.0",
125263
125307
  platform: body2.metadata.platform ?? "web",
125264
125308
  gameType: "hosted",
125265
- developerId: user.id,
125266
125309
  deploymentUrl: `http://localhost:4321`,
125267
125310
  metadata: body2.metadata.metadata ?? {}
125268
- }).returning();
125269
- game = inserted[0];
125311
+ };
125312
+ game = await db2.transaction(async (tx) => {
125313
+ const [inserted] = await tx.insert(games).values(gameValues).returning();
125314
+ await tx.insert(gameMembers).values({
125315
+ gameId: inserted.id,
125316
+ userId: user.id,
125317
+ role: "owner"
125318
+ });
125319
+ return inserted;
125320
+ });
125270
125321
  }
125271
125322
  let backendCode = body2.code;
125272
125323
  if (!backendCode && body2.codeUploadToken) {
@@ -125345,13 +125396,19 @@ var init_deploy = __esm(() => {
125345
125396
  const ctx = getSandboxContext();
125346
125397
  const game = await ctx.db.query.games.findFirst({
125347
125398
  where: eq(games.slug, slug2 ?? ""),
125348
- columns: { id: true, developerId: true }
125399
+ columns: { id: true }
125349
125400
  });
125350
125401
  if (!game) {
125351
125402
  return c2.json({ error: { code: "NOT_FOUND", message: "Game not found" } }, 404);
125352
125403
  }
125353
- if (game.developerId !== user.id && user.role !== "admin") {
125354
- return c2.json({ error: { code: "NOT_FOUND", message: "Game not found" } }, 404);
125404
+ if (user.role !== "admin") {
125405
+ const membership = await ctx.db.query.gameMembers.findFirst({
125406
+ where: and(eq(gameMembers.gameId, game.id), eq(gameMembers.userId, user.id)),
125407
+ columns: { id: true }
125408
+ });
125409
+ if (!membership) {
125410
+ return c2.json({ error: { code: "NOT_FOUND", message: "Game not found" } }, 404);
125411
+ }
125355
125412
  }
125356
125413
  const job = await ctx.db.query.gameDeployJobs.findFirst({
125357
125414
  where: and(eq(gameDeployJobs.id, jobId), eq(gameDeployJobs.gameId, game.id))
@@ -127644,7 +127701,7 @@ var import_picocolors12 = __toESM(require_picocolors(), 1);
127644
127701
  // package.json
127645
127702
  var package_default2 = {
127646
127703
  name: "@playcademy/vite-plugin",
127647
- version: "0.2.31",
127704
+ version: "0.2.32-beta.1",
127648
127705
  type: "module",
127649
127706
  exports: {
127650
127707
  ".": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/vite-plugin",
3
- "version": "0.2.31",
3
+ "version": "0.2.32-beta.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {