@basicbenframework/core 0.1.0
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/.github/workflows/publish.yml +35 -0
- package/README.md +588 -0
- package/bin/cli.js +8 -0
- package/create-basicben-app/index.js +205 -0
- package/create-basicben-app/package.json +30 -0
- package/create-basicben-app/template/.env.example +24 -0
- package/create-basicben-app/template/README.md +59 -0
- package/create-basicben-app/template/basicben.config.js +33 -0
- package/create-basicben-app/template/index.html +54 -0
- package/create-basicben-app/template/migrations/001_create_users.js +15 -0
- package/create-basicben-app/template/migrations/002_create_posts.js +18 -0
- package/create-basicben-app/template/public/.gitkeep +0 -0
- package/create-basicben-app/template/seeds/01_users.js +29 -0
- package/create-basicben-app/template/seeds/02_posts.js +43 -0
- package/create-basicben-app/template/src/client/components/Alert.jsx +11 -0
- package/create-basicben-app/template/src/client/components/Avatar.jsx +11 -0
- package/create-basicben-app/template/src/client/components/BackLink.jsx +10 -0
- package/create-basicben-app/template/src/client/components/Button.jsx +19 -0
- package/create-basicben-app/template/src/client/components/Card.jsx +10 -0
- package/create-basicben-app/template/src/client/components/Empty.jsx +6 -0
- package/create-basicben-app/template/src/client/components/Input.jsx +12 -0
- package/create-basicben-app/template/src/client/components/Loading.jsx +6 -0
- package/create-basicben-app/template/src/client/components/Logo.jsx +40 -0
- package/create-basicben-app/template/src/client/components/Nav/DarkModeToggle.jsx +23 -0
- package/create-basicben-app/template/src/client/components/Nav/DesktopNav.jsx +32 -0
- package/create-basicben-app/template/src/client/components/Nav/MobileNav.jsx +107 -0
- package/create-basicben-app/template/src/client/components/NavLink.jsx +10 -0
- package/create-basicben-app/template/src/client/components/PageHeader.jsx +8 -0
- package/create-basicben-app/template/src/client/components/PostCard.jsx +19 -0
- package/create-basicben-app/template/src/client/components/Textarea.jsx +12 -0
- package/create-basicben-app/template/src/client/components/ThemeContext.jsx +5 -0
- package/create-basicben-app/template/src/client/contexts/ToastContext.jsx +94 -0
- package/create-basicben-app/template/src/client/layouts/AppLayout.jsx +60 -0
- package/create-basicben-app/template/src/client/layouts/AuthLayout.jsx +33 -0
- package/create-basicben-app/template/src/client/layouts/DocsLayout.jsx +60 -0
- package/create-basicben-app/template/src/client/layouts/RootLayout.jsx +25 -0
- package/create-basicben-app/template/src/client/pages/Auth.jsx +55 -0
- package/create-basicben-app/template/src/client/pages/Authentication.jsx +236 -0
- package/create-basicben-app/template/src/client/pages/Database.jsx +426 -0
- package/create-basicben-app/template/src/client/pages/Feed.jsx +34 -0
- package/create-basicben-app/template/src/client/pages/FeedPost.jsx +37 -0
- package/create-basicben-app/template/src/client/pages/GettingStarted.jsx +136 -0
- package/create-basicben-app/template/src/client/pages/Home.jsx +206 -0
- package/create-basicben-app/template/src/client/pages/PostForm.jsx +69 -0
- package/create-basicben-app/template/src/client/pages/Posts.jsx +59 -0
- package/create-basicben-app/template/src/client/pages/Profile.jsx +68 -0
- package/create-basicben-app/template/src/client/pages/Routing.jsx +207 -0
- package/create-basicben-app/template/src/client/pages/Testing.jsx +251 -0
- package/create-basicben-app/template/src/client/pages/Validation.jsx +210 -0
- package/create-basicben-app/template/src/controllers/AuthController.js +81 -0
- package/create-basicben-app/template/src/controllers/HomeController.js +17 -0
- package/create-basicben-app/template/src/controllers/PostController.js +86 -0
- package/create-basicben-app/template/src/controllers/ProfileController.js +66 -0
- package/create-basicben-app/template/src/helpers/api.js +24 -0
- package/create-basicben-app/template/src/main.jsx +9 -0
- package/create-basicben-app/template/src/middleware/auth.js +16 -0
- package/create-basicben-app/template/src/models/Post.js +63 -0
- package/create-basicben-app/template/src/models/User.js +42 -0
- package/create-basicben-app/template/src/routes/App.jsx +38 -0
- package/create-basicben-app/template/src/routes/api/auth.js +7 -0
- package/create-basicben-app/template/src/routes/api/posts.js +15 -0
- package/create-basicben-app/template/src/routes/api/profile.js +8 -0
- package/create-basicben-app/template/src/server/index.js +16 -0
- package/create-basicben-app/template/vite.config.js +18 -0
- package/database.sqlite +0 -0
- package/my-test-app/.env.example +24 -0
- package/my-test-app/README.md +59 -0
- package/my-test-app/basicben.config.js +33 -0
- package/my-test-app/database.sqlite-shm +0 -0
- package/my-test-app/database.sqlite-wal +0 -0
- package/my-test-app/index.html +54 -0
- package/my-test-app/migrations/001_create_users.js +15 -0
- package/my-test-app/migrations/002_create_posts.js +18 -0
- package/my-test-app/package-lock.json +2160 -0
- package/my-test-app/package.json +29 -0
- package/my-test-app/public/.gitkeep +0 -0
- package/my-test-app/seeds/01_users.js +29 -0
- package/my-test-app/seeds/02_posts.js +43 -0
- package/my-test-app/src/client/components/Alert.jsx +11 -0
- package/my-test-app/src/client/components/Avatar.jsx +11 -0
- package/my-test-app/src/client/components/BackLink.jsx +10 -0
- package/my-test-app/src/client/components/Button.jsx +19 -0
- package/my-test-app/src/client/components/Card.jsx +10 -0
- package/my-test-app/src/client/components/Empty.jsx +6 -0
- package/my-test-app/src/client/components/Input.jsx +12 -0
- package/my-test-app/src/client/components/Loading.jsx +6 -0
- package/my-test-app/src/client/components/Logo.jsx +40 -0
- package/my-test-app/src/client/components/Nav/DarkModeToggle.jsx +23 -0
- package/my-test-app/src/client/components/Nav/DesktopNav.jsx +32 -0
- package/my-test-app/src/client/components/Nav/MobileNav.jsx +107 -0
- package/my-test-app/src/client/components/NavLink.jsx +10 -0
- package/my-test-app/src/client/components/PageHeader.jsx +8 -0
- package/my-test-app/src/client/components/PostCard.jsx +19 -0
- package/my-test-app/src/client/components/Textarea.jsx +12 -0
- package/my-test-app/src/client/components/ThemeContext.jsx +5 -0
- package/my-test-app/src/client/contexts/AppContext.jsx +13 -0
- package/my-test-app/src/client/contexts/ToastContext.jsx +94 -0
- package/my-test-app/src/client/layouts/AppLayout.jsx +60 -0
- package/my-test-app/src/client/layouts/AuthLayout.jsx +33 -0
- package/my-test-app/src/client/layouts/DocsLayout.jsx +60 -0
- package/my-test-app/src/client/layouts/RootLayout.jsx +25 -0
- package/my-test-app/src/client/pages/Auth.jsx +55 -0
- package/my-test-app/src/client/pages/Authentication.jsx +236 -0
- package/my-test-app/src/client/pages/Database.jsx +426 -0
- package/my-test-app/src/client/pages/Feed.jsx +34 -0
- package/my-test-app/src/client/pages/FeedPost.jsx +37 -0
- package/my-test-app/src/client/pages/GettingStarted.jsx +136 -0
- package/my-test-app/src/client/pages/Home.jsx +206 -0
- package/my-test-app/src/client/pages/PostForm.jsx +69 -0
- package/my-test-app/src/client/pages/Posts.jsx +59 -0
- package/my-test-app/src/client/pages/Profile.jsx +68 -0
- package/my-test-app/src/client/pages/Routing.jsx +207 -0
- package/my-test-app/src/client/pages/Testing.jsx +251 -0
- package/my-test-app/src/client/pages/Validation.jsx +210 -0
- package/my-test-app/src/controllers/AuthController.js +81 -0
- package/my-test-app/src/controllers/HomeController.js +17 -0
- package/my-test-app/src/controllers/PostController.js +86 -0
- package/my-test-app/src/controllers/ProfileController.js +66 -0
- package/my-test-app/src/helpers/api.js +24 -0
- package/my-test-app/src/main.jsx +9 -0
- package/my-test-app/src/middleware/auth.js +16 -0
- package/my-test-app/src/models/Post.js +63 -0
- package/my-test-app/src/models/User.js +42 -0
- package/my-test-app/src/routes/App.jsx +38 -0
- package/my-test-app/src/routes/api/auth.js +7 -0
- package/my-test-app/src/routes/api/posts.js +15 -0
- package/my-test-app/src/routes/api/profile.js +8 -0
- package/my-test-app/src/server/index.js +16 -0
- package/my-test-app/vite.config.js +18 -0
- package/package.json +61 -0
- package/scripts/test-app.sh +59 -0
- package/src/auth/jwt.js +195 -0
- package/src/auth/password.js +132 -0
- package/src/cli/colors.js +31 -0
- package/src/cli/dispatcher.js +168 -0
- package/src/cli/parser.js +91 -0
- package/src/client/context.js +4 -0
- package/src/client/hooks.js +50 -0
- package/src/client/index.js +3 -0
- package/src/client/router.js +184 -0
- package/src/commands/build.js +155 -0
- package/src/commands/dev.js +206 -0
- package/src/commands/help.js +84 -0
- package/src/commands/make-controller.js +36 -0
- package/src/commands/make-middleware.js +44 -0
- package/src/commands/make-migration.js +51 -0
- package/src/commands/make-model.js +38 -0
- package/src/commands/make-route.js +36 -0
- package/src/commands/make-seed.js +38 -0
- package/src/commands/migrate-fresh.js +32 -0
- package/src/commands/migrate-rollback.js +30 -0
- package/src/commands/migrate-status.js +41 -0
- package/src/commands/migrate.js +30 -0
- package/src/commands/seed.js +47 -0
- package/src/commands/start.js +69 -0
- package/src/commands/test.js +46 -0
- package/src/db/Grammar.js +125 -0
- package/src/db/QueryBuilder.js +476 -0
- package/src/db/adapters/neon.js +170 -0
- package/src/db/adapters/planetscale.js +146 -0
- package/src/db/adapters/postgres.js +166 -0
- package/src/db/adapters/sqlite.js +125 -0
- package/src/db/adapters/turso.js +165 -0
- package/src/db/index.js +156 -0
- package/src/db/migrator.js +250 -0
- package/src/db/seeder.js +124 -0
- package/src/index.js +12 -0
- package/src/scaffolding/index.js +152 -0
- package/src/server/body-parser.js +159 -0
- package/src/server/cors.js +63 -0
- package/src/server/default-entry.js +13 -0
- package/src/server/http.js +221 -0
- package/src/server/index.js +168 -0
- package/src/server/loader.js +128 -0
- package/src/server/router.js +281 -0
- package/src/server/static.js +139 -0
- package/src/validation/index.js +436 -0
- package/src/vite/config.js +49 -0
- package/stubs/controller.stub +48 -0
- package/stubs/middleware-auth.stub +29 -0
- package/stubs/middleware.stub +9 -0
- package/stubs/migration.stub +17 -0
- package/stubs/model.stub +77 -0
- package/stubs/route.stub +13 -0
- package/stubs/seed.stub +16 -0
- package/stubs/vite.config.stub +18 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { validate, rules } from '@basicbenframework/core/validation'
|
|
2
|
+
import { signJwt, verifyJwt, hashPassword, verifyPassword } from '@basicbenframework/core/auth'
|
|
3
|
+
import { User } from '../models/User.js'
|
|
4
|
+
|
|
5
|
+
export const AuthController = {
|
|
6
|
+
async register(req, res) {
|
|
7
|
+
const result = await validate(req.body, {
|
|
8
|
+
name: [rules.required, rules.string, rules.min(2)],
|
|
9
|
+
email: [rules.required, rules.email],
|
|
10
|
+
password: [rules.required, rules.min(8)]
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
if (result.fails()) {
|
|
14
|
+
return res.json({ errors: result.errors }, 422)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { name, email, password } = req.body
|
|
18
|
+
|
|
19
|
+
// Check if email exists
|
|
20
|
+
const existing = await User.findByEmail(email)
|
|
21
|
+
if (existing) {
|
|
22
|
+
return res.json({ error: 'Email already registered' }, 400)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Create user
|
|
26
|
+
const user = await User.create({
|
|
27
|
+
name,
|
|
28
|
+
email,
|
|
29
|
+
password: await hashPassword(password)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// Generate token
|
|
33
|
+
const token = signJwt({ userId: user.id }, process.env.APP_KEY, { expiresIn: '7d' })
|
|
34
|
+
|
|
35
|
+
res.json({
|
|
36
|
+
user: { id: user.id, name: user.name, email: user.email },
|
|
37
|
+
token
|
|
38
|
+
})
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
async login(req, res) {
|
|
42
|
+
const { email, password } = req.body
|
|
43
|
+
|
|
44
|
+
if (!email || !password) {
|
|
45
|
+
return res.json({ error: 'Email and password required' }, 400)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const user = await User.findByEmail(email)
|
|
49
|
+
if (!user || !(await verifyPassword(password, user.password))) {
|
|
50
|
+
return res.json({ error: 'Invalid credentials' }, 401)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const token = signJwt({ userId: user.id }, process.env.APP_KEY, { expiresIn: '7d' })
|
|
54
|
+
|
|
55
|
+
res.json({
|
|
56
|
+
user: { id: user.id, name: user.name, email: user.email },
|
|
57
|
+
token
|
|
58
|
+
})
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
async user(req, res) {
|
|
62
|
+
const token = req.headers.authorization?.replace('Bearer ', '')
|
|
63
|
+
if (!token) {
|
|
64
|
+
return res.json({ error: 'No token provided' }, 401)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const payload = verifyJwt(token, process.env.APP_KEY)
|
|
68
|
+
if (!payload) {
|
|
69
|
+
return res.json({ error: 'Invalid token' }, 401)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const user = await User.find(payload.userId)
|
|
73
|
+
if (!user) {
|
|
74
|
+
return res.json({ error: 'User not found' }, 404)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
res.json({
|
|
78
|
+
user: { id: user.id, name: user.name, email: user.email }
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Home Controller
|
|
3
|
+
*
|
|
4
|
+
* Handles basic application endpoints.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const HomeController = {
|
|
8
|
+
/**
|
|
9
|
+
* Hello endpoint
|
|
10
|
+
*/
|
|
11
|
+
hello: async (req, res) => {
|
|
12
|
+
res.json({
|
|
13
|
+
message: 'Welcome to BasicBen!',
|
|
14
|
+
timestamp: new Date().toISOString()
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { validate, rules } from '@basicbenframework/core/validation'
|
|
2
|
+
import { Post } from '../models/Post.js'
|
|
3
|
+
|
|
4
|
+
export const PostController = {
|
|
5
|
+
async index(req, res) {
|
|
6
|
+
const posts = await Post.findByUser(req.userId)
|
|
7
|
+
res.json({ posts })
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
async show(req, res) {
|
|
11
|
+
const post = await Post.find(req.params.id)
|
|
12
|
+
if (!post || post.user_id !== req.userId) {
|
|
13
|
+
return res.json({ error: 'Post not found' }, 404)
|
|
14
|
+
}
|
|
15
|
+
res.json({ post })
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
async store(req, res) {
|
|
19
|
+
const result = await validate(req.body, {
|
|
20
|
+
title: [rules.required, rules.string, rules.min(3), rules.max(200)],
|
|
21
|
+
content: [rules.required, rules.string, rules.min(10)]
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
if (result.fails()) {
|
|
25
|
+
return res.json({ errors: result.errors }, 422)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { title, content, published } = req.body
|
|
29
|
+
const post = await Post.create({
|
|
30
|
+
user_id: req.userId,
|
|
31
|
+
title,
|
|
32
|
+
content,
|
|
33
|
+
published: published || false
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
res.json({ post }, 201)
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
async update(req, res) {
|
|
40
|
+
const post = await Post.find(req.params.id)
|
|
41
|
+
if (!post || post.user_id !== req.userId) {
|
|
42
|
+
return res.json({ error: 'Post not found' }, 404)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const result = await validate(req.body, {
|
|
46
|
+
title: [rules.required, rules.string, rules.min(3), rules.max(200)],
|
|
47
|
+
content: [rules.required, rules.string, rules.min(10)]
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
if (result.fails()) {
|
|
51
|
+
return res.json({ errors: result.errors }, 422)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const { title, content, published } = req.body
|
|
55
|
+
const updated = await Post.update(req.params.id, {
|
|
56
|
+
title,
|
|
57
|
+
content,
|
|
58
|
+
published: published ? 1 : 0
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
res.json({ post: updated })
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
async destroy(req, res) {
|
|
65
|
+
const post = await Post.find(req.params.id)
|
|
66
|
+
if (!post || post.user_id !== req.userId) {
|
|
67
|
+
return res.json({ error: 'Post not found' }, 404)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await Post.delete(req.params.id)
|
|
71
|
+
res.json({ message: 'Post deleted' })
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
async feed(req, res) {
|
|
75
|
+
const posts = await Post.findPublished()
|
|
76
|
+
res.json({ posts })
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
async feedShow(req, res) {
|
|
80
|
+
const post = await Post.findPublishedById(req.params.id)
|
|
81
|
+
if (!post) {
|
|
82
|
+
return res.json({ error: 'Post not found' }, 404)
|
|
83
|
+
}
|
|
84
|
+
res.json({ post })
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { validate, rules } from '@basicbenframework/core/validation'
|
|
2
|
+
import { User } from '../models/User.js'
|
|
3
|
+
import { createHash } from 'node:crypto'
|
|
4
|
+
|
|
5
|
+
function hashPassword(password) {
|
|
6
|
+
return createHash('sha256').update(password).digest('hex')
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const ProfileController = {
|
|
10
|
+
async show(req, res) {
|
|
11
|
+
const user = await User.find(req.userId)
|
|
12
|
+
if (!user) {
|
|
13
|
+
return res.json({ error: 'User not found' }, 404)
|
|
14
|
+
}
|
|
15
|
+
res.json({
|
|
16
|
+
user: { id: user.id, name: user.name, email: user.email, created_at: user.created_at }
|
|
17
|
+
})
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
async update(req, res) {
|
|
21
|
+
const result = await validate(req.body, {
|
|
22
|
+
name: [rules.required, rules.string, rules.min(2)],
|
|
23
|
+
email: [rules.required, rules.email]
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
if (result.fails()) {
|
|
27
|
+
return res.json({ errors: result.errors }, 422)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { name, email } = req.body
|
|
31
|
+
const user = await User.find(req.userId)
|
|
32
|
+
|
|
33
|
+
if (email !== user.email) {
|
|
34
|
+
const existing = await User.findByEmail(email)
|
|
35
|
+
if (existing) {
|
|
36
|
+
return res.json({ error: 'Email already taken' }, 400)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const updated = await User.update(req.userId, { name, email })
|
|
41
|
+
res.json({
|
|
42
|
+
user: { id: updated.id, name: updated.name, email: updated.email }
|
|
43
|
+
})
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
async changePassword(req, res) {
|
|
47
|
+
const result = await validate(req.body, {
|
|
48
|
+
currentPassword: [rules.required],
|
|
49
|
+
newPassword: [rules.required, rules.min(8)]
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
if (result.fails()) {
|
|
53
|
+
return res.json({ errors: result.errors }, 422)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { currentPassword, newPassword } = req.body
|
|
57
|
+
const user = await User.find(req.userId)
|
|
58
|
+
|
|
59
|
+
if (user.password !== hashPassword(currentPassword)) {
|
|
60
|
+
return res.json({ error: 'Current password is incorrect' }, 400)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await User.update(req.userId, { password: hashPassword(newPassword) })
|
|
64
|
+
res.json({ message: 'Password updated successfully' })
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const api = async (path, options = {}) => {
|
|
2
|
+
const token = localStorage.getItem('token')
|
|
3
|
+
let res
|
|
4
|
+
try {
|
|
5
|
+
res = await fetch(path, {
|
|
6
|
+
...options,
|
|
7
|
+
headers: {
|
|
8
|
+
'Content-Type': 'application/json',
|
|
9
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
10
|
+
...options.headers
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
} catch {
|
|
14
|
+
throw new Error('Unable to connect to server')
|
|
15
|
+
}
|
|
16
|
+
let data
|
|
17
|
+
try {
|
|
18
|
+
data = await res.json()
|
|
19
|
+
} catch {
|
|
20
|
+
throw new Error(res.ok ? 'Invalid response from server' : `Server error (${res.status})`)
|
|
21
|
+
}
|
|
22
|
+
if (!res.ok) throw new Error(data.error || Object.values(data.errors || {})[0]?.[0] || 'Request failed')
|
|
23
|
+
return data
|
|
24
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { verifyJwt } from '@basicbenframework/core/auth'
|
|
2
|
+
|
|
3
|
+
export const auth = async (req, res, next) => {
|
|
4
|
+
const token = req.headers.authorization?.replace('Bearer ', '')
|
|
5
|
+
if (!token) {
|
|
6
|
+
return res.json({ error: 'Unauthorized' }, 401)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const payload = verifyJwt(token, process.env.APP_KEY)
|
|
10
|
+
if (!payload) {
|
|
11
|
+
return res.json({ error: 'Invalid token' }, 401)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
req.userId = payload.userId
|
|
15
|
+
next()
|
|
16
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { getDb } from '@basicbenframework/core/db'
|
|
2
|
+
|
|
3
|
+
export const Post = {
|
|
4
|
+
async all() {
|
|
5
|
+
const db = await getDb()
|
|
6
|
+
return db.all('SELECT * FROM posts ORDER BY created_at DESC')
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
async find(id) {
|
|
10
|
+
const db = await getDb()
|
|
11
|
+
return db.get('SELECT * FROM posts WHERE id = ?', [id])
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
async findByUser(userId) {
|
|
15
|
+
const db = await getDb()
|
|
16
|
+
return db.all('SELECT * FROM posts WHERE user_id = ? ORDER BY created_at DESC', [userId])
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
async findPublished() {
|
|
20
|
+
const db = await getDb()
|
|
21
|
+
return db.all(`
|
|
22
|
+
SELECT posts.*, users.name as author_name
|
|
23
|
+
FROM posts
|
|
24
|
+
JOIN users ON posts.user_id = users.id
|
|
25
|
+
WHERE posts.published = 1
|
|
26
|
+
ORDER BY posts.created_at DESC
|
|
27
|
+
`)
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
async findPublishedById(id) {
|
|
31
|
+
const db = await getDb()
|
|
32
|
+
return db.get(`
|
|
33
|
+
SELECT posts.*, users.name as author_name
|
|
34
|
+
FROM posts
|
|
35
|
+
JOIN users ON posts.user_id = users.id
|
|
36
|
+
WHERE posts.id = ? AND posts.published = 1
|
|
37
|
+
`, [id])
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
async create(data) {
|
|
41
|
+
const db = await getDb()
|
|
42
|
+
const result = await db.run(
|
|
43
|
+
'INSERT INTO posts (user_id, title, content, published) VALUES (?, ?, ?, ?)',
|
|
44
|
+
[data.user_id, data.title, data.content, data.published ? 1 : 0]
|
|
45
|
+
)
|
|
46
|
+
return { id: result.lastInsertRowid, ...data }
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
async update(id, data) {
|
|
50
|
+
const db = await getDb()
|
|
51
|
+
const fields = Object.keys(data).map(k => `${k} = ?`).join(', ')
|
|
52
|
+
await db.run(
|
|
53
|
+
`UPDATE posts SET ${fields}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`,
|
|
54
|
+
[...Object.values(data), id]
|
|
55
|
+
)
|
|
56
|
+
return this.find(id)
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
async delete(id) {
|
|
60
|
+
const db = await getDb()
|
|
61
|
+
return db.run('DELETE FROM posts WHERE id = ?', [id])
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getDb } from '@basicbenframework/core/db'
|
|
2
|
+
|
|
3
|
+
export const User = {
|
|
4
|
+
async all() {
|
|
5
|
+
const db = await getDb()
|
|
6
|
+
return db.all('SELECT * FROM users')
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
async find(id) {
|
|
10
|
+
const db = await getDb()
|
|
11
|
+
return db.get('SELECT * FROM users WHERE id = ?', [id])
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
async findByEmail(email) {
|
|
15
|
+
const db = await getDb()
|
|
16
|
+
return db.get('SELECT * FROM users WHERE email = ?', [email])
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
async create(data) {
|
|
20
|
+
const db = await getDb()
|
|
21
|
+
const result = await db.run(
|
|
22
|
+
'INSERT INTO users (name, email, password) VALUES (?, ?, ?)',
|
|
23
|
+
[data.name, data.email, data.password]
|
|
24
|
+
)
|
|
25
|
+
return { id: result.lastInsertRowid, ...data }
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
async update(id, data) {
|
|
29
|
+
const db = await getDb()
|
|
30
|
+
const fields = Object.keys(data).map(k => `${k} = ?`).join(', ')
|
|
31
|
+
await db.run(
|
|
32
|
+
`UPDATE users SET ${fields} WHERE id = ?`,
|
|
33
|
+
[...Object.values(data), id]
|
|
34
|
+
)
|
|
35
|
+
return this.find(id)
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
async delete(id) {
|
|
39
|
+
const db = await getDb()
|
|
40
|
+
return db.run('DELETE FROM users WHERE id = ?', [id])
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createClientApp } from '@basicbenframework/core/client'
|
|
2
|
+
import { AppLayout } from '../client/layouts/AppLayout'
|
|
3
|
+
import { AuthLayout } from '../client/layouts/AuthLayout'
|
|
4
|
+
import { DocsLayout } from '../client/layouts/DocsLayout'
|
|
5
|
+
import { Home } from '../client/pages/Home'
|
|
6
|
+
import { Auth } from '../client/pages/Auth'
|
|
7
|
+
import { Feed } from '../client/pages/Feed'
|
|
8
|
+
import { FeedPost } from '../client/pages/FeedPost'
|
|
9
|
+
import { Posts } from '../client/pages/Posts'
|
|
10
|
+
import { PostForm } from '../client/pages/PostForm'
|
|
11
|
+
import { Profile } from '../client/pages/Profile'
|
|
12
|
+
import { GettingStarted } from '../client/pages/GettingStarted'
|
|
13
|
+
import { Database } from '../client/pages/Database'
|
|
14
|
+
import { Routing } from '../client/pages/Routing'
|
|
15
|
+
import { Authentication } from '../client/pages/Authentication'
|
|
16
|
+
import { Validation } from '../client/pages/Validation'
|
|
17
|
+
import { Testing } from '../client/pages/Testing'
|
|
18
|
+
|
|
19
|
+
export default createClientApp({
|
|
20
|
+
layout: AppLayout,
|
|
21
|
+
routes: {
|
|
22
|
+
'/': Home,
|
|
23
|
+
'/login': { component: Auth, layout: AuthLayout, guest: true },
|
|
24
|
+
'/register': { component: Auth, layout: AuthLayout, guest: true },
|
|
25
|
+
'/feed': Feed,
|
|
26
|
+
'/feed/:id': FeedPost,
|
|
27
|
+
'/posts': { component: Posts, auth: true },
|
|
28
|
+
'/posts/new': { component: PostForm, auth: true },
|
|
29
|
+
'/posts/:id/edit': { component: PostForm, auth: true },
|
|
30
|
+
'/profile': { component: Profile, auth: true },
|
|
31
|
+
'/docs': { component: GettingStarted, layout: DocsLayout },
|
|
32
|
+
'/docs/routing': { component: Routing, layout: DocsLayout },
|
|
33
|
+
'/docs/database': { component: Database, layout: DocsLayout },
|
|
34
|
+
'/docs/authentication': { component: Authentication, layout: DocsLayout },
|
|
35
|
+
'/docs/validation': { component: Validation, layout: DocsLayout },
|
|
36
|
+
'/docs/testing': { component: Testing, layout: DocsLayout },
|
|
37
|
+
}
|
|
38
|
+
})
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { AuthController } from '../../controllers/AuthController.js'
|
|
2
|
+
|
|
3
|
+
export default (router) => {
|
|
4
|
+
router.post('/api/auth/register', AuthController.register)
|
|
5
|
+
router.post('/api/auth/login', AuthController.login)
|
|
6
|
+
router.get('/api/user', AuthController.user)
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PostController } from '../../controllers/PostController.js'
|
|
2
|
+
import { auth } from '../../middleware/auth.js'
|
|
3
|
+
|
|
4
|
+
export default (router) => {
|
|
5
|
+
// Public feed routes
|
|
6
|
+
router.get('/api/feed', PostController.feed)
|
|
7
|
+
router.get('/api/feed/:id', PostController.feedShow)
|
|
8
|
+
|
|
9
|
+
// Authenticated post routes
|
|
10
|
+
router.get('/api/posts', auth, PostController.index)
|
|
11
|
+
router.post('/api/posts', auth, PostController.store)
|
|
12
|
+
router.get('/api/posts/:id', auth, PostController.show)
|
|
13
|
+
router.put('/api/posts/:id', auth, PostController.update)
|
|
14
|
+
router.delete('/api/posts/:id', auth, PostController.destroy)
|
|
15
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ProfileController } from '../../controllers/ProfileController.js'
|
|
2
|
+
import { auth } from '../../middleware/auth.js'
|
|
3
|
+
|
|
4
|
+
export default (router) => {
|
|
5
|
+
router.get('/api/profile', auth, ProfileController.show)
|
|
6
|
+
router.put('/api/profile', auth, ProfileController.update)
|
|
7
|
+
router.put('/api/profile/password', auth, ProfileController.changePassword)
|
|
8
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server entry point (optional)
|
|
3
|
+
*
|
|
4
|
+
* Delete this file to use the default server.
|
|
5
|
+
* Customize here for websockets, custom middleware, etc.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createServer } from '@basicbenframework/core/server'
|
|
9
|
+
|
|
10
|
+
const app = await createServer()
|
|
11
|
+
|
|
12
|
+
const port = process.env.PORT || 3001
|
|
13
|
+
|
|
14
|
+
app.listen(port, () => {
|
|
15
|
+
console.log(`Server running at http://localhost:${port}`)
|
|
16
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import react from '@vitejs/plugin-react'
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [react()],
|
|
6
|
+
server: {
|
|
7
|
+
port: parseInt(process.env.VITE_PORT || 3000),
|
|
8
|
+
proxy: {
|
|
9
|
+
'/api': {
|
|
10
|
+
target: `http://localhost:${process.env.PORT || 3001}`,
|
|
11
|
+
changeOrigin: true
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
build: {
|
|
16
|
+
outDir: 'dist/client'
|
|
17
|
+
}
|
|
18
|
+
})
|
package/database.sqlite
ADDED
|
Binary file
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Application
|
|
2
|
+
APP_KEY=your-secret-key-here
|
|
3
|
+
|
|
4
|
+
# Server Ports
|
|
5
|
+
PORT=3001 # API server
|
|
6
|
+
VITE_PORT=3000 # Frontend dev server
|
|
7
|
+
|
|
8
|
+
# Database - uncomment ONE of the following:
|
|
9
|
+
|
|
10
|
+
# SQLite (default)
|
|
11
|
+
DATABASE_URL=./database.sqlite
|
|
12
|
+
|
|
13
|
+
# PostgreSQL
|
|
14
|
+
# DATABASE_URL=postgres://user:pass@localhost:5432/mydb
|
|
15
|
+
|
|
16
|
+
# Turso (edge SQLite)
|
|
17
|
+
# TURSO_URL=libsql://your-db-name.turso.io
|
|
18
|
+
# TURSO_AUTH_TOKEN=your-auth-token
|
|
19
|
+
|
|
20
|
+
# PlanetScale (serverless MySQL)
|
|
21
|
+
# DATABASE_URL=mysql://user:pass@host/database?ssl={"rejectUnauthorized":true}
|
|
22
|
+
|
|
23
|
+
# Neon (serverless Postgres)
|
|
24
|
+
# DATABASE_URL=postgres://user:pass@ep-cool-name.us-east-2.aws.neon.tech/mydb?sslmode=require
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# My BasicBen App
|
|
2
|
+
|
|
3
|
+
Built with [BasicBen](https://github.com/BasicBenFramework/basicben-framework) — a full-stack React framework with zero runtime dependencies.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
npm run dev
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Open [http://localhost:3000](http://localhost:3000) to view your app.
|
|
13
|
+
|
|
14
|
+
## Commands
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm run dev # Start development server
|
|
18
|
+
npm run build # Build for production
|
|
19
|
+
npm run start # Run production server
|
|
20
|
+
npm run test # Run tests
|
|
21
|
+
|
|
22
|
+
npm run migrate # Run database migrations
|
|
23
|
+
npm run migrate:fresh # Reset and re-run all migrations
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Project Structure
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
src/
|
|
30
|
+
├── main.jsx # React entry point
|
|
31
|
+
├── routes/
|
|
32
|
+
│ ├── App.jsx # Client routes
|
|
33
|
+
│ └── api/ # API routes (auto-loaded)
|
|
34
|
+
├── controllers/ # Business logic
|
|
35
|
+
├── models/ # Database queries
|
|
36
|
+
├── middleware/ # Route middleware
|
|
37
|
+
├── helpers/ # Utility functions
|
|
38
|
+
└── client/
|
|
39
|
+
├── layouts/ # Layout components
|
|
40
|
+
├── pages/ # Page components
|
|
41
|
+
└── components/ # Reusable UI
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Scaffolding
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx basicben make:controller User
|
|
48
|
+
npx basicben make:model User
|
|
49
|
+
npx basicben make:route users
|
|
50
|
+
npx basicben make:migration create_users
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Configuration
|
|
54
|
+
|
|
55
|
+
Edit `basicben.config.js` to customize server settings, CORS, database, and more.
|
|
56
|
+
|
|
57
|
+
## Documentation
|
|
58
|
+
|
|
59
|
+
Full documentation: [github.com/BasicBenFramework/basicben-framework](https://github.com/BasicBenFramework/basicben-framework)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BasicBen Configuration
|
|
3
|
+
*
|
|
4
|
+
* See documentation for all available options.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
// Server options
|
|
9
|
+
port: 3001,
|
|
10
|
+
|
|
11
|
+
// CORS configuration
|
|
12
|
+
cors: {
|
|
13
|
+
origin: '*',
|
|
14
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
|
|
15
|
+
credentials: true
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
// Body parser options
|
|
19
|
+
bodyParser: {
|
|
20
|
+
limit: '1mb'
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
// Static file serving
|
|
24
|
+
static: {
|
|
25
|
+
dir: 'public'
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
// Database configuration
|
|
29
|
+
// db: {
|
|
30
|
+
// driver: 'sqlite',
|
|
31
|
+
// url: process.env.DATABASE_URL || './data.db'
|
|
32
|
+
// }
|
|
33
|
+
}
|
|
Binary file
|
|
Binary file
|