Initial Commit
This commit is contained in:
31
src/index.ts
Normal file
31
src/index.ts
Normal 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);
|
||||
});
|
||||
17
src/organization/organization.controller.ts
Normal file
17
src/organization/organization.controller.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
18
src/organization/organization.route.ts
Normal file
18
src/organization/organization.route.ts
Normal 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
|
||||
);
|
||||
}
|
||||
52
src/organization/organization.schema.ts
Normal file
52
src/organization/organization.schema.ts
Normal 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" }
|
||||
);
|
||||
13
src/organization/organization.service.ts
Normal file
13
src/organization/organization.service.ts
Normal 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
8
src/routes.ts
Normal 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
12
src/tenant/tenant.ts
Normal 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,
|
||||
})
|
||||
);
|
||||
35
src/user/user.controller.ts
Normal file
35
src/user/user.controller.ts
Normal 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
30
src/user/user.route.ts
Normal 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
56
src/user/user.schema.ts
Normal 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
18
src/user/user.service.ts
Normal 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
36
src/utils/errors.ts
Normal 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
9
src/utils/id.ts
Normal 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();
|
||||
}
|
||||
Reference in New Issue
Block a user