feat: Контроллер и сервис пользователей
This commit is contained in:
83
apps/backend/src/controllers/users.controller.ts
Normal file
83
apps/backend/src/controllers/users.controller.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { Elysia, t } from 'elysia';
|
||||
import { usersService } from '@/services/users.service';
|
||||
|
||||
export const usersController = new Elysia({ prefix: '/users' })
|
||||
.get(
|
||||
'/by-name',
|
||||
async ({ query, set }) => {
|
||||
try {
|
||||
const name = query.name?.trim();
|
||||
|
||||
const foundUser = await usersService.getByName(name);
|
||||
|
||||
if (!foundUser) {
|
||||
set.status = 404;
|
||||
return { error: 'User not found' };
|
||||
}
|
||||
|
||||
return {
|
||||
id: foundUser.id,
|
||||
name: foundUser.name,
|
||||
email: foundUser.email,
|
||||
image: foundUser.image,
|
||||
emailVerified: foundUser.emailVerified,
|
||||
createdAt: foundUser.createdAt.toISOString(),
|
||||
updatedAt: foundUser.updatedAt.toISOString(),
|
||||
};
|
||||
} catch (error) {
|
||||
set.status = 500;
|
||||
return {
|
||||
error: error instanceof Error ? error.message : 'Failed to get user',
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
query: t.Object({
|
||||
name: t.String({ minLength: 1 }),
|
||||
}),
|
||||
detail: {
|
||||
tags: ['Users'],
|
||||
summary: 'Get user by name',
|
||||
description: 'Returns user information by name',
|
||||
},
|
||||
}
|
||||
)
|
||||
.get(
|
||||
'/:id',
|
||||
async ({ params: { id }, set }) => {
|
||||
try {
|
||||
const foundUser = await usersService.getById(id);
|
||||
|
||||
if (!foundUser) {
|
||||
set.status = 404;
|
||||
return { error: 'User not found' };
|
||||
}
|
||||
|
||||
return {
|
||||
id: foundUser.id,
|
||||
name: foundUser.name,
|
||||
email: foundUser.email,
|
||||
image: foundUser.image,
|
||||
emailVerified: foundUser.emailVerified,
|
||||
createdAt: foundUser.createdAt.toISOString(),
|
||||
updatedAt: foundUser.updatedAt.toISOString(),
|
||||
};
|
||||
} catch (error) {
|
||||
set.status = 500;
|
||||
return {
|
||||
error: error instanceof Error ? error.message : 'Failed to get user',
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
detail: {
|
||||
tags: ['Users'],
|
||||
summary: 'Get user by ID',
|
||||
description: 'Returns user information by ID',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@ -1,88 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS "account" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"account_id" text NOT NULL,
|
||||
"provider_id" text NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"access_token" text,
|
||||
"refresh_token" text,
|
||||
"id_token" text,
|
||||
"access_token_expires_at" timestamp,
|
||||
"refresh_token_expires_at" timestamp,
|
||||
"scope" text,
|
||||
"password" text,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "save" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"description" text DEFAULT '' NOT NULL,
|
||||
"type" varchar(50) NOT NULL,
|
||||
"tags" text[] DEFAULT '{}' NOT NULL,
|
||||
"visibility" varchar(10) DEFAULT 'link' NOT NULL,
|
||||
"share_url" text,
|
||||
"s3_key" text NOT NULL,
|
||||
"url" text NOT NULL,
|
||||
"file_size" integer NOT NULL,
|
||||
"mime_type" varchar(100) NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "save_share_url_unique" UNIQUE("share_url")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "session" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"expires_at" timestamp NOT NULL,
|
||||
"token" text NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
"ip_address" text,
|
||||
"user_agent" text,
|
||||
"user_id" text NOT NULL,
|
||||
CONSTRAINT "session_token_unique" UNIQUE("token")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "user" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"email" text NOT NULL,
|
||||
"email_verified" boolean DEFAULT false NOT NULL,
|
||||
"image" text,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "user_email_unique" UNIQUE("email")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "verification" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"identifier" text NOT NULL,
|
||||
"value" text NOT NULL,
|
||||
"expires_at" timestamp NOT NULL,
|
||||
"created_at" timestamp DEFAULT now(),
|
||||
"updated_at" timestamp DEFAULT now()
|
||||
);
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "save" ADD CONSTRAINT "save_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "user_id_idx" ON "save" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "visibility_idx" ON "save" USING btree ("visibility");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "share_url_idx" ON "save" USING btree ("share_url");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "tags_idx" ON "save" USING btree ("tags");
|
||||
@ -1,513 +0,0 @@
|
||||
{
|
||||
"id": "0b6d70a9-35a6-40bc-80cd-e296dc5758bd",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.account": {
|
||||
"name": "account",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"account_id": {
|
||||
"name": "account_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"provider_id": {
|
||||
"name": "provider_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"access_token": {
|
||||
"name": "access_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"refresh_token": {
|
||||
"name": "refresh_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"id_token": {
|
||||
"name": "id_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"access_token_expires_at": {
|
||||
"name": "access_token_expires_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"refresh_token_expires_at": {
|
||||
"name": "refresh_token_expires_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"scope": {
|
||||
"name": "scope",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"password": {
|
||||
"name": "password",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"account_user_id_user_id_fk": {
|
||||
"name": "account_user_id_user_id_fk",
|
||||
"tableFrom": "account",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.save": {
|
||||
"name": "save",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "''"
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "varchar(50)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"tags": {
|
||||
"name": "tags",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"visibility": {
|
||||
"name": "visibility",
|
||||
"type": "varchar(10)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'link'"
|
||||
},
|
||||
"share_url": {
|
||||
"name": "share_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"s3_key": {
|
||||
"name": "s3_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"url": {
|
||||
"name": "url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"file_size": {
|
||||
"name": "file_size",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"mime_type": {
|
||||
"name": "mime_type",
|
||||
"type": "varchar(100)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"user_id_idx": {
|
||||
"name": "user_id_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"visibility_idx": {
|
||||
"name": "visibility_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "visibility",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"share_url_idx": {
|
||||
"name": "share_url_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "share_url",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"tags_idx": {
|
||||
"name": "tags_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "tags",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"save_user_id_user_id_fk": {
|
||||
"name": "save_user_id_user_id_fk",
|
||||
"tableFrom": "save",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"save_share_url_unique": {
|
||||
"name": "save_share_url_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"share_url"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.session": {
|
||||
"name": "session",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"token": {
|
||||
"name": "token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"ip_address": {
|
||||
"name": "ip_address",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"user_agent": {
|
||||
"name": "user_agent",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"session_user_id_user_id_fk": {
|
||||
"name": "session_user_id_user_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"session_token_unique": {
|
||||
"name": "session_token_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"token"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email_verified": {
|
||||
"name": "email_verified",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"image": {
|
||||
"name": "image",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_email_unique": {
|
||||
"name": "user_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.verification": {
|
||||
"name": "verification",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"identifier": {
|
||||
"name": "identifier",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1762256138442,
|
||||
"tag": "0000_organic_namora",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -4,6 +4,7 @@ import { auth } from "@/lib/auth";
|
||||
import { env } from "./config/env";
|
||||
import { AuthOpenAPI } from "./lib/auth/openapi";
|
||||
import { purple } from "./lib/term/color";
|
||||
import { usersController } from "./controllers/users.controller";
|
||||
|
||||
const app = new Elysia()
|
||||
.use(openapi({
|
||||
@ -13,6 +14,7 @@ const app = new Elysia()
|
||||
}
|
||||
}))
|
||||
.mount('/auth', auth.handler)
|
||||
.use(usersController)
|
||||
.listen(env.PORT);
|
||||
|
||||
const hostname = app.server?.hostname
|
||||
@ -27,6 +29,6 @@ console.log(purple`
|
||||
/_/
|
||||
`)
|
||||
|
||||
console.log(` ${purple`started server`} @ ${hostname}:${port}`);
|
||||
console.log(` ${purple`visit scalar`} @ ${hostname}:${port}/openapi`)
|
||||
console.log(` ${purple`started server`} @ http://${hostname}:${port}`);
|
||||
console.log(` ${purple`visit scalar`} @ http://${hostname}:${port}/openapi`)
|
||||
|
||||
|
||||
90
apps/backend/src/services/redis.service.ts
Normal file
90
apps/backend/src/services/redis.service.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { env } from '@/config/env';
|
||||
import { RedisClient } from 'bun';
|
||||
|
||||
class RedisService {
|
||||
private client: any = null;
|
||||
private isConnected = false;
|
||||
|
||||
async connect() {
|
||||
if (this.isConnected) return;
|
||||
|
||||
try {
|
||||
this.client = new RedisClient(env.REDIS_URL);
|
||||
this.isConnected = true;
|
||||
console.log('Redis connected');
|
||||
} catch (error) {
|
||||
console.error('Failed to connect to Redis:', error);
|
||||
if (env.NODE_ENV === 'test') {
|
||||
console.warn('Running without Redis in test mode');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async get<T>(key: string): Promise<T | null> {
|
||||
if (!this.isConnected) return null;
|
||||
try {
|
||||
const value = await this.client.get(key);
|
||||
return value ? JSON.parse(value) : null;
|
||||
} catch (error) {
|
||||
console.error(`Redis GET error for key ${key}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async set(key: string, value: any, ttl?: number): Promise<void> {
|
||||
if (!this.isConnected) return;
|
||||
try {
|
||||
const serialized = JSON.stringify(value);
|
||||
if (ttl) {
|
||||
await this.client.setex(key, ttl, serialized);
|
||||
} else {
|
||||
await this.client.set(key, serialized);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Redis SET error for key ${key}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
async del(key: string): Promise<void> {
|
||||
if (!this.isConnected) return;
|
||||
try {
|
||||
await this.client.del(key);
|
||||
} catch (error) {
|
||||
console.error(`Redis DEL error for key ${key}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
async delPattern(pattern: string): Promise<void> {
|
||||
if (!this.isConnected) return;
|
||||
try {
|
||||
const keys = await this.client.keys(pattern);
|
||||
if (keys.length > 0) {
|
||||
await this.client.del(...keys);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Redis DEL pattern error for ${pattern}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
if (this.isConnected && this.client) {
|
||||
try {
|
||||
if (typeof this.client.quit === 'function') {
|
||||
await this.client.quit();
|
||||
} else if (typeof this.client.close === 'function') {
|
||||
await this.client.close();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Error disconnecting Redis:', error);
|
||||
} finally {
|
||||
this.isConnected = false;
|
||||
this.client = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const redis = new RedisService();
|
||||
|
||||
60
apps/backend/src/services/users.service.ts
Normal file
60
apps/backend/src/services/users.service.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { eq, ilike } from 'drizzle-orm';
|
||||
import { db, user } from '@/db';
|
||||
import { redis } from './redis.service';
|
||||
|
||||
class UsersService {
|
||||
private readonly CACHE_TTL = 3600;
|
||||
|
||||
async getById(userId: string): Promise<typeof user.$inferSelect | null> {
|
||||
const cacheKey = `user:${userId}`;
|
||||
const cached = await redis.get<typeof user.$inferSelect>(cacheKey);
|
||||
if (cached) return cached;
|
||||
|
||||
const [foundUser] = await db
|
||||
.select()
|
||||
.from(user)
|
||||
.where(eq(user.id, userId))
|
||||
.limit(1);
|
||||
|
||||
if (foundUser) {
|
||||
await redis.set(cacheKey, foundUser, this.CACHE_TTL);
|
||||
}
|
||||
|
||||
return foundUser || null;
|
||||
}
|
||||
|
||||
async getByName(name: string): Promise<typeof user.$inferSelect | null> {
|
||||
if (!name || name.trim().length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const searchName = name.trim();
|
||||
const cacheKey = `user_by_name:${searchName}`;
|
||||
|
||||
const cached = await redis.get<typeof user.$inferSelect>(cacheKey);
|
||||
if (cached) return cached;
|
||||
|
||||
let [foundUser] = await db
|
||||
.select()
|
||||
.from(user)
|
||||
.where(eq(user.name, searchName))
|
||||
.limit(1);
|
||||
|
||||
if (!foundUser) {
|
||||
[foundUser] = await db
|
||||
.select()
|
||||
.from(user)
|
||||
.where(ilike(user.name, searchName))
|
||||
.limit(1);
|
||||
}
|
||||
|
||||
if (foundUser) {
|
||||
await redis.set(cacheKey, foundUser, this.CACHE_TTL);
|
||||
}
|
||||
|
||||
return foundUser || null;
|
||||
}
|
||||
}
|
||||
|
||||
export const usersService = new UsersService();
|
||||
|
||||
Reference in New Issue
Block a user