Document Control
Version | Description | Date |
---|---|---|
1.0.0 | Init document | 30 Jan 2024 |
I. Overview
xStreamer comes with 3 big parts
API server
Frontend web server (front office, user)
Backend web server (back office, admin)
II. Details
1. Api
Api server uses these frameworks / softwares on the top:
NestJS as a main framework
DB: MongoDB
Cache: Redis
Messaging: Redis
Queue: Redis
1.1 Folder Structures
src --config --kernel --modules ----config ----controllers ----services ----schemas ----payloads --scripts templates ----emails ------contact.html ------email-verification.html ------other-email-templates.html --main.ts --app.service.ts --app.controller.ts --app.module.ts --script.ts test views --custom-view public --avatars --images --... .env env.example package.json tsconfig.json
With structures above we can see xStreamer split applications to smaller modules, each module is responsible the app logic
Modules | Description |
---|---|
Auth | Service provides APIs to execute all use-cases related to authentication and authorization such as Sign In, Sign Out, Access Token Verification and User's Roles and Permissions and etc. |
User | Service provides APIs to execute all use-cases related to user domain such as Get, Edit Profile and etc |
Payment | Service provides APIs to execute all use-cases related to payment such as CCBill payment, payment history… |
File | Service provides APIs to execute all use-cases related to user domain such as convert file, create thumbnails, save file to protected folder, get file details… |
Mailer | Service provides APIs to execute all use-cases related to send mail such as create content from template, send mail in queue, etc… |
Message | Service provides APIs to execute all use-cases related to message such as public chat, group chat, private chat |
Performer | Service provides APIs to execute all use-cases related to performer domain such as Get, Edit Profile and etc |
Assets | Service provides APIs to execute all use-cases related to performer domain such as Get, Edit, Upload media data, etc… |
Stream | Service provides APIs to execute all use-cases related to stream such as create connection to ant media |
Socket | Service provides method to communicate between server and browser in with real time message |
Settings | Service provides APIs to execute all user-cases related to settings such as update config, get public config |
Statistic | Service provides API to execute all user-cases related to statistic |
Utils | Service provides common APIs which using among services |
… |
|
1.2 Module structure
A general module will come with below structure.
controllers: provide Restful Apis to external / internal eg: GET /users/:userId to get user info
dtos: acronym of Data Transfer Object. It is an object which helps transfer data from request to database, among services, etc…
exceptions: contains http exceptions
listeners: listen and handle business logic when receive an event (like messaging listener service)
models: define Mongoose database model
payloads: define request payload from external to server and validate with class validator
providers: Providers are a fundamental concept in Nest. Many of the basic Nest classes may be treated as a provider – services, repositories, factories, helpers, and so on. The main idea of a provider is that it can inject dependencies; this means objects can create various relationships with each other, and the function of "wiring up" instances of objects can largely be delegated to the Nest runtime system.
schemas: provide mongo db schemas
services: service will be responsible for data storage and retrieval, provide method to communicate with another service
validators: provide custom class-validator methods
Example for user module
controllers --user.controller.ts --admin-user.controller.ts dtos --user.dto.ts --index.ts exceptions --account-not-found.exception.ts --index.ts listeners --user-connected.listener.ts --index.ts models --user.model.ts --index.ts payloads --user-create.payload.ts --user-update.payload.ts --... providers --user.providers.ts --index.ts schema --user.schema.ts --index.ts services --user.service.ts --user-search.service.ts --index.ts validators --username.validator.ts constants.ts user.module.ts
2. Frontend web
Frontend web use NextJS as a main framework, TailwindCSS for styling
2.1 Folder structure
src --app ---layout.tsx ---account -----page.tsx ---videos ----page.tsx --components --interfaces --lib @types style --default.scss public --fonts --icons --sounds --image-file.png --...
# | Description |
---|---|
src | Contains React components, Redux storage, Socket handler which we use in the app. Written with typescript Check here for nextJS structure |
style | Provide definition of style in the app such as color, font weight… |
public | Contains public static files such as icons, images, sounds |
3. Backend web
Frontend web use NextJS as a main framework, Ant design for UI components with structure is same Frontend web
III. Sample code snipplets
Login API
import { Body, Controller, Post, UsePipes, ValidationPipe } from '@nestjs/common'; import { DataResponse } from 'src/core'; import { LoginPasswordPayload } from 'src/payloads'; import { AuthService } from 'src/services'; @Controller('/auth/login') export class LoginController { constructor( private authService: AuthService ) {} @Post() @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) async register( @Body() payload: LoginPasswordPayload ) { const res = await this.authService.loginWithPassword(payload); return DataResponse.ok(res); } }
Login form
'use client'; import Link from 'next/link'; import { Formik, Form, Field, FieldProps } from 'formik'; import { signIn } from 'next-auth/react'; import { useTranslation } from 'react-i18next'; import * as yup from 'yup'; import { FormItem, Input } from '@components/ui/form'; import { useState } from 'react'; import { FormattedMessage } from '@components/i18n'; import { useMainThemeLayout } from 'src/providers/main-layout.provider'; const schema = yup.object().shape({ username: yup.string().required('Username is required!'), password: yup.string().required('Password is required!') }); export default function Login() { const { t } = useTranslation(); const [error, setError] = useState<string | null>(); const { register, forgotPassword, closePopup } = useMainThemeLayout(); return ( <div className="w-full space-y-5 p-10 text-center"> <Link href="/" className="font-bold text-4xl text-center"> xStreamer </Link> <h2 className="text-center text-2xl font-bold leading-9 tracking-tigh"> {t('signin', 'Sign in')} </h2> <Formik initialValues={{ username: '', password: '' }} validationSchema={schema} onSubmit={async (values, actions) => { const res = await signIn('credentials', { ...values, redirect: false }); if (res && res.ok) { closePopup(); // loadProfile(); return; } setError(res!.error); actions.setSubmitting(false); }} > {({ handleSubmit }) => ( <Form className="space-y-3" onSubmit={handleSubmit}> <Field name="username"> {({ field, meta }: FieldProps) => ( <FormItem> <Input placeholder="Username" {...field} /> {meta.touched && !!meta.error && ( <FormItem.Error>{meta.error}</FormItem.Error> )} </FormItem> )} </Field> <div> <Field name="password"> {({ field, meta }: FieldProps) => ( <FormItem> <Input type="password" placeholder="Password" {...field} /> {meta.touched && !!meta.error && ( <FormItem.Error>{meta.error}</FormItem.Error> )} </FormItem> )} </Field> </div> <div className="flex items-center justify-between"> <div className="text-sm text-primary"> <button onClick={forgotPassword} role="button" type="button"> <FormattedMessage id="forgotPassword" defaultValue="Forgot password" subifx="?" /> </button> </div> </div> {error && ( <div className="bg-red-100 dark:bg-red-950 border border-red-900 text-red-700 dark:text-white px-4 py-3 rounded relative" role="alert" > <span className="text-sm font-medium">{error}</span> </div> )} <div className="text-center"> <button type="submit" className="rounded-md bg-orange-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-orange-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-600" > {t('signin', 'Sign in')} </button> </div> </Form> )} </Formik> <p className="mt-10 text-center text-sm text-gray-500"> Don't have an account? {' '} <a onClick={register} className="font-semibold leading-6 cursor-pointer hover:text-primary" > {t('signup', 'Sign Up')} </a> </p> </div> ); }