Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CONTEXT-MAP.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Context map

This repo has six contexts, one per app. Each context has its own `CONTEXT.md` and `apps/<app>/docs/adr/`.
This repo has seven contexts, one per app. Each context has its own `CONTEXT.md` and `apps/<app>/docs/adr/`.

- **api** — NestJS backend API. See `apps/api/CONTEXT.md` (created lazily by `/domain-modeling`).
- **api-go** — Go Mirror of the API. See `apps/api-go/CONTEXT.md`.
- **api-axum** — Rust Axum Mirror of the API. See `apps/api-axum/CONTEXT.md`.
- **api-elysia** — Elysia/Bun Mirror of the API. See `apps/api-elysia/CONTEXT.md`.
- **api-effect** — Effect v4/Bun Mirror of the API. See `apps/api-effect/CONTEXT.md` (created lazily by `/domain-modeling`).
- **web** — frontend web app. See `apps/web/CONTEXT.md` (created lazily by `/domain-modeling`).
- **web-vue** — Vue Mirror of web. See `apps/web-vue/CONTEXT.md`.

Expand Down
51 changes: 51 additions & 0 deletions apps/api-effect/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Environment variables declared in this file are NOT automatically loaded by Prisma.
# Please add `import "dotenv/config";` to your `prisma.config.ts` file, or use the Prisma CLI with Bun
# to load environment variables from .env files: https://pris.ly/prisma-config-env-vars.

# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings

DATABASE_URL="postgresql://user:pass@localhost:5432/webdevstudios?schema=public"
PORT=4003
CORS_ORIGIN="http://localhost:3000"

# Csrf secret
CSRF_SECRET="csrf-secret"

# Mail
MAIL_USER=""
MAIL_PASS=""

# Payment
PAYOS_CLIENT_ID=""
PAYOS_API_KEY=""
PAYOS_CHECKSUM_KEY=""
PAYOS_RETURN_URL="http://localhost/v1/payments/*"
PAYOS_CANCEL_URL="http://localhost/v1/payments/*"

# JWT
JWT_SECRET_KEY=...

# OAuth Google
GOOGLE_CLIENT_ID="<id>.apps.googleusercontent.com"
GOOGLE_CLIENT_SECRET="key"
GOOGLE_CALLBACK_URL="http://localhost:4003/v1/auth/oauth/google/callback"

# OAuth GitHub
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
GITHUB_CALLBACK_URL=http://localhost:4003/v1/auth/oauth/github/callback

# Frontend URLs
FRONTEND_URL=http://localhost:3000

# R2
R2_ACCESS_KEY_ID=your_access_key
R2_SECRET_ACCESS_KEY=your_secret_key
R2_BUCKET_NAME=webdevstudios-storage
R2_PUBLIC_URL=https://pub-xxxxx.r2.dev
R2_ENDPOINT=https://xxxxx.r2.cloudflarestorage.com

# Redis
REDIS_HOST="localhost"
REDIS_PORT="6379"
8 changes: 8 additions & 0 deletions apps/api-effect/docs/adr/0001-pin-effect-4-rc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Pin Effect 4.0.0-rc.112 exactly

Effect v4 is only available as a fast-moving release candidate (rc.112 at project start; breaking changes land weekly, e.g. schema-aware RPC serialization). We pin `effect` and its `@effect/platform-*` packages to exact `4.0.0-rc.112` — no `^` — so the Mirror builds reproducibly; upgrades are deliberate, verified bumps.

## Considered Options

- Floating `^4.0.0-rc.x`: weekly surprise breakage mid-build. Rejected.
- Staying on Effect v3: not the point of this Mirror.
38 changes: 38 additions & 0 deletions apps/api-effect/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "api-effect",
"version": "1.0.0",
"description": "WebDev Studios E-commerce Platform - Effect v4/Bun mirror backend",
"author": "Xiro The Dev <lethanhtrung.trungle@gmail.com>",
"private": true,
"license": "SEE LICENSE IN LICENSE.md",
"type": "module",
"scripts": {
"dev": "bun --watch src/index.ts",
"start": "bun src/index.ts",
"test": "bun test ./src",
"lint": "bunx --bun --no-install oxlint --fix .",
"prisma:generate": "bunx --bun prisma generate",
"format": "bunx --bun --no-install prettier --write \"src/**/*.ts\""
},
"dependencies": {
"@aws-sdk/client-s3": "^3.1120.0",
"@effect/platform-bun": "4.0.0-rc.112",
"@prisma/adapter-pg": "^7.10.0",
"@prisma/client": "^7.10.0",
"@prisma/client-runtime-utils": "^7.10.0",
"argon2": "^0.45.1",
"effect": "4.0.0-rc.112",
"jose": "^6.2.10",
"nodemailer": "^9.0.6",
"qrcode": "^1.5.4",
"speakeasy": "^2.0.0"
},
"devDependencies": {
"@types/bun": "^1.4.0",
"@types/nodemailer": "^8.0.1",
"@types/qrcode": "^1.5.6",
"@types/speakeasy": "^2.0.10",
"dotenv": "^17.4.2",
"prisma": "^7.8.0"
}
}
35 changes: 35 additions & 0 deletions apps/api-effect/prisma.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Copyright (c) 2026 Xiro The Dev <lethanhtrung.trungle@gmail.com>
*
* Source Available License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to:
* - View and study the Software for educational purposes
* - Fork this repository on GitHub for personal reference
* - Share links to this repository
*
* THE FOLLOWING ARE PROHIBITED:
* - Using the Software in production or commercial applications
* - Copying substantial portions of the Software into other projects
* - Distributing modified versions of the Software
* - Removing or altering copyright notices
*
* For commercial licensing or usage permissions, contact: lethanhtrung.trungle@gmail.com
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
*/

/// <reference types="node" />

import 'dotenv/config';

import { join } from 'path';
import { defineConfig } from 'prisma/config';

export default defineConfig({
schema: join(__dirname, 'prisma', 'schema'),
datasource: {
url: process.env['DATABASE_URL'],
},
});
136 changes: 136 additions & 0 deletions apps/api-effect/prisma/schema/auth.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright (c) 2026 Xiro The Dev <lethanhtrung.trungle@gmail.com>
//
// Source Available License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to:
// - View and study the Software for educational purposes
// - Fork this repository on GitHub for personal reference
// - Share links to this repository
//
// THE FOLLOWING ARE PROHIBITED:
// - Using the Software in production or commercial applications
// - Copying substantial portions of the Software into other projects
// - Distributing modified versions of the Software
// - Removing or altering copyright notices
//
// For commercial licensing or usage permissions, contact: lethanhtrung.trungle@gmail.com
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
//
// =========================================
// OAUTH & AUTHENTICATION
// =========================================

// Store OAuth login information (Google, Facebook, etc.)
model ExternalAccount {
id String @id @default(cuid())
provider OAuthProvider
providerId String @db.VarChar(255) // ID from provider (e.g.: Google user ID)
providerEmail String? @db.VarChar(255) // Email from provider
accessToken String? @db.Text // Encrypted access token
refreshToken String? @db.Text // Encrypted refresh token
expiresAt DateTime? // Token expiration

userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

// Unique constraint: a provider can only be linked once with a user
@@unique([provider, providerId])
@@unique([provider, userId])
@@index([userId])
@@index([provider, providerId])
@@map("external_accounts")
}

// Manage user MFA methods
model UserMFAMethod {
id String @id @default(cuid())
methodType MFAMethod // TOTP, SMS, EMAIL, BACKUP_CODE
secret String? @db.Text // Encrypted secret (for TOTP)
phone String? @db.VarChar(15) // For SMS
isActive Boolean @default(true)
isVerified Boolean @default(false) // Whether verified

userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
lastUsedAt DateTime? // Last time used

@@index([userId])
@@map("mfa_methods")
}

// Backup codes for MFA (single use)
model MFABackupCode {
id String @id @default(cuid())
code String @db.VarChar(20) // Hashed backup code
isUsed Boolean @default(false)
usedAt DateTime?

userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)

createdAt DateTime @default(now())

@@index([userId])
@@map("mfa_backup_codes")
}

// Log login devices
model Device {
id String @id @default(cuid())
name String? @db.VarChar(255) // Device name (e.g.: "iPhone 14 Pro")
type DeviceType @default(UNKNOWN)
userAgent String? @db.Text // User agent string
ipAddress String? @db.VarChar(45) // IPv4 or IPv6
fingerprint String? @db.VarChar(255) // Device fingerprint

userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)

isTrusted Boolean @default(false) // User has trusted this device
lastSeenAt DateTime @default(now())
createdAt DateTime @default(now())

// Relations
session Session? // Current session of this device (one-to-one)

@@index([userId])
@@index([fingerprint])
@@map("devices")
}

// Manage login sessions
model Session {
id String @id @default(cuid())
token String @unique @db.Text // Session token (JWT or random string)
refreshToken String? @unique @db.Text // Refresh token

userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)

deviceId String? @unique // Link to Device (unique: one device has only one session)
device Device? @relation(fields: [deviceId], references: [id], onDelete: SetNull)

ipAddress String? @db.VarChar(45)
userAgent String? @db.Text

status SessionStatus @default(ACTIVE)
expiresAt DateTime // Expiration time
revokedAt DateTime? // Revocation time

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

@@index([userId])
@@index([token])
@@index([status])
@@index([expiresAt])
@@map("sessions")
}
36 changes: 36 additions & 0 deletions apps/api-effect/prisma/schema/base.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) 2026 Xiro The Dev <lethanhtrung.trungle@gmail.com>
//
// Source Available License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to:
// - View and study the Software for educational purposes
// - Fork this repository on GitHub for personal reference
// - Share links to this repository
//
// THE FOLLOWING ARE PROHIBITED:
// - Using the Software in production or commercial applications
// - Copying substantial portions of the Software into other projects
// - Distributing modified versions of the Software
// - Removing or altering copyright notices
//
// For commercial licensing or usage permissions, contact: lethanhtrung.trungle@gmail.com
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
//
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
//
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

generator client {
provider = "prisma-client-js"
output = "../../src/generated/prisma"
binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
previewFeatures = ["postgresqlExtensions", "views", "fullTextSearchPostgres"]
}

datasource db {
provider = "postgresql"
}
Loading
Loading