Skip to content
Closed
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
34 changes: 26 additions & 8 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
NODE_ENV=development

DATABASE_URL=${DB_DRIVER}://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_SCHEMA}

DB_DRIVER=
DB_USER=
DB_SCHEMA=
DB_PASSWORD=
DB_HOST=
DB_PORT=
POSTGRES_USERNAME=postgres
POSTGRES_DATABASE=
POSTGRES_PASSWORD=
POSTGRES_HOST=
POSTGRES_PORT=5432

DATABASE_URL=postgresql://${POSTGRES_USERNAME}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DATABASE}

REDIS_HOST=
REDIS_PORT=6379
REDIS_USERNAME=default
REDIS_PASSWORD=
Comment thread
itssimmons marked this conversation as resolved.

COOKIE_SECRET=

GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI=https://example.com/auth/google/callback

GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GITHUB_REDIRECT_URI=https://example.com/auth/google/callback

DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
DISCORD_REDIRECT_URI=https://example.com/auth/github/callback
Comment on lines +20 to +28
3 changes: 3 additions & 0 deletions @types/i18next.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare namespace i18next {
export type TFunction = (key: string, options?: unknown) => string;
}
92 changes: 92 additions & 0 deletions bun.lock

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions ci/Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM oven/bun:1
WORKDIR /usr/src/app

COPY package.json bun.lock ./
ENV CI=true
RUN bun install

COPY . .
RUN DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy" \
bunx --bun prisma generate

ENV NODE_ENV=development

USER bun
EXPOSE 8080/tcp
CMD [ "bun", "dev" ]
60 changes: 60 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: dojoh-api

services:
api:
container_name: dojoh-api
image: dojoh-api:latest
restart: unless-stopped
build:
dockerfile: ci/Dockerfile.dev
context: .
ports:
- "8080:8080"
env_file: .env
environment:
- NODE_ENV=development
Comment thread
itssimmons marked this conversation as resolved.
networks:
- dojoh-net
volumes:
- ./:/usr/src/app
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
Comment thread
itssimmons marked this conversation as resolved.
Comment thread
itssimmons marked this conversation as resolved.
interval: 30s
timeout: 10s
retries: 3
depends_on:
- pg
- redis

pg:
container_name: dojoh-pg
image: postgres:18
environment:
- POSTGRES_USER=dojoh
- POSTGRES_PASSWORD=my_secret_password
- POSTGRES_DB=dojoh_dev
ports:
- "5432:5432"
networks:
- dojoh-net
volumes:
- pg_data:/var/lib/postgresql/18/docker
Comment thread
itssimmons marked this conversation as resolved.

redis:
container_name: dojoh-redis
image: redis:7
ports:
- "6379:6379"
environment:
- REDIS_PASSWORD=my_secret_password
command: >
sh -c "redis-server --requirepass $$REDIS_PASSWORD"
networks:
- dojoh-net

networks:
dojoh-net:
driver: bridge

volumes:
pg_data: {}
6 changes: 4 additions & 2 deletions config/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import path from "node:path";

import type * as i18next from "i18next";

export default {
fallbackLng: "en-US",
supportedLngs: ["en-US", "es-ES", "fr-FR", "pt-BR"],
ns: ["messages", "errors"],
ns: ["messages", "errors", "zod"],
defaultNS: "messages",
backend: {
loadPath: path.join(process.cwd(), "locales/{{lng}}/{{ns}}.json"),
},
interpolation: {
escapeValue: false,
},
};
} satisfies i18next.InitOptions;
4 changes: 2 additions & 2 deletions config/jwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ export default {
secret: env("JWT_SECRET", "default_secret"),

access: {
expiresIn: Math.floor(Date.now() / 1000) + 60 * 60, // 1 hour in seconds
expiresIn: 60 * 60, // 1 hour in seconds
},

refresh: {
expiresIn: Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60, // 7 days in seconds
expiresIn: 60 * 60 * 24, // 1 day in seconds
},
} satisfies JwtConfig;
14 changes: 14 additions & 0 deletions database/prisma/migrations/20260502141005/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- AlterEnum
-- This migration adds more than one value to an enum.
-- With PostgreSQL versions 11 and earlier, this is not possible
-- in a single migration. This can be worked around by creating
-- multiple migrations, each migration adding only one value to
-- the enum.


ALTER TYPE "OAuthProvider" ADD VALUE 'DISCORD';
ALTER TYPE "OAuthProvider" ADD VALUE 'NATIVE';

-- AlterTable
ALTER TABLE "OAuthAccount" ADD COLUMN "provider_metadata" JSONB,
ALTER COLUMN "provider_avatar_url" DROP NOT NULL;
5 changes: 5 additions & 0 deletions database/prisma/migrations/20260502151239/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "OAuthAccount" ALTER COLUMN "user_id" DROP NOT NULL;

-- AddForeignKey
ALTER TABLE "OAuthAccount" ADD CONSTRAINT "OAuthAccount_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
21 changes: 13 additions & 8 deletions database/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -50,32 +50,37 @@ model User {
avatar Image? @relation(fields: [avatar_id], references: [id])
avatar_id Int?

created_at DateTime @default(now())
updated_at DateTime? @updatedAt
deleted_at DateTime?
created_at DateTime @default(now())
updated_at DateTime? @updatedAt
deleted_at DateTime?
oauthAccounts OAuthAccount[]

@@index([email])
@@index([nickname])
@@index([deleted_at])
}

model OAuthAccount {
id Int @id @default(autoincrement())
user_id Int
id Int @id @default(autoincrement())
user_id Int? // Allow null for accounts that are not linked to a user yet
user User? @relation(fields: [user_id], references: [id], onDelete: SetNull)

provider OAuthProvider
provider_user_id String
provider_username String
provider_avatar_url String
provider_avatar_url String?
provider_metadata Json?

created_at DateTime @default(now())
updated_at DateTime? @updatedAt

@@unique([user_id, provider])
@@unique([provider, provider_user_id])
@@unique([user_id, provider], name: "user_provider_idx")
@@unique([provider, provider_user_id], name: "provider_user_id_idx")
}

enum OAuthProvider {
GOOGLE
GITHUB
DISCORD
NATIVE
}
9 changes: 5 additions & 4 deletions database/redis/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { RedisClient } from "bun";

import databaseConfig from "@/config/database";

const { redis } = databaseConfig;
const { redis: redisConfig } = databaseConfig;
const { host, port, username, password } = redisConfig;

const client = new RedisClient(
`redis://${redis.username}:${redis.password}@${redis.host}:${redis.port}`,
const redis = new RedisClient(
`redis://${username}:${password}@${host}:${port}`,
);

export default client;
export default redis;
8 changes: 8 additions & 0 deletions exceptions/conflict.exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export class ConflictError extends Error {
statusCode: number;
constructor(message: string) {
super(message);
this.name = "ConflictError";
this.statusCode = 409;
}
}
3 changes: 3 additions & 0 deletions exceptions/index.exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./conflict.exception";
export * from "./notfound.exception";
export * from "./unauthorized.exception";
8 changes: 8 additions & 0 deletions exceptions/notfound.exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export class NotFoundError extends Error {
statusCode: number;
constructor(message: string) {
super(message);
this.name = "NotFoundError";
this.statusCode = 404;
}
}
8 changes: 8 additions & 0 deletions exceptions/unauthorized.exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export class UnauthorizedError extends Error {
statusCode: number;
constructor(message: string) {
super(message);
this.name = "UnauthorizedError";
this.statusCode = 401;
}
}
13 changes: 5 additions & 8 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import fastifyCookie from "@fastify/cookie";
import Fastify from "fastify";

import env from "./config/env";
Expand All @@ -7,20 +8,16 @@ const f = Fastify({
logger: true,
});

f.register(i18nPlugin);

f.get("/", async () => {
return { message: "Hello World!" };
f.register(fastifyCookie, {
secret: env("COOKIE_SECRET", undefined),
});

f.register(i18nPlugin);

f.get("/health", async () => {
return { status: "ok", timestamp: new Date().toISOString() };
});

f.post("/echo", async (request) => {
return { received: request.body };
});

// Register route modules
f.register(async (f) => {
const { default: version_1 } = await import("./routes/v1");
Expand Down
3 changes: 3 additions & 0 deletions locales/en-US/zod.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"nickname_rules": "The nickname must start and end with an alphanumeric character, and can contain dots, underscores, or hyphens in between. Consecutive dots, underscores, or hyphens are not allowed."
}
3 changes: 2 additions & 1 deletion locales/es-ES/errors.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"Invalid credentials": "Credenciales inválidas",
"Server error": "Error del servidor"
"Server error": "Error del servidor",
"Email already in use": "El correo electrónico ya está en uso"
}
3 changes: 3 additions & 0 deletions locales/es-ES/zod.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"nickname_rules": "El apodo debe comenzar y terminar con un carácter alfanumérico, y puede contener puntos, guiones bajos o guiones en el medio. No se permiten puntos, guiones bajos o guiones consecutivos."
}
1 change: 1 addition & 0 deletions locales/fr-FR/zod.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions locales/pt-BR/zod.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Loading
Loading