# Getting Started

Welcome to the documentation for Nzoni Boilerplate! This guide will walk you through the setup, configuration, and usage of Nzoni, a robust SaaS boilerplate designed to accelerate your web application

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><img src="/files/4ojUEUBafA0hN0LOuQXy" alt="" data-size="original"></td><td><a href="/pages/rdjhlUnHx2BkO7bqttpO">Angular / Firebase / Node.js</a></td><td><a href="/pages/rdjhlUnHx2BkO7bqttpO">/pages/rdjhlUnHx2BkO7bqttpO</a></td></tr><tr><td><img src="/files/UMcizZss4awl70OFRTU3" alt="" data-size="original"></td><td><a href="/pages/PeLehgqQO0nqalQhSq6G">Angular/Nest.js/PostgreSQL</a></td><td></td></tr><tr><td><img src="/files/TdM7QZ9reE9dSD345j0l" alt=""></td><td><a href="/pages/oKzRXN4xB84mzkDklxCH">Angular/Node.js/MongoDB</a></td><td></td></tr></tbody></table>

Make sure you have **node v18**+ and **npm v10+**

## Download repositories

Once you've purchased Nzoni, your github email account will be added to the related projects.\
Make sure you're already connected to github on your cli terminal or use Github Personal access token.

### Download Angular repository

```sh
git clone https://github.com/nzoni-app/nz-angular.git
```

### Download Nest.js repository

<pre class="language-sh"><code class="lang-sh"><strong>git clone https://github.com/nzoni-app/nz-nestjs.git
</strong></code></pre>

### Download Node.js repository

<pre class="language-sh"><code class="lang-sh"><strong>git clone https://github.com/nzoni-app/nz-nodejs-mongodb.git
</strong></code></pre>

## Installation

#### Install Node.js (if you haven't already)

If you haven't installed Node.js yet, you can follow the instructions [here](https://nodejs.org/en/learn/getting-started/how-to-install-nodejs).

#### Install Node Modules for Angular project

Navigate to the downloaded Angular project repostiory:

```sh
cd nz-angular
npm install
```

#### Install Node Modules for Nest.js project

Navigate to the downloaded Nest.js project repostiory:

```sh
cd nz-nestjs
npm install
```

## Configure Environment Variables

### Nest.js environnement

Nest.js supports environment variables out of the box. You can set defaults in .env (for all environments), .env.development (for development), and .env.production (for production).

By default, there is the <mark style="background-color:blue;">.env.example</mark> file. Rename it to <mark style="background-color:blue;">.env</mark> modify variables

```sh
ENV=developpement # OR production OR stagging

# Typeorm configuration for migration
TYPEORM_CONNECTION=postgres
TYPEORM_HOST=localhost
TYPEORM_PORT=5432
TYPEORM_USERNAME=
TYPEORM_PASSWORD=
TYPEORM_DATABASE=
TYPEORM_SYNCHRONIZE=false
TYPEORM_ENTITIES=src/**/*.entity.ts
TYPEORM_MIGRATIONS_TABLE_NAME=migrations
TYPEORM_MIGRATIONS_DIR=migrations
TYPEORM_MIGRATIONS=migrations/*.ts

# MAILING
SMTP_HOST=
SMTP_PORT=
SMTP_SECURE=false
SMTP_AUTH=
SMTP_PASSWORD=
SMTP_FROM='"Support name" <support@domain.name>'
MODERATION_MAIL=
# Logs
APP_DEBUG=true
JOB_ERROR_LOG_PATH='./'
DEFAULT_LOG_PATH='./logs'

# JWT
JWT_SECRET=A/iQ2KX0auTZZBwsbPEGC8H3a78HiiL23WD4S+QAoEq34LdeJ9aPrgpHdkkTvBzJ46K2JkM4apkg414erD3S+qLvwiYk3DTorANbbkA+54tVJsXrSGsaUjGifR31OaRK98aDVgICvl60Nymo3+I6527+BOkZalZsbCPzsJ7nALyTNu9Ud2FsvfK0WpAQVOf4teoHT4R/7E7ENChmgHRvI/TWumKfWcyr//Q7b9bLipFivk0EDzkpApdaEsClxE7JjT33aZhMvtuvLn5mQF3L5/ubGs5ZZy+nk4AyhR1DBTYjBYzKoamf94JW3BPobg5heH8gGnNoA+5l3WOJA7uvzw==
JWT_EXPIRATION=192h # 3 days

# FONT_END_URL
FRONT_END_URL=

#GOOGLE
GOOGLE_CLIENT_ID=
GOOGLE_APPLICATION_CREDENTIALS=google-service.json

#STRIPE
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=

#TRIAL PERIOD IF EXIST
TRIAL_DAYS=
```

### Angular Environnement

Edit src/environments/environnement.ts&#x20;

<pre class="language-typescript"><code class="lang-typescript">export const environment = {
<strong>        // GOOGLE AUTH
</strong>        google_client_id:
        // GOOGLE TAG for ANALYTICS
        google_tag_id:
}
</code></pre>

## Create Tables

To set up the required tables, navigate to the downloaded Nest.js project repostiory and run the following command:

```sh
npx ts-node ./node_modules/.bin/typeorm  migration:run -d ./datasource.ts
```

## Run

### Angular

To start Angular project, simply run:

```sh
ng serve
```

Angular instance is now running at [http://localhost:4200/](http://localhost:3000/).

### Nest.js

To start Nest.js project, simply run:

```sh
npm run start:dev
```

Nest.js instance is now running at <http://localhost:3000/>.

### Node.js/MongoDB

Run the following command to start the server:

```sh
npm run start
```

Node.js instance is now running at <http://localhost:3000/>.

Congratulations! Your Nzoni project  is now running!


# Angular

Nzoni is built on Angular, Tailwind CSS, and integrates seamlessly with Stripe for payment processing.<br>

### Features 🎉

<table><thead><tr><th width="220"></th><th></th></tr></thead><tbody><tr><td><h4>Authentication 🔐</h4></td><td><h4>SEO &#x26; SSR🌐</h4></td></tr><tr><td><h4>Landing Page 🌟</h4></td><td><h4>User Dashboard 🖥️</h4></td></tr><tr><td><h4>Blog ✍️</h4></td><td><h4>Admin Dashboard 👑</h4></td></tr></tbody></table>

####


# Project Structure

In this chapter, we will go through the structure of the Angular project from Nzoni boilerplate and give you an overview of how modules and components are structured.

It's seamlessly integrated with TailwinCSS, API connection and Stripe.

<pre><code>├── node_modules
├── src
│   ├── app                    # Application logic
│   ├── assets                 
│   ├── environments           # Environment variables
│   │  ├── environment.prod.ts # for production
│   │  ├── environment.ts      # for local
│   ├── favicon.ico
│   ├── index.html         
│   ├── main.server.ts.     # SSR main file
│   ├── main.ts             # 
│   ├── robots.txt          
│   ├── styles.scss         # Styling
├── .gitignore
├── .editorconfig           # WYSIWYG config
├── angular.json            # Angular config
├── package.json
├── server.ts.              # SSR logic
├── README.md
├── tailwind.config.ts      # Tailwind configuration
<strong>├── tsconfig.json           # TypeScript configuration 
</strong>├── tsconfig.server.json    # SSR tsonfig configuration
└── tsconfig.app.json       # App tsconfig

</code></pre>


# Authentication, Magic Link and Google Auth

The Nzoni boilerplate comes with built-in support for popular sign-in services. With just a few updates to environment variables

### Here's the look of the auth components folder

```
├── node_modules
├── src
│   ├── app  
│   │  ├── authentification
│   │  │   ├── login
│   │  │   ├── signup
│   │  │   ├── magic-link     # Magic link redirection
│   │  │   ├── reset          # Resetting password
```

## Configure Google OAuth

To configure Google Auth, first get <mark style="background-color:blue;">**Google Client ID**</mark>, you can follow how to get it [here](https://theonetechnologies.com/blog/post/how-to-get-google-app-client-id-and-client-secret).\
Edit <mark style="background-color:blue;">**google\_client\_id**</mark> environment variable in <mark style="color:blue;">**src/environments/environment.prod.ts**</mark> and  <mark style="color:blue;">**src/environments/environment.ts**</mark>

```typescript
export const environment = {
  ...
  google_client_id: ""
}
```

#### **Ensure that you publish your App in Google Cloud before deploying to production.**


# Landing page

The Nzoni boilerplate comes with preset landing page.

### Here's the look of the landing page components folder

```
├── node_modules
├── src
│   ├── app  
│   │  ├── landing-page
│   │  │   ├── components
│   │  │   │   ├── call-to-action
│   │  │   │   ├── facts
│   │  │   │   ├── faq
│   │  │   │   ├── features
│   │  │   │   ├── hero
│   │  │   │   ├── how-it-works
│   │  │   │   ├── preview
│   │  │   │   ├── pricing
│   │  │   │   ├── testimonial
│   │  │   │   ├── trust
│   │  │   ├── term-of-service
│   │  │   ├── landing-page.component.html
│   │  │   ├── landing-page.component.scss
│   │  │   ├── landing-page.component.ts
│   │  │   ├── landing-routing.module.ts
│   │  │   ├── landing.module.ts          
```

#### Find all components in  <mark style="background-color:blue;">landing-page.component.html</mark>

```html
<app-header></app-header>


<app-hero></app-hero>


<app-trust></app-trust>


<app-preview></app-preview>


<app-how-it-works id="howitworks"></app-how-it-works>


<app-features id="features"></app-features>


<app-testimonial></app-testimonial>


<app-pricing id="pricing"></app-pricing>


<app-faq></app-faq>


<app-call-to-action></app-call-to-action>


<app-facts></app-facts>


<!-- footer -->
<app-footer></app-footer>
```


# Payments and Plans

Managing subscription and one-time payments with data models

**src/app/models/plan.model.ts**

```typescript
export interface Plan {
    id: number;
    label: string;
    price: number;
    type: 'monthly' | 'annually' | 'onetime';
    devise: string;
    stripePlanId: string;
    description: string;
    features: string[];
    popular: boolean;
    createdAt: Date;
    updatedAt: Date;
}
```

**src/app/landing-page/components/pricing/pricing.component.htm**<mark style="background-color:blue;">l</mark>

```html
<section>
  <!-- Container -->
  <div class="mx-auto max-w-7xl px-5 py-16 md:px-10 md:py-24 lg:py-32">
    <!-- Heading Container -->
    <div class="mx-auto mb-8 max-w-3xl text-center md:mb-12 lg:mb-16">
      <!-- Heading -->
      <h2 class="text-3xl font-bold md:text-5xl">Simple &amp; Affordable Pricing</h2>
      <!-- Subheading -->
      <p class="mt-4 text-sm text-[#636262] sm:text-base">Simple &amp; fixed pricing. 30 days money-back guarantee</p>
    </div>
    <!-- Price Container -->
    <div class="grid grid-cols-1 gap-8 sm:grid-cols-2 md:grid-cols-3 md:gap-4">
      <!-- Price  -->
      <div class="b mx-auto flex w-full max-w-md flex-col items-start rounded-md border border-purple-300 p-8"
            *ngFor="let subscription of subscriptions"
            [ngClass]="{'bg-[#f2f2f7]': subscription.popular }">
        <div class="mb-4 rounded-[4px] bg-purple-500 px-4 py-1.5">
          <p class="text-sm font-bold text-white sm:text-sm">{{ subscription.label | uppercase }}</p>
        </div>
        <p class="mb-6 text-base font-light text-[#636262] md:mb-10 lg:mb-12">
          {{ subscription.description }}
        </p>
        <h2 class="mb-5 text-3xl font-bold md:mb-6 md:text-5xl lg:mb-8">
          {{ subscription.devise }}{{ subscription.price }}<span class="text-sm font-light sm:text-sm">{{ subscription.type }}</span>
        </h2>
        <a routerLink="/auth/signup" [queryParams]="{subscriptionId: subscription.id}"
           class="mb-5 w-full rounded-md bg-purple-700 px-6 py-3 text-center font-semibold text-white md:mb-6 lg:mb-8">Get started</a>
        <div class="mt-2 flex items-center" *ngFor="let feature of subscription.features" >
          <img src="https://assets.website-files.com/6458c625291a94a195e6cf3a/6458c625291a94a84be6cf60_check-mark.svg" alt="mark icon" class="mr-2 inline-block w-4" />
          <p class="text-base">{{ feature }}</p>
        </div>
      </div>
    </div>
  </div>
</section>
```

#### <mark style="background-color:blue;">src/app/dashboard/components/subscription/subscription.component.ts</mark>

```typescript
  ...
  constructor(private subscriptionService: SubscriptionService,
              private authService: AuthService) {

  } 

  async onChangeSubscription(id: number) {
    const res: { paymentUrl: string, success: boolean } = await this.subscriptionService.onChangeSubscription(id);

    if  (res && res.paymentUrl) {
      window.location.href = res.paymentUrl;
    }
  }
  
```

### Check subscriptions management from Admin dashboard [here](/angular/admin-dashboard)


# Blog and articles

### Public Articles views

```
├── node_modules
├── src
│   ├── app  
│   │  ├── blog
│   │  │   ├── article                        # public article view component
│   │  │   │   ├── article.component.html
│   │  │   │   ├── article.component.scss
│   │  │   │   ├── article.component.ts
│   │  │   ├── blog.component.html.            # article list component
│   │  │   ├── blog.component.scss
│   │  │   ├── blog.component.ts
│   │  │   ├── blog.module.ts
│   │  │   ├── blog-routing.module.ts
```

### Articles management from Admin dashboard [here](/angular/admin-dashboard)


# User Dashboard

In this chapter, we will go through the structure of the User dashboard module of Nzoni boilerplate and give you an overview of how components are structured.

```
├── node_modules
├── src
│   ├── app  
│   │  ├── dashboard
│   │  │   ├── components
│   │  │   │   ├── plans               # Plans management
│   │  │   │   ├── sidebar
│   │  │   │   ├── profile                     # User profil management
│   │  │   │   ├── header
│   │  │   ├── dashboard-routing.module.ts
│   │  │   ├── dashboard.component.html
│   │  │   ├── dashboard.component.scss
│   │  │   ├── dashboard.component.ts
│   │  │   ├── dashboard.module.ts
```


# Admin Dashboard

In this chapter, we will go through the structure of the Admin module of Nzoni boilerplate and give you an overview of how components are structured.

```
├── node_modules
├── src
│   ├── app  
│   │  ├── admin
│   │  │   ├── components
│   │  │   │   ├── account               # User profil management 
│   │  │   │   ├── blog-post             # Articles management
│   │  │   │   ├── overview                    
│   │  │   │   ├── plans                 # Plans and Subscriptions management
│   │  │   │   ├── users                 # Users management
│   │  │   ├── admin-routing.module.ts
│   │  │   ├── admin.component.html
│   │  │   ├── admin.component.scss
│   │  │   ├── admin.component.ts
│   │  │   ├── admin.module.ts
```


# SEO & SSR

In this chapter, we will explain the SEO module of  Nzoni Boilerplate

### Seo Service

```typescript
export class SeoService {

  constructor(private title: Title,
              private meta: Meta,
              @Inject(DOCUMENT) private dom: Document) { }

  setCanonicalUrl(url:string){
    const head = this.dom.getElementsByTagName('head')[0];
    let element: HTMLLinkElement = this.dom.querySelector(`link[rel='canonical']`) || null
    if (element === null) {
      element = this.dom.createElement('link') as HTMLLinkElement;
      head.appendChild(element);
    }
    element.setAttribute('rel', 'canonical')
    element.setAttribute('href', url)
  }

  setLang(lang: string) {
    const head = this.dom.getElementsByTagName('html')[0];
    head.setAttribute('lang', lang)
  }

  setMetaTitle(title: string) {
    this.title.setTitle(title);
  }

  setMetaDescription(content: string) {
    this.meta.updateTag({
      name: 'description',
      content
    });
  }

  addMetaTag(content: string, name: string) {
    this.meta.addTag({
      name,
      content
    });
  }
}

```

### How to set meta title, meta description and canonical url?

```typescript

	constructor(private seoService: SeoService) {
	}

        ngOnit() {
            // Set up meta title
	    this.seoService.setMetaTitle('your_title_here');
	    // Set up meta description
	    this.seoService.setMetaDescription('your_description_here');
	    // set up canonical url
	    this.seoService.setCanonicalUrl('your_url')
        }
```

### How build ssr

```
ng build ssr
ng build prerender
```


# Deploy Angular Project

```
cd nz-angular
# Without SSR
ng build --configuration=prod

# With SSR
ng build ssr
```

&#x20;Copy **dist/nz-angular** content and upload on server via FTP tool


# Nest.js

Welcome to Nzoni Boilerplate! A powerful SaaS (Software as a Service) boilerplate designed to help you ship fast. Built on Nest.js, TypeORM, PostgreSQL, and integrated with Stripe and email sending capabilities, Nzoni provides essential modules for building and launching your web application quickly.

### Features 🎉 <a href="#features-f09f8e89-2" id="features-f09f8e89-2"></a>

#### Authentication 🔐 <a href="#authentication-f09f9490-2" id="authentication-f09f9490-2"></a>

* **Email/Password**: Allow users to sign up and log in securely using their email and password.
* **Google Auth**: Seamlessly integrate Google authentication for a convenient sign-in experience.
* **Magic Link**: Implement magic links for effortless authentication without the need for passwords.

#### Blogpost ✍️ <a href="#blogpost-e29c8defb88f-2" id="blogpost-e29c8defb88f-2"></a>

* Create and manage blog posts to engage your audience and share valuable content.

#### Users 👥 <a href="#users-f09f91a5-2" id="users-f09f91a5-2"></a>

* Manage user accounts and permissions efficiently.

#### File Upload 📁 <a href="#file-upload-f09f9381-2" id="file-upload-f09f9381-2"></a>

* Enable users to upload files securely to your application.

#### Subscription 🔄 <a href="#subscription-f09f9484-2" id="subscription-f09f9484-2"></a>

* Implement subscription-based models to monetize your service.

#### Payments 💳 <a href="#payments-f09f92b3-2" id="payments-f09f92b3-2"></a>

* Integrate with Stripe for secure payment processing.


# Project Structure

In this chapter, we will go through the structure of the Nest.js project from Nzoni boilerplate and give you an overview of how modules are structured.

<pre><code>├── node_modules
<strong>├── mail-templates
</strong>├── migrations
├── src
│   ├── configs
│   ├── domains
│   │   ├── auth              # Auth module
│   │   ├── bogposts          # Blog post module
│   │   ├── file-uploads      # File uploads module
│   │   ├── payments          # Payment module
│   │   ├── plans             # Plan module
│   │   ├── users             # Users module
│   ├── services
│   ├── shared                   
│   ├── app.controller.ts                
│   ├── app.module.ts          # Main module
│   ├── app.service.ts        
│   ├── main.ts        
├── .gitignore
├── datasource.ts              # Datasource file
├── nest-cli.json              # NestJs config
├── package.json
├── tsconfig.build.json        # Typescript build config
├── tsconfig.json              # Typescript config
</code></pre>


# Authentication, Google auth and Magic link

<pre><code>├── node_modules
<strong>├── mail-templates
</strong>├── migrations
├── src
│   ├── configs
│   ├── domains
│   │   ├── auth             # Users module
</code></pre>


# Blogposts

<pre><code>├── node_modules
<strong>├── mail-templates
</strong>├── migrations
├── src
│   ├── configs
│   ├── domains
│   │   ├── bogposts          # Blog post module
</code></pre>


# Plans

### Subscriptions module path

<pre><code>├── node_modules
<strong>├── mail-templates
</strong>├── migrations
├── src
│   ├── configs
│   ├── domains
│   │   ├── plans     # Plan module
</code></pre>

### Entity

```typescript
  @Entity()
  export class Plan extends BaseEntity {
    @PrimaryGeneratedColumn()
    id: number;

    @Column({ nullable: true })
    label: string;

    @Column({ nullable: true })
    price: number;

    /** @type {SubscriptionType} */
    @Column({ nullable: false })
    type: string;

    @Column({ nullable: true })
    devise: string;

    @Column({ nullable: true, })
    description: string;

    @Column({ nullable: true, type: 'jsonb' })
    features: string[];

    @Column({ nullable: false })
    stripePlanId: string;

    @Column({ default: false })
    popular: boolean;

    @CreateDateColumn()
    createdAt: Date;

    @UpdateDateColumn()
    updatedAt: Date;

    @OneToMany(() => User, user => user.subscription)
    users: User[];
  }
```


# &#x20;Stripe Payment

### Payment Module Path

<pre><code>├── node_modules
<strong>├── mail-templates
</strong>├── migrations
├── src
│   ├── configs
│   ├── domains
│   │   ├── payments          # Payment module
</code></pre>


# Email and Templates

### All templates path

<pre><code>├── node_modules
<strong>├── mail-templates
</strong>│   ├── account-created.pug            # When account is created
│   ├── failed-payment.pug             # When a subscription renew failed
│   ├── reset-password.pug             # When user request lost password
│   ├── success-payment.pug            # When a payment is successfully
<strong>│   ├── updated-subscription.pug       # When user change subscription       
</strong></code></pre>

### Set up Email environment variables in <mark style="color:blue;">.env</mark>

```
# MAILING
SMTP_HOST=
SMTP_PORT=
SMTP_SECURE=false
SMTP_AUTH=
SMTP_PASSWORD=
SMTP_FROM='"Support name" <support@domain.name>'
MODERATION_MAIL=
```

### How To send a email?

```typescript
// import user service
import { UsersService } from 'src/domains/users/users.service';
constructor(private readonly usersService: UsersService) {}

// call send email to user
await this.usersService.sendMailToUser(...)
```


# Database and Migration

### All migrations path

<pre><code>├── node_modules
<strong>├── mail-templates
</strong>├── migrations
│   ├── 1661868238821-usersTable.ts              # Users table
│   ├── 1661868315626-subscriptionsTable.ts      # Subscriptions table
│   ├── 1708974949319-BlogpostTable.ts           # Blogposts table
│   ├── 1709027986137-FileUploadTable.ts         # File upload table
</code></pre>

### Set environnements variables in <mark style="background-color:blue;">.env</mark>

```
TYPEORM_HOST=
TYPEORM_PORT=
TYPEORM_USERNAME=
TYPEORM_PASSWORD=
TYPEORM_DATABASE=
```

### Run the migration

```
npx ts-node ./node_modules/.bin/typeorm  migration:run -d ./datasource.ts
```

### Create a migration

```
npx typeorm migration:create migrations/nameOfTable
```


# Image File upload

### File upload module path

<pre><code>├── node_modules
<strong>├── mail-templates
</strong>├── migrations
├── src
│   ├── configs
│   ├── domains
│   │   ├── file-uploads      # File uploads module
</code></pre>

### Upload an image

Find out the root below in the controller  <mark style="background-color:blue;">src/domains/file-uploads/file-uploads.controller.ts</mark>

```typescript
  @Post('/image')
  @ApiOperation({
    operationId: 'Upload an image',
    description: 'Upload a file containing an image',
  })
  @UseInterceptors(
    FileInterceptor('file', {
      fileFilter: (req, file, cb) => {
        if (file.size > 1000 * 1000) {
          cb(new BadRequestException('Image is too large'), false);
        } else if (!file.mimetype.startsWith('image')) {
          cb(new BadRequestException('Invalid file format'), false);
        } else {
          cb(null, true);
        }
      },
    }),
  )
  async uploadImage(@UploadedFile() file): Promise<FileUpload> {
    return await this.service.saveFile(file, 'image');
  }
```

### Read an image

Find out the root below in the controller  <mark style="background-color:blue;">src/domains/file-uploads/file-uploads.controller.ts</mark>

```typescript
  @Get('/:id/image')
  @ApiOperation({
    operationId: 'Get an image',
  })
  async getImage(@Res() res, @Param('id') id: number) {
    const image = await this.service.repo.findOne({ where: { id, type: 'image' }});
    if (!!image && existsSync(image.path)) {
      res.set('Content-Type', image.mimetype);
      createReadStream(image.path).pipe(res);
    } else {
      throw new BadRequestException(`File with id ${id} does not exists`);
    }
  }
```


# Users

In this chapter, we will go through the Users Module and give you an overview of how module works

### Users module path

<pre><code>├── node_modules
<strong>├── mail-templates
</strong>├── migrations
├── src
│   ├── configs
│   ├── domains
│   │   ├── users             # Users module
</code></pre>

### User entity

```typescript
@Entity()
export class User extends BaseEntity {
  // before insert and update hooks
  @BeforeInsert()
  @BeforeUpdate()
  async hashPassword() {
    if (!!this.password) {
      if (!this.password.includes('$2b$10')) {
        this.password = bcrypt.hashSync(this.password, 10);
      }
    }
  }

  @BeforeInsert()
  @BeforeUpdate()
  lowerCaseEmail() {
    this.email = this.email.toLowerCase();
  }

  @PrimaryGeneratedColumn()
  id: number;

  @Column({ default: false })
  isAdmin: boolean;

  @Column({ nullable: true })
  firstName: string;

  @Column({ nullable: true })
  lastName: string;

  @Index({ unique: true })
  @Column()
  email: string;

  @Column({ nullable: true })
  password: string;

  @Column({ default: null })
  resetPasswordToken: string;

  @Column({ default: false })
  confirmPayment: boolean;

  @Column({ default: false })
  errorPayment: boolean;

  @Column({ nullable: true })
  customerStripeId: string;

  @Column({ nullable: true })
  @IsOptional()
  endSubscription: Date;

  @Column({ default: false })
  canceledSubscription: boolean;

  @Column({ nullable: true})
  magicToken: string;

  @Column({ nullable: true })
  planId: number;

  @ManyToOne(() => Plan, plan => plan.users)
  @JoinColumn()
  plan: Plan;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;
}


export function password(length): string {
    let pass = '';
    for (let l=0; l < length; l++) {
        const rand = Math.random() * (126 - 33) + 33;
        pass += String.fromCharCode(~~rand);
    }
    return pass;
}
```

### Users routes

```typescript
@Controller('users')
export class UsersController implements CrudController<User> {
  private readonly logger = new Logger(UsersController.name);

  constructor(
    public service: UsersService,
    private readonly shared: SharedService,
    private readonly authService: AuthService,
  ) {}

  @Delete('/:id')
  @ApiOperation({
    operationId: 'Deactivate user',
    description: 'Deactivate an existing user',
  })
  @UseGuards(AdminGuard)
  async softDelete(@Param('id') id) {
    await this.service.softDelete(id);
    return null;
  }

  @Get('list')
  @UseGuards(AdminGuard)
  async getList(@Query('page') page: number, @Query('limit') limit: number, @Query('nameOrEmail') nameOrEmail: string = null) {
     return await this.service.getUsers(page, limit, nameOrEmail);
  }

  @Get('/me')
  @ApiOperation({
    operationId: 'Get current user',
    description: 'Get user information for logged user',
  })
  @ApiOkResponse({ type: UserResponse })
  @UseGuards(ConnectedUserGuard)
  async getCurrentUser(
    @CurrentUser() userPayload: TokenPayload,
  ): Promise<UserResponse> {
    const user = await this.service.findOne({ where: { id: userPayload.id }});
    delete user.password;
    delete user.resetPasswordToken;
    return user;
  }

  @Get('stats') 
  @UseGuards(AdminGuard)
  async getStats(@Query('from') from: Date, @Query('to') to: Date) {
      return await this.service.getStats(from, to);
  }

  @Put('/me')
  @ApiOperation({
    operationId: 'Update current user',
    description: 'Update user information for logged user',
  })
  @ApiOkResponse({ type: UserResponse })
  @UseGuards(ConnectedUserGuard)
  async updateCurrentUser(
    @Body() updatedUserInfo: UserUpdateRequest,
    @CurrentUser() userPayload: TokenPayload
  ) {
    const user = await this.service.findOne({ where: {id: userPayload.id }});
    let paymentUrl = null;

    if (typeof updatedUserInfo.canceledSubscription !== 'undefined') {
      if (updatedUserInfo.canceledSubscription) {
        // cancel subscription
        await this.shared.cancelSubscription(user.customerStripeId);
      } else {
        // reactivate subscription
        paymentUrl = await this.shared.getSubscriptionUrl(user, false, user.subscriptionId);
        updatedUserInfo.canceledSubscription = true;
      }
    }

    const resp = await this.service.repo.update({id: userPayload.id}, updatedUserInfo);

    // changer de plan
    if (!!updatedUserInfo.subscriptionId) {
      const subscription: Subscription = await this.service.repoSubscription.findOne({where: {id: updatedUserInfo.subscriptionId}});
      const subscriptions = await this.shared.getSubscriptions(user.customerStripeId);
      await this.shared.updateSubscription(subscriptions[subscriptions.length - 1].id, subscription.stripePlanId);
    }

    if (!resp) {
      throw new Error('Error updating user');
    }

    if (paymentUrl) {
      return {resp, paymentUrl};
    } else {
      return resp;
    }
  }


  @Put('/password')
  @ApiOperation({
    operationId: 'Update current user',
    description: 'Update user information for logged user',
  })
  @ApiOkResponse({ type: UserResponse })
  @UseGuards(ConnectedUserGuard)
  async updateUserPassword(
    @Body() updatedUserInfo: UserUpdateRequest,
    @CurrentUser() userPayload: TokenPayload
  ) {
    const validate = await this.authService.validateUser(userPayload.email, updatedUserInfo.oldPassword);
    if (!validate) {
      throw new Error('Incorrect Old Password');
    }
    return await this.service.repo.update({ id: userPayload.id }, { password: updatedUserInfo.password });
  }

}
```


# Deploy Nest.js project

```
nest build
```

Deploy folder content <mark style="background-color:blue;">dist/</mark>


# Node.js/MongoDB

## **Get Started**

Let's get your SaaS application up and running quickly :

1. **Clone the Project:** Use your GitHub access token to clone the project:

   ```
   git clone https://<your_access_token>@github.com/nzoni-app/nz-nodejs-mongodb.git
   ```
2. **Environment Variables:** Create a `.env` file based on the `.env.example` and fill in the following details:

`DEFAULT_PLAN_ID`: ID of the default plan assigned to new users is necessary

```markdown
ENV=developpement # OR production OR stagging
PORT=3000
# DB configuration
DB_HOST=localhost
DB_PORT=
DB_USERNAME=
DB_PASSWORD=
DB_NAME=
DB_LOCAL=false

# MAILING
SMTP_HOST=
SMTP_PORT=
SMTP_SECURE=false
SMTP_AUTH=
SMTP_PASSWORD=
SMTP_FROM='"Support name" <support@domain.name>'
MODERATION_MAIL=
# Logs
APP_DEBUG=true
JOB_ERROR_LOG_PATH='./'
DEFAULT_LOG_PATH='./logs'

# JWT
JWT_SECRET=A/iQ2KX0auTZZBwsbPEGC8H3a78HiiL23WD4S+QAoEq34LdeJ9aPrgpHdkkTvBzJ46K2JkM4apkg414erD3S+qLvwiYk3DTorANbbkA+54tVJsXrSGsaUjGifR31OaRK98aDVgICvl60Nymo3+I6527+BOkZalZsbCPzsJ7nALyTNu9Ud2FsvfK0WpAQVOf4teoHT4R/7E7ENChmgHRvI/TWumKfWcyr//Q7b9bLipFivk0EDzkpApdaEsClxE7JjT33aZhMvtuvLn5mQF3L5/ubGs5ZZy+nk4AyhR1DBTYjBYzKoamf94JW3BPobg5heH8gGnNoA+5l3WOJA7uvzw==
JWT_EXPIRATION=192h # 3 days

# FONT_END_URL
FRONT_END_URL=

#GOOGLE
GOOGLE_CLIENT_ID=
GOOGLE_APPLICATION_CREDENTIALS=google-service.json

#STRIPE
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=

#TRIAL PERIOD IF EXIST
TRIAL_DAYS=
DEFAULT_PLAN_ID=
```

* `DATABASE INFOS`: Connection details for your MongoDB database

3. **Install Dependencies:** Navigate to the project directory and install dependencies:

   ```sh
   cd nz-nodejs-mongodb
   npm install
   ```
4. **Start the Application:** Run the following command to start the server:

   ```sh
   npm run start
   ```

This will start the application, and you should be able to access it on the port specified in your configuration (usually [http://localhost:3000](http://localhost:3000/)).


# Project structure

This boilerplate is well-organized to keep your code maintainable. Here's a breakdown of the key directories:

```
├── config
│   ├── db_connection.js
├── controllers
│   ├── auth.controller.js
│   ├── blogpost.controller.js
│   ├── file-upload.controller.js
│   ├── payment.controller.js
│   ├── plan.controller.js
│   ├── user.controller.js
├── helpers
│   ├── auth.helper.js
│   ├── file.helper.js
│   ├── index.js
│   ├── mail.helper.js
│   ├── payment.helper.js
│   ├── user.helper.js
├── mail-templates
│   ├── partials
│   ├── account-created.pug
│   ├── canceled-subscription.pug
│   ├── created-subscription.pug
│   ├── default-template.pug
│   ├── failed-payment.pug
│   ├── reset-password.pug
│   ├── success-payment.pug
│   ├── updated-subscription.pug
├── middlewares
│   ├── adminGuard.js
│   ├── connectedUserGuard.js
│   ├── headers.js
│   ├── index.js
├── models
│   ├── Blogpost.js
│   ├── File-upload.js
│   ├── index.js
│   ├── Payment.js
│   ├── Plan.js
│   ├── User.js
├── routes
│   ├── auth.routes.js
│   ├── blogpost.routes.js
│   ├── file-upload.routes.js
│   ├── index.js
│   ├── payment.route.js
│   ├── plan.routes.js
│   ├── user.routes.js 
├── uploads
├── .env.example
├── index.js
├── package.json

```


# API endpoints

Here's an overview of the available API endpoints to manage various aspects of your SaaS application:

**Auth Routes** (Authentication)

* `POST /auth/signIn`: Login with email/password
* `POST /auth/signup`: Create a new user account 🆕
* `POST /auth/exist`: Check if an email address already exists
* `PUT /auth/password`: Update user's password
* `POST /auth/password-reset`: Send password reset link
* `POST /auth/google`: Authenticate with Google
* `GET /auth/magic-link`: Send a magic link for login via email 🪄
* `GET /auth/callback/magic-link`: Verify a magic link

**Blog Post Routes** (Blog management)

* `GET /blogposts/`: Get all blog posts
* `GET /blogposts/:id`: Get a specific blog post
* `GET /blogposts/:slug/slug`: Get a blog post by slug
* `GET /blogposts/:slug/latests`: Get latest blog posts (ignoring slug) 🆕
* `POST /blogposts/`: Create a new blog post
* `PUT /blogposts/:id`: Update a blog post ✏️
* `DELETE /blogposts/:id`: Delete a blog post ️

**File Uploads Routes** (Image uploads)

* `POST /file-uploads/image`: Upload an image ️
* `GET /file-uploads/:id/image`: Get an uploaded image ️

**Payment Routes** (STRIPE integration)

* `POST /payments/webhook`: Handle Stripe webhooks (for automatic subscription management)

**Plan Routes** (Subscription plans)

* `GET /plans/all`: Get all available subscription or payment plans
* `GET /plans/:id`: Get a specific plan
* `POST /plans/change`: Update a user's subscription or payment plan
* `POST /plans/`: Create a new subscription or payment plan ➕
* `PUT /plans/:id`: Update a subscription or payment plan ✏️
* `DELETE /plans/:id`: delete a subscription or payment plan


# Angular / Firebase / Node.js

This guide will walk you through the setup, configuration, and usage of Angular / Firebase / Node.js, a robust SaaS boilerplate designed to accelerate your SAAS

## Download repositories

Once you've purchased Nzoni, your github email account will be added to the related projects.\
Make sure you're already connected to github on your cli terminal or use Github Personal access token.

### Download Angular repository

```
git clone https://github.com/nzoni-app/nz-angular-firebase.git
```

### Download Node.js repository

```
git clone https://github.com/nzoni-app/nz-nodejs-firebase.git
```

## Firebase Configuration

Create a Firebase account if you don't have one already, by visiting firebase.google.com.

* Create a new project
* Download the service key JSON file and place it at the root of your Node.js project with the name `serviceAccountKey.json`.
* Enable Email/Password, Google, and passwordless email authentication.
* Enable Firebase and create two indexes:
  * Collection ID: `users`&#x20;
    * &#x20;`createdAt` = Ascending
    * `id` = Ascending
  * Collection ID: `plans`
    * &#x20;`active` = Ascending
    * &#x20;`position` = Ascending
* Activate Firebase storage
  * Set rules:

```bash
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read;
      allow write: if request.auth != null;
    }
  }
}
```

* Set `FIREBASE_PROJECT_ID` in the `.env` file.

## Installation

#### Install Node.js (if you haven't already)

If you haven't installed Node.js yet, you can follow the instructions [here](https://nodejs.org/en/learn/getting-started/how-to-install-nodejs).

#### Install Node Modules for Angular project

Navigate to the downloaded Angular project repostiory:

```
cd nz-angular-firebase
npm install
```

#### Install Node Modules for Node.js project

Navigate to the downloaded Node.js project repostiory:

```
cd nz-nodejs-firebase
npm install
```

## Configure Environment Variables

### Node.js environnement

Node.js supports environment variables out of the box. You can set defaults in .env (for all environments), .env.development (for development), and .env.production (for production).

By default, there is the <mark style="background-color:blue;">.env.example</mark> file. Rename it to <mark style="background-color:blue;">.env</mark> modify variables

```sh
ENV=developpement
PORT=3000

# DB configuration
DB_HOST=
DB_PORT=
DB_USERNAME=
DB_PASSWORD=
DB_NAME=
DB_LOCAL=true

# Mailing
SMTP_HOST=
SMTP_PORT=
SMTP_SECURE=false
SMTP_AUTH=
SMTP_PASSWORD=
SMTP_FROM='"Support name" <support@domain.name>'

# Logs
APP_DEBUG=true
JOB_ERROR_LOG_PATH='./'
DEFAULT_LOG_PATH='./logs'

# FONT_END_URL
FRONT_END_URL=

# Mail for moderation
MODERATION_MAIL=

STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=

TRIAL_DAYS=
DEFAULT_PLAN_ID=

FIREBASE_PROJECT_ID=
```

### Angular Environnement

Edit src/environments/environnement.ts&#x20;

```typescript
export const environment = {
  production: false,
  api: '', // backend-url
  domain: '', // frontend-url
  google_tag_id: '', // for google analytics
  firebase: {
    apiKey: "",
    authDomain: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: "",
    measurementId: ""
  }
}
```

## Run

### Angular

To start Angular project, simply run:

```sh
ng serve
```

Angular instance is now running at [http://localhost:4200/](http://localhost:3000/).

### Node.js

Run the following command to start the server:

```sh
npm run start
```

Node.js instance is now running at <http://localhost:3000/>.

Congratulations! Your Nzoni project  is now running!


# Create first and default plan

You'll find all the steps you need to create a default plan. Note that you will be able to create other plans directly from your admin dashboard.

By default, a plan is created, but you can modify it and/or create your own plans.

## Create a stripe product

To create the first plan, you must first create a product (one-time payment or subscription) from [stripe dashboard](https://dashboard.stripe.com/) where you will retrieve the <mark style="background-color:yellow;">**price\_id**</mark>

<figure><img src="/files/QI88biH8aFMh1jV3Y7tU" alt=""><figcaption></figcaption></figure>

## Insert plan from your DB manager

Use your DB manager to manually insert an item in the Plan table by adding stripe <mark style="background-color:yellow;">**price\_id**</mark> to the <mark style="background-color:yellow;">**stripePlanId**</mark> field.

Your plan can have as type '<mark style="background-color:yellow;">**monthly**</mark>' or '<mark style="background-color:yellow;">**annually**</mark>' or '<mark style="background-color:yellow;">**onetime**</mark>'.

Here's Plan data example:

```json
{
       "stripePlanId" : "price_1OnnunIaiqRv3A4CB5n2RoVA",
       "label" : "Starter",
       "devise" : "$",
       "price" : 18,
       "type" : "monthly",
       "createdAt" : ISODate("2024-02-25T18:45:09.764Z"),
       "updatedAt" : ISODate("2024-03-18T16:46:48.769Z"),
       "deletedAt" : null,
       "popular" : false,
       "description" : "Our Starter Website Package is perfect for individuals",
       "features" : [
              "Professionally designed website ",
              "Mobile-responsive layout",
              "Basic SEO setup to improve search",
              "Contact form integration ",
              "Social media integration"
         ],
        "active" : false,
        "position" : 1
}
```

## Set the default plan

#### Angular

Get your plan id et define it as the default plan in the *<mark style="background-color:yellow;">**signup.component.ts**</mark>* file.&#x20;

```typescript
...
export class SignupComponent implements OnInit, OnDestroy {
  ...
  planId = // Plan ID; 
  ...
}

```

#### Backend (it's optionnal)

Set the default plan Id in <mark style="background-color:yellow;">**.env**</mark>

```properties
...
DEFAULT_PLAN_ID=

```

If everything's ok, congratulations on creating your first plan.


# Create an Admin User

Discover how to create your  Admin User

#### Defaut Admin User

**Email**: <admin@admin.com>

**Password**: admin1234

#### Create a default plan&#x20;

First step is to create a default plan as described here [Create first and default plan](/create-first-and-default-plan)

#### Signup as regular user&#x20;

Go to <mark style="background-color:yellow;">**/auth/signup**</mark> and register as a simple user

<figure><img src="/files/3tLnD0AVHtOHMTNDGD14" alt=""><figcaption></figcaption></figure>

#### Edit isAdmin to true&#x20;

In your DB manager, set the <mark style="background-color:yellow;">**isAdmin**</mark> field to <mark style="background-color:yellow;">**true**</mark> in <mark style="background-color:yellow;">**User**</mark> table of the account you've just registered.

<figure><img src="/files/OxRZDMqAPao25fPJAYlv" alt=""><figcaption></figcaption></figure>

Access to admin dashboard at <mark style="background-color:yellow;">**/admin**</mark>   and start managing your app

<figure><img src="/files/uHN1O6dDrPJ26EYdl6xp" alt=""><figcaption></figcaption></figure>


# Support

For any support needs regarding Nzoni Boilerplate, our team is here to help. Whether you have questions, encounter issues, or simply need guidance, feel free to reach out to us at <support@nzoni.app> .\
We're dedicated to ensuring your experience with Nzoni Boilerplate is smooth and productive.


