···
5
5
"name": "takes",
6
6
"dependencies": {
7
7
"@sentry/bun": "^9.10.1",
8
8
+
"@types/react": "^19.1.2",
9
9
+
"@types/react-dom": "^19.1.2",
8
10
"bottleneck": "^2.19.5",
9
11
"colors": "^1.4.0",
10
12
"drizzle-kit": "^0.30.6",
11
13
"drizzle-orm": "^0.41.0",
14
14
+
"react": "^19.1.0",
15
15
+
"react-dom": "^19.1.0",
12
16
"slack-edge": "^1.3.7",
13
17
"yaml": "^2.7.1",
14
18
},
···
189
193
190
194
"@types/pg-pool": ["@types/pg-pool@2.0.6", "", { "dependencies": { "@types/pg": "*" } }, "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ=="],
191
195
196
196
+
"@types/react": ["@types/react@19.1.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw=="],
197
197
+
198
198
+
"@types/react-dom": ["@types/react-dom@19.1.2", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw=="],
199
199
+
192
200
"@types/shimmer": ["@types/shimmer@1.2.0", "", {}, "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg=="],
193
201
194
202
"@types/tedious": ["@types/tedious@4.0.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw=="],
···
208
216
"cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="],
209
217
210
218
"colors": ["colors@1.4.0", "", {}, "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="],
219
219
+
220
220
+
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
211
221
212
222
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
213
223
···
275
285
276
286
"promise-limit": ["promise-limit@2.7.0", "", {}, "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw=="],
277
287
288
288
+
"react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
289
289
+
290
290
+
"react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
291
291
+
278
292
"require-in-the-middle": ["require-in-the-middle@7.5.2", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3", "resolve": "^1.22.8" } }, "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ=="],
279
293
280
294
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
281
295
282
296
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
297
297
+
298
298
+
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
283
299
284
300
"semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
285
301
···
21
21
},
22
22
"dependencies": {
23
23
"@sentry/bun": "^9.10.1",
24
24
+
"@types/react": "^19.1.2",
25
25
+
"@types/react-dom": "^19.1.2",
24
26
"bottleneck": "^2.19.5",
25
27
"colors": "^1.4.0",
26
28
"drizzle-kit": "^0.30.6",
27
29
"drizzle-orm": "^0.41.0",
30
30
+
"react": "^19.1.0",
31
31
+
"react-dom": "^19.1.0",
28
32
"slack-edge": "^1.3.7",
29
33
"yaml": "^2.7.1"
30
34
}
···
1
1
+
<!doctype html>
2
2
+
<html>
3
3
+
<head>
4
4
+
<title>Smokie's Home</title>
5
5
+
</head>
6
6
+
<body>
7
7
+
<div id="root"></div>
8
8
+
<script type="module" src="../src/features/frontend/index.tsx"></script>
9
9
+
</body>
10
10
+
</html>
···
1
1
-
import { eq, desc, and } from "drizzle-orm";
1
1
+
import { eq, desc, and, or } from "drizzle-orm";
2
2
import { db } from "../../../libs/db";
3
3
import { takes as takesTable } from "../../../libs/schema";
4
4
import { handleApiError } from "../../../libs/apiError";
···
8
8
const recentTakes = await db
9
9
.select()
10
10
.from(takesTable)
11
11
-
.where(eq(takesTable.status, "approved"))
11
11
+
.where(
12
12
+
or(
13
13
+
eq(takesTable.status, "approved"),
14
14
+
eq(takesTable.status, "uploaded"),
15
15
+
),
16
16
+
)
12
17
.orderBy(desc(takesTable.completedAt))
13
18
.limit(40);
14
19
···
1
1
+
import { useEffect, useState } from "react";
2
2
+
import { prettyPrintTime } from "../../libs/time";
3
3
+
import { fetchUserData } from "../../libs/cachet";
4
4
+
5
5
+
export function App() {
6
6
+
const [takes, setTakes] = useState<
7
7
+
{
8
8
+
id: string;
9
9
+
userId: string;
10
10
+
description: string;
11
11
+
completedAt: Date;
12
12
+
status: string;
13
13
+
mp4Url: string;
14
14
+
elapsedTime: number;
15
15
+
}[]
16
16
+
>([]);
17
17
+
18
18
+
const [userData, setUserData] = useState<{
19
19
+
[key: string]: { displayName: string; imageUrl: string };
20
20
+
}>({});
21
21
+
useEffect(() => {
22
22
+
async function loadUserData() {
23
23
+
const userIds = takes.map((take) => take.userId);
24
24
+
const uniqueIds = [...new Set(userIds)];
25
25
+
try {
26
26
+
for (const id of uniqueIds) {
27
27
+
const data = await fetchUserData(id);
28
28
+
setUserData((prevData) => ({
29
29
+
...prevData,
30
30
+
[id]: {
31
31
+
displayName: data.displayName,
32
32
+
imageUrl: data.image,
33
33
+
},
34
34
+
}));
35
35
+
}
36
36
+
} catch (error) {
37
37
+
console.error("Error fetching user data:", error);
38
38
+
}
39
39
+
}
40
40
+
loadUserData();
41
41
+
}, [takes]);
42
42
+
43
43
+
useEffect(() => {
44
44
+
async function getTakes() {
45
45
+
const res = await fetch("/api/recentTakes");
46
46
+
const data = await res.json();
47
47
+
setTakes(data.takes);
48
48
+
}
49
49
+
getTakes();
50
50
+
}, []);
51
51
+
52
52
+
return (
53
53
+
<div className="container">
54
54
+
<h1 className="title">Recent Takes</h1>
55
55
+
<div className="takes-grid">
56
56
+
{takes.map((take) => (
57
57
+
<div key={take.id} className="take-card">
58
58
+
<div className="take-header">
59
59
+
<h2 className="take-title">{take.description}</h2>
60
60
+
<div className="user-pill">
61
61
+
<div className="user-info">
62
62
+
<img
63
63
+
src={userData[take.userId]?.imageUrl}
64
64
+
alt="Profile"
65
65
+
className="profile-image"
66
66
+
/>
67
67
+
<span className="user-name">
68
68
+
{userData[take.userId]?.displayName ??
69
69
+
take.userId}
70
70
+
</span>
71
71
+
</div>
72
72
+
<span
73
73
+
className={`status-badge status-${take.status}`}
74
74
+
>
75
75
+
{take.status}
76
76
+
</span>
77
77
+
</div>
78
78
+
</div>
79
79
+
80
80
+
<div className="take-meta">
81
81
+
<div className="meta-item">
82
82
+
<span className="meta-label">Completed:</span>
83
83
+
<span className="meta-value">
84
84
+
{new Date(
85
85
+
take.completedAt,
86
86
+
).toLocaleString()}
87
87
+
</span>
88
88
+
</div>
89
89
+
<div className="meta-item">
90
90
+
<span className="meta-label">Duration:</span>
91
91
+
<span className="meta-value">
92
92
+
{prettyPrintTime(take.elapsedTime)}
93
93
+
</span>
94
94
+
</div>
95
95
+
</div>
96
96
+
97
97
+
{take.mp4Url && (
98
98
+
<div className="video-container">
99
99
+
<video controls className="take-video">
100
100
+
<source
101
101
+
src={take.mp4Url}
102
102
+
type="video/mp4"
103
103
+
/>
104
104
+
<track
105
105
+
kind="captions"
106
106
+
src=""
107
107
+
label="Captions"
108
108
+
/>
109
109
+
</video>
110
110
+
</div>
111
111
+
)}
112
112
+
</div>
113
113
+
))}
114
114
+
</div>
115
115
+
</div>
116
116
+
);
117
117
+
}
···
1
1
+
import "./styles.css";
2
2
+
import { createRoot } from "react-dom/client";
3
3
+
import { App } from "./app.tsx";
4
4
+
5
5
+
document.addEventListener("DOMContentLoaded", () => {
6
6
+
const element = document.getElementById("root");
7
7
+
if (!element) throw new Error("Root element not found");
8
8
+
const root = createRoot(element);
9
9
+
root.render(<App />);
10
10
+
});
···
1
1
+
body {
2
2
+
background-color: #f5f5f5;
3
3
+
font-family:
4
4
+
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial,
5
5
+
sans-serif;
6
6
+
line-height: 1.6;
7
7
+
color: #333;
8
8
+
margin: 0;
9
9
+
padding: 20px;
10
10
+
min-height: 100vh;
11
11
+
}
12
12
+
13
13
+
.container {
14
14
+
max-width: 1200px;
15
15
+
margin: 0 auto;
16
16
+
padding: 2rem;
17
17
+
}
18
18
+
19
19
+
.title {
20
20
+
font-size: 2.5rem;
21
21
+
margin-bottom: 2rem;
22
22
+
text-align: center;
23
23
+
}
24
24
+
25
25
+
.takes-grid {
26
26
+
display: grid;
27
27
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
28
28
+
gap: 2rem;
29
29
+
}
30
30
+
31
31
+
.take-card {
32
32
+
background: white;
33
33
+
border-radius: 12px;
34
34
+
padding: 1.5rem;
35
35
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
36
36
+
}
37
37
+
38
38
+
.take-header {
39
39
+
display: flex;
40
40
+
justify-content: space-between;
41
41
+
align-items: center;
42
42
+
margin-bottom: 1rem;
43
43
+
}
44
44
+
45
45
+
.take-title {
46
46
+
font-size: 1.25rem;
47
47
+
margin: 0;
48
48
+
}
49
49
+
50
50
+
.profile-image {
51
51
+
width: 1.5rem;
52
52
+
border-radius: 50%;
53
53
+
margin-right: 0.5rem;
54
54
+
object-fit: cover;
55
55
+
}
56
56
+
57
57
+
.user-pill {
58
58
+
display: flex;
59
59
+
align-items: center;
60
60
+
padding: 0rem 0rem 0rem 0.3rem;
61
61
+
border-radius: 999px;
62
62
+
background: #f8f9fa;
63
63
+
gap: 0.75rem;
64
64
+
}
65
65
+
66
66
+
.user-info {
67
67
+
display: flex;
68
68
+
align-items: center;
69
69
+
}
70
70
+
71
71
+
.status-badge {
72
72
+
padding: 0.25rem 0.75rem;
73
73
+
border-radius: 999px;
74
74
+
}
75
75
+
76
76
+
.status-approved {
77
77
+
background: #e6f4ea;
78
78
+
color: #1e7e34;
79
79
+
}
80
80
+
81
81
+
.take-meta {
82
82
+
margin-bottom: 1rem;
83
83
+
}
84
84
+
85
85
+
.meta-item {
86
86
+
display: flex;
87
87
+
margin-bottom: 0.5rem;
88
88
+
}
89
89
+
90
90
+
.meta-label {
91
91
+
font-weight: 500;
92
92
+
margin-right: 0.5rem;
93
93
+
min-width: 80px;
94
94
+
}
95
95
+
96
96
+
.video-container {
97
97
+
margin-top: 1rem;
98
98
+
border-radius: 8px;
99
99
+
overflow: hidden;
100
100
+
}
101
101
+
102
102
+
.take-video {
103
103
+
width: 100%;
104
104
+
display: block;
105
105
+
}
···
1
1
import { SlackApp } from "slack-edge";
2
2
3
3
import { takes } from "./features/index";
4
4
+
import frontend from "../public/index.html";
4
5
5
6
import { t } from "./libs/template";
6
7
import { blog } from "./libs/Logger";
···
56
57
57
58
Bun.serve({
58
59
port: process.env.PORT || 3000,
59
59
-
development: environment === "development",
60
60
+
development: environment === "dev",
60
61
routes: {
61
61
-
"/": new Response(`Hello World from ${name}@${version}`),
62
62
+
"/": frontend,
62
63
"/health": new Response("OK"),
63
64
},
64
65
async fetch(request: Request) {
···
1
1
+
export async function fetchUserData(userId: string) {
2
2
+
const res = await fetch(`https://cachet.dunkirk.sh/users/${userId}/`);
3
3
+
const json = await res.json();
4
4
+
5
5
+
return {
6
6
+
id: json.id,
7
7
+
expiration: json.expiration,
8
8
+
user: json.user,
9
9
+
displayName: json.displayName,
10
10
+
image: json.image,
11
11
+
};
12
12
+
}
···
1
1
{
2
2
-
"compilerOptions": {
3
3
-
// Environment setup & latest features
4
4
-
"lib": ["esnext"],
5
5
-
"target": "ESNext",
6
6
-
"module": "ESNext",
7
7
-
"moduleDetection": "force",
8
8
-
"jsx": "react-jsx",
9
9
-
"allowJs": true,
2
2
+
"compilerOptions": {
3
3
+
// Environment setup & latest features
4
4
+
"lib": ["esnext", "dom"],
5
5
+
"target": "ESNext",
6
6
+
"module": "ESNext",
7
7
+
"moduleDetection": "force",
8
8
+
"jsx": "react-jsx",
9
9
+
"allowJs": true,
10
10
11
11
-
// Bundler mode
12
12
-
"moduleResolution": "bundler",
13
13
-
"allowImportingTsExtensions": true,
14
14
-
"verbatimModuleSyntax": true,
15
15
-
"noEmit": true,
11
11
+
// Bundler mode
12
12
+
"moduleResolution": "bundler",
13
13
+
"allowImportingTsExtensions": true,
14
14
+
"verbatimModuleSyntax": true,
15
15
+
"noEmit": true,
16
16
17
17
-
// Best practices
18
18
-
"strict": true,
19
19
-
"skipLibCheck": true,
20
20
-
"noFallthroughCasesInSwitch": true,
21
21
-
"noUncheckedIndexedAccess": true,
17
17
+
// Best practices
18
18
+
"strict": true,
19
19
+
"skipLibCheck": true,
20
20
+
"noFallthroughCasesInSwitch": true,
21
21
+
"noUncheckedIndexedAccess": true,
22
22
23
23
-
// Some stricter flags (disabled by default)
24
24
-
"noUnusedLocals": false,
25
25
-
"noUnusedParameters": false,
26
26
-
"noPropertyAccessFromIndexSignature": false
27
27
-
}
23
23
+
// Some stricter flags (disabled by default)
24
24
+
"noUnusedLocals": false,
25
25
+
"noUnusedParameters": false,
26
26
+
"noPropertyAccessFromIndexSignature": false
27
27
+
}
28
28
}