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.
- package/dist/constants.d.ts +24 -13
- package/dist/constants.js +29 -15
- package/dist/db.js +46 -18
- package/dist/index.js +248 -259
- package/dist/templates/api/sample-database.ts.template +18 -39
- package/dist/templates/api/sample-kv.ts.template +53 -46
- package/dist/templates/auth/auth.ts.template +1 -1
- package/dist/templates/database/db-schema-example.ts.template +29 -0
- package/dist/templates/database/db-schema-index.ts.template +1 -2
- package/dist/templates/database/db-seed.ts.template +12 -20
- package/dist/templates/database/db-types.ts.template +2 -10
- package/dist/templates/database/drizzle-config.ts.template +2 -2
- package/dist/utils.js +85 -62
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/templates/database/db-schema-scores.ts.template +0 -43
- package/dist/templates/database/db-schema-users.ts.template +0 -23
|
@@ -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
|
|
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
|
|
22
|
+
const items = await db.query.items.findMany({
|
|
22
23
|
limit: 10,
|
|
23
|
-
orderBy: desc(schema.
|
|
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
|
-
|
|
33
|
-
total:
|
|
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
|
|
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:
|
|
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 {
|
|
53
|
+
const { name, data } = await c.req.json()
|
|
56
54
|
|
|
57
|
-
if (!
|
|
58
|
-
return c.json({ success: false, error: '
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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: {
|
|
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
|
|
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
|
-
*
|
|
10
|
+
* Example data structure for KV storage
|
|
9
11
|
*/
|
|
10
|
-
interface
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
|
|
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
|
|
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
|
-
|
|
27
|
-
|
|
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-
|
|
30
|
-
const
|
|
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 (!
|
|
36
|
+
if (!dataJson) {
|
|
33
37
|
return c.json({
|
|
34
38
|
success: true,
|
|
35
39
|
data: null,
|
|
36
|
-
message: 'No
|
|
40
|
+
message: 'No data found',
|
|
37
41
|
})
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
const
|
|
44
|
+
const userData = JSON.parse(dataJson) as UserData
|
|
41
45
|
|
|
42
46
|
return c.json({
|
|
43
47
|
success: true,
|
|
44
|
-
data:
|
|
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
|
|
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
|
|
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
|
|
66
|
-
|
|
67
|
-
if (
|
|
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
|
|
76
|
+
const body = (await c.req.json()) as { data: Record<string, unknown> }
|
|
78
77
|
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
//
|
|
87
|
-
|
|
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:
|
|
92
|
-
message: '
|
|
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
|
|
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
|
|
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
|
|
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:${
|
|
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: '
|
|
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
|
|
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 '
|
|
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
|
+
|
|
@@ -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
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
/**
|
|
12
|
-
export type
|
|
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(),
|