playcademy 0.14.13 → 0.14.14-alpha.2

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.
@@ -3,41 +3,39 @@
3
3
  *
4
4
  * This route will be available at: https://<your-game-slug>.playcademy.gg/api/sample/database
5
5
  *
6
+ * Demonstrates basic Drizzle ORM operations with the example schema.
6
7
  */
7
8
 
8
9
  import { desc } from 'drizzle-orm'
9
10
 
10
- import { getDb, schema } from 'db'
11
+ import { getDb, schema } from '../../db'
11
12
 
12
13
  /**
13
14
  * GET /api/sample/database
14
15
  *
15
- * Example: Retrieve recent scores with user information
16
+ * Example: Retrieve items from the database
16
17
  */
17
18
  export async function GET(c: Context): Promise<Response> {
18
19
  try {
19
20
  const db = getDb(c.env.DB)
20
21
 
21
- const scores = await db.query.scores.findMany({
22
+ const items = await db.query.items.findMany({
22
23
  limit: 10,
23
- orderBy: desc(schema.scores.id),
24
- with: {
25
- user: true,
26
- },
24
+ orderBy: desc(schema.items.id),
27
25
  })
28
26
 
29
27
  return c.json({
30
28
  success: true,
31
29
  data: {
32
- scores,
33
- total: scores.length,
30
+ items,
31
+ total: items.length,
34
32
  },
35
33
  })
36
34
  } catch (error) {
37
35
  return c.json(
38
36
  {
39
37
  success: false,
40
- error: 'Failed to fetch scores',
38
+ error: 'Failed to fetch items',
41
39
  details: error instanceof Error ? error.message : String(error),
42
40
  },
43
41
  500,
@@ -48,55 +46,36 @@ export async function GET(c: Context): Promise<Response> {
48
46
  /**
49
47
  * POST /api/sample/database
50
48
  *
51
- * Example: Submit a new score for a user
49
+ * Example: Create a new item in the database
52
50
  */
53
51
  export async function POST(c: Context): Promise<Response> {
54
52
  try {
55
- const { score, level } = await c.req.json()
53
+ const { name, data } = await c.req.json()
56
54
 
57
- if (!score || score < 0) {
58
- return c.json({ success: false, error: 'Invalid score value' }, 400)
55
+ if (!name || typeof name !== 'string') {
56
+ return c.json({ success: false, error: 'name is required' }, 400)
59
57
  }
60
58
 
61
59
  const db = getDb(c.env.DB)
62
60
 
63
- // Get or create demo user
64
- let user = await db.select().from(schema.users).limit(1).get()
65
-
66
- if (!user) {
67
- ;[user] = await db
68
- .insert(schema.users)
69
- .values({
70
- name: 'Demo Player',
71
- updatedAt: new Date().toISOString(),
72
- createdAt: new Date().toISOString(),
73
- })
74
- .returning()
75
- }
76
-
77
- if (!user) {
78
- return c.json({ success: false, error: 'Failed to create user' }, 500)
79
- }
80
-
81
- const [newScore] = await db
82
- .insert(schema.scores)
61
+ const [newItem] = await db
62
+ .insert(schema.items)
83
63
  .values({
84
- userId: user.id,
85
- score,
86
- level: level ?? 1,
64
+ name,
65
+ data: data || {},
87
66
  createdAt: new Date().toISOString(),
88
67
  })
89
68
  .returning()
90
69
 
91
70
  return c.json({
92
71
  success: true,
93
- data: { score: newScore },
72
+ data: { item: newItem },
94
73
  })
95
74
  } catch (error) {
96
75
  return c.json(
97
76
  {
98
77
  success: false,
99
- error: 'Failed to save score',
78
+ error: 'Failed to create item',
100
79
  details: error instanceof Error ? error.message : String(error),
101
80
  },
102
81
  500,
@@ -2,52 +2,56 @@
2
2
  * Sample KV Storage API Route
3
3
  *
4
4
  * This route will be available at: https://<your-game-slug>.playcademy.gg/api/sample/kv
5
+ *
6
+ * Demonstrates KV storage patterns with user-scoped keys.
5
7
  */
6
8
 
7
9
  /**
8
- * Player state stored in KV
10
+ * Example data structure for KV storage
9
11
  */
10
- interface PlayerState {
11
- /** Player's current score */
12
- score: number
13
- /** Current level */
14
- level: number
15
- /** Last played timestamp */
16
- lastPlayed: string
12
+ interface UserData {
13
+ /** Arbitrary data payload */
14
+ data: Record<string, unknown>
15
+ /** Last updated timestamp */
16
+ updatedAt: string
17
17
  }
18
18
 
19
19
  /**
20
20
  * GET /api/sample/kv
21
21
  *
22
- * Retrieve player state from KV storage
22
+ * Retrieve user data from KV storage
23
+ * Uses authenticated playcademyUser from Bearer token
23
24
  */
24
25
  export async function GET(c: Context): Promise<Response> {
25
26
  try {
26
- // Get user ID from query parameter
27
- const userId = c.req.query('userId') || 'demo-user'
27
+ const playcademyUser = c.get('playcademyUser')
28
+
29
+ if (!playcademyUser?.sub) {
30
+ return c.json({ success: false, error: 'Authentication required' }, 401)
31
+ }
28
32
 
29
- // Read from KV using user-specific key
30
- const stateJson = await c.env.KV.get(`user:${userId}:state`)
33
+ // Read from KV using user-scoped key pattern
34
+ const dataJson = await c.env.KV.get(`user:${playcademyUser.sub}:data`)
31
35
 
32
- if (!stateJson) {
36
+ if (!dataJson) {
33
37
  return c.json({
34
38
  success: true,
35
39
  data: null,
36
- message: 'No saved state found',
40
+ message: 'No data found',
37
41
  })
38
42
  }
39
43
 
40
- const state = JSON.parse(stateJson) as PlayerState
44
+ const userData = JSON.parse(dataJson) as UserData
41
45
 
42
46
  return c.json({
43
47
  success: true,
44
- data: state,
48
+ data: userData,
45
49
  })
46
50
  } catch (error) {
47
51
  return c.json(
48
52
  {
49
53
  success: false,
50
- error: 'Failed to fetch player state',
54
+ error: 'Failed to fetch user data',
51
55
  details: error instanceof Error ? error.message : String(error),
52
56
  },
53
57
  500,
@@ -58,44 +62,42 @@ export async function GET(c: Context): Promise<Response> {
58
62
  /**
59
63
  * POST /api/sample/kv
60
64
  *
61
- * Save player state to KV storage
65
+ * Save user data to KV storage
66
+ * Uses authenticated playcademyUser from Bearer token
62
67
  */
63
68
  export async function POST(c: Context): Promise<Response> {
64
69
  try {
65
- const body = (await c.req.json()) as Partial<PlayerState> & { userId?: string }
66
-
67
- if (typeof body.score !== 'number' || body.score < 0) {
68
- return c.json(
69
- {
70
- success: false,
71
- error: 'Invalid score value',
72
- },
73
- 400,
74
- )
70
+ const playcademyUser = c.get('playcademyUser')
71
+
72
+ if (!playcademyUser?.sub) {
73
+ return c.json({ success: false, error: 'Authentication required' }, 401)
75
74
  }
76
75
 
77
- const userId = body.userId || 'demo-user'
76
+ const body = (await c.req.json()) as { data: Record<string, unknown> }
78
77
 
79
- // Prepare player state
80
- const state: PlayerState = {
81
- score: body.score,
82
- level: body.level ?? 1,
83
- lastPlayed: new Date().toISOString(),
78
+ if (!body.data || typeof body.data !== 'object') {
79
+ return c.json({ success: false, error: 'data object is required' }, 400)
84
80
  }
85
81
 
86
- // Write to KV using user-specific key
87
- await c.env.KV.put(`user:${userId}:state`, JSON.stringify(state))
82
+ // Prepare user data with timestamp
83
+ const userData: UserData = {
84
+ data: body.data,
85
+ updatedAt: new Date().toISOString(),
86
+ }
87
+
88
+ // Write to KV using user-scoped key pattern
89
+ await c.env.KV.put(`user:${playcademyUser.sub}:data`, JSON.stringify(userData))
88
90
 
89
91
  return c.json({
90
92
  success: true,
91
- data: state,
92
- message: 'Player state saved successfully',
93
+ data: userData,
94
+ message: 'Data saved successfully',
93
95
  })
94
96
  } catch (error) {
95
97
  return c.json(
96
98
  {
97
99
  success: false,
98
- error: 'Failed to save player state',
100
+ error: 'Failed to save data',
99
101
  details: error instanceof Error ? error.message : String(error),
100
102
  },
101
103
  500,
@@ -106,24 +108,29 @@ export async function POST(c: Context): Promise<Response> {
106
108
  /**
107
109
  * DELETE /api/sample/kv
108
110
  *
109
- * Clear player state from KV storage
111
+ * Clear user data from KV storage
112
+ * Uses authenticated playcademyUser from Bearer token
110
113
  */
111
114
  export async function DELETE(c: Context): Promise<Response> {
112
115
  try {
113
- const userId = c.req.query('userId') || 'demo-user'
116
+ const playcademyUser = c.get('playcademyUser')
117
+
118
+ if (!playcademyUser?.sub) {
119
+ return c.json({ success: false, error: 'Authentication required' }, 401)
120
+ }
114
121
 
115
- // Delete from KV
116
- await c.env.KV.delete(`user:${userId}:state`)
122
+ // Delete from KV using user-scoped key pattern
123
+ await c.env.KV.delete(`user:${playcademyUser.sub}:data`)
117
124
 
118
125
  return c.json({
119
126
  success: true,
120
- message: 'Player state cleared successfully',
127
+ message: 'Data cleared successfully',
121
128
  })
122
129
  } catch (error) {
123
130
  return c.json(
124
131
  {
125
132
  success: false,
126
- error: 'Failed to clear player state',
133
+ error: 'Failed to clear data',
127
134
  details: error instanceof Error ? error.message : String(error),
128
135
  },
129
136
  500,
@@ -9,7 +9,7 @@ import { drizzleAdapter } from 'better-auth/adapters/drizzle'
9
9
 
10
10
  import { playcademy } from '@playcademy/better-auth/server'
11
11
 
12
- import { getDb } from '../../db'
12
+ import { getDb } from '../db'
13
13
 
14
14
  function getAuthSecret(c: Context): string {
15
15
  const secret = c.env.secrets?.BETTER_AUTH_SECRET
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Example Schema
3
+ *
4
+ * Define your database tables here using Drizzle ORM.
5
+ * This is a starter example - customize for your game's needs.
6
+ */
7
+
8
+ import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
9
+
10
+ /**
11
+ * Example items table
12
+ *
13
+ * Demonstrates common Drizzle patterns:
14
+ * - Auto-incrementing primary key
15
+ * - Required and optional fields
16
+ * - JSON column for flexible data
17
+ * - Timestamp tracking
18
+ */
19
+ export const items = sqliteTable('items', {
20
+ /** Unique item ID */
21
+ id: integer('id').primaryKey({ autoIncrement: true }),
22
+ /** Item name */
23
+ name: text('name').notNull(),
24
+ /** Flexible JSON data column for item-specific attributes */
25
+ data: text('data', { mode: 'json' }).$type<Record<string, unknown>>(),
26
+ /** Timestamp when item was created */
27
+ createdAt: text('created_at').notNull(),
28
+ })
29
+
@@ -4,5 +4,4 @@
4
4
  * Export all schema definitions from this file.
5
5
  */
6
6
 
7
- export * from './users'
8
- export * from './scores'
7
+ export * from './example'
@@ -16,25 +16,17 @@ import * as schema from './schema'
16
16
  export async function seed(c: Context) {
17
17
  const db = getDb(c.env.DB)
18
18
 
19
- // Seed users
20
- const [user] = await db
21
- .insert(schema.users)
22
- .values({
23
- name: 'Demo User',
19
+ // Example: Seed some items
20
+ await db.insert(schema.items).values([
21
+ {
22
+ name: 'Example Item 1',
23
+ data: { type: 'demo', value: 42 },
24
24
  createdAt: new Date().toISOString(),
25
- updatedAt: new Date().toISOString(),
26
- })
27
- .returning()
28
-
29
- if (!user) {
30
- throw new Error('Failed to seed user')
31
- }
32
-
33
- // Seed scores
34
- await db.insert(schema.scores).values({
35
- userId: user.id,
36
- score: 100,
37
- level: 1,
38
- createdAt: new Date().toISOString(),
39
- })
25
+ },
26
+ {
27
+ name: 'Example Item 2',
28
+ data: { type: 'demo', value: 100 },
29
+ createdAt: new Date().toISOString(),
30
+ },
31
+ ])
40
32
  }
@@ -8,14 +8,6 @@ import * as schema from './schema'
8
8
 
9
9
  import type { InferSelectModel } from 'drizzle-orm'
10
10
 
11
- /** User record from database */
12
- export type User = InferSelectModel<typeof schema.users>
13
-
14
- /** Score record from database */
15
- export type Score = InferSelectModel<typeof schema.scores>
16
-
17
- /** Score with user information populated */
18
- export type ScoreWithUser = Score & {
19
- user: User | null
20
- }
11
+ /** Item record from database */
12
+ export type Item = InferSelectModel<typeof schema.items>
21
13
 
@@ -3,8 +3,8 @@ import { getPath } from 'playcademy/db'
3
3
  import { defineConfig } from 'drizzle-kit'
4
4
 
5
5
  export default defineConfig({
6
- schema: './db/schema/index.ts',
7
- out: './db/migrations',
6
+ schema: './server/db/schema/index.ts',
7
+ // out: './server/db/migrations', optional
8
8
  dialect: 'sqlite',
9
9
  dbCredentials: {
10
10
  url: getPath(),