Skip to content

Commit 2f2b31c

Browse files
stash
1 parent 92f65f5 commit 2f2b31c

31 files changed

+1868
-593
lines changed

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ public-hoist-pattern[]=vitest
66
public-hoist-pattern[]=@types/*
77
public-hoist-pattern[]=@vitest/*
88
public-hoist-pattern[]=@effect/vitest
9+
public-hoist-pattern[]=@astrojs/check
910

1011
recursive-install=true
1112
dedupe-peer-dependents=false

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"cSpell.enabled": true,
1111
"cSpell.words": [
1212
"apks",
13+
"astrojs",
1314
"authproxy",
1415
"bbnotes",
1516
"bbpost",

apps/tinyburg.app/api/handler.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { HttpApp, type HttpMiddleware } from "@effect/platform";
2+
import { type APIRoute } from "astro";
3+
import { Context, type ManagedRuntime } from "effect";
4+
5+
import { AppRuntime } from "./runtime";
6+
import { AstroContext } from "./tags";
7+
8+
export const makeAstroEndpoint = <E>(
9+
app: HttpApp.Default<E, AstroContext | ManagedRuntime.ManagedRuntime.Context<typeof AppRuntime>>,
10+
middleware?: HttpMiddleware.HttpMiddleware | undefined
11+
): APIRoute => {
12+
return async (apiContext) => {
13+
const runtime = await AppRuntime.runtime();
14+
const context = Context.make(AstroContext, apiContext);
15+
const appAstroContextOmitted = app as HttpApp.Default<E, never>;
16+
const handler = HttpApp.toWebHandlerRuntime(runtime)(appAstroContextOmitted, middleware);
17+
return await handler(apiContext.request, context);
18+
};
19+
};
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { Path } from "@effect/platform";
1+
import { FetchHttpClient, Path, PlatformConfigProvider } from "@effect/platform";
22
import { NodeContext } from "@effect/platform-node";
33
import { PgClient, PgMigrator } from "@effect/sql-pg";
4-
import { Config, Effect, Layer, String } from "effect";
4+
import { Config, Effect, Layer, ManagedRuntime, String } from "effect";
55

66
import { Repository } from "../domain/model.ts";
77

88
/**
99
* @since 1.0.0
1010
* @category Layers
1111
*/
12-
export const SqlLive = PgClient.layerConfig({
12+
const SqlLive = PgClient.layerConfig({
1313
url: Config.redacted("DATABASE_URL"),
1414
transformQueryNames: Config.succeed(String.camelToSnake),
1515
transformResultNames: Config.succeed(String.snakeToCamel),
@@ -19,7 +19,7 @@ export const SqlLive = PgClient.layerConfig({
1919
* @since 1.0.0
2020
* @category Layers
2121
*/
22-
export const MigratorLive = Effect.gen(function* () {
22+
const MigratorLive = Effect.gen(function* () {
2323
const path = yield* Path.Path;
2424
const migrations = yield* path.fromFileUrl(new URL("../migrations", import.meta.url));
2525
const loader = PgMigrator.fromFileSystem(migrations);
@@ -30,4 +30,19 @@ export const MigratorLive = Effect.gen(function* () {
3030
* @since 1.0.0
3131
* @category Layers
3232
*/
33-
export const DatabaseLive = Repository.Default.pipe(Layer.provide(MigratorLive), Layer.provide(SqlLive));
33+
const DatabaseLive = Repository.Default.pipe(Layer.provide(MigratorLive), Layer.provide(SqlLive));
34+
35+
/**
36+
* @since 1.0.0
37+
* @category Layers
38+
*/
39+
export const AppLive = Layer.mergeAll(DatabaseLive, FetchHttpClient.layer).pipe(
40+
Layer.provide(PlatformConfigProvider.layerDotEnvAdd("./.env")),
41+
Layer.provide(NodeContext.layer)
42+
);
43+
44+
/**
45+
* @since 1.0.0
46+
* @category Runtime
47+
*/
48+
export const AppRuntime = ManagedRuntime.make(AppLive);

apps/tinyburg.app/api/tags.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import type { APIContext } from "astro";
2+
3+
import { Context } from "effect";
4+
5+
export class AstroContext extends Context.Tag("AstroContext")<AstroContext, APIContext>() {}

apps/tinyburg.app/domain/model.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Model, SqlClient, SqlSchema } from "@effect/sql";
2+
import { PlayerAuthKeySchema, PlayerIdSchema } from "@tinyburg/nimblebit-sdk/NimblebitConfig";
23
import { DateTime, Duration, Effect, Schema } from "effect";
34

45
/**
@@ -43,6 +44,32 @@ export class Session extends Model.Class<Session>("Session")({
4344
expiresAt: Model.DateTimeFromDate,
4445
}) {}
4546

47+
/**
48+
* @since 1.0.0
49+
* @category Models
50+
*/
51+
export class TinyTowerAccount extends Model.Class<TinyTowerAccount>("TinyTowerAccount")({
52+
id: Model.Generated(Schema.UUID),
53+
userId: Schema.UUID,
54+
playerId: PlayerIdSchema,
55+
playerAuthKey: PlayerAuthKeySchema,
56+
playerEmail: Schema.String,
57+
createdAt: Model.DateTimeInsertFromDate,
58+
verifiedAt: Model.DateTimeFromDate,
59+
}) {}
60+
61+
/**
62+
* @since 1.0.0
63+
* @category Models
64+
*/
65+
export class PendingTinyTowerAccount extends Model.Class<PendingTinyTowerAccount>("PendingTinyTowerAccount")({
66+
id: Model.Generated(Schema.UUID),
67+
userId: Schema.UUID,
68+
playerId: PlayerIdSchema,
69+
playerEmail: Schema.String,
70+
createdAt: Model.DateTimeInsertFromDate,
71+
}) {}
72+
4673
/**
4774
* @since 1.0.0
4875
* @category Services
@@ -51,6 +78,7 @@ export class Repository extends Effect.Service<Repository>()("@tinyburg/tinyburg
5178
accessors: true,
5279
dependencies: [],
5380
effect: Effect.gen(function* () {
81+
console.log("here");
5482
const sql = yield* SqlClient.SqlClient;
5583

5684
const sessions = yield* Model.makeRepository(Session, {
@@ -59,6 +87,18 @@ export class Repository extends Effect.Service<Repository>()("@tinyburg/tinyburg
5987
spanPrefix: "tinyburg.app.domain.Repository.sessions",
6088
});
6189

90+
const _tinytowerAccounts = yield* Model.makeRepository(TinyTowerAccount, {
91+
idColumn: "id",
92+
tableName: "tinytower_accounts",
93+
spanPrefix: "tinyburg.app.domain.Repository.tinytowerAccounts",
94+
});
95+
96+
const _pendingTinyTowerAccounts = yield* Model.makeRepository(PendingTinyTowerAccount, {
97+
idColumn: "id",
98+
tableName: "pending_tinytower_accounts",
99+
spanPrefix: "tinyburg.app.domain.Repository.pendingTinyTowerAccounts",
100+
});
101+
62102
const deleteSession = sessions.delete;
63103
const createSession = (
64104
user: User,

apps/tinyburg.app/migrations/0001_add_users_and_oauth.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,38 @@ export default Effect.flatMap(
3232
expires_at TIMESTAMPTZ NOT NULL
3333
);
3434
35+
-- TinyTower accounts linked to users
36+
CREATE TABLE IF NOT EXISTS tinytower_accounts (
37+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
38+
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
39+
player_id TEXT NOT NULL UNIQUE,
40+
player_auth_key TEXT NOT NULL,
41+
player_email TEXT NOT NULL,
42+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
43+
verified_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
44+
);
45+
46+
-- Helper function to add one day interval because generated columns need to be immutable
47+
CREATE OR REPLACE FUNCTION add_one_day(ts TIMESTAMPTZ)
48+
RETURNS TIMESTAMPTZ AS $$
49+
SELECT ts + INTERVAL '1 day'
50+
$$ LANGUAGE SQL IMMUTABLE;
51+
52+
-- Pending TinyTower link requests
53+
CREATE TABLE IF NOT EXISTS pending_tinytower_accounts (
54+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
55+
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
56+
player_id TEXT NOT NULL,
57+
player_email TEXT NOT NULL,
58+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
59+
expires_at TIMESTAMPTZ NOT NULL GENERATED ALWAYS AS (add_one_day(created_at)) STORED
60+
);
61+
3562
-- Indexes for common query patterns
3663
CREATE INDEX IF NOT EXISTS idx_oauth_accounts_user_id ON oauth_accounts(user_id);
3764
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
65+
CREATE INDEX IF NOT EXISTS idx_tinytower_accounts_user_id ON tinytower_accounts(user_id);
66+
CREATE INDEX IF NOT EXISTS idx_tinytower_accounts_player_id ON tinytower_accounts(player_id);
67+
CREATE INDEX IF NOT EXISTS idx_pending_tinytower_accounts_user_id ON pending_tinytower_accounts(user_id);
3868
`
3969
);

apps/tinyburg.app/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"preview": "astro preview"
1111
},
1212
"dependencies": {
13+
"@astrojs/check": "0.9.6",
1314
"@astrojs/node": "9.2.2",
1415
"@effect/cluster": "0.56.1",
1516
"@effect/experimental": "0.58.0",
@@ -19,6 +20,7 @@
1920
"@effect/sql": "0.49.0",
2021
"@effect/sql-pg": "0.50.1",
2122
"@effect/workflow": "0.16.0",
23+
"@tinyburg/nimblebit-sdk": "workspace:*",
2224
"astro": "5.16.14",
2325
"effect": "3.19.14",
2426
"typescript": "5.9.3"
113 KB
Loading
607 KB
Loading

0 commit comments

Comments
 (0)