├── log.json
├── .gitignore
├── docs.go
├── .env.example
├── ui
├── public
│ └── favicon.ico
├── src
│ ├── vite-env.d.ts
│ ├── main.js
│ ├── App.svelte
│ ├── lib
│ │ ├── GetUserID.svelte
│ │ ├── Logout.svelte
│ │ └── Login.svelte
│ └── stores.js
├── vite.config.js
├── package.json
├── index.html
├── jsconfig.json
├── README.md
└── package-lock.json
├── redoc.html
├── models
├── category.go
├── comment.go
├── role.go
├── post.go
├── user.go
└── permissions
│ └── permissions.go
├── README.md
├── controllers
├── controllers.go
├── comments.go
├── categories.go
├── roles.go
├── users.go
└── posts.go
├── databases
├── gorm
│ ├── comments.go
│ ├── roles.go
│ ├── categories.go
│ ├── posts.go
│ ├── users.go
│ └── gormdb.go
└── database.go
├── Dockerfile
├── Makefile
├── tools
├── extract_user_id.go
├── db_config.go
└── token.go
├── todo.md
├── .github
└── workflows
│ └── main.yaml
├── saggerUI.html
├── log
└── log.go
├── middlewares
├── require_login.go
├── check_permissions.go
└── zap_logger.go
├── docker-compose.yml
├── session
└── session.go
├── main.go
├── go.mod
├── routes
└── routes.go
├── docs
├── swagger.yaml
├── swagger.json
└── docs.go
└── go.sum
/log.json:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | .fake
3 | storage
4 | ui/node_modules
5 | ui/dist
--------------------------------------------------------------------------------
/docs.go:
--------------------------------------------------------------------------------
1 | // docs.go
2 | //
3 | //go:generate swag init
4 | package main
5 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | REFRESH_TOKEN_SECRET=
2 | JWT_SECRET=
3 | PG_PASS=
4 | PG_USER=
5 | DB_NAME=
--------------------------------------------------------------------------------
/ui/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arshamalh/blogo/HEAD/ui/public/favicon.ico
--------------------------------------------------------------------------------
/ui/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/ui/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.svelte'
2 |
3 | const app = new App({
4 | target: document.getElementById('app')
5 | })
6 |
7 | export default app
8 |
--------------------------------------------------------------------------------
/ui/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import { svelte } from '@sveltejs/vite-plugin-svelte'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [svelte()]
7 | })
8 |
--------------------------------------------------------------------------------
/redoc.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Redoc
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/models/category.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "gorm.io/gorm"
4 |
5 | type Category struct {
6 | gorm.Model
7 | Name string `json:"name" gorm:"uniqueIndex"`
8 | Description string `json:"description"`
9 | Posts []Post `json:"posts" gorm:"many2many:post_categories;"`
10 | }
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Blogo
2 |
3 | Simple blog made using:
4 | - Go
5 | - Echo framework
6 | - Svelte frontend
7 | - Postgres Database
8 | - GORM orm
9 | - Docker virtualization
10 | - JWT
11 | - Github actions
12 | - Zap for logging
13 |
14 | In the near future
15 | - Swaggo
16 | - Dynamic Permission
17 |
--------------------------------------------------------------------------------
/models/comment.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "gorm.io/gorm"
4 |
5 | type Comment struct {
6 | gorm.Model
7 | User User `json:"user"`
8 | UserID uint
9 | PostID uint `json:"post_id" from:"post_id" binding:"required"`
10 | Text string `gorm:"not null" json:"text" from:"text" binding:"required"`
11 | }
12 |
--------------------------------------------------------------------------------
/controllers/controllers.go:
--------------------------------------------------------------------------------
1 | // controllers.go
2 | package controllers
3 |
4 | import (
5 | "github.com/arshamalh/blogo/databases"
6 | "go.uber.org/zap"
7 | )
8 |
9 | // basicAttributes defines the basic attributes shared by other controllers.
10 | type basicAttributes struct {
11 | db databases.Database
12 | logger *zap.Logger
13 | Gl *zap.Logger
14 | }
15 |
--------------------------------------------------------------------------------
/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ui",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "devDependencies": {
12 | "@sveltejs/vite-plugin-svelte": "^1.0.0-next.30",
13 | "svelte": "^3.44.0",
14 | "vite": "^2.9.9"
15 | }
16 | }
--------------------------------------------------------------------------------
/models/role.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "gorm.io/gorm"
5 | )
6 |
7 | // Any user has a role, and any role has some permissions and accessories in the future (TODO) (logo, cool name and more)
8 | type Role struct {
9 | gorm.Model
10 | Name string `gorm:"uniqueIndex" form:"name" json:"name" binding:"required"`
11 | Permissions string `form:"premissions" json:"premissions"`
12 | }
13 |
--------------------------------------------------------------------------------
/ui/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte + Vite App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/databases/gorm/comments.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import "github.com/arshamalh/blogo/models"
4 |
5 | func (gdb *gormdb) AddComment(comment *models.Comment) error {
6 | return gdb.db.Create(comment).Error
7 | }
8 |
9 | func (gdb *gormdb) GetComment(id uint) (*models.Comment, error) {
10 | var comment models.Comment
11 | err := gdb.db.Where("id = ?", id).First(&comment).Error
12 | return &comment, err
13 | }
14 |
--------------------------------------------------------------------------------
/models/post.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "gorm.io/gorm"
4 |
5 | type Post struct {
6 | gorm.Model
7 | Title string `json:"title"`
8 | Content string `json:"content"`
9 | AuthorID uint `json:"author_id"`
10 | Author User `json:"author"`
11 | Comments []Comment `json:"comments"`
12 | Categories []Category `json:"categories" gorm:"many2many:post_categories;"`
13 | }
14 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.21-alpine3.18 AS builder
2 | WORKDIR /app
3 | COPY go.mod go.sum ./
4 | RUN go mod download
5 | COPY . ./
6 | RUN go install github.com/swaggo/swag/cmd/swag@latest
7 | RUN swag init
8 | RUN CGO_ENABLED=0 GOOS=linux go build -a -o blogo .
9 |
10 | FROM node:18-alpine3.15 AS frontend
11 | WORKDIR /ui
12 | COPY ui .
13 | RUN npm install
14 | RUN npm run build
15 |
16 | FROM alpine:3.16
17 | COPY --from=builder /app/blogo .
18 | COPY --from=frontend /ui/dist /ui
19 | CMD ["./blogo"]
20 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Sample file is the default value, but shold be overriden like `make run File=data/another.json
2 | version=0.0.1
3 | name=kari
4 |
5 | help: # Generate list of targets with descriptions
6 | @grep '^.*\:\s#.*' Makefile | sed 's/\(.*\) # \(.*\)/\1 \2/' | column -t -s ":"
7 |
8 | build: # build the docker image with specified name and version
9 | docker build -t ${name}:${version} .
10 |
11 | dev: # run the application using go run command and is used for development
12 | # You should first install swag using: go install github.com/swaggo/swag/cmd/swag@latest
13 | ~/go/bin/swag init
14 | go run main.go serve
15 |
--------------------------------------------------------------------------------
/tools/extract_user_id.go:
--------------------------------------------------------------------------------
1 | package tools
2 |
3 | import (
4 | "strconv"
5 |
6 | "github.com/labstack/echo/v4"
7 | )
8 |
9 | // ExtractUserID extracts user ID from the request context.
10 | // returns error if there is no user ID or if the user ID is not a number.
11 | func ExtractUserID(ctx echo.Context) (uint, error) {
12 | user_id_str := ctx.Get("user_id")
13 | user_id, err := strconv.ParseUint(user_id_str.(string), 10, 64)
14 | if err != nil {
15 | return 0, err
16 | }
17 | return uint(user_id), nil
18 | }
19 |
20 | func ExtractPermissable(ctx echo.Context) bool {
21 | permissable := ctx.Get("permissable")
22 | return permissable.(bool)
23 | }
24 |
--------------------------------------------------------------------------------
/todo.md:
--------------------------------------------------------------------------------
1 | - [x] Use Zap for logging
2 | - [ ] Use swaggo for better and more clear API documentation
3 | - [ ] Remove `gorm` and use `bun` instead
4 | - [ ] Use cobra to make it a cli application
5 | - [ ] Add Edit post route, including editing posts categories, id of editor should be recorded if that's not the original author.
6 | - [ ] Add comments,
7 | - [ ] Add comments related permissions such as deleting or verifying
8 | - [ ] Permissions mechanism is complex, and compression is not a good way.
9 | - [ ] Add capability to include media, hashtags and others.
10 | - [ ] Write Tests.
11 | - [ ] Remove basicAttributes from controllers
12 | - [ ] Make it a cli tool
13 | - [ ] Fix docker compose (simplify)
14 |
--------------------------------------------------------------------------------
/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | name: main
2 |
3 | on:
4 | push:
5 | branches: [master]
6 |
7 | jobs:
8 | build-and-release:
9 | runs-on: ubuntu-20.04
10 | steps:
11 | - name: Checkout code
12 | uses: actions/checkout@v2
13 |
14 | - name: Login to Docker
15 | uses: docker/login-action@v1
16 | with:
17 | registry: docker.io
18 | username: ${{ secrets.DOCKER_USERNAME }}
19 | password: ${{ secrets.DOCKER_PASSWORD }}
20 |
21 | - name: Build and push Docker image
22 | uses: docker/build-push-action@v2
23 | with:
24 | context: .
25 | push: true
26 | tags: arshamalh/blogo:latest
27 | secrets: |
28 | DOCKER_BUILDKIT=1
29 |
--------------------------------------------------------------------------------
/ui/src/App.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
35 |
--------------------------------------------------------------------------------
/databases/gorm/roles.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import "github.com/arshamalh/blogo/models"
4 |
5 | func (gdb *gormdb) CreateRole(role *models.Role) error {
6 | err := gdb.db.Create(&role).Error
7 | return err
8 | }
9 |
10 | func (gdb *gormdb) UpdateRole(role *models.Role) error {
11 | err := gdb.db.Save(&role).Error
12 | return err
13 | }
14 |
15 | func (gdb *gormdb) DeleteRole(id uint) error {
16 | err := gdb.db.Delete(&models.Role{}, "id = ?", id).Error
17 | return err
18 | }
19 |
20 | func (gdb *gormdb) GetRole(id uint) (models.Role, error) {
21 | var role models.Role
22 | err := gdb.db.First(&role, id).Error
23 | return role, err
24 | }
25 |
26 | func (gdb *gormdb) GetRoles() ([]models.Role, error) {
27 | var roles []models.Role
28 | err := gdb.db.Find(&roles).Error
29 | return roles, err
30 | }
31 |
--------------------------------------------------------------------------------
/databases/gorm/categories.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import "github.com/arshamalh/blogo/models"
4 |
5 | func (gdb *gormdb) CheckCategoryExists(name string) bool {
6 | results := gdb.db.Take(&models.Category{}, "name = ?", name)
7 | return results.RowsAffected > 0
8 | }
9 |
10 | func (gdb *gormdb) CreateCategory(catg *models.Category) (uint, error) {
11 | err := gdb.db.Create(&catg).Error
12 | return catg.ID, err
13 | }
14 |
15 | func (gdb *gormdb) GetCategory(name string) (*models.Category, error) {
16 | catg := &models.Category{}
17 | err := gdb.db.Preload("Post").First(&catg, "name = ?", name).Error
18 | return catg, err
19 | }
20 |
21 | func (gdb *gormdb) GetCategories() ([]models.Category, error) {
22 | var categories []models.Category
23 | err := gdb.db.Find(&categories).Error
24 | return categories, err
25 | }
26 |
--------------------------------------------------------------------------------
/tools/db_config.go:
--------------------------------------------------------------------------------
1 | package tools
2 |
3 | import "fmt"
4 |
5 | type DBConfig struct {
6 | Host string
7 | User string
8 | Password string
9 | DBName string
10 | Port int
11 | SSLMode bool
12 | TimeZone string
13 | }
14 |
15 | func (dbc DBConfig) String() string {
16 | if dbc.Host == "" {
17 | dbc.Host = "localhost"
18 | }
19 |
20 | if dbc.Port == 0 {
21 | dbc.Port = 5432
22 | }
23 |
24 | var sslmode string
25 | if dbc.SSLMode {
26 | sslmode = "enable"
27 | } else {
28 | sslmode = "disable"
29 | }
30 |
31 | if dbc.TimeZone == "" {
32 | dbc.TimeZone = "Asia/Tehran"
33 | }
34 |
35 | return fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=%s",
36 | dbc.Host,
37 | dbc.User,
38 | dbc.Password,
39 | dbc.DBName,
40 | dbc.Port,
41 | sslmode,
42 | dbc.TimeZone,
43 | )
44 | }
45 |
--------------------------------------------------------------------------------
/saggerUI.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Swagger UI
6 |
7 |
8 |
9 |
10 |
11 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/log/log.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "os"
5 |
6 | "go.uber.org/zap"
7 | "go.uber.org/zap/zapcore"
8 | )
9 |
10 | var Gl *zap.Logger
11 |
12 | func InitializeLogger() *zap.Logger {
13 | config := zap.NewProductionEncoderConfig()
14 | config.EncodeTime = zapcore.ISO8601TimeEncoder
15 |
16 | fileEncoder := zapcore.NewJSONEncoder(config)
17 | consoleEncoder := zapcore.NewConsoleEncoder(config)
18 |
19 | logFile, _ := os.OpenFile("log.json", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
20 | writer := zapcore.AddSync(logFile)
21 | defaultLogLevel := zapcore.DebugLevel
22 | core := zapcore.NewTee(
23 | zapcore.NewCore(fileEncoder, writer, defaultLogLevel),
24 | zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), defaultLogLevel),
25 | )
26 |
27 | Gl = zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
28 | return Gl
29 | }
30 |
--------------------------------------------------------------------------------
/middlewares/require_login.go:
--------------------------------------------------------------------------------
1 | package middlewares
2 |
3 | import (
4 | "net/http"
5 | "os"
6 |
7 | "github.com/arshamalh/blogo/tools"
8 | "github.com/labstack/echo/v4"
9 | )
10 |
11 | func RequireLogin(next echo.HandlerFunc) echo.HandlerFunc {
12 | return func(ctx echo.Context) error {
13 | access_token, err := ctx.Cookie("access_token")
14 | if err != nil || access_token.Value == "" {
15 |
16 | return ctx.JSON(http.StatusUnauthorized, "you should log in")
17 | }
18 |
19 | jwt_access, err := tools.ExtractTokenData(access_token.Value, os.Getenv("JWT_SECRET"))
20 | if err != nil {
21 |
22 | return ctx.JSON(http.StatusUnauthorized, "invalid token")
23 | }
24 |
25 | // If access token is valid and not expired, extract data from it
26 | userID := jwt_access.Subject
27 | ctx.Set("user_id", userID)
28 |
29 | return next(ctx)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.8'
2 | services:
3 | blogo:
4 | container_name: blogo
5 | build: .
6 | ports:
7 | - "80:80"
8 | networks:
9 | - blogo
10 | depends_on:
11 | - postgres
12 | environment:
13 | JWT_SECRET: ${JWT_SECRET}
14 | REFRESH_TOKEN_SECRET: ${REFRESH_TOKEN_SECRET}
15 | PG_PASS: ${PG_PASS}
16 | PG_USER: ${PG_USER}
17 | DB_NAME: ${DB_NAME}
18 | HOST: "postgres"
19 | postgres:
20 | container_name: postgres
21 | image: postgres:10.20-alpine3.15
22 | restart: unless-stopped
23 | ports:
24 | - 5432:5432
25 | volumes:
26 | - ./storage/pgdata:/var/lib/postgresql/data
27 | networks:
28 | - blogo
29 | environment:
30 | POSTGRES_PASSWORD: ${PG_PASS}
31 | POSTGRES_USER: ${PG_USER}
32 | POSTGRES_DB: ${DB_NAME}
33 |
34 | networks:
35 | blogo:
36 | driver: bridge
37 |
--------------------------------------------------------------------------------
/databases/gorm/posts.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import "github.com/arshamalh/blogo/models"
4 |
5 | func (gdb *gormdb) CreatePost(post *models.Post) (uint, error) {
6 | err := gdb.db.Create(&post).Error
7 | return post.ID, err
8 | }
9 |
10 | func (gdb *gormdb) DeletePost(id uint) error {
11 | return gdb.db.Delete(&models.Post{}, "id = ?", id).Error
12 | }
13 |
14 | func (gdb *gormdb) UpdatePost(post *models.Post) error {
15 | return gdb.db.Updates(&post).Error
16 | }
17 |
18 | func (gdb *gormdb) GetPost(id uint) (models.Post, error) {
19 | // FIXME: This is not the best way to do this
20 | var post models.Post
21 | err := gdb.db.
22 | Preload("Author").
23 | Preload("Category").
24 | Preload("Comment").
25 | First(&post, id).Error
26 | return post, err
27 | }
28 |
29 | func (gdb *gormdb) GetPosts() ([]models.Post, error) {
30 | var posts []models.Post
31 | err := gdb.db.Preload("Author").Preload("Category").Find(&posts).Error
32 | return posts, err
33 | }
34 |
--------------------------------------------------------------------------------
/databases/gorm/users.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import (
4 | "github.com/arshamalh/blogo/models"
5 | "github.com/arshamalh/blogo/models/permissions"
6 | )
7 |
8 | func (gdb *gormdb) CheckUserExists(username string) bool {
9 | results := gdb.db.Take(&models.User{}, "username = ?", username)
10 | return results.RowsAffected > 0
11 | }
12 |
13 | func (gdb *gormdb) CreateUser(user *models.User) (uint, error) {
14 | err := gdb.db.Create(&user).Error
15 | return user.ID, err
16 | }
17 |
18 | func (gdb *gormdb) GetUserByUsername(username string) (*models.User, error) {
19 | user := &models.User{}
20 | err := gdb.db.First(&user, "username = ?", username).Error
21 | return user, err
22 | }
23 |
24 | func (gdb *gormdb) GetUserPermissions(user_id uint) []permissions.Permission {
25 | var user models.User
26 | err := gdb.db.Preload("Role").First(&user, user_id).Error
27 | if err != nil {
28 | return nil
29 | }
30 | return permissions.Decompress(user.Role.Permissions)
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/ui/src/lib/GetUserID.svelte:
--------------------------------------------------------------------------------
1 |
26 |
27 |
28 |
29 |
30 | {#if msg !== ""}
31 | {msg}
32 | {/if}
33 | {#if status !== 0}
34 | {status}
35 | {/if}
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ui/src/lib/Logout.svelte:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
30 | {#if msg !== ""}
31 | {msg}
32 | {/if}
33 | {#if status !== 0}
34 | {status}
35 | {/if}
36 |
37 |
38 |
--------------------------------------------------------------------------------
/models/user.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/arshamalh/blogo/log"
5 | "golang.org/x/crypto/bcrypt"
6 | "gorm.io/gorm"
7 | )
8 |
9 | type User struct {
10 | gorm.Model
11 | Username string `json:"username" gorm:"uniqueIndex"`
12 | Password []byte `json:"-"`
13 | Email string `json:"email"`
14 | FirstName string `json:"first_name"`
15 | LastName string `json:"last_name"`
16 | Posts []Post `gorm:"foreignKey:AuthorID"`
17 | Comments []Comment `gorm:"foreignKey:UserID"`
18 | Role Role `gorm:"foreignKey:RoleID"`
19 | RoleID uint `json:"role_id"`
20 | }
21 |
22 | func (user *User) SetPassword(password string) error {
23 | hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 12)
24 | if err != nil {
25 | log.Gl.Error(err.Error())
26 | return err
27 | }
28 |
29 | user.Password = hashedPassword
30 | return nil
31 | }
32 |
33 | func (user *User) ComparePasswords(password string) error {
34 | err := bcrypt.CompareHashAndPassword(user.Password, []byte(password))
35 | if err != nil {
36 | log.Gl.Error(err.Error())
37 | return err
38 | }
39 | return nil
40 | }
41 |
--------------------------------------------------------------------------------
/ui/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "node",
4 | "target": "esnext",
5 | "module": "esnext",
6 | /**
7 | * svelte-preprocess cannot figure out whether you have
8 | * a value or a type, so tell TypeScript to enforce using
9 | * `import type` instead of `import` for Types.
10 | */
11 | "importsNotUsedAsValues": "error",
12 | "isolatedModules": true,
13 | "resolveJsonModule": true,
14 | /**
15 | * To have warnings / errors of the Svelte compiler at the
16 | * correct position, enable source maps by default.
17 | */
18 | "sourceMap": true,
19 | "esModuleInterop": true,
20 | "skipLibCheck": true,
21 | "forceConsistentCasingInFileNames": true,
22 | "baseUrl": ".",
23 | /**
24 | * Typecheck JS in `.svelte` and `.js` files by default.
25 | * Disable this if you'd like to use dynamic types.
26 | */
27 | "checkJs": true
28 | },
29 | /**
30 | * Use global.d.ts instead of compilerOptions.types
31 | * to avoid limiting type declarations.
32 | */
33 | "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
34 | }
35 |
--------------------------------------------------------------------------------
/session/session.go:
--------------------------------------------------------------------------------
1 | package session
2 |
3 | import "strconv"
4 |
5 | // Sessions should be stored in a database,
6 | // and it's better to use Redis for session as it's fast,
7 | // but for now, we want to keep it simple.
8 |
9 | type Session struct {
10 | SessionID string `json:"session_id"`
11 | UserID uint `json:"user_id"`
12 | Valid bool `json:"valid"`
13 | }
14 |
15 | var sessions []Session
16 |
17 | func Create(user_id uint) *Session {
18 | for _, session := range sessions {
19 | if session.UserID == user_id {
20 | return &session
21 | }
22 | }
23 | session := Session{
24 | SessionID: strconv.Itoa(len(sessions) + 1),
25 | UserID: user_id,
26 | Valid: true,
27 | }
28 | sessions = append(sessions, session)
29 | return &session
30 | }
31 |
32 | func Get(session_id string) *Session {
33 | for _, session := range sessions {
34 | if session.SessionID == session_id {
35 | return &session
36 | }
37 | }
38 | return nil
39 | }
40 |
41 | func Invalidate(session_id string) {
42 | for i, session := range sessions {
43 | if session.SessionID == session_id {
44 | sessions[i].Valid = false
45 | return
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tools/token.go:
--------------------------------------------------------------------------------
1 | package tools
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "github.com/golang-jwt/jwt"
8 | )
9 |
10 | func GenerateToken(subject string, lifespan time.Duration, secret string) (string, error) {
11 | payload := jwt.StandardClaims{
12 | Subject: subject,
13 | ExpiresAt: time.Now().Add(lifespan).Unix(),
14 | }
15 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload)
16 | return token.SignedString([]byte(secret))
17 | }
18 |
19 | func ExtractTokenData(token string, secret string) (*jwt.StandardClaims, error) {
20 | jwt_token, err := jwt.ParseWithClaims(
21 | token, &jwt.StandardClaims{},
22 | func(t *jwt.Token) (interface{}, error) {
23 | return []byte(secret), nil
24 | },
25 | )
26 | payload := jwt_token.Claims.(*jwt.StandardClaims)
27 | if err != nil {
28 | // This error usually means that the token is expired
29 | return payload, err
30 | } else if !jwt_token.Valid {
31 | // This error usually means that the token is invalid because it was not signed with the correct secret
32 | // We have access to payloads here, but we don't return it.
33 | return nil, fmt.Errorf("invalid token")
34 | }
35 | return payload, nil
36 | }
37 |
--------------------------------------------------------------------------------
/databases/database.go:
--------------------------------------------------------------------------------
1 | package databases
2 |
3 | import (
4 | "github.com/arshamalh/blogo/models"
5 | "github.com/arshamalh/blogo/models/permissions"
6 | )
7 |
8 | type Database interface {
9 | // Users
10 | CheckUserExists(username string) bool
11 | CreateUser(user *models.User) (uint, error)
12 | GetUserByUsername(username string) (*models.User, error)
13 | GetUserPermissions(user_id uint) []permissions.Permission
14 |
15 | // Categories
16 | CheckCategoryExists(name string) bool
17 | CreateCategory(catg *models.Category) (uint, error)
18 | GetCategory(name string) (*models.Category, error)
19 | GetCategories() ([]models.Category, error)
20 |
21 | // Comments
22 | AddComment(comment *models.Comment) error
23 | GetComment(id uint) (*models.Comment, error)
24 |
25 | // Posts
26 | CreatePost(post *models.Post) (uint, error)
27 | DeletePost(id uint) error
28 | UpdatePost(post *models.Post) error
29 | GetPost(id uint) (models.Post, error)
30 | GetPosts() ([]models.Post, error)
31 |
32 | // Roles
33 | CreateRole(role *models.Role) error
34 | UpdateRole(role *models.Role) error
35 | DeleteRole(id uint) error
36 | GetRole(id uint) (models.Role, error)
37 | GetRoles() ([]models.Role, error)
38 | }
39 |
--------------------------------------------------------------------------------
/ui/src/stores.js:
--------------------------------------------------------------------------------
1 | import { readable, writable } from "svelte/store";
2 |
3 | const createWritableStore = (key, startValue) => {
4 | const { subscribe, set } = writable(startValue);
5 |
6 | return {
7 | subscribe,
8 | set,
9 | useLocalStorage: () => {
10 | const json = localStorage.getItem(key);
11 | if (json) {
12 | set(JSON.parse(json));
13 | }
14 |
15 | subscribe((current) => {
16 | localStorage.setItem(key, JSON.stringify(current));
17 | });
18 | },
19 | };
20 | };
21 |
22 | export const User = (function () {
23 | const { subscribe, set, useLocalStorage } = createWritableStore(
24 | "user",
25 | false
26 | );
27 | return {
28 | subscribe,
29 | signout: () => {
30 | set(false);
31 | },
32 | signin: (agent_name) => {
33 | set(agent_name);
34 | },
35 | useLocalStorage,
36 | };
37 | })();
38 |
39 | export const ActiveAccount = (function () {
40 | const { subscribe, set, useLocalStorage } = createWritableStore(
41 | "active_account",
42 | false
43 | );
44 | return {
45 | subscribe,
46 | set,
47 | useLocalStorage,
48 | };
49 | })();
50 |
51 | export const BaseURL = readable("http://localhost:80/api/v1");
52 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 |
6 | database "github.com/arshamalh/blogo/databases/gorm"
7 | "github.com/arshamalh/blogo/log"
8 | "github.com/arshamalh/blogo/routes"
9 | "github.com/arshamalh/blogo/tools"
10 | "github.com/joho/godotenv"
11 | "github.com/labstack/echo/v4"
12 | )
13 |
14 | // @title Blogo API server
15 | // @version 1.0
16 | // @description A simple blog for educational purposes
17 | // @contact.email arshamalh.github.io/
18 | // @host localhost:8080
19 | // @BasePath /api/v1
20 | func main() {
21 | // Load environment variables
22 | logger := log.InitializeLogger()
23 |
24 | if err := godotenv.Load(); err != nil {
25 | logger.Error(err.Error())
26 | }
27 |
28 | // Database
29 | dsn := tools.DBConfig{
30 | User: os.Getenv("PG_USER"),
31 | Password: os.Getenv("PG_PASS"),
32 | DBName: os.Getenv("DB_NAME"),
33 | Host: os.Getenv("HOST"),
34 | }
35 | db, err := database.Connect(dsn.String())
36 | if err != nil {
37 | log.Gl.Error(err.Error())
38 | return
39 | }
40 |
41 | // Router
42 | router := echo.New()
43 | routes.InitializeRoutes(router, db, logger)
44 | router.StaticFS("/", os.DirFS("./ui"))
45 |
46 | if err := router.Start(":8080"); err != nil {
47 | log.Gl.Error(err.Error())
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/middlewares/check_permissions.go:
--------------------------------------------------------------------------------
1 | package middlewares
2 |
3 | import (
4 | "github.com/arshamalh/blogo/databases"
5 | "github.com/arshamalh/blogo/models/permissions"
6 | "github.com/arshamalh/blogo/tools"
7 | "github.com/labstack/echo/v4"
8 | )
9 |
10 | // CheckPermissions is a middleware that checks if the user has the required permissions.
11 | func CheckPermissions(db databases.Database, permission permissions.Permission) func(next echo.HandlerFunc) echo.HandlerFunc {
12 | return func(next echo.HandlerFunc) echo.HandlerFunc {
13 | return func(ctx echo.Context) error {
14 | userID, _ := tools.ExtractUserID(ctx)
15 | hasPermission := HavePermissions(db, userID, permission)
16 |
17 | ctx.Set("permissable", hasPermission)
18 | return next(ctx)
19 | }
20 | }
21 | }
22 |
23 | // HavePermissions checks if the user has the required permissions, this can be used in other parts of the application.
24 | func HavePermissions(db databases.Database, userID uint, permission permissions.Permission) bool {
25 | for _, perm := range db.GetUserPermissions(userID) {
26 | if perm == permission ||
27 | perm == permissions.FullAccess ||
28 | (perm == permissions.FullContents && permission > permissions.FullContents) {
29 | return true
30 | }
31 | }
32 | return false
33 | }
34 |
--------------------------------------------------------------------------------
/databases/gorm/gormdb.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/arshamalh/blogo/models"
7 | "github.com/arshamalh/blogo/models/permissions"
8 | "gorm.io/driver/postgres"
9 | "gorm.io/gorm"
10 | )
11 |
12 | type gormdb struct {
13 | db *gorm.DB
14 | }
15 |
16 | func Connect(dsn string) (*gormdb, error) {
17 | DB, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
18 | SkipDefaultTransaction: true,
19 | })
20 | if err != nil {
21 | return nil, err
22 | }
23 | fmt.Println("Database connection successfully opened")
24 |
25 | // Auto migration
26 | err = DB.AutoMigrate(models.User{}, models.Post{}, models.Category{}, models.Role{}, models.Comment{})
27 | if err != nil {
28 | return nil, err
29 | }
30 | gdb := &gormdb{db: DB}
31 | gdb.AddBasicRoles()
32 | fmt.Println("Database Migrated")
33 | return gdb, nil
34 | }
35 |
36 | // Add some basic roles manually
37 | func (gdb *gormdb) AddBasicRoles() {
38 | gdb.CreateRole(&models.Role{Name: "superadmin", Permissions: permissions.Compress([]permissions.Permission{permissions.FullAccess})})
39 | gdb.CreateRole(&models.Role{Name: "moderator", Permissions: permissions.Compress([]permissions.Permission{permissions.FullContents})})
40 | gdb.CreateRole(&models.Role{Name: "author", Permissions: permissions.Compress([]permissions.Permission{permissions.CreatePost, permissions.FullContents})})
41 | }
42 |
--------------------------------------------------------------------------------
/ui/src/lib/Login.svelte:
--------------------------------------------------------------------------------
1 |
29 |
30 |
31 |
42 |
43 |
45 |
46 |
--------------------------------------------------------------------------------
/middlewares/zap_logger.go:
--------------------------------------------------------------------------------
1 | package middlewares
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "github.com/labstack/echo/v4"
8 | "go.uber.org/zap"
9 | "go.uber.org/zap/zapcore"
10 | )
11 |
12 | func ZapLogger(log *zap.Logger) echo.MiddlewareFunc {
13 | return func(next echo.HandlerFunc) echo.HandlerFunc {
14 | return func(c echo.Context) error {
15 | start := time.Now()
16 |
17 | err := next(c)
18 | if err != nil {
19 | c.Error(err)
20 | }
21 |
22 | req := c.Request()
23 | res := c.Response()
24 |
25 | fields := []zapcore.Field{
26 | zap.String("remote_ip", c.RealIP()),
27 | zap.String("latency", time.Since(start).String()),
28 | zap.String("host", req.Host),
29 | zap.String("request", fmt.Sprintf("%s %s", req.Method, req.RequestURI)),
30 | zap.Int("status", res.Status),
31 | zap.Int64("size", res.Size),
32 | zap.String("user_agent", req.UserAgent()),
33 | }
34 |
35 | id := req.Header.Get(echo.HeaderXRequestID)
36 | if id == "" {
37 | id = res.Header().Get(echo.HeaderXRequestID)
38 | }
39 | fields = append(fields, zap.String("request_id", id))
40 |
41 | n := res.Status
42 | switch {
43 | case n >= 500:
44 | log.With(zap.Error(err)).Error("Server error", fields...)
45 | case n >= 400:
46 | log.With(zap.Error(err)).Warn("Client error", fields...)
47 | case n >= 300:
48 | log.Info("Redirection", fields...)
49 | default:
50 | log.Info("Success", fields...)
51 | }
52 |
53 | return nil
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/models/permissions/permissions.go:
--------------------------------------------------------------------------------
1 | package permissions
2 |
3 | import (
4 | "strconv"
5 | "strings"
6 | )
7 |
8 | // Any user has permission to delete or edit their own posts or categories, or approve comments on their own posts.
9 | // Post permissions are not related to category permissions in any way.
10 | type Permission uint8
11 |
12 | func (perm Permission) String() string {
13 | return strconv.FormatUint(uint64(perm), 10)
14 | }
15 |
16 | const (
17 | // Full access means this user can do anything with other users, their posts, and categories.
18 | FullAccess Permission = 0
19 |
20 | CreateRole Permission = 1
21 | UpdateRole Permission = 2
22 | DeleteRole Permission = 3
23 |
24 | // Permission 4 to 20 are reserved for future use.
25 |
26 | // Full content access means this user can make or delete any post or category or comment, but can't do anything with other users.
27 | FullContents Permission = 20
28 |
29 | CreatePost Permission = 21
30 | // Users with this permission can edit others posts.
31 | EditPost Permission = 22
32 | // Users with this permission can delete others posts.
33 | DeletePost Permission = 23
34 | CreateCategory Permission = 24
35 | EditCategory Permission = 25
36 | DeleteCategory Permission = 26
37 | ApproveComment Permission = 27
38 | )
39 |
40 | func Compress(permissions []Permission) string {
41 | var compressed string
42 | for _, permission := range permissions {
43 | compressed += permission.String() + ":"
44 | }
45 | return compressed[:len(compressed)-1]
46 | }
47 |
48 | func Decompress(compressed string) []Permission {
49 | var perms []Permission
50 | for _, perm := range strings.Split(compressed, ":") {
51 | if perm != "" {
52 | uintPerm, _ := strconv.ParseUint(perm, 10, 8)
53 | perms = append(perms, Permission(uintPerm))
54 | }
55 | }
56 | return perms
57 | }
58 |
--------------------------------------------------------------------------------
/controllers/comments.go:
--------------------------------------------------------------------------------
1 | // comments.go
2 | package controllers
3 |
4 | import (
5 | "net/http"
6 |
7 | "github.com/arshamalh/blogo/databases"
8 | "github.com/arshamalh/blogo/log"
9 | "github.com/arshamalh/blogo/models"
10 | "github.com/arshamalh/blogo/tools"
11 | "github.com/labstack/echo/v4"
12 | "go.uber.org/zap"
13 | )
14 |
15 | // commentController defines the controller for managing comments.
16 | type commentController struct {
17 | db databases.Database
18 | }
19 |
20 | // NewCommentController creates a new instance of commentController.
21 | func NewCommentController(db databases.Database) *commentController {
22 | return &commentController{
23 | db: db,
24 | }
25 | }
26 |
27 | // @Summary Create a new comment
28 | // @Description Create a new comment
29 | // @ID create-comment
30 | // @Accept json
31 | // @Produce json
32 | // @Param comment body models.Comment true "Comment object"
33 | // @Success 201 {object} map[string]any
34 | // @Failure 400 {object} map[string]any
35 | // @Failure 401 {object} map[string]any
36 | // @Failure 500 {object} map[string]any
37 | // @Router /comments [post]
38 | func (cc *commentController) CreateComment(ctx echo.Context) error {
39 | var comment models.Comment
40 | userID, err := tools.ExtractUserID(ctx)
41 | if err != nil {
42 | log.Gl.Error(err.Error())
43 | return ctx.JSON(http.StatusUnauthorized, echo.Map{"error": "there is a problem with your user"})
44 | }
45 |
46 | if err := ctx.Bind(&comment); err != nil {
47 | log.Gl.Error(err.Error())
48 | return ctx.JSON(http.StatusBadRequest, echo.Map{"error": "unable to parse comment"})
49 | }
50 |
51 | comment.UserID = userID
52 |
53 | if err := cc.db.AddComment(&comment); err != nil {
54 | log.Gl.Error(err.Error())
55 | return ctx.JSON(http.StatusInternalServerError, echo.Map{"error": "unable to add comment"})
56 | }
57 |
58 | response := map[string]interface{}{
59 | "comment": comment,
60 | "author_id": userID,
61 | "comment_id": comment.ID,
62 | }
63 | log.Gl.Info("Comment created", zap.Any("response", response))
64 | return ctx.JSON(http.StatusCreated, response)
65 | }
66 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/arshamalh/blogo
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/golang-jwt/jwt v3.2.2+incompatible
7 | github.com/joho/godotenv v1.4.0
8 | github.com/swaggo/echo-swagger v1.4.1
9 | github.com/swaggo/swag v1.16.2
10 | go.uber.org/zap v1.24.0
11 | golang.org/x/crypto v0.15.0
12 | gorm.io/driver/postgres v1.3.7
13 | gorm.io/gorm v1.23.5
14 | )
15 |
16 | require (
17 | github.com/KyleBanks/depth v1.2.1 // indirect
18 | github.com/ghodss/yaml v1.0.0 // indirect
19 | github.com/go-openapi/jsonpointer v0.20.0 // indirect
20 | github.com/go-openapi/jsonreference v0.20.2 // indirect
21 | github.com/go-openapi/spec v0.20.9 // indirect
22 | github.com/go-openapi/swag v0.22.4 // indirect
23 | github.com/josharian/intern v1.0.0 // indirect
24 | github.com/labstack/gommon v0.4.0 // indirect
25 | github.com/mailru/easyjson v0.7.7 // indirect
26 | github.com/mattn/go-colorable v0.1.13 // indirect
27 | github.com/rogpeppe/go-internal v1.11.0 // indirect
28 | github.com/swaggo/files/v2 v2.0.0 // indirect
29 | github.com/valyala/bytebufferpool v1.0.0 // indirect
30 | github.com/valyala/fasttemplate v1.2.2 // indirect
31 | go.uber.org/atomic v1.11.0 // indirect
32 | go.uber.org/multierr v1.11.0 // indirect
33 | golang.org/x/time v0.3.0 // indirect
34 | golang.org/x/tools v0.15.0 // indirect
35 | gopkg.in/yaml.v2 v2.4.0 // indirect
36 | gopkg.in/yaml.v3 v3.0.1 // indirect
37 | )
38 |
39 | require (
40 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect
41 | github.com/jackc/pgconn v1.12.1 // indirect
42 | github.com/jackc/pgio v1.0.0 // indirect
43 | github.com/jackc/pgpassfile v1.0.0 // indirect
44 | github.com/jackc/pgproto3/v2 v2.3.0 // indirect
45 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
46 | github.com/jackc/pgtype v1.11.0 // indirect
47 | github.com/jackc/pgx/v4 v4.16.1 // indirect
48 | github.com/jinzhu/inflection v1.0.0 // indirect
49 | github.com/jinzhu/now v1.1.4 // indirect
50 | github.com/labstack/echo/v4 v4.10.2
51 | github.com/mattn/go-isatty v0.0.17 // indirect
52 | golang.org/x/net v0.18.0 // indirect
53 | golang.org/x/sys v0.14.0 // indirect
54 | golang.org/x/text v0.14.0 // indirect
55 | )
56 |
--------------------------------------------------------------------------------
/controllers/categories.go:
--------------------------------------------------------------------------------
1 | // category_controller.go
2 | package controllers
3 |
4 | import (
5 | "net/http"
6 |
7 | "github.com/arshamalh/blogo/databases"
8 | "github.com/arshamalh/blogo/log"
9 | "github.com/arshamalh/blogo/models"
10 | "github.com/labstack/echo/v4"
11 | "go.uber.org/zap"
12 | )
13 |
14 | type categoryController struct {
15 | basicAttributes
16 | }
17 |
18 | func NewCategoryController(db databases.Database, logger *zap.Logger) *categoryController {
19 | return &categoryController{
20 | basicAttributes: basicAttributes{
21 | db: db,
22 | logger: logger,
23 | },
24 | }
25 | }
26 |
27 | // @Summary Create a new category
28 | // @Description Create a new category
29 | // @ID create-category
30 | // @Accept json
31 | // @Produce json
32 | // @Param category body models.Category true "Category object"
33 | // @Success 201 {object} map[string]any
34 | // @Failure 400 {object} map[string]any
35 | // @Router /categories [post]
36 | func (cc *categoryController) CreateCategory(ctx echo.Context) error {
37 | var category models.Category
38 | if err := ctx.Bind(&category); err != nil {
39 | log.Gl.Error(err.Error())
40 | return ctx.JSON(http.StatusBadRequest, echo.Map{"status": "invalid request"})
41 | } else if cc.db.CheckCategoryExists(category.Name) {
42 | return ctx.JSON(http.StatusConflict, echo.Map{"status": "category already exists"})
43 | } else {
44 | _, err := cc.db.CreateCategory(&category)
45 | if err != nil {
46 | log.Gl.Error(err.Error())
47 | return ctx.JSON(http.StatusConflict, echo.Map{"status": "cannot create category"})
48 | }
49 | return ctx.JSON(http.StatusCreated, echo.Map{"status": "category created"})
50 | }
51 | }
52 |
53 | // @Summary Get a category by name
54 | // @Description Get category details by name
55 | // @ID get-category
56 | // @Param name path string true "Category Name"
57 | // @Produce json
58 | // @Success 200 {object} models.Category
59 | // @Failure 404 {object} map[string]any
60 | // @Router /categories/{name} [get]
61 | func (cc *categoryController) GetCategory(ctx echo.Context) error {
62 | category, err := cc.db.GetCategory(ctx.Param("name"))
63 | if err != nil || category.ID == 0 {
64 | return ctx.JSON(http.StatusNotFound, echo.Map{"status": "category not found"})
65 | }
66 | log.Gl.Info("Category retrieved", zap.String("category_name", category.Name))
67 | return ctx.JSON(http.StatusOK, category)
68 | }
69 |
70 | // @Summary Get all categories
71 | // @Description Get a list of all categories
72 | // @ID get-all-categories
73 | // @Produce json
74 | // @Success 200 {array} models.Category
75 | // @Failure 404 {object} map[string]any
76 | // @Router /categories [get]
77 | func (cc *categoryController) GetCategories(ctx echo.Context) error {
78 | categories, err := cc.db.GetCategories()
79 | if err != nil {
80 | log.Gl.Error(err.Error())
81 | return ctx.JSON(http.StatusNotFound, echo.Map{"status": "categories not found"})
82 | }
83 |
84 | return ctx.JSON(http.StatusOK, categories)
85 | }
86 |
--------------------------------------------------------------------------------
/ui/README.md:
--------------------------------------------------------------------------------
1 | # Svelte + Vite
2 |
3 | This template should help get you started developing with Svelte in Vite.
4 |
5 | ## Recommended IDE Setup
6 |
7 | [VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
8 |
9 | ## Need an official Svelte framework?
10 |
11 | Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
12 |
13 | ## Technical considerations
14 |
15 | **Why use this over SvelteKit?**
16 |
17 | - It brings its own routing solution which might not be preferable for some users.
18 | - It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
19 | `vite dev` and `vite build` wouldn't work in a SvelteKit environment, for example.
20 |
21 | This template contains as little as possible to get started with Vite + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
22 |
23 | Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
24 |
25 | **Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
26 |
27 | Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
28 |
29 | **Why include `.vscode/extensions.json`?**
30 |
31 | Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
32 |
33 | **Why enable `checkJs` in the JS template?**
34 |
35 | It is likely that most cases of changing variable types in runtime are likely to be accidental, rather than deliberate. This provides advanced typechecking out of the box. Should you like to take advantage of the dynamically-typed nature of JavaScript, it is trivial to change the configuration.
36 |
37 | **Why is HMR not preserving my local component state?**
38 |
39 | HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
40 |
41 | If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
42 |
43 | ```js
44 | // store.js
45 | // An extremely simple external store
46 | import { writable } from 'svelte/store'
47 | export default writable(0)
48 | ```
49 |
--------------------------------------------------------------------------------
/routes/routes.go:
--------------------------------------------------------------------------------
1 | package routes
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/arshamalh/blogo/controllers"
7 | "github.com/arshamalh/blogo/databases"
8 | "github.com/arshamalh/blogo/middlewares"
9 | "go.uber.org/zap"
10 |
11 | _ "github.com/arshamalh/blogo/docs"
12 | "github.com/arshamalh/blogo/models/permissions"
13 | "github.com/labstack/echo/v4"
14 | "github.com/labstack/echo/v4/middleware"
15 | echoSwagger "github.com/swaggo/echo-swagger"
16 | )
17 |
18 | func InitializeRoutes(router *echo.Echo, db databases.Database, logger *zap.Logger) {
19 | // Basic configurations and middleware.
20 | router.Use(middleware.CORSWithConfig(middleware.CORSConfig{
21 | AllowOrigins: []string{"*"},
22 | }))
23 |
24 | router.Use(middlewares.ZapLogger(logger))
25 |
26 | router.GET("/health", func(c echo.Context) error {
27 | return c.JSON(http.StatusNoContent, nil)
28 | })
29 |
30 | router.GET("/docs/*", echoSwagger.WrapHandler)
31 |
32 | /// ** Routes
33 |
34 | user_routes := router.Group("/api/v1/users")
35 | uc := controllers.NewUserController(db, logger)
36 | {
37 | user_routes.POST("/register", uc.UserRegister)
38 | user_routes.POST("/login", uc.UserLogin)
39 | user_routes.POST("/logout", uc.UserLogout, middlewares.RequireLogin)
40 | user_routes.GET("/check_username", uc.CheckUsername)
41 | user_routes.GET("/id", uc.UserID, middlewares.RequireLogin)
42 | // Get & Update Profile and more
43 | // user_routes.GET("/info/:id", controllers.UserInfo)
44 | }
45 |
46 | post_routes := router.Group("api/v1/posts")
47 | pc := controllers.NewPostController(db, logger)
48 | {
49 | post_routes.POST("/", pc.CreatePost, middlewares.RequireLogin, middlewares.CheckPermissions(db, permissions.CreatePost))
50 | post_routes.GET("/:id", pc.GetPost)
51 | post_routes.GET("/", pc.GetPosts)
52 | post_routes.PATCH("/:id", pc.UpdatePost, middlewares.RequireLogin, middlewares.CheckPermissions(db, permissions.EditPost))
53 | post_routes.DELETE("/:id", pc.DeletePost, middlewares.RequireLogin, middlewares.CheckPermissions(db, permissions.DeletePost))
54 | }
55 |
56 | comment_routes := router.Group("api/v1/comments")
57 | comment_controller := controllers.NewCommentController(db)
58 | {
59 | comment_routes.POST("/", comment_controller.CreateComment, middlewares.RequireLogin)
60 | }
61 |
62 | category_routes := router.Group("api/v1/categories")
63 | cc := controllers.NewCategoryController(db, logger)
64 | {
65 | category_routes.POST("/", cc.CreateCategory, middlewares.RequireLogin, middlewares.CheckPermissions(db, permissions.CreateCategory))
66 | category_routes.GET("/:id", cc.GetCategory)
67 | category_routes.GET("/", cc.GetCategories)
68 | // category_routes.PATCH("/:id", middlewares.RequireLogin, middlewares.CheckPermissions(db, permissions.UpdateCategory), controllers.UpdateCategory)
69 | // category_routes.DELETE("/:id", middlewares.RequireLogin, middlewares.CheckPermissions(db, permissions.DeleteCategory), controllers.DeleteCategory)
70 | }
71 |
72 | role_routes := router.Group("api/v1/roles")
73 | rc := controllers.NewRoleController(db, logger)
74 | {
75 | role_routes.POST("/", rc.CreateRole, middlewares.RequireLogin, middlewares.CheckPermissions(db, permissions.CreateRole))
76 | role_routes.PATCH("/", rc.UpdateRole, middlewares.RequireLogin, middlewares.CheckPermissions(db, permissions.UpdateRole))
77 | role_routes.GET("/:id", rc.GetRole)
78 | role_routes.GET("/", rc.GetRoles)
79 | role_routes.DELETE("/:id", rc.DeleteRole, middlewares.RequireLogin, middlewares.CheckPermissions(db, permissions.DeleteRole))
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/docs/swagger.yaml:
--------------------------------------------------------------------------------
1 | basePath: /api/v1
2 | definitions:
3 | models.Category:
4 | type: object
5 | models.Comment:
6 | type: object
7 | host: localhost:8080
8 | info:
9 | contact:
10 | email: arshamalh.github.io/
11 | description: A simple blog for educational purposes
12 | title: Blogo API server
13 | version: "1.0"
14 | paths:
15 | /categories:
16 | get:
17 | description: Get a list of all categories
18 | operationId: get-all-categories
19 | produces:
20 | - application/json
21 | responses:
22 | "200":
23 | description: OK
24 | schema:
25 | items:
26 | $ref: '#/definitions/models.Category'
27 | type: array
28 | "404":
29 | description: Not Found
30 | schema:
31 | additionalProperties: true
32 | type: object
33 | summary: Get all categories
34 | post:
35 | consumes:
36 | - application/json
37 | description: Create a new category
38 | operationId: create-category
39 | parameters:
40 | - description: Category object
41 | in: body
42 | name: category
43 | required: true
44 | schema:
45 | $ref: '#/definitions/models.Category'
46 | produces:
47 | - application/json
48 | responses:
49 | "201":
50 | description: Created
51 | schema:
52 | additionalProperties: true
53 | type: object
54 | "400":
55 | description: Bad Request
56 | schema:
57 | additionalProperties: true
58 | type: object
59 | summary: Create a new category
60 | /categories/{name}:
61 | get:
62 | description: Get category details by name
63 | operationId: get-category
64 | parameters:
65 | - description: Category Name
66 | in: path
67 | name: name
68 | required: true
69 | type: string
70 | produces:
71 | - application/json
72 | responses:
73 | "200":
74 | description: OK
75 | schema:
76 | $ref: '#/definitions/models.Category'
77 | "404":
78 | description: Not Found
79 | schema:
80 | additionalProperties: true
81 | type: object
82 | summary: Get a category by name
83 | /comments:
84 | post:
85 | consumes:
86 | - application/json
87 | description: Create a new comment
88 | operationId: create-comment
89 | parameters:
90 | - description: Comment object
91 | in: body
92 | name: comment
93 | required: true
94 | schema:
95 | $ref: '#/definitions/models.Comment'
96 | produces:
97 | - application/json
98 | responses:
99 | "201":
100 | description: Created
101 | schema:
102 | additionalProperties: true
103 | type: object
104 | "400":
105 | description: Bad Request
106 | schema:
107 | additionalProperties: true
108 | type: object
109 | "401":
110 | description: Unauthorized
111 | schema:
112 | additionalProperties: true
113 | type: object
114 | "500":
115 | description: Internal Server Error
116 | schema:
117 | additionalProperties: true
118 | type: object
119 | summary: Create a new comment
120 | /users/register:
121 | post:
122 | consumes:
123 | - application/json
124 | description: Register a user
125 | parameters:
126 | - description: Username
127 | in: body
128 | name: Username
129 | required: true
130 | schema:
131 | type: string
132 | - description: Password
133 | in: body
134 | name: Password
135 | required: true
136 | schema:
137 | type: string
138 | - description: Email
139 | in: body
140 | name: Email
141 | required: true
142 | schema:
143 | type: string
144 | - description: FirstName
145 | in: body
146 | name: FirstName
147 | schema:
148 | type: string
149 | - description: LastName
150 | in: body
151 | name: LastName
152 | schema:
153 | type: string
154 | produces:
155 | - application/json
156 | responses:
157 | "201":
158 | description: Created
159 | schema:
160 | additionalProperties: true
161 | type: object
162 | "400":
163 | description: Bad Request
164 | schema:
165 | additionalProperties: true
166 | type: object
167 | "409":
168 | description: Conflict
169 | schema:
170 | additionalProperties: true
171 | type: object
172 | summary: Register a user
173 | swagger: "2.0"
174 |
--------------------------------------------------------------------------------
/controllers/roles.go:
--------------------------------------------------------------------------------
1 | // roles.go
2 | package controllers
3 |
4 | import (
5 | "net/http"
6 | "strconv"
7 |
8 | "github.com/arshamalh/blogo/databases"
9 | "github.com/arshamalh/blogo/log"
10 | "github.com/arshamalh/blogo/models"
11 | "github.com/labstack/echo/v4"
12 | "go.uber.org/zap"
13 | )
14 |
15 | // roleController handles HTTP requests related to user roles.
16 | type roleController struct {
17 | basicAttributes
18 | }
19 |
20 | // NewRoleController creates a new instance of roleController.
21 | func NewRoleController(db databases.Database, logger *zap.Logger) *roleController {
22 | return &roleController{
23 | basicAttributes: basicAttributes{
24 | db: db,
25 | logger: logger,
26 | },
27 | }
28 | }
29 |
30 | // CreateRole creates a new user role.
31 | // swagger:route POST /roles CreateRole
32 | // Creates a new user role.
33 | // responses:
34 | //
35 | // 201: map[string]interface{} "Role created"
36 | // 400: map[string]interface{} "Invalid request"
37 | // 500: map[string]interface{} "Internal server error"
38 | func (rc *roleController) CreateRole(ctx echo.Context) error {
39 | var role models.Role
40 | if err := ctx.Bind(&role); err != nil {
41 | log.Gl.Error(err.Error())
42 | return ctx.JSON(http.StatusBadRequest, echo.Map{"status": "invalid request"})
43 | }
44 | if err := rc.db.CreateRole(&role); err != nil {
45 | log.Gl.Error(err.Error())
46 | return ctx.JSON(http.StatusInternalServerError, echo.Map{"error": err.Error()})
47 | }
48 | log.Gl.Info("Role created", zap.String("role_name", role.Name))
49 | return ctx.JSON(http.StatusCreated, echo.Map{"role": role})
50 | }
51 |
52 | // UpdateRole updates an existing user role.
53 | // swagger:route PUT /roles UpdateRole
54 | // Updates an existing user role.
55 | // responses:
56 | //
57 | // 200: map[string]interface{} "Role updated"
58 | // 400: map[string]interface{} "Invalid request"
59 | // 500: map[string]interface{} "Internal server error"
60 | func (rc *roleController) UpdateRole(ctx echo.Context) error {
61 | var role models.Role
62 | if err := ctx.Bind(&role); err != nil {
63 | log.Gl.Error(err.Error())
64 | return ctx.JSON(http.StatusBadRequest, echo.Map{"status": "invalid request"})
65 | }
66 | if err := rc.db.UpdateRole(&role); err != nil {
67 | log.Gl.Error(err.Error())
68 | return ctx.JSON(http.StatusInternalServerError, echo.Map{"error": err.Error()})
69 | }
70 | log.Gl.Info("Role updated", zap.String("role_name", role.Name))
71 | return ctx.JSON(http.StatusOK, echo.Map{"role": role})
72 | }
73 |
74 | // DeleteRole deletes an existing user role.
75 | // swagger:route DELETE /roles/{id} DeleteRole
76 | // Deletes an existing user role.
77 | // parameters:
78 | // - name: id
79 | // in: path
80 | // description: The ID of the role to delete.
81 | // required: true
82 | // type: integer
83 | //
84 | // responses:
85 | //
86 | // 200: map[string]interface{} "Role deleted"
87 | // 500: map[string]interface{} "Internal server error"
88 | func (rc *roleController) DeleteRole(ctx echo.Context) error {
89 | id, _ := strconv.ParseUint(ctx.Param("id"), 10, 64)
90 | if err := rc.db.DeleteRole(uint(id)); err != nil {
91 | log.Gl.Error(err.Error())
92 | return ctx.JSON(http.StatusInternalServerError, echo.Map{"error": err.Error()})
93 | }
94 | log.Gl.Info("Role deleted", zap.Uint64("role_id", id))
95 | return ctx.JSON(http.StatusOK, echo.Map{"message": "Role deleted"})
96 | }
97 |
98 | // GetRole retrieves a user role by ID.
99 | // swagger:route GET /roles/{id} GetRole
100 | // Retrieves a user role by ID.
101 | // parameters:
102 | // - name: id
103 | // in: path
104 | // description: The ID of the role to retrieve.
105 | // required: true
106 | // type: integer
107 | //
108 | // responses:
109 | //
110 | // 200: map[string]interface{} "Role retrieved"
111 | // 404: map[string]interface{} "Role not found"
112 | // 500: map[string]interface{} "Internal server error"
113 | func (rc *roleController) GetRole(ctx echo.Context) error {
114 | id, _ := strconv.ParseUint(ctx.Param("id"), 10, 64)
115 | role, err := rc.db.GetRole(uint(id))
116 | if err != nil {
117 | log.Gl.Error(err.Error())
118 | return ctx.JSON(http.StatusInternalServerError, echo.Map{"error": err.Error()})
119 | }
120 | log.Gl.Info("Role retrieved", zap.Uint64("role_id", id))
121 | return ctx.JSON(http.StatusOK, echo.Map{"role": role})
122 | }
123 |
124 | // GetRoles retrieves all user roles.
125 | // swagger:route GET /roles GetRoles
126 | // Retrieves all user roles.
127 | // responses:
128 | //
129 | // 200: map[string]interface{} "Roles retrieved"
130 | // 500: map[string]interface{} "Internal server error"
131 | func (rc *roleController) GetRoles(ctx echo.Context) error {
132 | roles, err := rc.db.GetRoles()
133 | if err != nil {
134 | log.Gl.Error(err.Error())
135 | return ctx.JSON(http.StatusInternalServerError, echo.Map{"error": err.Error()})
136 | }
137 | log.Gl.Info("Roles retrieved", zap.Int("total_roles", len(roles)))
138 | return ctx.JSON(http.StatusOK, echo.Map{"roles": roles})
139 | }
140 |
--------------------------------------------------------------------------------
/controllers/users.go:
--------------------------------------------------------------------------------
1 | // user.go
2 | package controllers
3 |
4 | import (
5 | "net/http"
6 | "time"
7 |
8 | "github.com/arshamalh/blogo/databases"
9 | "github.com/arshamalh/blogo/log"
10 | "github.com/arshamalh/blogo/models"
11 | "github.com/arshamalh/blogo/session"
12 | "github.com/labstack/echo/v4"
13 | "go.uber.org/zap"
14 | )
15 |
16 | // userController handles HTTP requests related to user management
17 | type userController struct {
18 | basicAttributes
19 | }
20 |
21 | // NewUserController creates a new instance of userController.
22 | func NewUserController(db databases.Database, logger *zap.Logger) *userController {
23 | return &userController{
24 | basicAttributes: basicAttributes{
25 | db: db,
26 | logger: logger,
27 | },
28 | }
29 | }
30 |
31 | // UserRegisterRequest represents the request format for user registration.
32 | // swagger:parameters UserRegister
33 | type UserRegisterRequest struct {
34 | Username string `form:"username" json:"username" binding:"required"`
35 | Password string `form:"password" json:"password" binding:"required"`
36 | Email string `form:"email" json:"email" binding:"required"`
37 | FirstName string `form:"first_name" json:"first_name"`
38 | LastName string `form:"last_name" json:"last_name"`
39 | }
40 |
41 | // UserLoginRequest represents the request format for user login.
42 | // swagger:parameters UserLogin
43 | type UserLoginRequest struct {
44 | Username string `form:"username" json:"username" binding:"required"`
45 | Password string `form:"password" json:"password" binding:"required"`
46 | }
47 |
48 | // ShowAccount godoc
49 | // @Summary Register a user
50 | // @Description Register a user
51 | // @Accept json
52 | // @Produce json
53 | // @Param Username body string true "Username"
54 | // @Param Password body string true "Password"
55 | // @Param Email body string true "Email"
56 | // @Param FirstName body string false "FirstName"
57 | // @Param LastName body string false "LastName"
58 | // @Success 201 {object} map[string]any
59 | // @Failure 400 {object} map[string]any
60 | // @Failure 409 {object} map[string]any
61 | // @Router /users/register [post]
62 | func (uc *userController) UserRegister(ctx echo.Context) error {
63 | var user UserRegisterRequest
64 | if ctx.Bind(&user) != nil {
65 | return ctx.JSON(http.StatusBadRequest, echo.Map{"message": "invalid request"})
66 | }
67 | if uc.db.CheckUserExists(user.Username) {
68 | return ctx.JSON(http.StatusConflict, echo.Map{"message": "user already exists"})
69 | }
70 | new_user := models.User{
71 | Username: user.Username,
72 | Email: user.Email,
73 | FirstName: user.FirstName,
74 | LastName: user.LastName,
75 | RoleID: 3,
76 | }
77 | if err := new_user.SetPassword(user.Password); err != nil {
78 | return ctx.JSON(http.StatusInternalServerError, echo.Map{"message": "cannot register you"})
79 | }
80 | uid, err := uc.db.CreateUser(&new_user)
81 | if err != nil {
82 | log.Gl.Error(err.Error())
83 | return ctx.JSON(http.StatusConflict, echo.Map{"message": "Failed to create user"})
84 | }
85 | log.Gl.Info("User created", zap.String("username", new_user.Username))
86 | return ctx.JSON(http.StatusCreated, echo.Map{"message": "user created", "uid": uid})
87 | }
88 |
89 | // CheckUsername checks the availability of a username.
90 | // swagger:route GET /users/check-username CheckUsername
91 | // Checks the availability of a username.
92 | // responses:
93 | //
94 | // 200: map[string]interface{} "Username available"
95 | // 400: map[string]interface{} "Invalid request"
96 | // 409: map[string]interface{} "Username already taken"
97 | func (uc *userController) UserLogin(ctx echo.Context) error {
98 | // Decode the body of request
99 | var user UserLoginRequest
100 | if ctx.Bind(&user) != nil {
101 | return ctx.JSON(http.StatusBadRequest, echo.Map{"message": "invalid request"})
102 | }
103 |
104 | // Check if user exists
105 | dbUser, err := uc.db.GetUserByUsername(user.Username)
106 | if err != nil {
107 | log.Gl.Error(err.Error())
108 | return ctx.JSON(http.StatusInternalServerError, echo.Map{"message": "error getting user"})
109 | }
110 |
111 | // Check if password is correct
112 | if dbUser.ComparePasswords(user.Password) != nil {
113 | return ctx.JSON(http.StatusUnauthorized, echo.Map{"message": "wrong password"})
114 | }
115 |
116 | // Store username in the session
117 | sn := session.Create(dbUser.ID)
118 |
119 | // Generate access token and refresh token
120 | return ctx.JSON(http.StatusOK, echo.Map{"message": "login success", "session": sn})
121 | }
122 |
123 | func (uc *userController) CheckUsername(ctx echo.Context) error {
124 | var username string
125 | if ctx.Bind(&username) != nil {
126 | return ctx.JSON(http.StatusBadRequest, echo.Map{"message": "invalid request"})
127 | } else if uc.db.CheckUserExists(username) {
128 | return ctx.JSON(http.StatusConflict, echo.Map{"message": "username is already taken"})
129 | } else {
130 | return ctx.JSON(http.StatusOK, echo.Map{"message": "username available"})
131 | }
132 | }
133 |
134 | // UserLogout logs out a user.
135 | // swagger:route POST /users/logout UserLogout
136 | // Logs out a user.
137 | // responses:
138 | //
139 | // 200: map[string]interface{} "Logout success"
140 | func (uc *userController) UserLogout(ctx echo.Context) error {
141 | ctx.SetCookie(&http.Cookie{
142 | Name: "access_token",
143 | Path: "/",
144 | Expires: time.Now(),
145 | })
146 | return ctx.JSON(http.StatusOK, echo.Map{"message": "logout success"})
147 | }
148 |
149 | // UserID retrieves the ID of the logged-in user.
150 | // swagger:route GET /users/user-id UserID
151 | // Retrieves the ID of the logged-in user.
152 | // responses:
153 | //
154 | // 200: map[string]interface{} "User ID retrieved"
155 | func (uc *userController) UserID(ctx echo.Context) error {
156 | return ctx.JSON(http.StatusOK, echo.Map{"user_id": ctx.Get("user_id")})
157 | }
158 |
--------------------------------------------------------------------------------
/controllers/posts.go:
--------------------------------------------------------------------------------
1 | // posts.go
2 | package controllers
3 |
4 | import (
5 | "net/http"
6 | "strconv"
7 |
8 | "github.com/arshamalh/blogo/databases"
9 | "github.com/arshamalh/blogo/log"
10 | "github.com/arshamalh/blogo/models"
11 | "github.com/arshamalh/blogo/tools"
12 | "github.com/labstack/echo/v4"
13 | "go.uber.org/zap"
14 | )
15 |
16 | // PostRequest represents the request structure for creating a post.
17 | // swagger:parameters CreatePost
18 | type PostRequest struct {
19 | Title string `form:"title" json:"title" binding:"required"`
20 | Content string `form:"content" json:"content" binding:"required"`
21 | Categories []string `form:"categories" json:"categories"`
22 | }
23 |
24 | // postController handles HTTP requests related to blog posts.
25 | type postController struct {
26 | basicAttributes
27 | }
28 |
29 | // NewPostController creates a new instance of postController.
30 | func NewPostController(db databases.Database, logger *zap.Logger) *postController {
31 | return &postController{
32 | basicAttributes: basicAttributes{
33 | db: db,
34 | logger: logger,
35 | },
36 | }
37 | }
38 |
39 | // CreatePost creates a new blog post.
40 | // swagger:route POST /posts CreatePost
41 | // Creates a new blog post.
42 | // responses:
43 | //
44 | // 201: map[string]interface{} "Post created successfully"
45 | // 400: map[string]interface{} "Invalid request"
46 | // 401: map[string]interface{} "Unauthorized"
47 | // 409: map[string]interface{} "Cannot make the post"
48 | // 500: map[string]interface{} "Internal server error"
49 | func (pc *postController) CreatePost(ctx echo.Context) error {
50 | var post PostRequest
51 | userID, _ := tools.ExtractUserID(ctx)
52 | if err := ctx.Bind(&post); err != nil {
53 | log.Gl.Error(err.Error())
54 | return ctx.JSON(500, echo.Map{"error": err.Error()})
55 | }
56 |
57 | categories := []models.Category{}
58 | for _, category := range post.Categories {
59 | categories = append(categories, models.Category{Name: category})
60 | }
61 |
62 | newPost := models.Post{
63 | Title: post.Title,
64 | Content: post.Content,
65 | AuthorID: userID,
66 | Categories: categories,
67 | }
68 | postID, err := pc.db.CreatePost(&newPost)
69 | if err != nil {
70 | log.Gl.Error(err.Error())
71 | return ctx.JSON(http.StatusConflict, echo.Map{"message": "cannot make the post"})
72 | }
73 |
74 | log.Gl.Info("Post created", zap.Any("post_id", postID))
75 | return ctx.JSON(http.StatusCreated, echo.Map{
76 | "message": "Post created successfully",
77 | "post_id": postID,
78 | })
79 | }
80 |
81 | // DeletePost deletes a blog post.
82 | // swagger:route DELETE /posts/{id} DeletePost
83 | // Deletes a blog post.
84 | // parameters:
85 | // - name: id
86 | // in: path
87 | // description: The ID of the post to delete.
88 | // required: true
89 | // type: integer
90 | //
91 | // responses:
92 | //
93 | // 200: map[string]interface{} "Post deleted"
94 | // 401: map[string]interface{} "Unauthorized"
95 | // 500: map[string]interface{} "Internal server error"
96 | func (pc *postController) DeletePost(ctx echo.Context) error {
97 | userID, _ := tools.ExtractUserID(ctx)
98 | postID, _ := strconv.ParseUint(ctx.Param("id"), 10, 64)
99 |
100 | // If the user doesn't have the permission to delete posts, check if it's their own post.
101 | if !tools.ExtractPermissable(ctx) {
102 | post, _ := pc.db.GetPost(uint(postID))
103 | if post.AuthorID != userID {
104 | return ctx.JSON(http.StatusUnauthorized, echo.Map{
105 | "message": "you don't have enough permissions",
106 | })
107 | }
108 | }
109 |
110 | if err := pc.db.DeletePost(uint(postID)); err != nil {
111 | log.Gl.Error(err.Error())
112 | return ctx.JSON(http.StatusInternalServerError, echo.Map{"error": err.Error()})
113 | }
114 | log.Gl.Info("Post deleted", zap.Uint("post_id", uint(postID)))
115 | return ctx.JSON(http.StatusOK, echo.Map{"message": "Post deleted"})
116 | }
117 |
118 | // UpdatePost updates a blog post.
119 | // swagger:route PUT /posts/{id} UpdatePost
120 | // Updates a blog post.
121 | // parameters:
122 | // - name: id
123 | // in: path
124 | // description: The ID of the post to update.
125 | // required: true
126 | // type: integer
127 | //
128 | // responses:
129 | //
130 | // 200: map[string]interface{} "Post updated"
131 | // 400: map[string]interface{} "Invalid request"
132 | // 500: map[string]interface{} "Internal server error"
133 | func (pc *postController) UpdatePost(ctx echo.Context) error {
134 | var post PostRequest
135 | if err := ctx.Bind(&post); err != nil {
136 | log.Gl.Error(err.Error())
137 | return ctx.JSON(http.StatusBadRequest, echo.Map{"status": "invalid request"})
138 | }
139 |
140 | // Make a new updated post
141 | categories := []models.Category{}
142 | for _, category := range post.Categories {
143 | categories = append(categories, models.Category{Name: category})
144 | }
145 | newPost := models.Post{
146 | Title: post.Title,
147 | Content: post.Content,
148 | Categories: categories,
149 | }
150 | postID, _ := strconv.ParseUint(ctx.Param("id"), 10, 64)
151 | newPost.ID = uint(postID)
152 |
153 | // Update post
154 | if err := pc.db.UpdatePost(&newPost); err != nil {
155 | log.Gl.Error(err.Error())
156 | return ctx.JSON(500, echo.Map{"error": err.Error()})
157 | }
158 | log.Gl.Info("Post updated", zap.Uint("post_id", uint(postID)))
159 | return ctx.JSON(200, echo.Map{"post": newPost})
160 | }
161 |
162 | // GetPost retrieves a blog post by ID.
163 | // swagger:route GET /posts/{id} GetPost
164 | // Retrieves a blog post by ID.
165 | // parameters:
166 | // - name: id
167 | // in: path
168 | // description: The ID of the post to retrieve.
169 | // required: true
170 | // type: integer
171 | //
172 | // responses:
173 | //
174 | // 200: map[string]interface{} "Post retrieved"
175 | // 404: map[string]interface{} "Post not found"
176 | // 500: map[string]interface{} "Internal server error"
177 | func (pc *postController) GetPost(ctx echo.Context) error {
178 | postID, _ := strconv.ParseUint(ctx.Param("id"), 10, 64)
179 | post, _ := pc.db.GetPost(uint(postID))
180 | log.Gl.Info("Post retrieved", zap.Uint("post_id", uint(postID)))
181 | return ctx.JSON(200, echo.Map{"post": post})
182 | }
183 |
184 | // GetPosts retrieves all blog posts.
185 | // swagger:route GET /posts GetPosts
186 | // Retrieves all blog posts.
187 | // responses:
188 | //
189 | // 200: map[string]interface{} "Posts retrieved"
190 | // 404: map[string]interface{} "Posts not found"
191 | // 500: map[string]interface{} "Internal server error"
192 | func (pc *postController) GetPosts(ctx echo.Context) error {
193 | posts, _ := pc.db.GetPosts()
194 | return ctx.JSON(200, echo.Map{"posts": posts})
195 | }
196 |
--------------------------------------------------------------------------------
/docs/swagger.json:
--------------------------------------------------------------------------------
1 | {
2 | "swagger": "2.0",
3 | "info": {
4 | "description": "A simple blog for educational purposes",
5 | "title": "Blogo API server",
6 | "contact": {
7 | "email": "arshamalh.github.io/"
8 | },
9 | "version": "1.0"
10 | },
11 | "host": "localhost:8080",
12 | "basePath": "/api/v1",
13 | "paths": {
14 | "/categories": {
15 | "get": {
16 | "description": "Get a list of all categories",
17 | "produces": [
18 | "application/json"
19 | ],
20 | "summary": "Get all categories",
21 | "operationId": "get-all-categories",
22 | "responses": {
23 | "200": {
24 | "description": "OK",
25 | "schema": {
26 | "type": "array",
27 | "items": {
28 | "$ref": "#/definitions/models.Category"
29 | }
30 | }
31 | },
32 | "404": {
33 | "description": "Not Found",
34 | "schema": {
35 | "type": "object",
36 | "additionalProperties": true
37 | }
38 | }
39 | }
40 | },
41 | "post": {
42 | "description": "Create a new category",
43 | "consumes": [
44 | "application/json"
45 | ],
46 | "produces": [
47 | "application/json"
48 | ],
49 | "summary": "Create a new category",
50 | "operationId": "create-category",
51 | "parameters": [
52 | {
53 | "description": "Category object",
54 | "name": "category",
55 | "in": "body",
56 | "required": true,
57 | "schema": {
58 | "$ref": "#/definitions/models.Category"
59 | }
60 | }
61 | ],
62 | "responses": {
63 | "201": {
64 | "description": "Created",
65 | "schema": {
66 | "type": "object",
67 | "additionalProperties": true
68 | }
69 | },
70 | "400": {
71 | "description": "Bad Request",
72 | "schema": {
73 | "type": "object",
74 | "additionalProperties": true
75 | }
76 | }
77 | }
78 | }
79 | },
80 | "/categories/{name}": {
81 | "get": {
82 | "description": "Get category details by name",
83 | "produces": [
84 | "application/json"
85 | ],
86 | "summary": "Get a category by name",
87 | "operationId": "get-category",
88 | "parameters": [
89 | {
90 | "type": "string",
91 | "description": "Category Name",
92 | "name": "name",
93 | "in": "path",
94 | "required": true
95 | }
96 | ],
97 | "responses": {
98 | "200": {
99 | "description": "OK",
100 | "schema": {
101 | "$ref": "#/definitions/models.Category"
102 | }
103 | },
104 | "404": {
105 | "description": "Not Found",
106 | "schema": {
107 | "type": "object",
108 | "additionalProperties": true
109 | }
110 | }
111 | }
112 | }
113 | },
114 | "/comments": {
115 | "post": {
116 | "description": "Create a new comment",
117 | "consumes": [
118 | "application/json"
119 | ],
120 | "produces": [
121 | "application/json"
122 | ],
123 | "summary": "Create a new comment",
124 | "operationId": "create-comment",
125 | "parameters": [
126 | {
127 | "description": "Comment object",
128 | "name": "comment",
129 | "in": "body",
130 | "required": true,
131 | "schema": {
132 | "$ref": "#/definitions/models.Comment"
133 | }
134 | }
135 | ],
136 | "responses": {
137 | "201": {
138 | "description": "Created",
139 | "schema": {
140 | "type": "object",
141 | "additionalProperties": true
142 | }
143 | },
144 | "400": {
145 | "description": "Bad Request",
146 | "schema": {
147 | "type": "object",
148 | "additionalProperties": true
149 | }
150 | },
151 | "401": {
152 | "description": "Unauthorized",
153 | "schema": {
154 | "type": "object",
155 | "additionalProperties": true
156 | }
157 | },
158 | "500": {
159 | "description": "Internal Server Error",
160 | "schema": {
161 | "type": "object",
162 | "additionalProperties": true
163 | }
164 | }
165 | }
166 | }
167 | },
168 | "/users/register": {
169 | "post": {
170 | "description": "Register a user",
171 | "consumes": [
172 | "application/json"
173 | ],
174 | "produces": [
175 | "application/json"
176 | ],
177 | "summary": "Register a user",
178 | "parameters": [
179 | {
180 | "description": "Username",
181 | "name": "Username",
182 | "in": "body",
183 | "required": true,
184 | "schema": {
185 | "type": "string"
186 | }
187 | },
188 | {
189 | "description": "Password",
190 | "name": "Password",
191 | "in": "body",
192 | "required": true,
193 | "schema": {
194 | "type": "string"
195 | }
196 | },
197 | {
198 | "description": "Email",
199 | "name": "Email",
200 | "in": "body",
201 | "required": true,
202 | "schema": {
203 | "type": "string"
204 | }
205 | },
206 | {
207 | "description": "FirstName",
208 | "name": "FirstName",
209 | "in": "body",
210 | "schema": {
211 | "type": "string"
212 | }
213 | },
214 | {
215 | "description": "LastName",
216 | "name": "LastName",
217 | "in": "body",
218 | "schema": {
219 | "type": "string"
220 | }
221 | }
222 | ],
223 | "responses": {
224 | "201": {
225 | "description": "Created",
226 | "schema": {
227 | "type": "object",
228 | "additionalProperties": true
229 | }
230 | },
231 | "400": {
232 | "description": "Bad Request",
233 | "schema": {
234 | "type": "object",
235 | "additionalProperties": true
236 | }
237 | },
238 | "409": {
239 | "description": "Conflict",
240 | "schema": {
241 | "type": "object",
242 | "additionalProperties": true
243 | }
244 | }
245 | }
246 | }
247 | }
248 | },
249 | "definitions": {
250 | "models.Category": {
251 | "type": "object"
252 | },
253 | "models.Comment": {
254 | "type": "object"
255 | }
256 | }
257 | }
--------------------------------------------------------------------------------
/docs/docs.go:
--------------------------------------------------------------------------------
1 | // Package docs Code generated by swaggo/swag. DO NOT EDIT
2 | package docs
3 |
4 | import "github.com/swaggo/swag"
5 |
6 | const docTemplate = `{
7 | "schemes": {{ marshal .Schemes }},
8 | "swagger": "2.0",
9 | "info": {
10 | "description": "{{escape .Description}}",
11 | "title": "{{.Title}}",
12 | "contact": {
13 | "email": "arshamalh.github.io/"
14 | },
15 | "version": "{{.Version}}"
16 | },
17 | "host": "{{.Host}}",
18 | "basePath": "{{.BasePath}}",
19 | "paths": {
20 | "/categories": {
21 | "get": {
22 | "description": "Get a list of all categories",
23 | "produces": [
24 | "application/json"
25 | ],
26 | "summary": "Get all categories",
27 | "operationId": "get-all-categories",
28 | "responses": {
29 | "200": {
30 | "description": "OK",
31 | "schema": {
32 | "type": "array",
33 | "items": {
34 | "$ref": "#/definitions/models.Category"
35 | }
36 | }
37 | },
38 | "404": {
39 | "description": "Not Found",
40 | "schema": {
41 | "type": "object",
42 | "additionalProperties": true
43 | }
44 | }
45 | }
46 | },
47 | "post": {
48 | "description": "Create a new category",
49 | "consumes": [
50 | "application/json"
51 | ],
52 | "produces": [
53 | "application/json"
54 | ],
55 | "summary": "Create a new category",
56 | "operationId": "create-category",
57 | "parameters": [
58 | {
59 | "description": "Category object",
60 | "name": "category",
61 | "in": "body",
62 | "required": true,
63 | "schema": {
64 | "$ref": "#/definitions/models.Category"
65 | }
66 | }
67 | ],
68 | "responses": {
69 | "201": {
70 | "description": "Created",
71 | "schema": {
72 | "type": "object",
73 | "additionalProperties": true
74 | }
75 | },
76 | "400": {
77 | "description": "Bad Request",
78 | "schema": {
79 | "type": "object",
80 | "additionalProperties": true
81 | }
82 | }
83 | }
84 | }
85 | },
86 | "/categories/{name}": {
87 | "get": {
88 | "description": "Get category details by name",
89 | "produces": [
90 | "application/json"
91 | ],
92 | "summary": "Get a category by name",
93 | "operationId": "get-category",
94 | "parameters": [
95 | {
96 | "type": "string",
97 | "description": "Category Name",
98 | "name": "name",
99 | "in": "path",
100 | "required": true
101 | }
102 | ],
103 | "responses": {
104 | "200": {
105 | "description": "OK",
106 | "schema": {
107 | "$ref": "#/definitions/models.Category"
108 | }
109 | },
110 | "404": {
111 | "description": "Not Found",
112 | "schema": {
113 | "type": "object",
114 | "additionalProperties": true
115 | }
116 | }
117 | }
118 | }
119 | },
120 | "/comments": {
121 | "post": {
122 | "description": "Create a new comment",
123 | "consumes": [
124 | "application/json"
125 | ],
126 | "produces": [
127 | "application/json"
128 | ],
129 | "summary": "Create a new comment",
130 | "operationId": "create-comment",
131 | "parameters": [
132 | {
133 | "description": "Comment object",
134 | "name": "comment",
135 | "in": "body",
136 | "required": true,
137 | "schema": {
138 | "$ref": "#/definitions/models.Comment"
139 | }
140 | }
141 | ],
142 | "responses": {
143 | "201": {
144 | "description": "Created",
145 | "schema": {
146 | "type": "object",
147 | "additionalProperties": true
148 | }
149 | },
150 | "400": {
151 | "description": "Bad Request",
152 | "schema": {
153 | "type": "object",
154 | "additionalProperties": true
155 | }
156 | },
157 | "401": {
158 | "description": "Unauthorized",
159 | "schema": {
160 | "type": "object",
161 | "additionalProperties": true
162 | }
163 | },
164 | "500": {
165 | "description": "Internal Server Error",
166 | "schema": {
167 | "type": "object",
168 | "additionalProperties": true
169 | }
170 | }
171 | }
172 | }
173 | },
174 | "/users/register": {
175 | "post": {
176 | "description": "Register a user",
177 | "consumes": [
178 | "application/json"
179 | ],
180 | "produces": [
181 | "application/json"
182 | ],
183 | "summary": "Register a user",
184 | "parameters": [
185 | {
186 | "description": "Username",
187 | "name": "Username",
188 | "in": "body",
189 | "required": true,
190 | "schema": {
191 | "type": "string"
192 | }
193 | },
194 | {
195 | "description": "Password",
196 | "name": "Password",
197 | "in": "body",
198 | "required": true,
199 | "schema": {
200 | "type": "string"
201 | }
202 | },
203 | {
204 | "description": "Email",
205 | "name": "Email",
206 | "in": "body",
207 | "required": true,
208 | "schema": {
209 | "type": "string"
210 | }
211 | },
212 | {
213 | "description": "FirstName",
214 | "name": "FirstName",
215 | "in": "body",
216 | "schema": {
217 | "type": "string"
218 | }
219 | },
220 | {
221 | "description": "LastName",
222 | "name": "LastName",
223 | "in": "body",
224 | "schema": {
225 | "type": "string"
226 | }
227 | }
228 | ],
229 | "responses": {
230 | "201": {
231 | "description": "Created",
232 | "schema": {
233 | "type": "object",
234 | "additionalProperties": true
235 | }
236 | },
237 | "400": {
238 | "description": "Bad Request",
239 | "schema": {
240 | "type": "object",
241 | "additionalProperties": true
242 | }
243 | },
244 | "409": {
245 | "description": "Conflict",
246 | "schema": {
247 | "type": "object",
248 | "additionalProperties": true
249 | }
250 | }
251 | }
252 | }
253 | }
254 | },
255 | "definitions": {
256 | "models.Category": {
257 | "type": "object"
258 | },
259 | "models.Comment": {
260 | "type": "object"
261 | }
262 | }
263 | }`
264 |
265 | // SwaggerInfo holds exported Swagger Info so clients can modify it
266 | var SwaggerInfo = &swag.Spec{
267 | Version: "1.0",
268 | Host: "localhost:8080",
269 | BasePath: "/api/v1",
270 | Schemes: []string{},
271 | Title: "Blogo API server",
272 | Description: "A simple blog for educational purposes",
273 | InfoInstanceName: "swagger",
274 | SwaggerTemplate: docTemplate,
275 | LeftDelim: "{{",
276 | RightDelim: "}}",
277 | }
278 |
279 | func init() {
280 | swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
281 | }
282 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2 | github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
3 | github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
4 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
5 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
6 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
7 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
8 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
9 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
10 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
11 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
12 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
13 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
14 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
16 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
17 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
18 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
19 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
20 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
21 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
22 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
23 | github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ=
24 | github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA=
25 | github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
26 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
27 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
28 | github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8=
29 | github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
30 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
31 | github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
32 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
33 | github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
34 | github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
35 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
36 | github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
37 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
38 | github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
39 | github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
40 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
41 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
42 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
43 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
44 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
45 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
46 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
47 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
48 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
49 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
50 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
51 | github.com/jackc/pgconn v1.12.1 h1:rsDFzIpRk7xT4B8FufgpCCeyjdNpKyghZeSefViE5W8=
52 | github.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono=
53 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
54 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
55 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
56 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
57 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
58 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
59 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
60 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
61 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
62 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
63 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
64 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
65 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
66 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
67 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
68 | github.com/jackc/pgproto3/v2 v2.3.0 h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL8Y=
69 | github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
70 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
71 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
72 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
73 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
74 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
75 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
76 | github.com/jackc/pgtype v1.11.0 h1:u4uiGPz/1hryuXzyaBhSk6dnIyyG2683olG2OV+UUgs=
77 | github.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
78 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
79 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
80 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
81 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
82 | github.com/jackc/pgx/v4 v4.16.1 h1:JzTglcal01DrghUqt+PmzWsZx/Yh7SC/CTQmSBMTd0Y=
83 | github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ=
84 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
85 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
86 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
87 | github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
88 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
89 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
90 | github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
91 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
92 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
93 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
94 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
95 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
96 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
97 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
98 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
99 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
100 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
101 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
102 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
103 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
104 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
105 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
106 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
107 | github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
108 | github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
109 | github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
110 | github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
111 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
112 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
113 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
114 | github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
115 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
116 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
117 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
118 | github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
119 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
120 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
121 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
122 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
123 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
124 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
125 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
126 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
127 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
128 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
129 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
130 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
131 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
132 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
133 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
134 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
135 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
136 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
137 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
138 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
139 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
140 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
141 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
142 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
143 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
144 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
145 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
146 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
147 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
148 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
149 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
150 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
151 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
152 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
153 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
154 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
155 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
156 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
157 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
158 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
159 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
160 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
161 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
162 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
163 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
164 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
165 | github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk=
166 | github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc=
167 | github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=
168 | github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
169 | github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04=
170 | github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E=
171 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
172 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
173 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
174 | github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
175 | github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
176 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
177 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
178 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
179 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
180 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
181 | go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
182 | go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
183 | go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
184 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
185 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
186 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
187 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
188 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
189 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
190 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
191 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
192 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
193 | go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
194 | go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
195 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
196 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
197 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
198 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
199 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
200 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
201 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
202 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
203 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
204 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
205 | golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
206 | golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
207 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
208 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
209 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
210 | golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
211 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
212 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
213 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
214 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
215 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
216 | golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
217 | golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
218 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
219 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
220 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
221 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
222 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
223 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
224 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
225 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
226 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
227 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
228 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
229 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
230 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
231 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
232 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
233 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
234 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
235 | golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
236 | golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
237 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
238 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
239 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
240 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
241 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
242 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
243 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
244 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
245 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
246 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
247 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
248 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
249 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
250 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
251 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
252 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
253 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
254 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
255 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
256 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
257 | golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
258 | golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
259 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
260 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
261 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
262 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
263 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
264 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
265 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
266 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
267 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
268 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
269 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
270 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
271 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
272 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
273 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
274 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
275 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
276 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
277 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
278 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
279 | gorm.io/driver/postgres v1.3.7 h1:FKF6sIMDHDEvvMF/XJvbnCl0nu6KSKUaPXevJ4r+VYQ=
280 | gorm.io/driver/postgres v1.3.7/go.mod h1:f02ympjIcgtHEGFMZvdgTxODZ9snAHDb4hXfigBVuNI=
281 | gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
282 | gorm.io/gorm v1.23.5 h1:TnlF26wScKSvknUC/Rn8t0NLLM22fypYBlvj1+aH6dM=
283 | gorm.io/gorm v1.23.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
284 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
285 |
--------------------------------------------------------------------------------
/ui/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ui",
3 | "version": "0.0.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "ui",
9 | "version": "0.0.0",
10 | "devDependencies": {
11 | "@sveltejs/vite-plugin-svelte": "^1.0.0-next.30",
12 | "svelte": "^3.44.0",
13 | "vite": "^2.9.9"
14 | }
15 | },
16 | "node_modules/@rollup/pluginutils": {
17 | "version": "4.2.1",
18 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
19 | "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
20 | "dev": true,
21 | "dependencies": {
22 | "estree-walker": "^2.0.1",
23 | "picomatch": "^2.2.2"
24 | },
25 | "engines": {
26 | "node": ">= 8.0.0"
27 | }
28 | },
29 | "node_modules/@sveltejs/vite-plugin-svelte": {
30 | "version": "1.0.0-next.49",
31 | "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.49.tgz",
32 | "integrity": "sha512-AKh0Ka8EDgidnxWUs8Hh2iZLZovkETkefO99XxZ4sW4WGJ7VFeBx5kH/NIIGlaNHLcrIvK3CK0HkZwC3Cici0A==",
33 | "dev": true,
34 | "dependencies": {
35 | "@rollup/pluginutils": "^4.2.1",
36 | "debug": "^4.3.4",
37 | "deepmerge": "^4.2.2",
38 | "kleur": "^4.1.4",
39 | "magic-string": "^0.26.2",
40 | "svelte-hmr": "^0.14.12"
41 | },
42 | "engines": {
43 | "node": "^14.13.1 || >= 16"
44 | },
45 | "peerDependencies": {
46 | "diff-match-patch": "^1.0.5",
47 | "svelte": "^3.44.0",
48 | "vite": "^2.9.0"
49 | },
50 | "peerDependenciesMeta": {
51 | "diff-match-patch": {
52 | "optional": true
53 | }
54 | }
55 | },
56 | "node_modules/debug": {
57 | "version": "4.3.4",
58 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
59 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
60 | "dev": true,
61 | "dependencies": {
62 | "ms": "2.1.2"
63 | },
64 | "engines": {
65 | "node": ">=6.0"
66 | },
67 | "peerDependenciesMeta": {
68 | "supports-color": {
69 | "optional": true
70 | }
71 | }
72 | },
73 | "node_modules/deepmerge": {
74 | "version": "4.2.2",
75 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
76 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
77 | "dev": true,
78 | "engines": {
79 | "node": ">=0.10.0"
80 | }
81 | },
82 | "node_modules/esbuild": {
83 | "version": "0.14.43",
84 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.43.tgz",
85 | "integrity": "sha512-Uf94+kQmy/5jsFwKWiQB4hfo/RkM9Dh7b79p8yqd1tshULdr25G2szLz631NoH3s2ujnKEKVD16RmOxvCNKRFA==",
86 | "dev": true,
87 | "hasInstallScript": true,
88 | "bin": {
89 | "esbuild": "bin/esbuild"
90 | },
91 | "engines": {
92 | "node": ">=12"
93 | },
94 | "optionalDependencies": {
95 | "esbuild-android-64": "0.14.43",
96 | "esbuild-android-arm64": "0.14.43",
97 | "esbuild-darwin-64": "0.14.43",
98 | "esbuild-darwin-arm64": "0.14.43",
99 | "esbuild-freebsd-64": "0.14.43",
100 | "esbuild-freebsd-arm64": "0.14.43",
101 | "esbuild-linux-32": "0.14.43",
102 | "esbuild-linux-64": "0.14.43",
103 | "esbuild-linux-arm": "0.14.43",
104 | "esbuild-linux-arm64": "0.14.43",
105 | "esbuild-linux-mips64le": "0.14.43",
106 | "esbuild-linux-ppc64le": "0.14.43",
107 | "esbuild-linux-riscv64": "0.14.43",
108 | "esbuild-linux-s390x": "0.14.43",
109 | "esbuild-netbsd-64": "0.14.43",
110 | "esbuild-openbsd-64": "0.14.43",
111 | "esbuild-sunos-64": "0.14.43",
112 | "esbuild-windows-32": "0.14.43",
113 | "esbuild-windows-64": "0.14.43",
114 | "esbuild-windows-arm64": "0.14.43"
115 | }
116 | },
117 | "node_modules/esbuild-android-64": {
118 | "version": "0.14.43",
119 | "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.43.tgz",
120 | "integrity": "sha512-kqFXAS72K6cNrB6RiM7YJ5lNvmWRDSlpi7ZuRZ1hu1S3w0zlwcoCxWAyM23LQUyZSs1PbjHgdbbfYAN8IGh6xg==",
121 | "cpu": [
122 | "x64"
123 | ],
124 | "dev": true,
125 | "optional": true,
126 | "os": [
127 | "android"
128 | ],
129 | "engines": {
130 | "node": ">=12"
131 | }
132 | },
133 | "node_modules/esbuild-android-arm64": {
134 | "version": "0.14.43",
135 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.43.tgz",
136 | "integrity": "sha512-bKS2BBFh+7XZY9rpjiHGRNA7LvWYbZWP87pLehggTG7tTaCDvj8qQGOU/OZSjCSKDYbgY7Q+oDw8RlYQ2Jt2BA==",
137 | "cpu": [
138 | "arm64"
139 | ],
140 | "dev": true,
141 | "optional": true,
142 | "os": [
143 | "android"
144 | ],
145 | "engines": {
146 | "node": ">=12"
147 | }
148 | },
149 | "node_modules/esbuild-darwin-64": {
150 | "version": "0.14.43",
151 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.43.tgz",
152 | "integrity": "sha512-/3PSilx011ttoieRGkSZ0XV8zjBf2C9enV4ScMMbCT4dpx0mFhMOpFnCHkOK0pWGB8LklykFyHrWk2z6DENVUg==",
153 | "cpu": [
154 | "x64"
155 | ],
156 | "dev": true,
157 | "optional": true,
158 | "os": [
159 | "darwin"
160 | ],
161 | "engines": {
162 | "node": ">=12"
163 | }
164 | },
165 | "node_modules/esbuild-darwin-arm64": {
166 | "version": "0.14.43",
167 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.43.tgz",
168 | "integrity": "sha512-1HyFUKs8DMCBOvw1Qxpr5Vv/ThNcVIFb5xgXWK3pyT40WPvgYIiRTwJCvNs4l8i5qWF8/CK5bQxJVDjQvtv0Yw==",
169 | "cpu": [
170 | "arm64"
171 | ],
172 | "dev": true,
173 | "optional": true,
174 | "os": [
175 | "darwin"
176 | ],
177 | "engines": {
178 | "node": ">=12"
179 | }
180 | },
181 | "node_modules/esbuild-freebsd-64": {
182 | "version": "0.14.43",
183 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.43.tgz",
184 | "integrity": "sha512-FNWc05TPHYgaXjbPZO5/rJKSBslfG6BeMSs8GhwnqAKP56eEhvmzwnIz1QcC9cRVyO+IKqWNfmHFkCa1WJTULA==",
185 | "cpu": [
186 | "x64"
187 | ],
188 | "dev": true,
189 | "optional": true,
190 | "os": [
191 | "freebsd"
192 | ],
193 | "engines": {
194 | "node": ">=12"
195 | }
196 | },
197 | "node_modules/esbuild-freebsd-arm64": {
198 | "version": "0.14.43",
199 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.43.tgz",
200 | "integrity": "sha512-amrYopclz3VohqisOPR6hA3GOWA3LZC1WDLnp21RhNmoERmJ/vLnOpnrG2P/Zao+/erKTCUqmrCIPVtj58DRoA==",
201 | "cpu": [
202 | "arm64"
203 | ],
204 | "dev": true,
205 | "optional": true,
206 | "os": [
207 | "freebsd"
208 | ],
209 | "engines": {
210 | "node": ">=12"
211 | }
212 | },
213 | "node_modules/esbuild-linux-32": {
214 | "version": "0.14.43",
215 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.43.tgz",
216 | "integrity": "sha512-KoxoEra+9O3AKVvgDFvDkiuddCds6q71owSQEYwjtqRV7RwbPzKxJa6+uyzUulHcyGVq0g15K0oKG5CFBcvYDw==",
217 | "cpu": [
218 | "ia32"
219 | ],
220 | "dev": true,
221 | "optional": true,
222 | "os": [
223 | "linux"
224 | ],
225 | "engines": {
226 | "node": ">=12"
227 | }
228 | },
229 | "node_modules/esbuild-linux-64": {
230 | "version": "0.14.43",
231 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.43.tgz",
232 | "integrity": "sha512-EwINwGMyiJMgBby5/SbMqKcUhS5AYAZ2CpEBzSowsJPNBJEdhkCTtEjk757TN/wxgbu3QklqDM6KghY660QCUw==",
233 | "cpu": [
234 | "x64"
235 | ],
236 | "dev": true,
237 | "optional": true,
238 | "os": [
239 | "linux"
240 | ],
241 | "engines": {
242 | "node": ">=12"
243 | }
244 | },
245 | "node_modules/esbuild-linux-arm": {
246 | "version": "0.14.43",
247 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.43.tgz",
248 | "integrity": "sha512-e6YzQUoDxxtyamuF12eVzzRC7bbEFSZohJ6igQB9tBqnNmIQY3fI6Cns3z2wxtbZ3f2o6idkD2fQnlvs2902Dg==",
249 | "cpu": [
250 | "arm"
251 | ],
252 | "dev": true,
253 | "optional": true,
254 | "os": [
255 | "linux"
256 | ],
257 | "engines": {
258 | "node": ">=12"
259 | }
260 | },
261 | "node_modules/esbuild-linux-arm64": {
262 | "version": "0.14.43",
263 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.43.tgz",
264 | "integrity": "sha512-UlSpjMWllAc70zYbHxWuDS3FJytyuR/gHJYBr8BICcTNb/TSOYVBg6U7b3jZ3mILTrgzwJUHwhEwK18FZDouUQ==",
265 | "cpu": [
266 | "arm64"
267 | ],
268 | "dev": true,
269 | "optional": true,
270 | "os": [
271 | "linux"
272 | ],
273 | "engines": {
274 | "node": ">=12"
275 | }
276 | },
277 | "node_modules/esbuild-linux-mips64le": {
278 | "version": "0.14.43",
279 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.43.tgz",
280 | "integrity": "sha512-f+v8cInPEL1/SDP//CfSYzcDNgE4CY3xgDV81DWm3KAPWzhvxARrKxB1Pstf5mB56yAslJDxu7ryBUPX207EZA==",
281 | "cpu": [
282 | "mips64el"
283 | ],
284 | "dev": true,
285 | "optional": true,
286 | "os": [
287 | "linux"
288 | ],
289 | "engines": {
290 | "node": ">=12"
291 | }
292 | },
293 | "node_modules/esbuild-linux-ppc64le": {
294 | "version": "0.14.43",
295 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.43.tgz",
296 | "integrity": "sha512-5wZYMDGAL/K2pqkdIsW+I4IR41kyfHr/QshJcNpUfK3RjB3VQcPWOaZmc+74rm4ZjVirYrtz+jWw0SgxtxRanA==",
297 | "cpu": [
298 | "ppc64"
299 | ],
300 | "dev": true,
301 | "optional": true,
302 | "os": [
303 | "linux"
304 | ],
305 | "engines": {
306 | "node": ">=12"
307 | }
308 | },
309 | "node_modules/esbuild-linux-riscv64": {
310 | "version": "0.14.43",
311 | "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.43.tgz",
312 | "integrity": "sha512-lYcAOUxp85hC7lSjycJUVSmj4/9oEfSyXjb/ua9bNl8afonaduuqtw7hvKMoKuYnVwOCDw4RSfKpcnIRDWq+Bw==",
313 | "cpu": [
314 | "riscv64"
315 | ],
316 | "dev": true,
317 | "optional": true,
318 | "os": [
319 | "linux"
320 | ],
321 | "engines": {
322 | "node": ">=12"
323 | }
324 | },
325 | "node_modules/esbuild-linux-s390x": {
326 | "version": "0.14.43",
327 | "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.43.tgz",
328 | "integrity": "sha512-27e43ZhHvhFE4nM7HqtUbMRu37I/4eNSUbb8FGZWszV+uLzMIsHDwLoBiJmw7G9N+hrehNPeQ4F5Ujad0DrUKQ==",
329 | "cpu": [
330 | "s390x"
331 | ],
332 | "dev": true,
333 | "optional": true,
334 | "os": [
335 | "linux"
336 | ],
337 | "engines": {
338 | "node": ">=12"
339 | }
340 | },
341 | "node_modules/esbuild-netbsd-64": {
342 | "version": "0.14.43",
343 | "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.43.tgz",
344 | "integrity": "sha512-2mH4QF6hHBn5zzAfxEI/2eBC0mspVsZ6UVo821LpAJKMvLJPBk3XJO5xwg7paDqSqpl7p6IRrAenW999AEfJhQ==",
345 | "cpu": [
346 | "x64"
347 | ],
348 | "dev": true,
349 | "optional": true,
350 | "os": [
351 | "netbsd"
352 | ],
353 | "engines": {
354 | "node": ">=12"
355 | }
356 | },
357 | "node_modules/esbuild-openbsd-64": {
358 | "version": "0.14.43",
359 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.43.tgz",
360 | "integrity": "sha512-ZhQpiZjvqCqO8jKdGp9+8k9E/EHSA+zIWOg+grwZasI9RoblqJ1QiZqqi7jfd6ZrrG1UFBNGe4m0NFxCFbMVbg==",
361 | "cpu": [
362 | "x64"
363 | ],
364 | "dev": true,
365 | "optional": true,
366 | "os": [
367 | "openbsd"
368 | ],
369 | "engines": {
370 | "node": ">=12"
371 | }
372 | },
373 | "node_modules/esbuild-sunos-64": {
374 | "version": "0.14.43",
375 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.43.tgz",
376 | "integrity": "sha512-DgxSi9DaHReL9gYuul2rrQCAapgnCJkh3LSHPKsY26zytYppG0HgkgVF80zjIlvEsUbGBP/GHQzBtrezj/Zq1Q==",
377 | "cpu": [
378 | "x64"
379 | ],
380 | "dev": true,
381 | "optional": true,
382 | "os": [
383 | "sunos"
384 | ],
385 | "engines": {
386 | "node": ">=12"
387 | }
388 | },
389 | "node_modules/esbuild-windows-32": {
390 | "version": "0.14.43",
391 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.43.tgz",
392 | "integrity": "sha512-Ih3+2O5oExiqm0mY6YYE5dR0o8+AspccQ3vIAtRodwFvhuyGLjb0Hbmzun/F3Lw19nuhPMu3sW2fqIJ5xBxByw==",
393 | "cpu": [
394 | "ia32"
395 | ],
396 | "dev": true,
397 | "optional": true,
398 | "os": [
399 | "win32"
400 | ],
401 | "engines": {
402 | "node": ">=12"
403 | }
404 | },
405 | "node_modules/esbuild-windows-64": {
406 | "version": "0.14.43",
407 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.43.tgz",
408 | "integrity": "sha512-8NsuNfI8xwFuJbrCuI+aBqNTYkrWErejFO5aYM+yHqyHuL8mmepLS9EPzAzk8rvfaJrhN0+RvKWAcymViHOKEw==",
409 | "cpu": [
410 | "x64"
411 | ],
412 | "dev": true,
413 | "optional": true,
414 | "os": [
415 | "win32"
416 | ],
417 | "engines": {
418 | "node": ">=12"
419 | }
420 | },
421 | "node_modules/esbuild-windows-arm64": {
422 | "version": "0.14.43",
423 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.43.tgz",
424 | "integrity": "sha512-7ZlD7bo++kVRblJEoG+cepljkfP8bfuTPz5fIXzptwnPaFwGS6ahvfoYzY7WCf5v/1nX2X02HDraVItTgbHnKw==",
425 | "cpu": [
426 | "arm64"
427 | ],
428 | "dev": true,
429 | "optional": true,
430 | "os": [
431 | "win32"
432 | ],
433 | "engines": {
434 | "node": ">=12"
435 | }
436 | },
437 | "node_modules/estree-walker": {
438 | "version": "2.0.2",
439 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
440 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
441 | "dev": true
442 | },
443 | "node_modules/fsevents": {
444 | "version": "2.3.2",
445 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
446 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
447 | "dev": true,
448 | "hasInstallScript": true,
449 | "optional": true,
450 | "os": [
451 | "darwin"
452 | ],
453 | "engines": {
454 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
455 | }
456 | },
457 | "node_modules/function-bind": {
458 | "version": "1.1.1",
459 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
460 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
461 | "dev": true
462 | },
463 | "node_modules/has": {
464 | "version": "1.0.3",
465 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
466 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
467 | "dev": true,
468 | "dependencies": {
469 | "function-bind": "^1.1.1"
470 | },
471 | "engines": {
472 | "node": ">= 0.4.0"
473 | }
474 | },
475 | "node_modules/is-core-module": {
476 | "version": "2.9.0",
477 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
478 | "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
479 | "dev": true,
480 | "dependencies": {
481 | "has": "^1.0.3"
482 | },
483 | "funding": {
484 | "url": "https://github.com/sponsors/ljharb"
485 | }
486 | },
487 | "node_modules/kleur": {
488 | "version": "4.1.4",
489 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz",
490 | "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==",
491 | "dev": true,
492 | "engines": {
493 | "node": ">=6"
494 | }
495 | },
496 | "node_modules/magic-string": {
497 | "version": "0.26.2",
498 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz",
499 | "integrity": "sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==",
500 | "dev": true,
501 | "dependencies": {
502 | "sourcemap-codec": "^1.4.8"
503 | },
504 | "engines": {
505 | "node": ">=12"
506 | }
507 | },
508 | "node_modules/ms": {
509 | "version": "2.1.2",
510 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
511 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
512 | "dev": true
513 | },
514 | "node_modules/nanoid": {
515 | "version": "3.3.4",
516 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
517 | "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
518 | "dev": true,
519 | "bin": {
520 | "nanoid": "bin/nanoid.cjs"
521 | },
522 | "engines": {
523 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
524 | }
525 | },
526 | "node_modules/path-parse": {
527 | "version": "1.0.7",
528 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
529 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
530 | "dev": true
531 | },
532 | "node_modules/picocolors": {
533 | "version": "1.0.0",
534 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
535 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
536 | "dev": true
537 | },
538 | "node_modules/picomatch": {
539 | "version": "2.3.1",
540 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
541 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
542 | "dev": true,
543 | "engines": {
544 | "node": ">=8.6"
545 | },
546 | "funding": {
547 | "url": "https://github.com/sponsors/jonschlinkert"
548 | }
549 | },
550 | "node_modules/postcss": {
551 | "version": "8.4.14",
552 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
553 | "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
554 | "dev": true,
555 | "funding": [
556 | {
557 | "type": "opencollective",
558 | "url": "https://opencollective.com/postcss/"
559 | },
560 | {
561 | "type": "tidelift",
562 | "url": "https://tidelift.com/funding/github/npm/postcss"
563 | }
564 | ],
565 | "dependencies": {
566 | "nanoid": "^3.3.4",
567 | "picocolors": "^1.0.0",
568 | "source-map-js": "^1.0.2"
569 | },
570 | "engines": {
571 | "node": "^10 || ^12 || >=14"
572 | }
573 | },
574 | "node_modules/resolve": {
575 | "version": "1.22.0",
576 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
577 | "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
578 | "dev": true,
579 | "dependencies": {
580 | "is-core-module": "^2.8.1",
581 | "path-parse": "^1.0.7",
582 | "supports-preserve-symlinks-flag": "^1.0.0"
583 | },
584 | "bin": {
585 | "resolve": "bin/resolve"
586 | },
587 | "funding": {
588 | "url": "https://github.com/sponsors/ljharb"
589 | }
590 | },
591 | "node_modules/rollup": {
592 | "version": "2.75.6",
593 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.6.tgz",
594 | "integrity": "sha512-OEf0TgpC9vU6WGROJIk1JA3LR5vk/yvqlzxqdrE2CzzXnqKXNzbAwlWUXis8RS3ZPe7LAq+YUxsRa0l3r27MLA==",
595 | "dev": true,
596 | "bin": {
597 | "rollup": "dist/bin/rollup"
598 | },
599 | "engines": {
600 | "node": ">=10.0.0"
601 | },
602 | "optionalDependencies": {
603 | "fsevents": "~2.3.2"
604 | }
605 | },
606 | "node_modules/source-map-js": {
607 | "version": "1.0.2",
608 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
609 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
610 | "dev": true,
611 | "engines": {
612 | "node": ">=0.10.0"
613 | }
614 | },
615 | "node_modules/sourcemap-codec": {
616 | "version": "1.4.8",
617 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
618 | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
619 | "dev": true
620 | },
621 | "node_modules/supports-preserve-symlinks-flag": {
622 | "version": "1.0.0",
623 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
624 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
625 | "dev": true,
626 | "engines": {
627 | "node": ">= 0.4"
628 | },
629 | "funding": {
630 | "url": "https://github.com/sponsors/ljharb"
631 | }
632 | },
633 | "node_modules/svelte": {
634 | "version": "3.48.0",
635 | "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.48.0.tgz",
636 | "integrity": "sha512-fN2YRm/bGumvjUpu6yI3BpvZnpIm9I6A7HR4oUNYd7ggYyIwSA/BX7DJ+UXXffLp6XNcUijyLvttbPVCYa/3xQ==",
637 | "dev": true,
638 | "engines": {
639 | "node": ">= 8"
640 | }
641 | },
642 | "node_modules/svelte-hmr": {
643 | "version": "0.14.12",
644 | "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.14.12.tgz",
645 | "integrity": "sha512-4QSW/VvXuqVcFZ+RhxiR8/newmwOCTlbYIezvkeN6302YFRE8cXy0naamHcjz8Y9Ce3ITTZtrHrIL0AGfyo61w==",
646 | "dev": true,
647 | "engines": {
648 | "node": "^12.20 || ^14.13.1 || >= 16"
649 | },
650 | "peerDependencies": {
651 | "svelte": ">=3.19.0"
652 | }
653 | },
654 | "node_modules/vite": {
655 | "version": "2.9.12",
656 | "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.12.tgz",
657 | "integrity": "sha512-suxC36dQo9Rq1qMB2qiRorNJtJAdxguu5TMvBHOc/F370KvqAe9t48vYp+/TbPKRNrMh/J55tOUmkuIqstZaew==",
658 | "dev": true,
659 | "dependencies": {
660 | "esbuild": "^0.14.27",
661 | "postcss": "^8.4.13",
662 | "resolve": "^1.22.0",
663 | "rollup": "^2.59.0"
664 | },
665 | "bin": {
666 | "vite": "bin/vite.js"
667 | },
668 | "engines": {
669 | "node": ">=12.2.0"
670 | },
671 | "optionalDependencies": {
672 | "fsevents": "~2.3.2"
673 | },
674 | "peerDependencies": {
675 | "less": "*",
676 | "sass": "*",
677 | "stylus": "*"
678 | },
679 | "peerDependenciesMeta": {
680 | "less": {
681 | "optional": true
682 | },
683 | "sass": {
684 | "optional": true
685 | },
686 | "stylus": {
687 | "optional": true
688 | }
689 | }
690 | }
691 | },
692 | "dependencies": {
693 | "@rollup/pluginutils": {
694 | "version": "4.2.1",
695 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
696 | "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
697 | "dev": true,
698 | "requires": {
699 | "estree-walker": "^2.0.1",
700 | "picomatch": "^2.2.2"
701 | }
702 | },
703 | "@sveltejs/vite-plugin-svelte": {
704 | "version": "1.0.0-next.49",
705 | "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.49.tgz",
706 | "integrity": "sha512-AKh0Ka8EDgidnxWUs8Hh2iZLZovkETkefO99XxZ4sW4WGJ7VFeBx5kH/NIIGlaNHLcrIvK3CK0HkZwC3Cici0A==",
707 | "dev": true,
708 | "requires": {
709 | "@rollup/pluginutils": "^4.2.1",
710 | "debug": "^4.3.4",
711 | "deepmerge": "^4.2.2",
712 | "kleur": "^4.1.4",
713 | "magic-string": "^0.26.2",
714 | "svelte-hmr": "^0.14.12"
715 | }
716 | },
717 | "debug": {
718 | "version": "4.3.4",
719 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
720 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
721 | "dev": true,
722 | "requires": {
723 | "ms": "2.1.2"
724 | }
725 | },
726 | "deepmerge": {
727 | "version": "4.2.2",
728 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
729 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
730 | "dev": true
731 | },
732 | "esbuild": {
733 | "version": "0.14.43",
734 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.43.tgz",
735 | "integrity": "sha512-Uf94+kQmy/5jsFwKWiQB4hfo/RkM9Dh7b79p8yqd1tshULdr25G2szLz631NoH3s2ujnKEKVD16RmOxvCNKRFA==",
736 | "dev": true,
737 | "requires": {
738 | "esbuild-android-64": "0.14.43",
739 | "esbuild-android-arm64": "0.14.43",
740 | "esbuild-darwin-64": "0.14.43",
741 | "esbuild-darwin-arm64": "0.14.43",
742 | "esbuild-freebsd-64": "0.14.43",
743 | "esbuild-freebsd-arm64": "0.14.43",
744 | "esbuild-linux-32": "0.14.43",
745 | "esbuild-linux-64": "0.14.43",
746 | "esbuild-linux-arm": "0.14.43",
747 | "esbuild-linux-arm64": "0.14.43",
748 | "esbuild-linux-mips64le": "0.14.43",
749 | "esbuild-linux-ppc64le": "0.14.43",
750 | "esbuild-linux-riscv64": "0.14.43",
751 | "esbuild-linux-s390x": "0.14.43",
752 | "esbuild-netbsd-64": "0.14.43",
753 | "esbuild-openbsd-64": "0.14.43",
754 | "esbuild-sunos-64": "0.14.43",
755 | "esbuild-windows-32": "0.14.43",
756 | "esbuild-windows-64": "0.14.43",
757 | "esbuild-windows-arm64": "0.14.43"
758 | }
759 | },
760 | "esbuild-android-64": {
761 | "version": "0.14.43",
762 | "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.43.tgz",
763 | "integrity": "sha512-kqFXAS72K6cNrB6RiM7YJ5lNvmWRDSlpi7ZuRZ1hu1S3w0zlwcoCxWAyM23LQUyZSs1PbjHgdbbfYAN8IGh6xg==",
764 | "dev": true,
765 | "optional": true
766 | },
767 | "esbuild-android-arm64": {
768 | "version": "0.14.43",
769 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.43.tgz",
770 | "integrity": "sha512-bKS2BBFh+7XZY9rpjiHGRNA7LvWYbZWP87pLehggTG7tTaCDvj8qQGOU/OZSjCSKDYbgY7Q+oDw8RlYQ2Jt2BA==",
771 | "dev": true,
772 | "optional": true
773 | },
774 | "esbuild-darwin-64": {
775 | "version": "0.14.43",
776 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.43.tgz",
777 | "integrity": "sha512-/3PSilx011ttoieRGkSZ0XV8zjBf2C9enV4ScMMbCT4dpx0mFhMOpFnCHkOK0pWGB8LklykFyHrWk2z6DENVUg==",
778 | "dev": true,
779 | "optional": true
780 | },
781 | "esbuild-darwin-arm64": {
782 | "version": "0.14.43",
783 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.43.tgz",
784 | "integrity": "sha512-1HyFUKs8DMCBOvw1Qxpr5Vv/ThNcVIFb5xgXWK3pyT40WPvgYIiRTwJCvNs4l8i5qWF8/CK5bQxJVDjQvtv0Yw==",
785 | "dev": true,
786 | "optional": true
787 | },
788 | "esbuild-freebsd-64": {
789 | "version": "0.14.43",
790 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.43.tgz",
791 | "integrity": "sha512-FNWc05TPHYgaXjbPZO5/rJKSBslfG6BeMSs8GhwnqAKP56eEhvmzwnIz1QcC9cRVyO+IKqWNfmHFkCa1WJTULA==",
792 | "dev": true,
793 | "optional": true
794 | },
795 | "esbuild-freebsd-arm64": {
796 | "version": "0.14.43",
797 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.43.tgz",
798 | "integrity": "sha512-amrYopclz3VohqisOPR6hA3GOWA3LZC1WDLnp21RhNmoERmJ/vLnOpnrG2P/Zao+/erKTCUqmrCIPVtj58DRoA==",
799 | "dev": true,
800 | "optional": true
801 | },
802 | "esbuild-linux-32": {
803 | "version": "0.14.43",
804 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.43.tgz",
805 | "integrity": "sha512-KoxoEra+9O3AKVvgDFvDkiuddCds6q71owSQEYwjtqRV7RwbPzKxJa6+uyzUulHcyGVq0g15K0oKG5CFBcvYDw==",
806 | "dev": true,
807 | "optional": true
808 | },
809 | "esbuild-linux-64": {
810 | "version": "0.14.43",
811 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.43.tgz",
812 | "integrity": "sha512-EwINwGMyiJMgBby5/SbMqKcUhS5AYAZ2CpEBzSowsJPNBJEdhkCTtEjk757TN/wxgbu3QklqDM6KghY660QCUw==",
813 | "dev": true,
814 | "optional": true
815 | },
816 | "esbuild-linux-arm": {
817 | "version": "0.14.43",
818 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.43.tgz",
819 | "integrity": "sha512-e6YzQUoDxxtyamuF12eVzzRC7bbEFSZohJ6igQB9tBqnNmIQY3fI6Cns3z2wxtbZ3f2o6idkD2fQnlvs2902Dg==",
820 | "dev": true,
821 | "optional": true
822 | },
823 | "esbuild-linux-arm64": {
824 | "version": "0.14.43",
825 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.43.tgz",
826 | "integrity": "sha512-UlSpjMWllAc70zYbHxWuDS3FJytyuR/gHJYBr8BICcTNb/TSOYVBg6U7b3jZ3mILTrgzwJUHwhEwK18FZDouUQ==",
827 | "dev": true,
828 | "optional": true
829 | },
830 | "esbuild-linux-mips64le": {
831 | "version": "0.14.43",
832 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.43.tgz",
833 | "integrity": "sha512-f+v8cInPEL1/SDP//CfSYzcDNgE4CY3xgDV81DWm3KAPWzhvxARrKxB1Pstf5mB56yAslJDxu7ryBUPX207EZA==",
834 | "dev": true,
835 | "optional": true
836 | },
837 | "esbuild-linux-ppc64le": {
838 | "version": "0.14.43",
839 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.43.tgz",
840 | "integrity": "sha512-5wZYMDGAL/K2pqkdIsW+I4IR41kyfHr/QshJcNpUfK3RjB3VQcPWOaZmc+74rm4ZjVirYrtz+jWw0SgxtxRanA==",
841 | "dev": true,
842 | "optional": true
843 | },
844 | "esbuild-linux-riscv64": {
845 | "version": "0.14.43",
846 | "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.43.tgz",
847 | "integrity": "sha512-lYcAOUxp85hC7lSjycJUVSmj4/9oEfSyXjb/ua9bNl8afonaduuqtw7hvKMoKuYnVwOCDw4RSfKpcnIRDWq+Bw==",
848 | "dev": true,
849 | "optional": true
850 | },
851 | "esbuild-linux-s390x": {
852 | "version": "0.14.43",
853 | "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.43.tgz",
854 | "integrity": "sha512-27e43ZhHvhFE4nM7HqtUbMRu37I/4eNSUbb8FGZWszV+uLzMIsHDwLoBiJmw7G9N+hrehNPeQ4F5Ujad0DrUKQ==",
855 | "dev": true,
856 | "optional": true
857 | },
858 | "esbuild-netbsd-64": {
859 | "version": "0.14.43",
860 | "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.43.tgz",
861 | "integrity": "sha512-2mH4QF6hHBn5zzAfxEI/2eBC0mspVsZ6UVo821LpAJKMvLJPBk3XJO5xwg7paDqSqpl7p6IRrAenW999AEfJhQ==",
862 | "dev": true,
863 | "optional": true
864 | },
865 | "esbuild-openbsd-64": {
866 | "version": "0.14.43",
867 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.43.tgz",
868 | "integrity": "sha512-ZhQpiZjvqCqO8jKdGp9+8k9E/EHSA+zIWOg+grwZasI9RoblqJ1QiZqqi7jfd6ZrrG1UFBNGe4m0NFxCFbMVbg==",
869 | "dev": true,
870 | "optional": true
871 | },
872 | "esbuild-sunos-64": {
873 | "version": "0.14.43",
874 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.43.tgz",
875 | "integrity": "sha512-DgxSi9DaHReL9gYuul2rrQCAapgnCJkh3LSHPKsY26zytYppG0HgkgVF80zjIlvEsUbGBP/GHQzBtrezj/Zq1Q==",
876 | "dev": true,
877 | "optional": true
878 | },
879 | "esbuild-windows-32": {
880 | "version": "0.14.43",
881 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.43.tgz",
882 | "integrity": "sha512-Ih3+2O5oExiqm0mY6YYE5dR0o8+AspccQ3vIAtRodwFvhuyGLjb0Hbmzun/F3Lw19nuhPMu3sW2fqIJ5xBxByw==",
883 | "dev": true,
884 | "optional": true
885 | },
886 | "esbuild-windows-64": {
887 | "version": "0.14.43",
888 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.43.tgz",
889 | "integrity": "sha512-8NsuNfI8xwFuJbrCuI+aBqNTYkrWErejFO5aYM+yHqyHuL8mmepLS9EPzAzk8rvfaJrhN0+RvKWAcymViHOKEw==",
890 | "dev": true,
891 | "optional": true
892 | },
893 | "esbuild-windows-arm64": {
894 | "version": "0.14.43",
895 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.43.tgz",
896 | "integrity": "sha512-7ZlD7bo++kVRblJEoG+cepljkfP8bfuTPz5fIXzptwnPaFwGS6ahvfoYzY7WCf5v/1nX2X02HDraVItTgbHnKw==",
897 | "dev": true,
898 | "optional": true
899 | },
900 | "estree-walker": {
901 | "version": "2.0.2",
902 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
903 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
904 | "dev": true
905 | },
906 | "fsevents": {
907 | "version": "2.3.2",
908 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
909 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
910 | "dev": true,
911 | "optional": true
912 | },
913 | "function-bind": {
914 | "version": "1.1.1",
915 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
916 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
917 | "dev": true
918 | },
919 | "has": {
920 | "version": "1.0.3",
921 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
922 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
923 | "dev": true,
924 | "requires": {
925 | "function-bind": "^1.1.1"
926 | }
927 | },
928 | "is-core-module": {
929 | "version": "2.9.0",
930 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
931 | "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
932 | "dev": true,
933 | "requires": {
934 | "has": "^1.0.3"
935 | }
936 | },
937 | "kleur": {
938 | "version": "4.1.4",
939 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz",
940 | "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==",
941 | "dev": true
942 | },
943 | "magic-string": {
944 | "version": "0.26.2",
945 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz",
946 | "integrity": "sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==",
947 | "dev": true,
948 | "requires": {
949 | "sourcemap-codec": "^1.4.8"
950 | }
951 | },
952 | "ms": {
953 | "version": "2.1.2",
954 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
955 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
956 | "dev": true
957 | },
958 | "nanoid": {
959 | "version": "3.3.4",
960 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
961 | "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
962 | "dev": true
963 | },
964 | "path-parse": {
965 | "version": "1.0.7",
966 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
967 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
968 | "dev": true
969 | },
970 | "picocolors": {
971 | "version": "1.0.0",
972 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
973 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
974 | "dev": true
975 | },
976 | "picomatch": {
977 | "version": "2.3.1",
978 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
979 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
980 | "dev": true
981 | },
982 | "postcss": {
983 | "version": "8.4.14",
984 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
985 | "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
986 | "dev": true,
987 | "requires": {
988 | "nanoid": "^3.3.4",
989 | "picocolors": "^1.0.0",
990 | "source-map-js": "^1.0.2"
991 | }
992 | },
993 | "resolve": {
994 | "version": "1.22.0",
995 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
996 | "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
997 | "dev": true,
998 | "requires": {
999 | "is-core-module": "^2.8.1",
1000 | "path-parse": "^1.0.7",
1001 | "supports-preserve-symlinks-flag": "^1.0.0"
1002 | }
1003 | },
1004 | "rollup": {
1005 | "version": "2.75.6",
1006 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.6.tgz",
1007 | "integrity": "sha512-OEf0TgpC9vU6WGROJIk1JA3LR5vk/yvqlzxqdrE2CzzXnqKXNzbAwlWUXis8RS3ZPe7LAq+YUxsRa0l3r27MLA==",
1008 | "dev": true,
1009 | "requires": {
1010 | "fsevents": "~2.3.2"
1011 | }
1012 | },
1013 | "source-map-js": {
1014 | "version": "1.0.2",
1015 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
1016 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
1017 | "dev": true
1018 | },
1019 | "sourcemap-codec": {
1020 | "version": "1.4.8",
1021 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
1022 | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
1023 | "dev": true
1024 | },
1025 | "supports-preserve-symlinks-flag": {
1026 | "version": "1.0.0",
1027 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
1028 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
1029 | "dev": true
1030 | },
1031 | "svelte": {
1032 | "version": "3.48.0",
1033 | "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.48.0.tgz",
1034 | "integrity": "sha512-fN2YRm/bGumvjUpu6yI3BpvZnpIm9I6A7HR4oUNYd7ggYyIwSA/BX7DJ+UXXffLp6XNcUijyLvttbPVCYa/3xQ==",
1035 | "dev": true
1036 | },
1037 | "svelte-hmr": {
1038 | "version": "0.14.12",
1039 | "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.14.12.tgz",
1040 | "integrity": "sha512-4QSW/VvXuqVcFZ+RhxiR8/newmwOCTlbYIezvkeN6302YFRE8cXy0naamHcjz8Y9Ce3ITTZtrHrIL0AGfyo61w==",
1041 | "dev": true,
1042 | "requires": {}
1043 | },
1044 | "vite": {
1045 | "version": "2.9.12",
1046 | "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.12.tgz",
1047 | "integrity": "sha512-suxC36dQo9Rq1qMB2qiRorNJtJAdxguu5TMvBHOc/F370KvqAe9t48vYp+/TbPKRNrMh/J55tOUmkuIqstZaew==",
1048 | "dev": true,
1049 | "requires": {
1050 | "esbuild": "^0.14.27",
1051 | "fsevents": "~2.3.2",
1052 | "postcss": "^8.4.13",
1053 | "resolve": "^1.22.0",
1054 | "rollup": "^2.59.0"
1055 | }
1056 | }
1057 | }
1058 | }
1059 |
--------------------------------------------------------------------------------