From 1350fed880246ef237803ceb0980271b0df92c03 Mon Sep 17 00:00:00 2001 From: "ben.harris" Date: Sun, 18 May 2025 23:31:23 -0400 Subject: [PATCH 1/8] Got it working with the custom createServer and getContext --- devvit.yaml | 4 ++-- package.json | 12 +++++----- src/devvit/main.tsx | 2 +- src/server/index.ts | 48 +++++++++++++++++++++++++--------------- src/server/middleware.ts | 20 ----------------- 5 files changed, 39 insertions(+), 47 deletions(-) delete mode 100644 src/server/middleware.ts diff --git a/devvit.yaml b/devvit.yaml index c285ec0..fdc0d54 100644 --- a/devvit.yaml +++ b/devvit.yaml @@ -1,2 +1,2 @@ -name: YOUR_APP_NAME -version: 0.0.0.0 +name: ss-webbit-tests +version: 0.0.1.5 diff --git a/package.json b/package.json index 58d0719..81dcd2b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "name": "devvit-hello-world-experimental", + "name": "ss-webbit-testing", "version": "0.0.0", "license": "BSD-3-Clause", "type": "module", @@ -9,16 +9,16 @@ "deploy": "npm run build:client && devvit upload", "dev": "concurrently -p \"[{name}]\" -n \"VITE,DEVVIT,GAME\" -c \"blue,green,magenta\" \"npm run dev:vite\" \"npm run dev:devvit\" \"npm run dev:client\" --restart-tries 2", "dev:client": "cd src/client && vite build --watch", - "dev:devvit": "devvit playtest YOUR_SUBREDDIT_NAME", + "dev:devvit": "MY_PORTAL=0 mydevvit playtest SnooSnekTest", "dev:vite": "cd src/client && vite --port 7474", "login": "devvit login", "type-check": "tsc --build" }, "dependencies": { - "@devvit/client": "next", - "@devvit/public-api": "next", - "@devvit/server": "next", - "devvit": "next", + "@devvit/client": "../../snoodev-and-friends/devvit/packages/client", + "@devvit/public-api": "../../snoodev-and-friends/devvit/packages/public-api", + "@devvit/server": "../../snoodev-and-friends/devvit/packages/server", + "devvit": "../../snoodev-and-friends/devvit/packages/devvit", "express": "5.1.0" }, "devDependencies": { diff --git a/src/devvit/main.tsx b/src/devvit/main.tsx index 72a7c32..05d7ccc 100644 --- a/src/devvit/main.tsx +++ b/src/devvit/main.tsx @@ -12,7 +12,7 @@ defineConfig({ inline: true, menu: { enable: true, - label: "New Hello World Post", + label: "[Webbit] New Hello World Post", postTitle: "Hello World", }, }); diff --git a/src/server/index.ts b/src/server/index.ts index 9516885..0d2f132 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,6 +1,5 @@ import express from "express"; -import { createServer } from "http"; -import { devvitMiddleware } from "./middleware"; +import { createServer, getContext } from "@devvit/server"; import { InitResponse, IncrementResponse, @@ -16,16 +15,14 @@ app.use(express.urlencoded({ extended: true })); // Middleware for plain text body parsing app.use(express.text()); -// Apply Devvit middleware -app.use(devvitMiddleware); - const router = express.Router(); router.get< { postId: string }, InitResponse | { status: string; message: string } ->("/api/init", async (req, res): Promise => { - const { postId } = req.devvit; +>("/api/init", async (_req, res): Promise => { + const context = getContext(); + const postId = context.postId; if (!postId) { console.error("API Init Error: postId not found in devvit context"); @@ -37,7 +34,7 @@ router.get< } try { - const count = await req.devvit.redis.get("count"); + const count = await context.redis.get("count"); res.json({ type: "init", postId: postId, @@ -57,8 +54,10 @@ router.post< { postId: string }, IncrementResponse | { status: string; message: string }, unknown ->("/api/increment", async (req, res): Promise => { - const { postId } = req.devvit; +>("/api/increment", async (_req, res): Promise => { + const context = getContext(); + const postId = context.postId; + if (!postId) { res.status(400).json({ status: "error", @@ -68,7 +67,7 @@ router.post< } res.json({ - count: await req.devvit.redis.incrBy("count", 1), + count: await context.redis.incrBy("count", 1), postId, type: "increment", }); @@ -78,8 +77,9 @@ router.post< { postId: string }, DecrementResponse | { status: string; message: string }, unknown ->("/api/decrement", async (req, res): Promise => { - const { postId } = req.devvit; +>("/api/decrement", async (_req, res): Promise => { + const context = getContext(); + const postId = context.postId; if (!postId) { res.status(400).json({ status: "error", @@ -89,7 +89,7 @@ router.post< } res.json({ - count: await req.devvit.redis.incrBy("count", -1), + count: await context.redis.incrBy("count", -1), postId, type: "decrement", }); @@ -98,9 +98,21 @@ router.post< // Use router middleware app.use(router); -// Get port from environment variable with fallback -const port = process.env.WEBBIT_PORT || 3000; - const server = createServer(app); server.on("error", (err) => console.error(`server error; ${err.stack}`)); -server.listen(port, () => console.log(`http://localhost:${port}`)); +server.startDevvitServer(() => { + const addr = server.address(); + if (addr === null) { + console.log("Server address is null, but I'm listening!"); + return; + } + if (typeof addr === "string") { + console.log(`Server is listening on ${addr}`); + return; + } + if (typeof addr === "object") { + console.log(`Server is listening on ${addr.address}:${addr.port}`); + return; + } + console.error('...the hell is it doing?'); +}); diff --git a/src/server/middleware.ts b/src/server/middleware.ts deleted file mode 100644 index 9c66e8b..0000000 --- a/src/server/middleware.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { RequestContext } from '@devvit/server'; -import { Request, Response, NextFunction } from 'express'; -import { Devvit } from '@devvit/public-api'; - -// Extend Express Request type to include devvit context -declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Express { - export interface Request { - devvit: Devvit.Context; // Make it non-optional, middleware should add it - } - } -} - -// Create the middleware function -export function devvitMiddleware(req: Request, _res: Response, next: NextFunction): void { - // Add the devvit property to the request - req.devvit = RequestContext(req.headers) as Devvit.Context; - next(); -} From 8e51c4317ba51d560765c284e04f9f63e10f9f0e Mon Sep 17 00:00:00 2001 From: "ben.harris" Date: Tue, 20 May 2025 10:17:08 -0400 Subject: [PATCH 2/8] Typing tweaks & getServerPort() fix --- src/server/index.ts | 33 +++++++-------------------------- src/shared/types/api.ts | 11 ++++++++--- 2 files changed, 15 insertions(+), 29 deletions(-) diff --git a/src/server/index.ts b/src/server/index.ts index 0d2f132..09e2135 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,26 +1,15 @@ import express from "express"; -import { createServer, getContext } from "@devvit/server"; +import type {Response} from "express"; +import {createServer, getContext, getServerPort} from "@devvit/server"; import { InitResponse, IncrementResponse, DecrementResponse, } from "../shared/types/api"; -const app = express(); - -// Middleware for JSON body parsing -app.use(express.json()); -// Middleware for URL-encoded body parsing -app.use(express.urlencoded({ extended: true })); -// Middleware for plain text body parsing -app.use(express.text()); - const router = express.Router(); -router.get< - { postId: string }, - InitResponse | { status: string; message: string } ->("/api/init", async (_req, res): Promise => { +router.get("/api/init", async (_req, res: Response): Promise => { const context = getContext(); const postId = context.postId; @@ -50,11 +39,7 @@ router.get< } }); -router.post< - { postId: string }, - IncrementResponse | { status: string; message: string }, - unknown ->("/api/increment", async (_req, res): Promise => { +router.post("/api/increment", async (_req, res: Response): Promise => { const context = getContext(); const postId = context.postId; @@ -73,11 +58,7 @@ router.post< }); }); -router.post< - { postId: string }, - DecrementResponse | { status: string; message: string }, - unknown ->("/api/decrement", async (_req, res): Promise => { +router.post("/api/decrement", async (_req, res: Response): Promise => { const context = getContext(); const postId = context.postId; if (!postId) { @@ -95,12 +76,12 @@ router.post< }); }); -// Use router middleware +const app = express(); app.use(router); const server = createServer(app); server.on("error", (err) => console.error(`server error; ${err.stack}`)); -server.startDevvitServer(() => { +server.listen(getServerPort(), () => { const addr = server.address(); if (addr === null) { console.log("Server address is null, but I'm listening!"); diff --git a/src/shared/types/api.ts b/src/shared/types/api.ts index 61a0355..b1087aa 100644 --- a/src/shared/types/api.ts +++ b/src/shared/types/api.ts @@ -1,17 +1,22 @@ -export type InitResponse = { +export type InitResponse = ErrorResponse | { type: "init"; postId: string; count: number; }; -export type IncrementResponse = { +export type IncrementResponse = ErrorResponse | { type: "increment"; postId: string; count: number; }; -export type DecrementResponse = { +export type DecrementResponse = ErrorResponse | { type: "decrement"; postId: string; count: number; }; + +export type ErrorResponse = { + status: "error"; + message: string; +}; From 66f30bb8e0fe7041b979c9068b007be76dc6ed06 Mon Sep 17 00:00:00 2001 From: "ben.harris" Date: Tue, 20 May 2025 15:20:22 -0400 Subject: [PATCH 3/8] Swap out context.redis for getRedisClient() --- package.json | 1 + src/server/index.ts | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 81dcd2b..37fb920 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@devvit/client": "../../snoodev-and-friends/devvit/packages/client", "@devvit/public-api": "../../snoodev-and-friends/devvit/packages/public-api", "@devvit/server": "../../snoodev-and-friends/devvit/packages/server", + "@devvit/redis": "../../snoodev-and-friends/devvit/packages/redis", "devvit": "../../snoodev-and-friends/devvit/packages/devvit", "express": "5.1.0" }, diff --git a/src/server/index.ts b/src/server/index.ts index 09e2135..7ac4c2d 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,6 +1,8 @@ import express from "express"; import type {Response} from "express"; +import {getRedisClient, RedisKeyScope} from "@devvit/redis"; import {createServer, getContext, getServerPort} from "@devvit/server"; + import { InitResponse, IncrementResponse, @@ -11,6 +13,7 @@ const router = express.Router(); router.get("/api/init", async (_req, res: Response): Promise => { const context = getContext(); + const redis = getRedisClient(RedisKeyScope.INSTALLATION); const postId = context.postId; if (!postId) { @@ -23,7 +26,7 @@ router.get("/api/init", async (_req, res: Response): Promise } try { - const count = await context.redis.get("count"); + const count = await redis.get("count"); res.json({ type: "init", postId: postId, @@ -41,6 +44,7 @@ router.get("/api/init", async (_req, res: Response): Promise router.post("/api/increment", async (_req, res: Response): Promise => { const context = getContext(); + const redis = getRedisClient(RedisKeyScope.INSTALLATION); const postId = context.postId; if (!postId) { @@ -52,7 +56,7 @@ router.post("/api/increment", async (_req, res: Response): Pr } res.json({ - count: await context.redis.incrBy("count", 1), + count: await redis.incrBy("count", 1), postId, type: "increment", }); @@ -60,6 +64,7 @@ router.post("/api/increment", async (_req, res: Response): Pr router.post("/api/decrement", async (_req, res: Response): Promise => { const context = getContext(); + const redis = getRedisClient(RedisKeyScope.INSTALLATION); const postId = context.postId; if (!postId) { res.status(400).json({ @@ -70,7 +75,7 @@ router.post("/api/decrement", async (_req, res: Response): Pr } res.json({ - count: await context.redis.incrBy("count", -1), + count: await redis.incrBy("count", -1), postId, type: "decrement", }); From c99b8e6ab45fa73d9f6915a16e3f14441ce3b4cd Mon Sep 17 00:00:00 2001 From: "ben.harris" Date: Tue, 20 May 2025 17:00:15 -0400 Subject: [PATCH 4/8] getRedisClient() -> getRedis() --- src/server/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/server/index.ts b/src/server/index.ts index 7ac4c2d..319ff76 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,6 +1,6 @@ import express from "express"; import type {Response} from "express"; -import {getRedisClient, RedisKeyScope} from "@devvit/redis"; +import {getRedis} from "@devvit/redis"; import {createServer, getContext, getServerPort} from "@devvit/server"; import { @@ -13,7 +13,7 @@ const router = express.Router(); router.get("/api/init", async (_req, res: Response): Promise => { const context = getContext(); - const redis = getRedisClient(RedisKeyScope.INSTALLATION); + const redis = getRedis(); const postId = context.postId; if (!postId) { @@ -44,7 +44,7 @@ router.get("/api/init", async (_req, res: Response): Promise router.post("/api/increment", async (_req, res: Response): Promise => { const context = getContext(); - const redis = getRedisClient(RedisKeyScope.INSTALLATION); + const redis = getRedis(); const postId = context.postId; if (!postId) { @@ -64,7 +64,7 @@ router.post("/api/increment", async (_req, res: Response): Pr router.post("/api/decrement", async (_req, res: Response): Promise => { const context = getContext(); - const redis = getRedisClient(RedisKeyScope.INSTALLATION); + const redis = getRedis(); const postId = context.postId; if (!postId) { res.status(400).json({ From c0030f5ac17817b462a2768b05ff59f280e31f80 Mon Sep 17 00:00:00 2001 From: "ben.harris" Date: Tue, 27 May 2025 16:20:42 -0400 Subject: [PATCH 5/8] Minor redis tweaks; add Reddit client & use it --- package.json | 1 + src/server/index.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 37fb920..100ff7c 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@devvit/public-api": "../../snoodev-and-friends/devvit/packages/public-api", "@devvit/server": "../../snoodev-and-friends/devvit/packages/server", "@devvit/redis": "../../snoodev-and-friends/devvit/packages/redis", + "@devvit/reddit": "../../snoodev-and-friends/devvit/packages/reddit", "devvit": "../../snoodev-and-friends/devvit/packages/devvit", "express": "5.1.0" }, diff --git a/src/server/index.ts b/src/server/index.ts index 319ff76..c18ea99 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -8,6 +8,7 @@ import { IncrementResponse, DecrementResponse, } from "../shared/types/api"; +import {getReddit} from "../../../../snoodev-and-friends/devvit/packages/reddit"; const router = express.Router(); @@ -25,6 +26,9 @@ router.get("/api/init", async (_req, res: Response): Promise return; } + const user = await getReddit().getCurrentUser(); + console.log('API Init for user: ', user?.username); + try { const count = await redis.get("count"); res.json({ @@ -56,7 +60,7 @@ router.post("/api/increment", async (_req, res: Response): Pr } res.json({ - count: await redis.incrBy("count", 1), + count: await redis.incrby("count", 1), postId, type: "increment", }); @@ -75,7 +79,7 @@ router.post("/api/decrement", async (_req, res: Response): Pr } res.json({ - count: await redis.incrBy("count", -1), + count: await redis.incrby("count", -1), postId, type: "decrement", }); From abfc7015c9fd4bc961e0d9e8e59e624307e503f4 Mon Sep 17 00:00:00 2001 From: Andrew Gunsch Date: Wed, 28 May 2025 16:19:03 -0400 Subject: [PATCH 6/8] server build and bundler --- .gitignore | 3 +- build-server.sh | 20 ++++++ bundle.server.template.json | 98 ++++++++++++++++++++++++++ esbuild.mjs | 53 ++++++++++++++ package.json | 15 ++-- src/server/index.ts | 137 +++++++++++++++++++----------------- 6 files changed, 255 insertions(+), 71 deletions(-) create mode 100755 build-server.sh create mode 100644 bundle.server.template.json create mode 100755 esbuild.mjs diff --git a/.gitignore b/.gitignore index 235067b..201facc 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ yarn-debug.log* yarn-error.log* .DS_Store webroot -dist \ No newline at end of file +dist +bin \ No newline at end of file diff --git a/build-server.sh b/build-server.sh new file mode 100755 index 0000000..209bf10 --- /dev/null +++ b/build-server.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -euo pipefail + +OUTPUT_FILE=bin/index.cjs + +# Build the server bundle using esbuild +./esbuild.mjs + +# Create a temporary directory +TEMP_DIR=$(mktemp -d) +trap 'rm -rf "$TEMP_DIR"' EXIT + +jq --rawfile code "$OUTPUT_FILE" \ + --rawfile sourcemap "${OUTPUT_FILE}.map" \ + '.code = $code | .sourceMap = $sourcemap' \ + bundle.server.template.json > bin/bundle.server.json + +echo "Bundle created at bundle.json" + diff --git a/bundle.server.template.json b/bundle.server.template.json new file mode 100644 index 0000000..cadb553 --- /dev/null +++ b/bundle.server.template.json @@ -0,0 +1,98 @@ +{ + "actor": { + "name": "main", + "owner": "snoo", + "version": "0.0.0" + }, + "code": "", + "sourceMap": "", + "hostname": "main.local", + "provides": [ + { + "fullName": "devvit.actor.hello.Hello", + "methods": [ + { + "fullName": "/devvit.actor.hello.Hello/Ping", + "name": "Ping", + "requestType": "devvit.actor.hello.PingMessage", + "responseType": "devvit.actor.hello.PingMessage" + } + ], + "name": "Hello" + } + ], + "uses": [ + { + "actor": { + "name": "default", + "owner": "devvit", + "version": "1.0.0" + }, + "hostname": "wrappertypes.plugins.local", + "provides": [ + { + "fullName": "devvit.actor.test.WrapperTypes", + "methods": [ + { + "fullName": "/devvit.actor.test.WrapperTypes/StringRequest", + "name": "StringRequest", + "requestType": "google.protobuf.StringValue", + "responseType": "google.protobuf.StringValue" + }, + { + "fullName": "/devvit.actor.test.WrapperTypes/BoolRequest", + "name": "BoolRequest", + "requestType": "google.protobuf.BoolValue", + "responseType": "google.protobuf.BoolValue" + }, + { + "fullName": "/devvit.actor.test.WrapperTypes/Int32Request", + "name": "Int32Request", + "requestType": "google.protobuf.Int32Value", + "responseType": "google.protobuf.Int32Value" + }, + { + "fullName": "/devvit.actor.test.WrapperTypes/UInt32Request", + "name": "UInt32Request", + "requestType": "google.protobuf.UInt32Value", + "responseType": "google.protobuf.UInt32Value" + }, + { + "fullName": "/devvit.actor.test.WrapperTypes/Int64Request", + "name": "Int64Request", + "requestType": "google.protobuf.Int64Value", + "responseType": "google.protobuf.Int64Value" + }, + { + "fullName": "/devvit.actor.test.WrapperTypes/UInt64Request", + "name": "UInt64Request", + "requestType": "google.protobuf.UInt64Value", + "responseType": "google.protobuf.UInt64Value" + }, + { + "fullName": "/devvit.actor.test.WrapperTypes/FloatRequest", + "name": "FloatRequest", + "requestType": "google.protobuf.FloatValue", + "responseType": "google.protobuf.FloatValue" + }, + { + "fullName": "/devvit.actor.test.WrapperTypes/DoubleRequest", + "name": "DoubleRequest", + "requestType": "google.protobuf.DoubleValue", + "responseType": "google.protobuf.DoubleValue" + } + ], + "name": "WrapperTypes" + } + ] + } + ], + "buildInfo": { + "created": "2025-03-17T17:25:08.874Z", + "dependencies": { + "node": "22.6.0", + "@devvit/protos": "0.11.11-dev", + "@devvit/public-api": "0.11.11-dev" + } + } +} diff --git a/esbuild.mjs b/esbuild.mjs new file mode 100755 index 0000000..3200be4 --- /dev/null +++ b/esbuild.mjs @@ -0,0 +1,53 @@ +#!/usr/bin/env node + +/* eslint-disable no-console */ +import * as esbuild from "esbuild"; +import { nodeExternalsPlugin } from "esbuild-node-externals"; + +import path from "path"; + +/** + * @param {boolean} watch + * @returns {Promise> | undefined} + */ +async function build(watch) { + /** @type {esbuild.BuildOptions} */ + const opts = { + entryPoints: ["src/server/index.ts"], + outdir: "bin", + format: "cjs", + entryNames: "[name]", + outExtension: { ".js": ".cjs" }, + plugins: [ + nodeExternalsPlugin({ + packagePath: path.resolve("./package.json"), + allowList: [ + 'express', + /^@devvit\// + ], + }), + ], + sourcemap: true, + bundle: true, + platform: "node", + }; + + if (!watch) { + await esbuild.build(opts); + console.log( + `[esbuild/server] Build finished. Output is in ${path.resolve("bin")}` + ); + return; + } + + console.log("[esbuild/server] Starting watch mode..."); + const ctx = await esbuild.context(opts); + await ctx.watch(); +} + +async function main() { + const enableWatchMode = process.argv.includes("--watch"); + await build(enableWatchMode); +} + +await main(); diff --git a/package.json b/package.json index 100ff7c..fc41cf9 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "type": "module", "scripts": { "build:client": "cd src/client && vite build", + "build:server": "./build-server.sh", "deploy": "npm run build:client && devvit upload", "dev": "concurrently -p \"[{name}]\" -n \"VITE,DEVVIT,GAME\" -c \"blue,green,magenta\" \"npm run dev:vite\" \"npm run dev:devvit\" \"npm run dev:client\" --restart-tries 2", "dev:client": "cd src/client && vite build --watch", @@ -15,17 +16,19 @@ "type-check": "tsc --build" }, "dependencies": { - "@devvit/client": "../../snoodev-and-friends/devvit/packages/client", - "@devvit/public-api": "../../snoodev-and-friends/devvit/packages/public-api", - "@devvit/server": "../../snoodev-and-friends/devvit/packages/server", - "@devvit/redis": "../../snoodev-and-friends/devvit/packages/redis", - "@devvit/reddit": "../../snoodev-and-friends/devvit/packages/reddit", - "devvit": "../../snoodev-and-friends/devvit/packages/devvit", + "@devvit/client": "../../devvit/packages/client", + "@devvit/public-api": "../../devvit/packages/public-api", + "@devvit/server": "../../devvit/packages/server", + "@devvit/redis": "../../devvit/packages/redis", + "@devvit/reddit": "../../devvit/packages/reddit", + "devvit": "../../devvit/packages/devvit", "express": "5.1.0" }, "devDependencies": { "@types/express": "5.0.1", "concurrently": "9.1.2", + "esbuild": "0.23.0", + "esbuild-node-externals": "1.15.0", "typescript": "5.8.2", "vite": "6.2.4" } diff --git a/src/server/index.ts b/src/server/index.ts index c18ea99..5fcb79c 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,89 +1,98 @@ import express from "express"; -import type {Response} from "express"; -import {getRedis} from "@devvit/redis"; -import {createServer, getContext, getServerPort} from "@devvit/server"; +import type { Response } from "express"; +import { getReddit } from "@devvit/reddit"; +import { getRedis } from "@devvit/redis"; +import { createServer, getContext, getServerPort } from "@devvit/server"; import { InitResponse, IncrementResponse, DecrementResponse, } from "../shared/types/api"; -import {getReddit} from "../../../../snoodev-and-friends/devvit/packages/reddit"; const router = express.Router(); -router.get("/api/init", async (_req, res: Response): Promise => { - const context = getContext(); - const redis = getRedis(); - const postId = context.postId; +router.get( + "/api/init", + async (_req, res: Response): Promise => { + const context = getContext(); + const redis = getRedis(); + const postId = context.postId; - if (!postId) { - console.error("API Init Error: postId not found in devvit context"); - res.status(400).json({ - status: "error", - message: "postId is required but missing from context", - }); - return; - } + if (!postId) { + console.error("API Init Error: postId not found in devvit context"); + res.status(400).json({ + status: "error", + message: "postId is required but missing from context", + }); + return; + } - const user = await getReddit().getCurrentUser(); - console.log('API Init for user: ', user?.username); + const user = await getReddit().getCurrentUser(); + console.log("API Init for user: ", user?.username); - try { - const count = await redis.get("count"); - res.json({ - type: "init", - postId: postId, - count: count ? parseInt(count) : 0, - }); - } catch (error) { - console.error(`API Init Error for post ${postId}:`, error); - let errorMessage = "Unknown error during initialization"; - if (error instanceof Error) { - errorMessage = `Initialization failed: ${error.message}`; + try { + const count = await redis.get("count"); + res.json({ + type: "init", + postId: postId, + count: count ? parseInt(count) : 0, + }); + } catch (error) { + console.error(`API Init Error for post ${postId}:`, error); + let errorMessage = "Unknown error during initialization"; + if (error instanceof Error) { + errorMessage = `Initialization failed: ${error.message}`; + } + res.status(400).json({ status: "error", message: errorMessage }); } - res.status(400).json({ status: "error", message: errorMessage }); } -}); +); -router.post("/api/increment", async (_req, res: Response): Promise => { - const context = getContext(); - const redis = getRedis(); - const postId = context.postId; +router.post( + "/api/increment", + async (_req, res: Response): Promise => { + const context = getContext(); + const redis = getRedis(); + const postId = context.postId; + + if (!postId) { + res.status(400).json({ + status: "error", + message: "postId is required", + }); + return; + } - if (!postId) { - res.status(400).json({ - status: "error", - message: "postId is required", + res.json({ + count: await redis.incrby("count", 1), + postId, + type: "increment", }); - return; } +); - res.json({ - count: await redis.incrby("count", 1), - postId, - type: "increment", - }); -}); +router.post( + "/api/decrement", + async (_req, res: Response): Promise => { + const context = getContext(); + const redis = getRedis(); + const postId = context.postId; + if (!postId) { + res.status(400).json({ + status: "error", + message: "postId is required", + }); + return; + } -router.post("/api/decrement", async (_req, res: Response): Promise => { - const context = getContext(); - const redis = getRedis(); - const postId = context.postId; - if (!postId) { - res.status(400).json({ - status: "error", - message: "postId is required", + res.json({ + count: await redis.incrby("count", -1), + postId, + type: "decrement", }); - return; } - - res.json({ - count: await redis.incrby("count", -1), - postId, - type: "decrement", - }); -}); +); const app = express(); app.use(router); @@ -104,5 +113,5 @@ server.listen(getServerPort(), () => { console.log(`Server is listening on ${addr.address}:${addr.port}`); return; } - console.error('...the hell is it doing?'); + console.error("...the hell is it doing?"); }); From ac927b5fc8f469e1d7ac7959f02d2eb4b9e41ce8 Mon Sep 17 00:00:00 2001 From: Andrew Gunsch Date: Fri, 30 May 2025 01:59:30 -0400 Subject: [PATCH 7/8] add /api/hello route --- build-server.sh | 2 +- bundle.server.template.json | 1 + src/server/index.ts | 33 +++++++++++++++++++++++++++++++-- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/build-server.sh b/build-server.sh index 209bf10..d5ca572 100755 --- a/build-server.sh +++ b/build-server.sh @@ -16,5 +16,5 @@ jq --rawfile code "$OUTPUT_FILE" \ '.code = $code | .sourceMap = $sourcemap' \ bundle.server.template.json > bin/bundle.server.json -echo "Bundle created at bundle.json" +echo "Bundle created at bin/bundle.server.json" diff --git a/bundle.server.template.json b/bundle.server.template.json index cadb553..1f12505 100644 --- a/bundle.server.template.json +++ b/bundle.server.template.json @@ -6,6 +6,7 @@ }, "code": "", "sourceMap": "", + "devvitJson": { "name": "abc", "server": {} }, "hostname": "main.local", "provides": [ { diff --git a/src/server/index.ts b/src/server/index.ts index 5fcb79c..02e2f44 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,8 +1,13 @@ import express from "express"; -import type { Response } from "express"; +import type { Request, Response } from "express"; import { getReddit } from "@devvit/reddit"; import { getRedis } from "@devvit/redis"; -import { createServer, getContext, getServerPort } from "@devvit/server"; +import { + createServer, + getContext, + getServerPort, + webbitEnable, +} from "@devvit/server"; import { InitResponse, @@ -94,6 +99,30 @@ router.post( } ); +router.get( + "/api/hello", + async ( + req: Request, + res: Response< + { message: string; status: string } | { postId: string; message: string } + > + ): Promise => { + const context = getContext(); + const postId = context.postId; + if (!postId) { + res.status(400).json({ + status: "error", + message: "postId is required", + }); + return; + } + + const message = + typeof req.query["message"] === "string" ? req.query["message"] : ""; + res.json({ message, postId }); + } +); + const app = express(); app.use(router); From 43f82274f30517fe77057673ab7d31cc27939af1 Mon Sep 17 00:00:00 2001 From: Andrew Gunsch Date: Mon, 2 Jun 2025 22:57:58 -0400 Subject: [PATCH 8/8] server code bundling --- build-server.sh | 16 ++++++++++------ src/devvit/main.tsx | 39 ++++++++++++++++++++++++++++----------- src/server/index.ts | 14 ++++++++++++++ 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/build-server.sh b/build-server.sh index d5ca572..b77b2e3 100755 --- a/build-server.sh +++ b/build-server.sh @@ -2,7 +2,11 @@ set -euo pipefail -OUTPUT_FILE=bin/index.cjs +SERVER_JS_OUTPUT=bin/index.cjs +OUTPUT_BUNDLE=dist/main.bundle.json + +# This should spit out a bundle using the classic bundler approach +npx devvit bundle actor main # Build the server bundle using esbuild ./esbuild.mjs @@ -11,10 +15,10 @@ OUTPUT_FILE=bin/index.cjs TEMP_DIR=$(mktemp -d) trap 'rm -rf "$TEMP_DIR"' EXIT -jq --rawfile code "$OUTPUT_FILE" \ - --rawfile sourcemap "${OUTPUT_FILE}.map" \ - '.code = $code | .sourceMap = $sourcemap' \ - bundle.server.template.json > bin/bundle.server.json +jq --rawfile code "$SERVER_JS_OUTPUT" \ + --rawfile sourcemap "${SERVER_JS_OUTPUT}.map" \ + '.standaloneServerCode = $code | .standaloneServerSourceMap = $sourcemap | .actor.name = "main" | .actor.version = "0.0.0"' \ + ${OUTPUT_BUNDLE} > bin/bundle.combined.json -echo "Bundle created at bin/bundle.server.json" +echo "Bundle created at bin/bundle.combined.json" diff --git a/src/devvit/main.tsx b/src/devvit/main.tsx index 05d7ccc..0e5cf76 100644 --- a/src/devvit/main.tsx +++ b/src/devvit/main.tsx @@ -1,19 +1,36 @@ import { Devvit } from "@devvit/public-api"; // Side effect import to bundle the server. The /index is required for server splitting. -import "../server/index"; -import { defineConfig } from "@devvit/server"; +// import "../server/index"; +// import { defineConfig } from "@devvit/server"; -defineConfig({ +// defineConfig({ +// name: "Hello World", +// description: "Hello World", +// entry: "index.html", +// height: "tall", +// inline: true, +// menu: { +// enable: true, +// label: "[Webbit] New Hello World Post", +// postTitle: "Hello World", +// }, +// }); + +Devvit.addCustomPostType({ name: "Hello World", - description: "Hello World", - entry: "index.html", - height: "tall", - inline: true, - menu: { - enable: true, - label: "[Webbit] New Hello World Post", - postTitle: "Hello World", + description: "A simple hello world post type", + render: (context) => { + if (context.postId === "t3_THROW_ERROR") { + throw new Error("Test error."); + } + + console.log("Post ID!!:", context.postId); + console.log("App Name!!:", context.appName); + console.log("User ID!!:", context.userId); + console.log("Subreddit ID!!:", context.subredditId); + console.log("App version!!:", context.appVersion); + return ; }, }); diff --git a/src/server/index.ts b/src/server/index.ts index 02e2f44..956b8ca 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -123,6 +123,20 @@ router.get( } ); +router.get( + "/api/error", + async ( + _req: Request, + _res: Response< + { message: string; status: string } | { postId: string; message: string } + > + ): Promise => { + const context = getContext(); + const postId = context.postId; + throw new Error("This is a test error from the API: " + postId); + } +); + const app = express(); app.use(router);