Initial Commit

This commit is contained in:
2024-12-19 13:54:15 +05:30
commit 970a972b11
17 changed files with 1487 additions and 0 deletions

31
src/index.ts Normal file
View File

@@ -0,0 +1,31 @@
import mongoose from "mongoose";
import fastify from "fastify";
import routes from "./routes";
import { userSchemas } from "./user/user.schema";
import { orgSchemas } from "./organization/organization.schema";
import { errorHandler } from "./utils/errors";
const app = fastify({ logger: true });
app.get("/health", (req, res) => {
return { status: "OK" };
});
app.register(routes, { prefix: "/api/v1" });
app.setErrorHandler(errorHandler);
(async () => {
const PORT = parseInt(process.env.PORT ?? "8000");
const DB_URI = process.env.DB_URI ?? "";
for (const schema of [...userSchemas, ...orgSchemas]) {
app.addSchema(schema);
}
await mongoose.connect(DB_URI);
await app.listen({ port: PORT });
})().catch((err) => {
console.log(err);
process.exit(1);
});

View File

@@ -0,0 +1,17 @@
import { FastifyRequest, FastifyReply } from "fastify";
import { CreateOrgInput } from "./organization.schema";
import { createOrg } from "./organization.service";
export async function createOrgHandler(
req: FastifyRequest<{ Body: CreateOrgInput }>,
res: FastifyReply
) {
const input = req.body;
try {
const org = await createOrg(input);
return res.code(201).send(org);
} catch (err) {
return err;
}
}

View File

@@ -0,0 +1,18 @@
import { FastifyInstance } from "fastify";
import { $org } from "./organization.schema";
import { createOrgHandler } from "./organization.controller";
export default function organizationRoutes(fastify: FastifyInstance) {
fastify.post(
"/",
{
schema: {
body: $org("createOrgInput"),
response: {
201: $org("createOrgResponse"),
},
},
},
createOrgHandler
);
}

View File

@@ -0,0 +1,52 @@
import { buildJsonSchemas } from "fastify-zod";
import mongoose from "mongoose";
import { z } from "zod";
export const orgModel = mongoose.model(
"organization",
new mongoose.Schema({
tenantId: String,
pid: {
type: String,
unique: true,
},
name: String,
domain: {
type: String,
unique: true,
},
avatar: String,
type: String,
status: String,
createdAt: Date,
createdBy: mongoose.Types.ObjectId,
})
);
const orgCore = {
name: z.string().max(30),
domain: z.string().max(30),
avatar: z.string().url().optional(),
type: z.enum(["county", "builder"], {
message: "Must be county or builder",
}),
};
const createOrgInput = z.object({
...orgCore,
});
const createOrgResponse = z.object({
pid: z.string(),
...orgCore,
});
export type CreateOrgInput = z.infer<typeof createOrgInput>;
export const { schemas: orgSchemas, $ref: $org } = buildJsonSchemas(
{
createOrgInput,
createOrgResponse,
},
{ $id: "org" }
);

View File

@@ -0,0 +1,13 @@
import { generateId } from "../utils/id";
import { CreateOrgInput, orgModel } from "./organization.schema";
export async function createOrg(input: CreateOrgInput) {
const org = await orgModel.create({
tenantId: "abc",
pid: generateId(),
createdAt: new Date(),
...input,
});
return org;
}

8
src/routes.ts Normal file
View File

@@ -0,0 +1,8 @@
import { FastifyInstance } from "fastify";
import userRoutes from "./user/user.route";
import organizationRoutes from "./organization/organization.route";
export default async function routes(fastify: FastifyInstance) {
fastify.register(userRoutes, { prefix: "/users" });
fastify.register(organizationRoutes, { prefix: "/orgs" });
}

12
src/tenant/tenant.ts Normal file
View File

@@ -0,0 +1,12 @@
import mongoose from "mongoose";
export const tenantModel = mongoose.model(
"tenant",
new mongoose.Schema({
pid: String,
name: String,
avatar: String,
createdAt: Date,
deleted: Boolean,
})
);

View File

@@ -0,0 +1,35 @@
import { FastifyReply, FastifyRequest } from "fastify";
import { createUser, getUser } from "./user.service";
import { CreateUserInput } from "./user.schema";
export async function createUserHandler(
req: FastifyRequest<{
Body: CreateUserInput;
}>,
res: FastifyReply
) {
const body = req.body;
try {
const user = await createUser(body);
return res.code(201).send(user);
} catch (err) {
return err;
}
}
export async function getUserHandler(
req: FastifyRequest<{ Params: { userId: string } }>,
res: FastifyReply
) {
const { userId } = req.params;
try {
const user = await getUser(userId);
if (user == null) return res.code(404).send({ error: "user not found" });
return res.code(200).send(user);
} catch (err) {
return err;
}
}

30
src/user/user.route.ts Normal file
View File

@@ -0,0 +1,30 @@
import { FastifyInstance } from "fastify";
import { createUserHandler, getUserHandler } from "./user.controller";
import { $user } from "./user.schema";
export default async function userRoutes(fastify: FastifyInstance) {
fastify.post(
"/",
{
schema: {
body: $user("createUserInput"),
response: {
201: $user("createUserResponse"),
},
},
},
createUserHandler
);
fastify.get(
"/:userId",
{
schema: {
response: {
200: $user("createUserResponse"),
},
},
},
getUserHandler
);
}

56
src/user/user.schema.ts Normal file
View File

@@ -0,0 +1,56 @@
import { buildJsonSchemas } from "fastify-zod";
import mongoose from "mongoose";
import { z } from "zod";
export const userModel = mongoose.model(
"user",
new mongoose.Schema({
tenantId: String,
pid: {
type: String,
unique: true,
},
firstName: String,
lastName: String,
email: {
type: String,
unique: true,
},
avatar: String,
status: String,
createdAt: Date,
createdBy: mongoose.Types.ObjectId,
lastLogin: Date,
})
);
const userCore = {
firstName: z.string().max(30),
lastName: z.string().max(30),
email: z
.string({
required_error: "Email is required",
invalid_type_error: "Email must be a valid string",
})
.email(),
avatar: z.string().url().optional(),
};
const createUserInput = z.object({
...userCore,
});
const createUserResponse = z.object({
pid: z.string().cuid2(),
...userCore,
});
export type CreateUserInput = z.infer<typeof createUserInput>;
export const { schemas: userSchemas, $ref: $user } = buildJsonSchemas(
{
createUserInput,
createUserResponse,
},
{ $id: "user" }
);

18
src/user/user.service.ts Normal file
View File

@@ -0,0 +1,18 @@
import { generateId } from "../utils/id";
import { CreateUserInput, userModel } from "./user.schema";
export async function createUser(input: CreateUserInput) {
const user = await userModel.create({
tenantId: "abc",
pid: generateId(),
createdAt: new Date(),
...input,
});
return user;
}
export async function getUser(userId: string) {
const user = await userModel.findOne({ pid: userId });
return user;
}

36
src/utils/errors.ts Normal file
View File

@@ -0,0 +1,36 @@
import { FastifyError, FastifyReply, FastifyRequest } from "fastify";
import mongoose from "mongoose";
export function errorHandler(
error: any,
req: FastifyRequest,
res: FastifyReply
) {
if (process.env.DEV) {
console.dir(error, { depth: null });
}
if (error.validation) {
const errMsg = {
type: "validation_error",
context: error.validationContext,
msg: error.validation[0].message,
params: error.validation[0].params,
};
return res.code(400).send(errMsg);
}
if (error instanceof mongoose.mongo.MongoServerError) {
if (error.code === 11000) {
return res.code(400).send({
type: "duplicate_key",
context: "body",
msg: "value already exists",
params: error.keyValue,
});
}
}
return res.code(500).send();
}

9
src/utils/id.ts Normal file
View File

@@ -0,0 +1,9 @@
import { init } from "@paralleldrive/cuid2";
const id = init({
length: 15,
});
export function generateId(perfix?: string) {
return perfix ? perfix + id() : id();
}