postbase 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.
Files changed (126) hide show
  1. package/.github/workflows/test.yml +74 -0
  2. package/CLA.md +60 -0
  3. package/CONTRIBUTORS.md +35 -0
  4. package/LICENSE +661 -0
  5. package/README.md +211 -0
  6. package/admin/404.html +33 -0
  7. package/admin/README.md +21 -0
  8. package/admin/index.html +15 -0
  9. package/admin/jsconfig.json +20 -0
  10. package/admin/lib/postbase.js +222 -0
  11. package/admin/package-lock.json +3746 -0
  12. package/admin/package.json +27 -0
  13. package/admin/public/assets/img/admin-ui.png +0 -0
  14. package/admin/public/assets/img/blank-profile-picture-960_720.webp +0 -0
  15. package/admin/public/assets/img/chart-active-users.png +0 -0
  16. package/admin/public/assets/img/icon-transparent.png +0 -0
  17. package/admin/src/App.jsx +48 -0
  18. package/admin/src/auth.js +11 -0
  19. package/admin/src/common/formatDateTime.js +18 -0
  20. package/admin/src/components/AuthPanel.jsx +88 -0
  21. package/admin/src/components/Header.jsx +67 -0
  22. package/admin/src/main.jsx +6 -0
  23. package/admin/src/pages/Dashboard.jsx +24 -0
  24. package/admin/src/pages/Home.jsx +52 -0
  25. package/admin/src/pages/Login.jsx +10 -0
  26. package/admin/src/pages/authentication/Users.jsx +199 -0
  27. package/admin/src/pages/firestore/Database.jsx +29 -0
  28. package/admin/src/pages/storage/files.jsx +29 -0
  29. package/admin/src/postbase.js +15 -0
  30. package/admin/src/styles.css +3 -0
  31. package/admin/tailwind.config.cjs +11 -0
  32. package/admin/template.env +2 -0
  33. package/admin/vite.config.js +21 -0
  34. package/assets/img/HomePageScreenshot.png +0 -0
  35. package/assets/img/better-auth-logo-dark.136b122f.png +0 -0
  36. package/assets/img/better-auth-logo-light.4b03f444.png +0 -0
  37. package/assets/img/expresjs.png +0 -0
  38. package/assets/img/icon-transparent.png +0 -0
  39. package/assets/img/icon.png +0 -0
  40. package/assets/img/letsencrypt-logo-horizontal.png +0 -0
  41. package/assets/img/logo.png +0 -0
  42. package/assets/img/node.js_logo.png +0 -0
  43. package/assets/img/nodejsLight.svg +39 -0
  44. package/assets/img/postgres.png +0 -0
  45. package/backend/README.md +49 -0
  46. package/backend/admin/auth.js +9 -0
  47. package/backend/app.js +68 -0
  48. package/backend/auth.js +92 -0
  49. package/backend/env.js +12 -0
  50. package/backend/lib/postbase/adminClient.js +520 -0
  51. package/backend/lib/postbase/compat/admin.js +44 -0
  52. package/backend/lib/postbase/db.js +17 -0
  53. package/backend/lib/postbase/genericRouter.js +603 -0
  54. package/backend/lib/postbase/local-storage.js +56 -0
  55. package/backend/lib/postbase/metadataCache.js +32 -0
  56. package/backend/lib/postbase/middlewares/auth.js +57 -0
  57. package/backend/lib/postbase/migrations/1765239687559_rtdb-nodes.js +93 -0
  58. package/backend/lib/postbase/package-lock.json +5873 -0
  59. package/backend/lib/postbase/package.json +19 -0
  60. package/backend/lib/postbase/rtdb/router.js +190 -0
  61. package/backend/lib/postbase/rtdb/rulesEngine.js +63 -0
  62. package/backend/lib/postbase/rtdb/ws.js +84 -0
  63. package/backend/lib/postbase/rulesEngine.js +62 -0
  64. package/backend/lib/postbase/storage.js +130 -0
  65. package/backend/lib/postbase/tests/README.md +22 -0
  66. package/backend/lib/postbase/tests/db.js +9 -0
  67. package/backend/lib/postbase/tests/rtdb.rest.test.js +46 -0
  68. package/backend/lib/postbase/tests/rtdb.ws.test.js +113 -0
  69. package/backend/lib/postbase/tests/rules.js +26 -0
  70. package/backend/lib/postbase/tests/testServer.js +46 -0
  71. package/backend/lib/postbase/websocket.js +131 -0
  72. package/backend/local.js +6 -0
  73. package/backend/main.js +20 -0
  74. package/backend/middlewares/auth_middleware.js +10 -0
  75. package/backend/migrations/1762137399366-init.sql +98 -0
  76. package/backend/migrations/1762137399367_init_jsonb_schema.js +68 -0
  77. package/backend/migrations/1762149999999_enable_realtime_changes.js +48 -0
  78. package/backend/migrations/1765224247654_rtdb-nodes.js +93 -0
  79. package/backend/package-lock.json +2374 -0
  80. package/backend/package.json +27 -0
  81. package/backend/postbase_db_rules.js +128 -0
  82. package/backend/postbase_rtdb_rules.js +27 -0
  83. package/backend/postbase_storage_rules.js +45 -0
  84. package/backend/template.env +10 -0
  85. package/backend-systemd/README.md +39 -0
  86. package/backend-systemd/your_website.com.service +12 -0
  87. package/frontend/404.html +33 -0
  88. package/frontend/README.md +25 -0
  89. package/frontend/index.html +15 -0
  90. package/frontend/jsconfig.json +20 -0
  91. package/frontend/lib/postbase/auth.js +132 -0
  92. package/frontend/lib/postbase/compat/firebase/app.js +3 -0
  93. package/frontend/lib/postbase/compat/firebase/auth.js +115 -0
  94. package/frontend/lib/postbase/compat/firebase/database.js +11 -0
  95. package/frontend/lib/postbase/compat/firebase/firestore/lite.js +61 -0
  96. package/frontend/lib/postbase/compat/firebase/storage.js +10 -0
  97. package/frontend/lib/postbase/db.js +657 -0
  98. package/frontend/lib/postbase/package-lock.json +6284 -0
  99. package/frontend/lib/postbase/package.json +17 -0
  100. package/frontend/lib/postbase/rtdb.js +108 -0
  101. package/frontend/lib/postbase/storage.js +293 -0
  102. package/frontend/lib/postbase/tests/rtdb.client.test.js +88 -0
  103. package/frontend/lib/postbase/tests/waitFor.js +13 -0
  104. package/frontend/lib/postbase/utils.js +1 -0
  105. package/frontend/package-lock.json +2977 -0
  106. package/frontend/package.json +24 -0
  107. package/frontend/src/App.jsx +38 -0
  108. package/frontend/src/auth.js +52 -0
  109. package/frontend/src/components/AuthPanel.jsx +85 -0
  110. package/frontend/src/components/Header.jsx +54 -0
  111. package/frontend/src/main.jsx +5 -0
  112. package/frontend/src/pages/Dashboard.jsx +24 -0
  113. package/frontend/src/pages/Home.jsx +178 -0
  114. package/frontend/src/pages/Login.jsx +10 -0
  115. package/frontend/src/postbase.js +14 -0
  116. package/frontend/src/styles.css +1 -0
  117. package/frontend/tailwind.config.cjs +11 -0
  118. package/frontend/template.env +2 -0
  119. package/frontend/vite.config.js +18 -0
  120. package/git/hooks/README.md +31 -0
  121. package/git/hooks/post-receive +26 -0
  122. package/nginx/README.md +84 -0
  123. package/nginx/apt/www.your_website.com.conf +80 -0
  124. package/nginx/homebrew/www.your_website.com.conf +80 -0
  125. package/nginx/letsencrypt/README +14 -0
  126. package/package.json +8 -0
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "postbase-frontend",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "author": "Umair Ashraf",
6
+ "url": "http://github.com/umrashrf/postbase",
7
+ "scripts": {
8
+ "dev": "vite",
9
+ "build": "vite build",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "preact": "^10.27.0",
14
+ "preact-iso": "^2.9.1",
15
+ "preact-router": "^4.1.2",
16
+ "tailwindcss": "^4.1.11",
17
+ "@tailwindcss/vite": "^4.1.11",
18
+ "better-auth": "^1.3.34"
19
+ },
20
+ "devDependencies": {
21
+ "@preact/preset-vite": "^2.9.3",
22
+ "vite": "^6.0.4"
23
+ }
24
+ }
@@ -0,0 +1,38 @@
1
+ import { useEffect, useState } from 'preact/hooks';
2
+ import { Router } from 'preact-router';
3
+ import Header from './components/Header';
4
+ import Home from './pages/Home';
5
+ import Dashboard from './pages/Dashboard';
6
+ import Login from './pages/Login';
7
+ import { getSession } from './auth';
8
+
9
+ export default function App() {
10
+ const [user, setUser] = useState(null);
11
+
12
+ useEffect(() => {
13
+ (async () => {
14
+ const { data } = await getSession();
15
+ if (data && data.hasOwnProperty('user')) {
16
+ setUser(data.user);
17
+ }
18
+ })();
19
+ }, []);
20
+
21
+ return (
22
+ <div className="min-h-screen bg-gray-50 text-gray-800">
23
+ <Header user={user} />
24
+
25
+ <Router>
26
+ <Home path="/" user={user} />
27
+ <Dashboard path="/dashboard" user={user} />
28
+ <Login path="/login" user={user} />
29
+ </Router>
30
+
31
+ <footer className="bg-white border-t py-6 mt-10">
32
+ <div className="container mx-auto text-center text-sm text-gray-500">
33
+ © {new Date().getFullYear()} Postbase Demo
34
+ </div>
35
+ </footer>
36
+ </div>
37
+ );
38
+ }
@@ -0,0 +1,52 @@
1
+ import { createAuthClient as createBetterAuthClient } from 'better-auth/client';
2
+ import { phoneNumberClient } from "better-auth/client/plugins"
3
+ import { createAuthClient } from '../lib/postbase/auth';
4
+ import { createFirebaseAuthClient } from '../lib/postbase/compat/firebase/auth';
5
+
6
+ export const betterAuthClient = createBetterAuthClient({
7
+ baseURL: import.meta.env.VITE_API_BASE + '/auth', // Specify if on a different domain/path,
8
+ plugins: [
9
+ phoneNumberClient()
10
+ ]
11
+ });
12
+
13
+ export const postbaseAuthClient = createAuthClient(betterAuthClient);
14
+
15
+ // better-auth
16
+ export const {
17
+ signUp,
18
+ signIn,
19
+ signOut: _signOut,
20
+ getSession,
21
+ sendVerificationEmail,
22
+ requestPasswordReset,
23
+ resetPassword,
24
+ phoneNumber,
25
+ deleteUser,
26
+ } = betterAuthClient;
27
+
28
+ // rename to logOut because firebase has reserved signOut
29
+ export const logout = async (...args) => {
30
+ const authToken = window.sessionStorage.getItem('authToken');
31
+ if (authToken) {
32
+ window.sessionStorage.removeItem('authToken');
33
+ }
34
+ // at the end because it can trigger navigation
35
+ await _signOut(...args);
36
+ };
37
+
38
+ // firebase auth
39
+ export const getAuth = () => postbaseAuthClient;
40
+ export const { getBetterAuthToken } = postbaseAuthClient;
41
+
42
+ export const {
43
+ createUserWithEmailAndPassword,
44
+ sendEmailVerification,
45
+ sendPasswordResetEmail,
46
+ signInWithEmailAndPassword,
47
+ updateProfile,
48
+ updateEmail,
49
+ updatePassword,
50
+ onAuthStateChanged,
51
+ signOut,
52
+ } = createFirebaseAuthClient(postbaseAuthClient);
@@ -0,0 +1,85 @@
1
+ import { useState } from 'preact/hooks';
2
+ import { signUp, signIn, signOut } from '../auth';
3
+
4
+ export default function AuthPanel({ user }) {
5
+ const [email, setEmail] = useState('');
6
+ const [password, setPassword] = useState('');
7
+
8
+ const loginWithGoogle = async () => {
9
+ alert("Finish setting up Sign in with Google. Learn more at https://www.better-auth.com/docs/authentication/google")
10
+ await signIn.social({
11
+ provider: "google",
12
+ callbackURL: import.meta.env.VITE_FRONTEND_URL + '/dashboard'
13
+ });
14
+ };
15
+
16
+ return (
17
+ <div className="bg-white border rounded-lg p-4">
18
+ <h3 className="font-semibold">Account</h3>
19
+ {!user ? (
20
+ <div className="mt-3 space-y-3">
21
+ <button onClick={loginWithGoogle} className="w-full bg-red-600 text-white py-2 rounded">Sign in with Google</button>
22
+
23
+ <div className="flex gap-2">
24
+ <input className="flex-1 border p-2 rounded w-full" type="text" placeholder="email" value={email} onInput={e => setEmail(e.target.value)} />
25
+ <input className="flex-1 border p-2 rounded w-full" type="password" placeholder="password" value={password} onInput={e => setPassword(e.target.value)} />
26
+ </div>
27
+
28
+ <div className="flex gap-2">
29
+ <button className="px-3 py-2 border rounded"
30
+ onClick={async () => {
31
+ alert("Setup PostgreSQL (migration, connection string). Learn more at https://www.better-auth.com/docs/installation#create-database-tables");
32
+ await signUp.email(
33
+ { email, password, name: email.split('@', 1)[0], callbackURL: "/dashboard" },
34
+ {
35
+ onRequest: (ctx) => {
36
+ //show loading
37
+ },
38
+ onSuccess: (ctx) => {
39
+ //redirect to the dashboard or sign in page
40
+ window.location = import.meta.env.VITE_FRONTEND_URL;
41
+ },
42
+ onError: (ctx) => {
43
+ // display the error message
44
+ console.error('Sign up failed', ctx);
45
+ alert('Sign up failed');
46
+ },
47
+ });
48
+ }}>
49
+ Sign up
50
+ </button>
51
+ <button className="px-3 py-2 border rounded"
52
+ onClick={async () => {
53
+ alert("Setup PostgreSQL (migration, connection string). Learn more at https://www.better-auth.com/docs/installation#create-database-tables");
54
+ await signIn.email({ email, password, callbackURL: '/dashboard' });
55
+ }}>
56
+ Login
57
+ </button>
58
+ </div>
59
+ </div>
60
+ ) : (
61
+ <div className="mt-3">
62
+ <div className="flex items-center gap-3">
63
+ <img src={user.image} className="w-10 h-10 rounded-full" />
64
+ <div>
65
+ <div className="font-medium">{user.name || user.email}</div>
66
+ <div className="text-sm text-gray-500">{user.email}</div>
67
+ </div>
68
+ </div>
69
+
70
+ <div className="mt-3 flex gap-2">
71
+ <a href="/dashboard" className="px-3 py-2 border rounded">Dashboard</a>
72
+ <a href="/billing" className="px-3 py-2 border rounded">Billing</a>
73
+ <button onClick={() => signOut({
74
+ fetchOptions: {
75
+ onSuccess: () => {
76
+ location.href = import.meta.env.VITE_FRONTEND_URL;
77
+ },
78
+ },
79
+ })} className="px-3 py-2 border rounded cursor-pointer">Logout</button>
80
+ </div>
81
+ </div>
82
+ )}
83
+ </div>
84
+ );
85
+ }
@@ -0,0 +1,54 @@
1
+ import { useState, useEffect } from 'preact/hooks';
2
+ import { signOut } from '../auth';
3
+
4
+ export default function Header({ user }) {
5
+ const [menu, setMenu] = useState(false);
6
+ const [scrolled, setScrolled] = useState(false);
7
+
8
+ useEffect(() => {
9
+ const onScroll = () => setScrolled(window.scrollY > 30);
10
+ window.addEventListener('scroll', onScroll);
11
+ return () => window.removeEventListener('scroll', onScroll);
12
+ }, []);
13
+
14
+ return (
15
+ <header className={`w-full z-40 transition-all ${scrolled ? 'bg-white shadow' : 'bg-transparent'} backdrop-blur`}>
16
+ <div className="max-w-6xl mx-auto px-6 py-4 flex justify-between items-center">
17
+ <a href="/" className="text-2xl font-extrabold text-blue-600">Postbase Demo</a>
18
+ <nav className="hidden md:flex gap-8 text-sm font-medium">
19
+ <a href="#better-auth" className="hover:text-blue-600">BetterAuth</a>
20
+ <a href="#familiar-api" className="hover:text-blue-600">Familiar API</a>
21
+ <a href="#why" className="hover:text-blue-600">Why Postbase?</a>
22
+ </nav>
23
+
24
+ <div className="flex items-center gap-4">
25
+ {user ? (
26
+ <div className="relative">
27
+ <button onClick={() => setMenu(!menu)} className="flex items-center gap-2 px-3 py-1 border rounded-md hover:bg-gray-50">
28
+ <img src={user.image} className="w-8 h-8 rounded-full" alt="profile" />
29
+ <span className="hidden sm:inline text-sm">{user.name || user.email}</span>
30
+ </button>
31
+ {menu && (
32
+ <div className="absolute right-0 mt-2 bg-white border rounded-md shadow-lg w-48 z-50">
33
+ <a href="/dashboard" className="block px-4 py-2 hover:bg-gray-50">Dashboard</a>
34
+ <a href="/billing" className="block px-4 py-2 hover:bg-gray-50">Billing</a>
35
+ <button onClick={() => signOut({
36
+ fetchOptions: {
37
+ onSuccess: () => {
38
+ location.href = import.meta.env.VITE_FRONTEND_URL;
39
+ },
40
+ },
41
+ })} className="w-full text-left px-4 py-2 hover:bg-gray-50 cursor-pointer">Logout</button>
42
+ </div>
43
+ )}
44
+ </div>
45
+ ) : (
46
+ <a href="/login" className="bg-blue-600 text-white px-4 py-2 rounded-md font-medium hover:bg-blue-700 transition">
47
+ Sign In
48
+ </a>
49
+ )}
50
+ </div>
51
+ </div>
52
+ </header>
53
+ );
54
+ }
@@ -0,0 +1,5 @@
1
+ import { render } from 'preact';
2
+ import App from './App';
3
+ import './styles.css';
4
+
5
+ render(<App />, document.getElementById('app'));
@@ -0,0 +1,24 @@
1
+ import { betterAuthClient } from '../auth';
2
+
3
+ export default function Dashboard({ user }) {
4
+ if (!user) {
5
+ return (
6
+ <div className="container mx-auto py-10 text-center">
7
+ <p className="text-gray-600">Please sign in to view your dashboard.</p>
8
+ </div>
9
+ );
10
+ }
11
+
12
+ return (
13
+ <div className="container mx-auto py-10">
14
+ <h1 className="text-2xl font-semibold mb-6">Dashboard</h1>
15
+ <div className="mt-2">
16
+ <button className="w-full bg-red-600 text-white py-2 rounded cursor-pointer"
17
+ onClick={async e => {
18
+ await betterAuthClient.deleteUser();
19
+ location.href = '/';
20
+ }}>Close Account</button>
21
+ </div>
22
+ </div>
23
+ );
24
+ }
@@ -0,0 +1,178 @@
1
+ import AuthPanel from '../components/AuthPanel';
2
+
3
+ export default function Home({ user }) {
4
+ return (
5
+ <main className="text-gray-800">
6
+ {/* HERO */}
7
+ <section className="bg-gradient-to-br from-blue-50 to-white pt-24 pb-32">
8
+ <div className="max-w-6xl mx-auto px-6 grid md:grid-cols-2 gap-10">
9
+ <div>
10
+ <h1 className="text-5xl font-extrabold leading-tight text-gray-900">
11
+ Backend ❤️<br />
12
+ <span className="text-blue-600">that just works.</span>
13
+ </h1>
14
+ <p className="mt-5 text-lg text-gray-600 max-w-lg">
15
+ Self Hosted Firebase/Supabase Alternative using Node.js, Express.js, BetterAuth and PostgreSQL (JSONB)
16
+ </p>
17
+ <p className="mt-6 text-2xl">
18
+ Firebase 💔 | Supabase 💔 | Postbase ❤️
19
+ </p>
20
+
21
+ <div className="mt-8 flex gap-4">
22
+ <a href="/login" className="bg-blue-600 text-white px-6 py-3 rounded-md font-medium shadow hover:bg-blue-700 transition">Login with BetterAuth</a>
23
+ </div>
24
+ </div>
25
+
26
+ <div className="flex flex-col gap-2 relative">
27
+ <div className="bg-gray-900 text-green-400 font-mono text-sm rounded-lg shadow-lg p-6 overflow-x-auto">
28
+ <code className="block whitespace-pre w-xs md:w-xl lg:w-4xl text-xs text-nowrap overflow-x-auto">{
29
+ `import { initializeApp } from "../lib/postbase/compat/firebase/app";
30
+ import { getFirestore } from "../lib/postbase/compat/firebase/firestore/lite";
31
+ import { getStorage } from "../lib/postbase/compat/firebase/storage";
32
+ import { getDatabase } from "../lib/postbase/compat/firebase/database";
33
+
34
+ const firebaseConfig = {
35
+ baseUrl: import.meta.env.VITE_API_BASE,
36
+ };
37
+
38
+ const app = initializeApp(firebaseConfig);
39
+
40
+ export const db = getFirestore(app);
41
+ export const storage = getStorage(app);
42
+ export const rtdbClient = getDatabase(app);`
43
+ }</code>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </section>
48
+
49
+ <section id="better-auth" className="bg-gradient-to-br from-blue-50 to-white pt-24 pb-32">
50
+ <div className="max-w-6xl mx-auto px-6 grid md:grid-cols-2 gap-10">
51
+ <div>
52
+ <h1 className="text-5xl font-extrabold leading-tight text-gray-900">
53
+ BetterAuth
54
+ </h1>
55
+ <p className="mt-5 text-lg text-gray-600 max-w-lg">
56
+ Did not reinvent the wheel
57
+ </p>
58
+
59
+ <div className="mt-8 flex gap-4">
60
+ </div>
61
+ </div>
62
+
63
+ <div className="relative">
64
+ <div className="bg-gray-900 text-green-400 font-mono text-sm rounded-lg shadow-lg p-6 overflow-x-auto">
65
+ <code className="block whitespace-pre w-xs md:w-xl lg:w-4xl text-xs text-nowrap overflow-x-scroll">{
66
+ `import {
67
+ getAuth,
68
+ signInWithEmailAndPassword,
69
+ signOut,
70
+ } from "./auth";
71
+
72
+ const auth = getAuth();
73
+
74
+ const userCredential = await signInWithEmailAndPassword(auth, 'email', 'password');
75
+
76
+ await signOut(auth);`
77
+ }</code>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </section>
82
+
83
+ <section id="familiar-api" className="bg-gradient-to-br from-blue-50 to-white pt-24 pb-32">
84
+ <div className="max-w-6xl mx-auto px-6 grid md:grid-cols-2 gap-10">
85
+ <div>
86
+ <h1 className="text-5xl font-extrabold leading-tight text-gray-900">
87
+ Familiar API
88
+ </h1>
89
+ <p className="mt-5 text-lg text-gray-600 max-w-lg">
90
+ Inspired by Firebase's API
91
+ </p>
92
+
93
+ <div className="mt-8 flex gap-4">
94
+ </div>
95
+ </div>
96
+
97
+ <div className="flex flex-col gap-2 relative">
98
+ <div className="bg-gray-900 text-green-400 font-mono text-sm rounded-lg shadow-lg p-6">
99
+ <code className="block whitespace-pre w-xs md:w-xl lg:w-4xl text-xs text-nowrap overflow-x-scroll">{
100
+ `import { db } from "./postbase";
101
+
102
+ await db.collection('users').addDoc({ name: "Umair" });
103
+
104
+ await db.collection('users').where('name', '==', 'Umair');
105
+
106
+ await db.collection('users').doc('docId').get();
107
+ `
108
+ }</code>
109
+ </div>
110
+ <div className="bg-gray-900 text-green-400 font-mono text-sm rounded-lg shadow-lg p-6 overflow-x-auto">
111
+ <pre>
112
+ <code className="block whitespace-pre w-xs md:w-xl lg:w-4xl text-xs text-nowrap overflow-x-scroll">{
113
+ `import { storage } from "./postbase";
114
+
115
+ const profilePicFile = new File(...); // HTMLFileInput
116
+
117
+ const fileRef = storage.ref('path/to/directory/' + profilePicFile.name);
118
+
119
+ const remoteFile = await fileRef.put(profilePicFile);
120
+
121
+ const profileUrl = await remoteFile.ref.getDownloadURL();`
122
+ }</code>
123
+ </pre>
124
+ </div>
125
+ <div className="bg-gray-900 text-green-400 font-mono text-sm rounded-lg shadow-lg p-6 overflow-x-auto">
126
+ <code className="block whitespace-pre w-xs md:w-xl lg:w-4xl text-xs text-nowrap overflow-x-scroll">{
127
+ `import { rtdbClient } from '../postbase';
128
+
129
+ // please check frontend/lib/postbase/tests/rtdb.client.test.js
130
+ `
131
+ }</code>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ </section>
136
+
137
+ {/* FEATURES */}
138
+ <section id="why" className="bg-white py-24">
139
+ <div className="max-w-6xl mx-auto px-6 text-center">
140
+ <h2 className="text-3xl font-bold mb-12 text-gray-900">Why Postbase?</h2>
141
+ <div className="grid md:grid-cols-3 gap-10">
142
+ {[
143
+ {
144
+ title: "Self Hosted",
145
+ desc: "Based on mostly open source easily installable projects",
146
+ icon: "🧩"
147
+ },
148
+ {
149
+ title: "Cost Effective",
150
+ desc: "Avoid surprise cloud charges",
151
+ icon: "💳"
152
+ },
153
+ {
154
+ title: "Scalable",
155
+ desc: "Scale to millions of requests per day — no rate limits on your growth.",
156
+ icon: "⚡"
157
+ },
158
+
159
+ ].map(f => (
160
+ <div key={f.title} className="bg-gray-50 border rounded-lg p-8 shadow-sm hover:shadow transition">
161
+ <div className="text-4xl mb-3">{f.icon}</div>
162
+ <h3 className="text-xl font-semibold mb-2">{f.title}</h3>
163
+ <p className="text-gray-600">{f.desc}</p>
164
+ </div>
165
+ ))}
166
+ </div>
167
+ </div>
168
+ </section>
169
+
170
+ {/* AUTH (optional section near footer) */}
171
+ <section className="bg-gray-50 py-16">
172
+ <div className="max-w-lg mx-auto px-6">
173
+ <AuthPanel user={user} />
174
+ </div>
175
+ </section>
176
+ </main>
177
+ );
178
+ }
@@ -0,0 +1,10 @@
1
+ import { h } from 'preact';
2
+ import AuthPanel from '../components/AuthPanel';
3
+
4
+ export default function Login({ user }) {
5
+ return (
6
+ <div className="container mx-auto py-10">
7
+ <AuthPanel user={user} />
8
+ </div>
9
+ );
10
+ }
@@ -0,0 +1,14 @@
1
+ import { initializeApp } from "../lib/postbase/compat/firebase/app";
2
+ import { getFirestore } from "../lib/postbase/compat/firebase/firestore/lite";
3
+ import { getStorage } from "../lib/postbase/compat/firebase/storage";
4
+ import { getDatabase } from "../lib/postbase/compat/firebase/database";
5
+
6
+ const firebaseConfig = {
7
+ baseUrl: import.meta.env.VITE_API_BASE,
8
+ };
9
+
10
+ const app = initializeApp(firebaseConfig);
11
+
12
+ export const db = getFirestore(app);
13
+ export const storage = getStorage(app);
14
+ export const rtdbClient = getDatabase(app);
@@ -0,0 +1 @@
1
+ @import "tailwindcss";
@@ -0,0 +1,11 @@
1
+ module.exports = {
2
+ content: ['./index.html', './src/**/*.{js,jsx,ts,tsx}'],
3
+ theme: {
4
+ extend: {
5
+ fontFamily: {
6
+ sans: ['Inter', 'system-ui', 'sans-serif']
7
+ },
8
+ }
9
+ },
10
+ plugins: []
11
+ };
@@ -0,0 +1,2 @@
1
+ VITE_API_BASE=http://localhost:8081/api
2
+ VITE_FRONTEND_URL=http://localhost:8080
@@ -0,0 +1,18 @@
1
+ import { defineConfig, transformWithEsbuild } from 'vite';
2
+ import preact from '@preact/preset-vite';
3
+ import tailwindcss from '@tailwindcss/vite'
4
+
5
+ export default defineConfig({
6
+ plugins: [
7
+ preact({ include: /\.(mdx|js|jsx|ts|tsx)$/ }),
8
+ tailwindcss(),
9
+ ],
10
+ optimizeDeps: {
11
+ exclude: [],
12
+ esbuildOptions: {
13
+ loader: {
14
+ '.js': 'jsx',
15
+ },
16
+ },
17
+ },
18
+ });
@@ -0,0 +1,31 @@
1
+ # Git Hooks
2
+
3
+ ## post-receive hook
4
+
5
+ The purpose of this hook is to automate deployment of your app with `git push`.
6
+
7
+ ## How to enable post-receive hook on your server?
8
+
9
+ 1. Login to your ssh server
10
+ 2. mkdir /path/to/your/app/repo
11
+ 3. cd /path/to/your/app/repo
12
+ 4. git init
13
+
14
+ Locally, in your dev environment:
15
+
16
+ ```
17
+ git remote add deploy server_username@your_server:/your/server/your_repo
18
+ git push deploy
19
+ ```
20
+
21
+ Back to ssh server:
22
+
23
+ ```
24
+ ln -s `pwd`/git/hooks/post-receive `pwd`/.git/hooks/post-receive
25
+ ```
26
+
27
+ Allow post-receive hook to copy over files.
28
+
29
+ ```
30
+ sudo chown -R you_username:root /var/www/html
31
+ ```
@@ -0,0 +1,26 @@
1
+ #!/bin/sh
2
+
3
+ set -x
4
+
5
+ GIT_WORK_TREE="/your/server/your_repo"
6
+ export GIT_WORK_TREE
7
+
8
+ # This is needed for git push based deploy
9
+ git reset --hard
10
+
11
+ cd $GIT_WORK_TREE
12
+ git checkout .
13
+
14
+ cd backend
15
+ npm install
16
+
17
+ cd ../frontend
18
+ npm install
19
+ npm run build
20
+
21
+ cd ../admin
22
+ npm install
23
+ npm run build
24
+
25
+ rsync -avh /your/server/your_repo/frontend/dist/ /var/www/html/your_website
26
+ rsync -avh /your/server/your_repo/admin/dist/ /var/www/html/your_website/admin