Merge branch 'account' into 'dev'
feat: account See merge request pena-services/hub_admin_backend_service!1
This commit is contained in:
commit
9aeb01258a
@ -2,6 +2,10 @@
|
||||
HTTP_HOST=localhost
|
||||
HTTP_PORT=8080
|
||||
|
||||
# Auth service
|
||||
AUTH_SERVICE_HOST=localhost
|
||||
AUTH_SERVICE_PORT=8081
|
||||
|
||||
# Database Options
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=27017
|
||||
|
@ -130,7 +130,7 @@
|
||||
"error",
|
||||
{
|
||||
"props": true,
|
||||
"ignorePropertyModificationsFor": ["accamulator"]
|
||||
"ignorePropertyModificationsFor": ["accamulator", "request"]
|
||||
}
|
||||
],
|
||||
"object-shorthand": "off",
|
||||
|
@ -28,6 +28,13 @@ DB_PASSWORD - mongo password
|
||||
DB_NAME - database name
|
||||
```
|
||||
|
||||
**Для подключения к сервису авторизации**
|
||||
|
||||
```
|
||||
AUTH_SERVICE_HOST - auth service host
|
||||
AUTH_SERVICE_PORT - auth service port
|
||||
```
|
||||
|
||||
## Среды окружения
|
||||
|
||||
```
|
||||
|
@ -23,6 +23,7 @@
|
||||
"@fastify/cookie": "^8.3.0",
|
||||
"@fastify/cors": "^8.2.0",
|
||||
"@fastify/jwt": "^6.3.3",
|
||||
"axios": "^1.2.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"dotenv": "^16.0.3",
|
||||
"fastify": "^4.9.2",
|
||||
|
16
src/clients/auth/index.ts
Normal file
16
src/clients/auth/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { authService } from "./instance";
|
||||
|
||||
import type { User } from "@/types/models/user.type";
|
||||
import type { GetUserRequest } from "./types";
|
||||
|
||||
export const getUser = async (request: GetUserRequest): Promise<User | null> => {
|
||||
try {
|
||||
const { data } = await authService.get<User>(`/user/${request.id}`);
|
||||
|
||||
return data;
|
||||
} catch (nativeError) {
|
||||
console.error(nativeError);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
7
src/clients/auth/instance.ts
Normal file
7
src/clients/auth/instance.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import axios from "axios";
|
||||
|
||||
import { CONFIGURATION } from "@/constants/configuration";
|
||||
|
||||
export const authService = axios.create({
|
||||
baseURL: `${CONFIGURATION.authService.host}:${CONFIGURATION.authService.port}`,
|
||||
});
|
3
src/clients/auth/types.ts
Normal file
3
src/clients/auth/types.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export type GetUserRequest = {
|
||||
id: string;
|
||||
};
|
@ -11,10 +11,11 @@ export const CONFIGURATION = {
|
||||
database: process.env.DB_NAME || "database",
|
||||
},
|
||||
service: {
|
||||
privateAccessSecretKey: process.env.PRIVATE_ACCESS_SECRET_KEY || "",
|
||||
publicAccessSecretKey: process.env.PUBLIC_ACCESS_SECRET_KEY || "",
|
||||
privateRefreshSecretKey: process.env.PRIVATE_REFRESH_SECRET_KEY || "",
|
||||
publicRefreshSecretKey: process.env.PUBLIC_REFRESH_SECRET_KEY || "",
|
||||
salt: Number(process.env.SALT) || 10,
|
||||
},
|
||||
authService: {
|
||||
host: process.env.AUTH_SERVICE_HOST || "",
|
||||
port: Number(process.env.AUTH_SERVICE_PORT) || 8081,
|
||||
},
|
||||
} as const;
|
||||
|
52
src/handlers/account/index.ts
Normal file
52
src/handlers/account/index.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { Types } from "mongoose";
|
||||
|
||||
import { AccountModel } from "@/models/account.model";
|
||||
|
||||
import { getUser } from "@/clients/auth";
|
||||
import { validateEmptyFields } from "@/utils/validate-empty-fields";
|
||||
|
||||
import type { FastifyReply } from "fastify";
|
||||
import type { CreateAccountRequest, GetAccountRequest } from "./types";
|
||||
|
||||
export const createAccount = async (request: CreateAccountRequest, reply: FastifyReply) => {
|
||||
if (!Types.ObjectId.isValid(request.user.id)) {
|
||||
reply.status(400);
|
||||
return new Error("invalid user id");
|
||||
}
|
||||
|
||||
const account = await AccountModel.findById(request.user.id).lean();
|
||||
|
||||
if (account) {
|
||||
reply.status(409);
|
||||
return new Error("account already exist");
|
||||
}
|
||||
|
||||
const user = await getUser({ id: request.user.id });
|
||||
|
||||
if (!user) {
|
||||
reply.status(404);
|
||||
return new Error("user not found");
|
||||
}
|
||||
|
||||
const createdAccount = new AccountModel({
|
||||
userId: user._id,
|
||||
});
|
||||
|
||||
return createdAccount.save();
|
||||
};
|
||||
|
||||
export const getAccount = async (request: GetAccountRequest, reply: FastifyReply) => {
|
||||
const [getAccountRequestBody, error] = validateEmptyFields(request.params || {}, ["userId"]);
|
||||
|
||||
if (!getAccountRequestBody || error) {
|
||||
reply.status(400);
|
||||
return error;
|
||||
}
|
||||
|
||||
if (!Types.ObjectId.isValid(getAccountRequestBody.userId)) {
|
||||
reply.status(400);
|
||||
return new Error("invalid user id");
|
||||
}
|
||||
|
||||
return AccountModel.findById(getAccountRequestBody.userId).lean();
|
||||
};
|
19
src/handlers/account/middleware.ts
Normal file
19
src/handlers/account/middleware.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import type { FastifyRequest, FastifyReply, HookHandlerDoneFunction as Done } from "fastify";
|
||||
|
||||
export const verifyUser = async (request: FastifyRequest, reply: FastifyReply, done: Done) => {
|
||||
try {
|
||||
const { id } = await request.jwtVerify<{ id?: string }>({ onlyCookie: true });
|
||||
|
||||
if (!id) {
|
||||
reply.status(401);
|
||||
return reply.send("user id is empty");
|
||||
}
|
||||
|
||||
request.user = { id };
|
||||
} catch (nativeError) {
|
||||
reply.status(401);
|
||||
return reply.send(nativeError);
|
||||
}
|
||||
|
||||
done();
|
||||
};
|
6
src/handlers/account/types.ts
Normal file
6
src/handlers/account/types.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import type { FastifyRequest } from "fastify";
|
||||
|
||||
import type { CreateAccountRoute, GetAccountRoute } from "@/types/routes/account-routes.type";
|
||||
|
||||
export type CreateAccountRequest = FastifyRequest<CreateAccountRoute>;
|
||||
export type GetAccountRequest = FastifyRequest<GetAccountRoute>;
|
@ -15,7 +15,11 @@ const server = new Server({
|
||||
jwt: {
|
||||
secret: {
|
||||
public: CONFIGURATION.service.publicAccessSecretKey,
|
||||
private: CONFIGURATION.service.privateAccessSecretKey,
|
||||
private: "",
|
||||
},
|
||||
verify: {
|
||||
allowedIss: "pena-auth-service",
|
||||
allowedAud: "pena",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2,24 +2,17 @@ import { Schema, model, SchemaDefinition } from "mongoose";
|
||||
|
||||
import { eloquentModelSchema } from "./eloquent-model.schema";
|
||||
|
||||
import type { User } from "@/types/models/user.type";
|
||||
import type { Account } from "@/types/models/account.type";
|
||||
|
||||
const schema: SchemaDefinition<User> = {
|
||||
login: {
|
||||
const schema: SchemaDefinition<Account> = {
|
||||
userId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
phoneNumber: {
|
||||
nickname: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "nickname",
|
||||
},
|
||||
avatar: {
|
||||
type: String,
|
||||
@ -34,9 +27,9 @@ const schema: SchemaDefinition<User> = {
|
||||
|
||||
const schemaSettings = {
|
||||
versionKey: false,
|
||||
collection: "users",
|
||||
collection: "accounts",
|
||||
};
|
||||
|
||||
const UserSchema = new Schema<User>(schema, schemaSettings);
|
||||
const AccountSchema = new Schema<Account>(schema, schemaSettings);
|
||||
|
||||
export const UserModel = model("User", UserSchema);
|
||||
export const AccountModel = model("Account", AccountSchema);
|
@ -1,5 +1,5 @@
|
||||
import type { SchemaDefinition } from "mongoose";
|
||||
import type { EloquentModel } from "@/types/models/eloquent-model";
|
||||
import type { EloquentModel } from "@/types/models/eloquent-model.type";
|
||||
|
||||
export const eloquentModelSchema: SchemaDefinition<EloquentModel> = {
|
||||
createdAt: {
|
||||
|
@ -1,23 +0,0 @@
|
||||
import { Schema, model, SchemaDefinition } from "mongoose";
|
||||
|
||||
import type { Token } from "@/types/models/token.type";
|
||||
|
||||
const schema: SchemaDefinition<Token> = {
|
||||
userId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
refreshToken: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
};
|
||||
|
||||
const schemaSettings = {
|
||||
versionKey: false,
|
||||
collection: "tokens",
|
||||
};
|
||||
|
||||
const TokenSchema = new Schema<Token>(schema, schemaSettings);
|
||||
|
||||
export const TokenModel = model("Token", TokenSchema);
|
12
src/routes/account.routes.ts
Normal file
12
src/routes/account.routes.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { createAccount, getAccount } from "@/handlers/account";
|
||||
import { verifyUser } from "@/handlers/account/middleware";
|
||||
|
||||
import type { FastifyInstance, FastifyPluginOptions } from "fastify";
|
||||
import type { GetAccountRoute, CreateAccountRoute } from "@/types/routes/account-routes.type";
|
||||
|
||||
export const setRoleRoutes = <T = FastifyPluginOptions>(server: FastifyInstance, opts: T, done: () => void): void => {
|
||||
server.get<GetAccountRoute>("/:userId", getAccount);
|
||||
server.post<CreateAccountRoute>("/", { preHandler: [verifyUser] }, createAccount);
|
||||
|
||||
done();
|
||||
};
|
8
src/types/fastify-jwt.d.ts
vendored
Normal file
8
src/types/fastify-jwt.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
import "@fastify/jwt";
|
||||
|
||||
declare module "@fastify/jwt" {
|
||||
interface FastifyJWT {
|
||||
payload: { id: string };
|
||||
user: { id: string };
|
||||
}
|
||||
}
|
7
src/types/models/account.type.ts
Normal file
7
src/types/models/account.type.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export type Account = {
|
||||
userId: string;
|
||||
nickname: string;
|
||||
avatar: string;
|
||||
role: string;
|
||||
privilegies: Record<string, string>;
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import type { EloquentModel } from "./eloquent-model";
|
||||
import type { EloquentModel } from "./eloquent-model.type";
|
||||
|
||||
export type Role = EloquentModel & {
|
||||
name: string;
|
||||
|
8
src/types/models/user.type.ts
Normal file
8
src/types/models/user.type.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import type { EloquentModel } from "./eloquent-model.type";
|
||||
|
||||
export type User = EloquentModel & {
|
||||
_id: string;
|
||||
login: string;
|
||||
email: string;
|
||||
phoneNumber: string;
|
||||
};
|
13
src/types/routes/account-routes.type.ts
Normal file
13
src/types/routes/account-routes.type.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import type { RequestGenericInterface } from "fastify";
|
||||
|
||||
export type GetAccountRoute = RequestGenericInterface & {
|
||||
Params?: {
|
||||
userId?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type CreateAccountRoute = RequestGenericInterface & {
|
||||
Params?: {
|
||||
userId?: string;
|
||||
};
|
||||
};
|
57
yarn.lock
57
yarn.lock
@ -2049,6 +2049,11 @@ async-mutex@^0.3.2:
|
||||
dependencies:
|
||||
tslib "^2.3.1"
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
|
||||
|
||||
atomic-sleep@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b"
|
||||
@ -2063,6 +2068,15 @@ avvio@^8.2.0:
|
||||
debug "^4.0.0"
|
||||
fastq "^1.6.1"
|
||||
|
||||
axios@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.2.1.tgz#44cf04a3c9f0c2252ebd85975361c026cb9f864a"
|
||||
integrity sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==
|
||||
dependencies:
|
||||
follow-redirects "^1.15.0"
|
||||
form-data "^4.0.0"
|
||||
proxy-from-env "^1.1.0"
|
||||
|
||||
babel-jest@^29.3.1:
|
||||
version "29.3.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.3.1.tgz#05c83e0d128cd48c453eea851482a38782249f44"
|
||||
@ -2366,6 +2380,13 @@ color-name@~1.1.4:
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
combined-stream@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
commander@^9.0.0:
|
||||
version "9.4.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd"
|
||||
@ -2530,6 +2551,11 @@ define-properties@^1.1.3, define-properties@^1.1.4:
|
||||
has-property-descriptors "^1.0.0"
|
||||
object-keys "^1.1.1"
|
||||
|
||||
delayed-stream@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
|
||||
|
||||
denque@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1"
|
||||
@ -3134,6 +3160,20 @@ flatted@^3.1.0:
|
||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
|
||||
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
|
||||
|
||||
follow-redirects@^1.15.0:
|
||||
version "1.15.2"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
|
||||
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
|
||||
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
forwarded@0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
|
||||
@ -4357,6 +4397,18 @@ micromatch@^4.0.4:
|
||||
braces "^3.0.2"
|
||||
picomatch "^2.3.1"
|
||||
|
||||
mime-db@1.52.0:
|
||||
version "1.52.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
||||
|
||||
mime-types@^2.1.12:
|
||||
version "2.1.35"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
|
||||
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||
dependencies:
|
||||
mime-db "1.52.0"
|
||||
|
||||
mimic-fn@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||
@ -4843,6 +4895,11 @@ proxy-addr@^2.0.7:
|
||||
forwarded "0.2.0"
|
||||
ipaddr.js "1.9.1"
|
||||
|
||||
proxy-from-env@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||
|
||||
pstree.remy@^1.1.8:
|
||||
version "1.1.8"
|
||||
resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
|
||||
|
Loading…
Reference in New Issue
Block a user