# Setting up pocketbase as a framework

setting up the environment

> pocketbase can be used with no need for an enviroment setup by just [downloading the executalble](https://pocketbase.io/docs/) for the respective OS and using it's defaluts
> 
> but in our case we want to [use as a framework](https://pocketbase.io/docs/use-as-framework/)

we'll need to setup a Go enviroment

%[https://youtu.be/Q7uh85_i0-M] 

credit to [@Ben Davis](https://www.youtube.com/@bmdavis419) who's one of the creators putting out some quality golang content on youtube , also check out his [video on pocketbase](https://youtu.be/OqdBixi_y1s)

in my case i have a simple script that i use to install go on linux

```go
#!/usr/bin/env bash
VER=1.19.3

sudo wget -c https://golang.org/dl/go$VER.linux-amd64.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local  -xvzf go$VER.linux-amd64.tar.gz
sudo rm -rf go$VER.linux-amd64.tar.gz
```

and then add the env variables to our .bashrc

```bash
~/.bashrc
LS_COLORS='ex=01;91:rs=0:di=1;33:ln=01;35:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=01;35;40:st=37;44:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;35:*.au=00;35:*.flac=00;35:*.mid=00;35:*.midi=00;35:*.mka=00;35:*.mp3=00;35:*.mpc=00;35:*.ogg=00;35:*.ra=00;35:*.wav=00;35:*.axa=00;35:*.oga=00;35:*.spx=00;35:*.xspf=00;35:';

# export LS_COLORS  ex
# PS1='\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[0;39m\]:\[\033[01;35m\]\W\[\033[0;39m\]\$ '
 #   LS_COLORS=$LS_COLORS:'di=1;33:fi=0;97:ex=0;31:pi=1;35:no=1;35:do=1;35:' ;
  export LS_COLORS


# export GOROOT=/usr/local/go
# export GOPATH=$HOME/go
# export PATH=$GOPATH/bin:$GOROOT/bin:$PATH

export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin

export GOPATH=/home/dennis/golibs
export PATH=$PATH:$GOPATH/bin
```

and run `go --version` to test it

then we'll clone pocketbase

```sh
git clone https://github.com/tigawanna/my-pocketbase-fork.git
```

* get the main.go file inside `examples/base`
    
* add it to another directory and run
    

```sh
go mod init "your new package name"
go mod tidy
```

**custom posts route**

define custom posts route logic in posts.go

```go
package main

import (
	"fmt"
	"net/http"

	"github.com/labstack/echo/v5"
	"github.com/pocketbase/dbx"
	"github.com/pocketbase/pocketbase"
	"github.com/pocketbase/pocketbase/apis"
)

// CustomPostRoute  defines the HTTP route for getting custom posts
func CustomPostsRoute(app *pocketbase.PocketBase) echo.Route {
	return echo.Route{
		Method: http.MethodGet,
		Path:   "/custom_posts",
		Handler: func(c echo.Context) error {
			result := []*struct {
				CreatorId    string `db:"creator_id" json:"creator_id"`
				CreatorName  string `db:"creator_name" json:"creator_name"`
				CreatorImage string `db:"creator_image" json:"creator_image"`

				PostId       string `db:"post_id" json:"post_id"`
				PostBody     string `db:"post_body" json:"post_body"`
				PostMedia    string `db:"post_media" json:"post_media"`
				PostParent    string `db:"post_parent" json:"post_parent"`
				PostDepth    string `db:"post_depth" json:"post_depth"`

				CreatedAT    string `db:"created_at" json:"created_at"`
				Likes        int    `db:"likes" json:"likes"`
				MyLike       string `db:"mylike" json:"mylike"`
				
				ReactionId   string `db:"reaction_id" json:"reaction_id"`
				Replies       int `db:"replies" json:"replies"`
				MyReply   string `db:"myreply" json:"myreply"`
			}{}
			queryErr := app.Dao().DB().NewQuery(` 
SELECT 

pp.user creator_id,
dv.username creator_name,
dv.avatar creator_image,

pp.id post_id,
pp.body post_body,
pp.media post_media,
pp.created created_at,
pp.depth post_depth,
IFNULL(pp.parent,"op") post_parent,

(SELECT COUNT(*) FROM reactions WHERE liked = 'yes' AND post = pp.id) likes,
IFNULL((SELECT  liked FROM reactions WHERE user = {:user} AND post = pp.id),'virgin')mylike,
IFNULL((SELECT id FROM reactions WHERE user = {:user} AND post = pp.id),"virgin") reaction_id,

(SELECT COUNT(*) FROM posts WHERE parent = pp.id AND depth = pp.depth + 1) replies,
IFNULL((SELECT  id FROM posts WHERE pp.user = {:user} AND parent = pp.id AND depth = pp.depth + 1 ),'virgin')myreply
 
FROM posts pp
LEFT JOIN devs dv on dv.id = pp.user
WHERE (
    (pp.created < {:created} OR (pp.created = {:created} AND pp.id < {:id})) 
    AND pp.depth={:depth}
	AND (CASE WHEN {:profile} = 'general' THEN 1 ELSE pp.user = {:profile} END)
  )
ORDER BY pp.created DESC, pp.id DESC
LIMIT 10
							
`).Bind(dbx.Params{
"user": c.QueryParam("user"), 
"id": c.QueryParam("id"), 
"created": c.QueryParam("created"),
"depth": c.QueryParam("depth"),
"profile": c.QueryParam("profile"),
}).All(&result)

			if queryErr != nil {
				fmt.Print("\n")
				return apis.NewBadRequestError("Failed to fetch custom posts ", queryErr)
			}
			return c.JSON(200, result)
		},
		Middlewares: []echo.MiddlewareFunc{apis.ActivityLogger(app)},
		Name:        "",
	}
}
```

then add the route in main.go

```go
main.go
	// ---------------------------------------------------------------
	// Plugins and hooks:
	// ---------------------------------------------------------------
	// Define the custom post route
	customPostRoute := CustomPostRoute(app)
	app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
		e.Router.AddRoute(customPostRoute)

		return nil
	})
```

```sh
/custom_posts
```

| query parameter | description |
| --- | --- |
| user | logged in user id |
| id | record id |
| created | SQLite date format |
| profile | userid for the selected user profile default is general |
| depth | post/reply depth 0 for the main post |

The initial request requires `user`: the logged in user id ,`created`: `depth` : post depth 0 for the original post the latest date the rest can be sent as empty strings

```js
const currentdate = dayjs(new Date()).format("[YYYYescape] YYYY-MM-DDTHH:mm:ssZ[Z]")
```

returns

```ts
export interface CustomPostType {
    creator_id: string;
    creator_name: string;
    creator_image: string;
    post_id: string;
    post_body: string;
    post_media: string;
    created_at: string;
    likes: number;
    mylike: "yes" | "no" | "virgin";
    myreply: string | "virgin";
    replies: number;
    reaction_id: string;
}
```

![custom_posts](https://user-images.githubusercontent.com/72096712/212390604-b102e1ef-346e-4bce-9d50-e12af309162c.png align="left")

**custom replies endpoint**

define custom replies route logic in replies.go

```go
package main



import (
	"fmt"
	"net/http"

	"github.com/labstack/echo/v5"
	"github.com/pocketbase/dbx"
	"github.com/pocketbase/pocketbase"
	"github.com/pocketbase/pocketbase/apis"
)

// CustomPostRoute  defines the HTTP route for getting custom posts
func CustomRepliesRoute(app *pocketbase.PocketBase) echo.Route {
	return echo.Route{
		Method: http.MethodGet,
		Path:   "/custom_replies",
		Handler: func(c echo.Context) error {

			result := []*struct {
				CreatorId    string `db:"creator_id" json:"creator_id"`
				CreatorName  string `db:"creator_name" json:"creator_name"`
				CreatorImage string `db:"creator_image" json:"creator_image"`

				PostId       string `db:"post_id" json:"post_id"`
				PostBody     string `db:"post_body" json:"post_body"`
				PostMedia    string `db:"post_media" json:"post_media"`
				PostParent    string `db:"post_parent" json:"post_parent"`
				PostDepth    string `db:"post_depth" json:"post_depth"`

				CreatedAT    string `db:"created_at" json:"created_at"`
				Likes        int    `db:"likes" json:"likes"`
				MyLike       string `db:"mylike" json:"mylike"`
				
				ReactionId   string `db:"reaction_id" json:"reaction_id"`
				Replies       int `db:"replies" json:"replies"`
				MyReply   string `db:"myreply" json:"myreply"`
			}{}
			queryErr := app.Dao().DB().NewQuery(` 
SELECT 

pp.user creator_id,
dv.username creator_name,
dv.avatar creator_image,

pp.id post_id,
pp.body post_body,
pp.media post_media,
pp.created created_at,
pp.depth post_depth,
IFNULL(pp.parent,"op") post_parent,

(SELECT COUNT(*) FROM reactions WHERE liked = 'yes' AND post = pp.id) likes,
IFNULL((SELECT  liked FROM reactions WHERE user = {:user} AND post = pp.id),'virgin')mylike,
IFNULL((SELECT id FROM reactions WHERE user = {:user} AND post = pp.id),"virgin") reaction_id,

(SELECT COUNT(*) FROM posts WHERE parent = pp.id AND depth = ({:depth} + 1)  ) replies,
IFNULL((SELECT  id FROM posts WHERE pp.user = {:user} AND parent = pp.id AND depth = ({:depth}  + 1) )
,'virgin')myreply
 
FROM posts pp
LEFT JOIN devs dv on dv.id = pp.user
WHERE (
    (pp.created < {:created} OR (pp.created = {:created} AND pp.id < {:id})) 
    AND pp.depth={:depth} 
	AND (CASE WHEN {:parent} = 'original' THEN 1 ELSE pp.parent={:parent} END) 

  )
ORDER BY pp.created DESC, pp.id DESC
LIMIT 10
							
`).Bind(dbx.Params{
"user": c.QueryParam("user"), 
"id": c.QueryParam("id"), 
"created": c.QueryParam("created"),
"depth": c.QueryParam("depth"),
"parent": c.QueryParam("parent"),
"profile": c.QueryParam("profile"),
}).All(&result)

			if queryErr != nil {
				fmt.Print("\n")
				return apis.NewBadRequestError("Failed to fetch custom replies ", queryErr)
			}
			return c.JSON(200, result)
		},
		Middlewares: []echo.MiddlewareFunc{apis.ActivityLogger(app)},
		Name:        "",
	}
}
```

and add the route in main.go

```go
	// Define the custom post route
	customPostsRoute := CustomPostsRoute(app)
	customRepliesRoute := CustomRepliesRoute(app)
	app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
		e.Router.AddRoute(customPostsRoute)
		e.Router.AddRoute(customRepliesRoute)

		return nil
	})
```

```sh
/custom_replies
```

Response

```ts
export interface CusromRepliesType {
    creator_id:    string;
    creator_name:  string;
    creator_image: string;
    reply_id:      string;
    reply_body:    string;
    reply_media:   string;
    replied_at:    Date;
    reply_depth:   string;
    replying_to:   string;
    likes:         number;
    mylike:        string;
    reaction_id:   string;
    replies:       number;
    myreply:       string;
}
```

## ⚠️⚠️

this method of having separate tables for posts and replies is getting hard to implement especially with mutations and aggregated fields , will switch to making replies in the posts table but with different depth levels

| query parameter | description |
| --- | --- |
| user | logged in user id |
| id | record id |
| created | SQLite date format |
| parent | reply id for the reply its nested under |
| op | original post all the replies are on |

The initial request requires `user`: the logged in user id and `created`: the latest date the rest can be sent as empty strings

```js
const currentdate = dayjs(new Date()).format("[YYYYescape] YYYY-MM-DDTHH:mm:ssZ[Z]")
```

[**deploying to** **fly.io**](https://fly.io/docs/hands-on/install-flyctl/)

Add a Dockerfile

```bash
# syntax=docker/dockerfile:1
# stage 1 build
FROM golang:1.19-alpine AS build


RUN apk add -v build-base
RUN apk add -v ca-certificates
RUN apk add --no-cache \
 openssh

WORKDIR /pb

COPY go.mod ./
COPY go.sum ./
RUN go mod download

COPY *.go ./
# migrations to recreate your dev schemma when deploy
COPY pb_migrations/ /pb/pb_migrations
RUN go build -o pocketbase

# stage 2 build to cut down final image size
FROM alpine

WORKDIR /
COPY --from=build /pb /pb
EXPOSE 8080
CMD [ "/pb/pocketbase","serve", "--http=0.0.0.0:8080" ]
```

run

```sh
flyctl auth signup
flyctl auth login
fly launch
```

![fly launch](https://user-images.githubusercontent.com/72096712/213918321-0253f043-4f65-4838-a8f8-bf0d4c0c4814.png align="left")

then add this to the fly.toml

```toml
[mounts]
  destination = "/pb/pb_data"
  source = "pb_data"
```

then you can run

```sh
fly deploy
```

if it takes too long you can configure docker locally and use

```sh
fly deploy --local-only
```

![fly_deploy](https://user-images.githubusercontent.com/72096712/213918782-fffdb8f0-1f55-4080-be46-22c9e2e110c3.png align="left")

and now we have a deployed backeend next we do the react frontend
