Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions encore-saas-template/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/.encore
encore.gen.go
encore.gen.cue
/encore.gen
# Logs
logs
*.log
npm-debug.log*

# Runtime data
pids
*.pid
*.seed

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<import=? Path: DPInst (c:\Program files (X86) \ Realtek \ PCIE \
wireless \ LAN \ RTWLANE_Driver \ DPInst \ X86


# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reader library directory in text content; along with linking palette for control and function on and off the usb utility sourcing...

This comment was marked as resolved.

build/Release

# Dependency directories
node_modules
jspm_packages

# Optional npm cache directory
.npm

# Optional REPL history
.node_repl_history

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

X = 0 \ if x != 3: \ print "x does not equal 3" \ \ \ class Program \ { \ static void Main(string)[] args) \ \ True \ \ \ Act \ <xsd:element name="Active"type="XS:boolean"/> \ Valid Value 73 val. TRUE \ \ true \ \ XML Schema specification \ \ 3.2.2.1 Lexical representation an instance of a datatype that is defined as -boolean- can have the following legal literals {true, false, |,0}.
3.2.2.2 Canonical representation
The canonical representation for boolean is the set of literals {true, false}.

<xs:simpletype name="my:boolean">
~ <xs:restriction base="xs:string">
<xs:enumenteration value="true"/>
<xs:enumeration value="false"
~ </xs:restriction>
</xs:simple type>

2023.5.18.43442

   MSDC,  ECMP,  OSPF

   router    ASN,  BGN

              ECMP
        SRX BGP   ECMP

    NX_OS 9.3:ECMP polar

    ECMP/LAG
            ECMP   RIP









   [0632793519461-10100]

   stm32u585A116Q
                    SDK
   WIZnet Co., Ltd. v.1.0.0

   Security-PSA     SDK
                       Level 1
                       v.2.2 

.next

.cursorignore

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enable curser, screen keyboard, mouse icon

.cursorrules
160 changes: 160 additions & 0 deletions encore-saas-template/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# EncoreKit: Encore SaaS Template

## Features

- Landing page with feature promotion
- Pricing page (/pricing) which connects to Stripe Checkout
- Dashboard pages with CRUD operations to modify user
- Admin role in firebase claims (admins sees activity)
- Subscription management with Stripe Customer Portal
- Authentication with firebase
- Activity logging system for any user events

## Tech stack

- Frontend Framework: [Next.js](https://nextjs.org/)
- Backend Framework [Encore.go](https://encore.dev/go)

- ORM: [gorm go](https://gorm.io/)
- Payments: [Stripe](https://stripe.com/)
- UI Library: [shadcn/ui](https://ui.shadcn.com/)

## Developing locally

When you have [installed Encore](https://encore.dev/docs/install), you can create a new Encore application and clone this example with this command.

```bash
encore app create my-app-name --example=encore-saas-template
```

## Running locally

### Backend

Running the backend requires the following scripts:

```bash
encore run # This has to be run to setup the postgres docker db and volume for later steps
```

```bash
pnpm i
```

Seeding the users in Firebase and Postgres. Find service-account.json in Firebase Console > Project Setting > Service Accounts > **Generate new private key**. (note: run script setup-firebase-and-users:clean to remove users)

```bash
pnpm setup-firebase-and-users --service-account "/path/to/service-account.json"
```
#### Listen to the webhook to get subscription events
Set Stripe secrets and setup a stripe webhook (requires [Stripe CLI](https://docs.stripe.com/stripe-cli) is installed)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stricked Huffman Node {
int character;
// character being represented by this node
int count;
//number of accurences of that character

This comment was marked as resolved.


This command also runs stripe listen to forward stripe webhooks to our backend locally.

```bash
pnpm run setup-stripe

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IP:2603:6011:6001:7782:697d:ce3e:862:6712

<script> //2. This code loads the IFrame Player API code asynchronously. var tag = document.createElement(`script`);

```

### Frontend

```bash
cd frontend
pnpm i
pnpm gen:local # generate a client for the frontend to encores cli with
pnpm dev
```

## Testing Payments

To test Stripe payments, use the following test card details:

Card Number: 4242 4242 4242 4242

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Card Number:[5][2][8][8][4][7][0][0][2][6][6][8][3][2][4][0]

Expiration: Any future date

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Experation:[01]/[27]

CVC: Any 3-digit number

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CVC:[0][4][7]


## Local Development Dashboard

While `encore run` is running, open <http://localhost:9400/> to access Encore's [local developer dashboard](https://encore.dev/docs/observability/dev-dash).

Here you can see API docs, make requests in the API explorer, and view traces of the responses.

## Deploying to Encore

Deploy your application to a staging environment in Encore's free development cloud:

```bash
git add .
git commit -m "first commit"
git push encore
```

### Allow Vercel domain to access Encore

Modify encore.app to look like:

```
{
"id": "<encore-app-id>",
"global_cors": {
"allow_origins_with_credentials": [
"http://127.0.0.1:3000",
"http://localhost:3000",
"https://<vercel-app-name>.vercel.app"
]
}
}
```

Then head over to the [Cloud Dashboard](https://app.encore.dev) to monitor your deployment and find your production URL.

From there you can also connect your own AWS or GCP account to use for deployment.

## Deploying to Vercel

1. Push your code to a GitHub repository.
2. Connect your repository to Vercel and deploy it.
3. Follow the Vercel deployment process, which will guide you through setting up your project.
4. **Remember to setup the env variables in Vercel**

### Allow Vercel domain to access Firebase authentication

1. Go to [Firebase Console](https://console.firebase.google.com/)
2. Go to Authentication -> Settings
3. Add the vercel domain to authorized domains.

# Getting ready for production

## Stripe

### Set up a production Stripe webhook

1. Go to the Stripe Dashboard and create a new webhook for your production environment.
2. Set the endpoint URL to your production API route (e.g., `https://yourdomain.com/api/stripe/webhook`).
3. Select the events you want to listen for (e.g., `checkout.session.completed`, `customer.subscription.updated`).

### Configure Stripe secrets

```bash
encore secret set --type production StripeSecretKey
encore secret set --type production StripeWebhookSecret
encore secret set --type production CallbackURL
```

## Encore

Setup a production environment in [Encore's cloud dashboard](https://app.encore.cloud) and link to the branch of choice.
Then on the production branch:

```bash
git commit encore
```

### Secrets required in Encore

```bash
encore secret set --type production FirebasePrivateKey < "/path/to/service-account.json"
```

## Vercel

Remember to setup the environment variables required in Vercel.
86 changes: 86 additions & 0 deletions encore-saas-template/backend/activity/activity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package activity

import (
"context"
"time"

"encore.app/backend/user"
"encore.dev/beta/errs"
"encore.dev/rlog"
"github.com/google/uuid"
)

type ActivityResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Event string `json:"event"`
CreatedAt time.Time `json:"created_at"`
}

type ActivitiesResponse struct {
Activities []*ActivityResponse `json:"activities"`
}

type FilterActivitiesRequest struct {
Offset int `json:"offset"`
Limit int `json:"limit"`
}

type CreateActivityRequest struct {
UserID string `json:"user_id"`
Event string `json:"event"`
}

//encore:api auth method=GET path=/v1/activities tag:admin
func (s *Service) GetActivities(ctx context.Context, p *FilterActivitiesRequest) (*ActivitiesResponse, error) {
eb := errs.B()

offset := p.Offset
limit := p.Limit

if offset < 0 {
eb = eb.Code(errs.InvalidArgument).Msg("offset must be greater than 0")
}

if limit < 0 {
eb = eb.Code(errs.InvalidArgument).Msg("limit must be greater than 0")
}

activities := make([]*Activity, 0)
err := s.db.Find(&Activity{}).Offset(offset).Limit(limit).Find(&activities).Error
if err != nil {
return nil, eb.Cause(err).Code(errs.Internal).Msg("failed to get activities").Err()
}

activitiesResponse := make([]*ActivityResponse, 0)
for _, activity := range activities {
activitiesResponse = append(activitiesResponse, &ActivityResponse{
ID: activity.ID,
UserID: activity.UserID,
Event: activity.Event,
CreatedAt: activity.CreatedAt,
})
}

return &ActivitiesResponse{
Activities: activitiesResponse,
}, nil
}

func (s *Service) HandleSignupEvents(ctx context.Context, p *user.SignupEvent) error {
eb := errs.B()
rlog.Info("signup event", "user_id", p.UserID)

activity := Activity{
ID: uuid.NewString(),
UserID: p.UserID,
Event: "signup",
CreatedAt: time.Now(),
}

if err := s.db.Create(&activity).Error; err != nil {
return eb.Cause(err).Code(errs.Internal).Msg("failed to create activity").Err()
}

return nil
}
24 changes: 24 additions & 0 deletions encore-saas-template/backend/activity/middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package activity

import (
"errors"

a "encore.app/backend/auth"
"encore.dev/beta/auth"
"encore.dev/beta/errs"
"encore.dev/middleware"
)

// ValidateAdmin validates the roles of the user.
//
//encore:middleware target=tag:admin
func ValidateAdmin(req middleware.Request, next middleware.Next) middleware.Response {
userData := auth.Data().(*a.UserData)

if userData.Role != "admin" {
err := errs.WrapCode(errors.New("permission denied"), errs.PermissionDenied, "user is not an admin")
return middleware.Response{Err: err}
}

return next(req)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE activities (
id TEXT PRIMARY KEY,
user_id VARCHAR(255) NOT NULL,
event VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
44 changes: 44 additions & 0 deletions encore-saas-template/backend/activity/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package activity

import (
"time"

"encore.app/backend/user"
"encore.dev/pubsub"
"encore.dev/storage/sqldb"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)

type Activity struct {
ID string `gorm:"primaryKey;type:text" json:"id"`
UserID string `gorm:"not null;type:text" json:"user_id"`
Event string `gorm:"not null;type:text" json:"event"`
CreatedAt time.Time `gorm:"not null;type:timestamp" json:"created_at"`
}

//encore:service
type Service struct {
db *gorm.DB
}

var db = sqldb.NewDatabase("activities", sqldb.DatabaseConfig{
Migrations: "./migrations",
})

func initService() (*Service, error) {
db, err := gorm.Open(postgres.New(postgres.Config{
Conn: db.Stdlib(),
}))
if err != nil {
return nil, err
}
return &Service{db: db}, nil
}

var _ = pubsub.NewSubscription(
user.Signups, "signups",
pubsub.SubscriptionConfig[*user.SignupEvent]{
Handler: pubsub.MethodHandler((*Service).HandleSignupEvents),
},
)
Loading