mirror of
https://gitlab.com/djdietrick/docs
synced 2026-05-03 03:50:54 -04:00
Compare commits
18 Commits
df494fbc70
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| c601784b5c | |||
| cfaeadf198 | |||
| 6ff585e133 | |||
| f619093d76 | |||
| c2366ac927 | |||
| 65ead5a6f2 | |||
| f69fcfc50b | |||
| 2644b07951 | |||
| 9458db3381 | |||
| 894a15525a | |||
| 2b8ca93fab | |||
| d14a1a05c8 | |||
| 8bd8196841 | |||
| 6dc2e294b4 | |||
| 5ae50d9381 | |||
| 2c05f038da | |||
| 15b2440d19 | |||
| d17eb8dfce |
46
docs/.vitepress/cache/deps/@theme_index.js
vendored
46
docs/.vitepress/cache/deps/@theme_index.js
vendored
@@ -9,31 +9,31 @@ import {
|
||||
} from "./chunk-3YS4HNIT.js";
|
||||
|
||||
// node_modules/vitepress/dist/client/theme-default/index.js
|
||||
import "C:/git/docs/node_modules/vitepress/dist/client/theme-default/styles/fonts.css";
|
||||
import "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/styles/fonts.css";
|
||||
|
||||
// node_modules/vitepress/dist/client/theme-default/without-fonts.js
|
||||
import "C:/git/docs/node_modules/vitepress/dist/client/theme-default/styles/vars.css";
|
||||
import "C:/git/docs/node_modules/vitepress/dist/client/theme-default/styles/base.css";
|
||||
import "C:/git/docs/node_modules/vitepress/dist/client/theme-default/styles/utils.css";
|
||||
import "C:/git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/custom-block.css";
|
||||
import "C:/git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code.css";
|
||||
import "C:/git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code-group.css";
|
||||
import "C:/git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-doc.css";
|
||||
import "C:/git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-sponsor.css";
|
||||
import VPBadge from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
|
||||
import Layout from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/Layout.vue";
|
||||
import { default as default2 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
|
||||
import { default as default3 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPImage.vue";
|
||||
import { default as default4 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPButton.vue";
|
||||
import { default as default5 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeHero.vue";
|
||||
import { default as default6 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeFeatures.vue";
|
||||
import { default as default7 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeSponsors.vue";
|
||||
import { default as default8 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPDocAsideSponsors.vue";
|
||||
import { default as default9 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPSponsors.vue";
|
||||
import { default as default10 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPage.vue";
|
||||
import { default as default11 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageTitle.vue";
|
||||
import { default as default12 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageSection.vue";
|
||||
import { default as default13 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamMembers.vue";
|
||||
import "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/styles/vars.css";
|
||||
import "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/styles/base.css";
|
||||
import "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/styles/utils.css";
|
||||
import "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/custom-block.css";
|
||||
import "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code.css";
|
||||
import "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code-group.css";
|
||||
import "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-doc.css";
|
||||
import "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-sponsor.css";
|
||||
import VPBadge from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
|
||||
import Layout from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/Layout.vue";
|
||||
import { default as default2 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
|
||||
import { default as default3 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPImage.vue";
|
||||
import { default as default4 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPButton.vue";
|
||||
import { default as default5 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeHero.vue";
|
||||
import { default as default6 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeFeatures.vue";
|
||||
import { default as default7 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeSponsors.vue";
|
||||
import { default as default8 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPDocAsideSponsors.vue";
|
||||
import { default as default9 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPSponsors.vue";
|
||||
import { default as default10 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPage.vue";
|
||||
import { default as default11 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageTitle.vue";
|
||||
import { default as default12 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageSection.vue";
|
||||
import { default as default13 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamMembers.vue";
|
||||
|
||||
// node_modules/vitepress/dist/client/shared.js
|
||||
var inBrowser = typeof document !== "undefined";
|
||||
|
||||
16
docs/.vitepress/cache/deps/_metadata.json
vendored
16
docs/.vitepress/cache/deps/_metadata.json
vendored
@@ -1,31 +1,31 @@
|
||||
{
|
||||
"hash": "cb1f9d5c",
|
||||
"configHash": "9ebaad5b",
|
||||
"lockfileHash": "6f849b3a",
|
||||
"browserHash": "c342cb39",
|
||||
"hash": "b31dd5f2",
|
||||
"configHash": "9af315cb",
|
||||
"lockfileHash": "951d497c",
|
||||
"browserHash": "14d9b0dd",
|
||||
"optimized": {
|
||||
"vue": {
|
||||
"src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
|
||||
"file": "vue.js",
|
||||
"fileHash": "d958a5e2",
|
||||
"fileHash": "6eb7142a",
|
||||
"needsInterop": false
|
||||
},
|
||||
"vitepress > @vue/devtools-api": {
|
||||
"src": "../../../../node_modules/@vue/devtools-api/dist/index.js",
|
||||
"file": "vitepress___@vue_devtools-api.js",
|
||||
"fileHash": "7d35fe85",
|
||||
"fileHash": "f77c0dac",
|
||||
"needsInterop": false
|
||||
},
|
||||
"vitepress > @vueuse/core": {
|
||||
"src": "../../../../node_modules/@vueuse/core/index.mjs",
|
||||
"file": "vitepress___@vueuse_core.js",
|
||||
"fileHash": "c964aa0c",
|
||||
"fileHash": "4488c276",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@theme/index": {
|
||||
"src": "../../../../node_modules/vitepress/dist/client/theme-default/index.js",
|
||||
"file": "@theme_index.js",
|
||||
"fileHash": "8c3760fb",
|
||||
"fileHash": "f16ffef2",
|
||||
"needsInterop": false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -15,6 +15,10 @@ export default {
|
||||
"/interview/": require("../interview/sidebar.json"),
|
||||
"/server/": require("../server/sidebar.json"),
|
||||
"/aws/": require("../aws/sidebar.json"),
|
||||
"/go": require("../go/sidebar.json"),
|
||||
"/react": require("../react/sidebar.json"),
|
||||
"/vue": require("../vue/sidebar.json"),
|
||||
"/java": require("../java/sidebar.json"),
|
||||
"/": [
|
||||
{
|
||||
text: "Home",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Frameworks
|
||||
|
||||
[Nuxtjs](/nuxt/)
|
||||
|
||||
[React](/react/)
|
||||
|
||||
41
docs/go/advanced/context.md
Normal file
41
docs/go/advanced/context.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Context
|
||||
|
||||
Context is a way of passing information or enforcing timeouts when calling functions throughout your application as a single requests goes from layer to layer. This is usually passed as the first parameter in your functions if being used.
|
||||
|
||||
```go
|
||||
// create an empty context
|
||||
ctx := context.Background()
|
||||
|
||||
// append value to context
|
||||
ctx = context.WithValue(ctx, "key", "value")
|
||||
|
||||
value := ctx.Value("key")
|
||||
```
|
||||
|
||||
## Timeouts
|
||||
|
||||
```go
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2 * time.Second)
|
||||
|
||||
defer cancel() // can call before timeout
|
||||
go longRunningFunc(ctx)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
fms.Println("done blocking main thread also!")
|
||||
}
|
||||
time.Sleep(2*time.Second)
|
||||
|
||||
func longRunningFunc(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
fmt.Println("timed out")
|
||||
return
|
||||
default:
|
||||
fmt.Println("still processing")
|
||||
}
|
||||
time.Sleep(1*time.Second)
|
||||
}
|
||||
}
|
||||
```
|
||||
555
docs/go/advanced/db.md
Normal file
555
docs/go/advanced/db.md
Normal file
@@ -0,0 +1,555 @@
|
||||
# Databases
|
||||
|
||||
## Postgres
|
||||
|
||||
```bash
|
||||
go get github.com/jackc/pgconn
|
||||
go get github.com/jackc/pgx/v4
|
||||
go get github.com/jackc/pgx/v4/stdlib
|
||||
```
|
||||
|
||||
```go
|
||||
// models.go
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
const dbTimeout = time.Second * 3
|
||||
|
||||
var db *sql.DB
|
||||
|
||||
type PostgresRepository struct {
|
||||
Conn *sql.DB
|
||||
}
|
||||
|
||||
func NewPostgresRepository(conn *sql.DB) *PostgresRepository {
|
||||
db = conn
|
||||
return &PostgresRepository{
|
||||
Conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Email string `json:"email"`
|
||||
FirstName string `json:"first_name,omitempty"`
|
||||
LastName string `json:"last_name,omitempty"`
|
||||
Password string `json:"-"`
|
||||
Active int `json:"active"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// GetAll returns a slice of all users, sorted by last name
|
||||
func (u *PostgresRepository) GetAll() ([]*User, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
|
||||
defer cancel()
|
||||
|
||||
query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at
|
||||
from users order by last_name`
|
||||
|
||||
rows, err := u.Conn.QueryContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var users []*User
|
||||
|
||||
for rows.Next() {
|
||||
var user User
|
||||
err := rows.Scan(
|
||||
&user.ID,
|
||||
&user.Email,
|
||||
&user.FirstName,
|
||||
&user.LastName,
|
||||
&user.Password,
|
||||
&user.Active,
|
||||
&user.CreatedAt,
|
||||
&user.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
log.Println("Error scanning", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users = append(users, &user)
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// GetByEmail returns one user by email
|
||||
func (u *PostgresRepository) GetByEmail(email string) (*User, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
|
||||
defer cancel()
|
||||
|
||||
query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at from users where email = $1`
|
||||
|
||||
var user User
|
||||
row := db.QueryRowContext(ctx, query, email)
|
||||
|
||||
err := row.Scan(
|
||||
&user.ID,
|
||||
&user.Email,
|
||||
&user.FirstName,
|
||||
&user.LastName,
|
||||
&user.Password,
|
||||
&user.Active,
|
||||
&user.CreatedAt,
|
||||
&user.UpdatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetOne returns one user by id
|
||||
func (u *PostgresRepository) GetOne(id int) (*User, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
|
||||
defer cancel()
|
||||
|
||||
query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at from users where id = $1`
|
||||
|
||||
var user User
|
||||
row := db.QueryRowContext(ctx, query, id)
|
||||
|
||||
err := row.Scan(
|
||||
&user.ID,
|
||||
&user.Email,
|
||||
&user.FirstName,
|
||||
&user.LastName,
|
||||
&user.Password,
|
||||
&user.Active,
|
||||
&user.CreatedAt,
|
||||
&user.UpdatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// Update updates one user in the database, using the information
|
||||
// stored in the receiver u
|
||||
func (u *PostgresRepository) Update(user User) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
|
||||
defer cancel()
|
||||
|
||||
stmt := `update users set
|
||||
email = $1,
|
||||
first_name = $2,
|
||||
last_name = $3,
|
||||
user_active = $4,
|
||||
updated_at = $5
|
||||
where id = $6
|
||||
`
|
||||
|
||||
_, err := db.ExecContext(ctx, stmt,
|
||||
user.Email,
|
||||
user.FirstName,
|
||||
user.LastName,
|
||||
user.Active,
|
||||
time.Now(),
|
||||
user.ID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteByID deletes one user from the database, by ID
|
||||
func (u *PostgresRepository) DeleteByID(id int) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
|
||||
defer cancel()
|
||||
|
||||
stmt := `delete from users where id = $1`
|
||||
|
||||
_, err := db.ExecContext(ctx, stmt, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Insert inserts a new user into the database, and returns the ID of the newly inserted row
|
||||
func (u *PostgresRepository) Insert(user User) (int, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
|
||||
defer cancel()
|
||||
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), 12)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var newID int
|
||||
stmt := `insert into users (email, first_name, last_name, password, user_active, created_at, updated_at)
|
||||
values ($1, $2, $3, $4, $5, $6, $7) returning id`
|
||||
|
||||
err = db.QueryRowContext(ctx, stmt,
|
||||
user.Email,
|
||||
user.FirstName,
|
||||
user.LastName,
|
||||
hashedPassword,
|
||||
user.Active,
|
||||
time.Now(),
|
||||
time.Now(),
|
||||
).Scan(&newID)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return newID, nil
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// main.go, instantiate connection and repo
|
||||
package main
|
||||
|
||||
import (
|
||||
"authentication/data"
|
||||
|
||||
_ "github.com/jackc/pgconn"
|
||||
_ "github.com/jackc/pgx/v4"
|
||||
_ "github.com/jackc/pgx/v4/stdlib"
|
||||
)
|
||||
func connectToDB() *sql.DB {
|
||||
dsn := os.Getenv("DSN")
|
||||
|
||||
for {
|
||||
conn, err := openDB(dsn)
|
||||
if err != nil {
|
||||
log.Println("DB not ready yet...")
|
||||
counts++
|
||||
} else {
|
||||
log.Println("Connected to DB!")
|
||||
return conn
|
||||
}
|
||||
if counts > 10 {
|
||||
log.Println(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Println("Backing off for 2 seconds...")
|
||||
time.Sleep(time.Second * 2)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
func (app *Config) setupRepo(conn *sql.DB) {
|
||||
db := data.NewPostgresRepository(conn)
|
||||
app.Repo = db
|
||||
}
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
It is best to use a repository pattern when dealing with databases, because it allows you to implement a mock repo that implements all the same functions as the real repo with mock return values.
|
||||
|
||||
```go
|
||||
// repository.go
|
||||
package data
|
||||
|
||||
type Repository interface {
|
||||
GetAll() ([]*User, error)
|
||||
GetByEmail(email string) (*User, error)
|
||||
GetOne(id int) (*User, error)
|
||||
Insert(user User) (int, error)
|
||||
Update(user User) error
|
||||
DeleteByID(id int) error
|
||||
ResetPassword(password string, user User) error
|
||||
PasswordMatches(password string, user User) (bool, error)
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
package data
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PostgresTestRepository struct {
|
||||
Conn *sql.DB
|
||||
}
|
||||
|
||||
func NewPostgresTestRepository(conn *sql.DB) *PostgresTestRepository {
|
||||
return &PostgresTestRepository{
|
||||
Conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
// Implement all methods of Repository interface
|
||||
func (repo *PostgresTestRepository) GetAll() ([]*User, error) {
|
||||
users := []*User{}
|
||||
return users, nil
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// setup_test.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"authentication/data"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testApp Config
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
repo := data.NewPostgresTestRepository(nil)
|
||||
testApp.Repo = repo
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
```
|
||||
|
||||
## Mongo
|
||||
|
||||
```bash
|
||||
go get go.mongodb.org/mongo-driver/mongo
|
||||
go get go.mongodb.org/mongo-driver/mongo/options
|
||||
```
|
||||
|
||||
```go
|
||||
// models.go
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
var client *mongo.Client
|
||||
|
||||
func New(mongo *mongo.Client) Models {
|
||||
client = mongo
|
||||
|
||||
return Models{
|
||||
LogEntry: LogEntry{},
|
||||
}
|
||||
}
|
||||
|
||||
type Models struct {
|
||||
LogEntry LogEntry
|
||||
}
|
||||
|
||||
type LogEntry struct {
|
||||
ID string `bson:"_id,omitempty" json:"id,omitempty"`
|
||||
Name string `bson:"name" json:"name"`
|
||||
Data string `bson:"data" json:"data"`
|
||||
CreatedAt time.Time `bson:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `bson:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (l *LogEntry) Insert(entry LogEntry) error {
|
||||
collection := client.Database("logs").Collection("logs")
|
||||
|
||||
_, err := collection.InsertOne(context.TODO(), LogEntry{
|
||||
Name: entry.Name,
|
||||
Data: entry.Data,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
log.Println("Error insertint into logs: ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LogEntry) All() ([]*LogEntry, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
collection := client.Database("logs").Collection("logs")
|
||||
|
||||
opts := options.Find()
|
||||
opts.SetSort(bson.D{{"created_at", -1}})
|
||||
|
||||
cursor, err := collection.Find(context.TODO(), bson.D{}, opts)
|
||||
if err != nil {
|
||||
log.Println("Finding all docs error: ", err)
|
||||
return nil, err
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
var logs []*LogEntry
|
||||
|
||||
for cursor.Next(ctx) {
|
||||
var item LogEntry
|
||||
|
||||
err := cursor.Decode(&item)
|
||||
if err != nil {
|
||||
log.Println("Error decoding log into slice: ", err)
|
||||
return nil, err
|
||||
} else {
|
||||
logs = append(logs, &item)
|
||||
}
|
||||
}
|
||||
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
func (l *LogEntry) GetOne(id string) (*LogEntry, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
collection := client.Database("logs").Collection("logs")
|
||||
|
||||
docId, err := primitive.ObjectIDFromHex(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var entry LogEntry
|
||||
err = collection.FindOne(ctx, bson.M{"_id": docId}).Decode(&entry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &entry, nil
|
||||
}
|
||||
|
||||
func (l *LogEntry) DropCollections() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
collection := client.Database("logs").Collection("logs")
|
||||
|
||||
if err := collection.Drop(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LogEntry) Update() (*mongo.UpdateResult, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
collection := client.Database("logs").Collection("logs")
|
||||
docId, err := primitive.ObjectIDFromHex(l.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := collection.UpdateOne(
|
||||
ctx,
|
||||
bson.M{"_id": docId},
|
||||
bson.D{
|
||||
{"$set", bson.D{
|
||||
{"name", l.Name},
|
||||
{"data", l.Data},
|
||||
{"updated_at", time.Now()},
|
||||
}},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, err
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// main.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"logger/data"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/rpc"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
const (
|
||||
mongoURL = "mongodb://mongo:27017"
|
||||
)
|
||||
|
||||
var client *mongo.Client
|
||||
|
||||
type Config struct {
|
||||
Models data.Models
|
||||
}
|
||||
|
||||
func main() {
|
||||
// connect to mongo
|
||||
mongoClient, err := connectToMongo()
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
client = mongoClient
|
||||
|
||||
// create a context to disconnect
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// close connection
|
||||
defer func() {
|
||||
if err = client.Disconnect(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
app := Config{
|
||||
Models: data.New(client),
|
||||
}
|
||||
}
|
||||
|
||||
func connectToMongo() (*mongo.Client, error) {
|
||||
// create connection options
|
||||
clientOptions := options.Client().ApplyURI(mongoURL)
|
||||
clientOptions.SetAuth(options.Credential{
|
||||
Username: "admin",
|
||||
Password: "password",
|
||||
})
|
||||
|
||||
c, err := mongo.Connect(context.TODO(), clientOptions)
|
||||
if err != nil {
|
||||
log.Println("Error connecting: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
```
|
||||
62
docs/go/advanced/flags.md
Normal file
62
docs/go/advanced/flags.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Flags
|
||||
|
||||
Flags allow you to define command line arguments to your applications. This gives you some more useful features over `os.Args` such as help strings and validation.
|
||||
|
||||
```go
|
||||
var age = flag.Int("age", 20, "age of user") // arg name, default, help
|
||||
fmt.Println(*age) // gives us a pointer to the value
|
||||
// or
|
||||
var age int
|
||||
flag.IntVar(&age, "age", 20, "age of user")
|
||||
fmt.Println(age)
|
||||
|
||||
// with structs
|
||||
type ServerConfig struct {
|
||||
port int
|
||||
env string
|
||||
}
|
||||
|
||||
var config ServerConfig
|
||||
flag.IntVar(&config.port, "port", 8000, "server port")
|
||||
flag.StringVar(&config.env, "env", "dev", "environment")
|
||||
flag.Parse()
|
||||
```
|
||||
|
||||
## Custom Flags
|
||||
|
||||
Normally flags can only be strings, ints, or booleans, but you can create your own custom flag types by implementing the `Value` interface which has two functions, `String` and `Set`.
|
||||
|
||||
```go
|
||||
type AddrVal struct {
|
||||
addr *string
|
||||
}
|
||||
|
||||
func (a AddrVal) String() string {
|
||||
if a.addr == nil {
|
||||
return ""
|
||||
}
|
||||
return *a.addr
|
||||
}
|
||||
|
||||
func (a AddrVal) Set(s string) error {
|
||||
if err := validateAddr(s); err != nil {
|
||||
return err
|
||||
}
|
||||
*a.addr = s
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAddr(addr string) error {
|
||||
// validate address
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
addr := ":8080" // default value
|
||||
flag.Var(AddrVal{&addr}, "addr", "address to listen on")
|
||||
flag.Parse()
|
||||
|
||||
fmt.Println("address: ", addr)
|
||||
}
|
||||
|
||||
```
|
||||
212
docs/go/advanced/grpc.md
Normal file
212
docs/go/advanced/grpc.md
Normal file
@@ -0,0 +1,212 @@
|
||||
# gRPC
|
||||
|
||||
GRPC is an enhancement to RPC in that both sides don't have to be written in Go, because there are libraries for a lot of other languages. It also defines it's payload types in separate proto files so they can be shared among teams.
|
||||
|
||||
Documentation: https://grpc.io/docs/languages/go/basics/
|
||||
|
||||
## Install GRPC
|
||||
|
||||
```bash
|
||||
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.27
|
||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
|
||||
```
|
||||
|
||||
## Proto Files
|
||||
|
||||
```proto
|
||||
// log.proto
|
||||
syntax = "proto3";
|
||||
|
||||
package logs;
|
||||
|
||||
option go_package = "/logs";
|
||||
|
||||
message Log{
|
||||
string name = 1;
|
||||
string data = 2;
|
||||
}
|
||||
|
||||
message LogRequest{
|
||||
Log logEntry = 1;
|
||||
}
|
||||
|
||||
message LogResponse{
|
||||
string result = 1;
|
||||
}
|
||||
|
||||
service LogService{
|
||||
rpc WriteLog(LogRequest) returns (LogResponse);
|
||||
}
|
||||
```
|
||||
|
||||
## Compile Protos
|
||||
|
||||
Proto compiler link: https://grpc.io/docs/protoc-installation/. This should be stored somewhere in your path, preferably in your `~/go/bin` directory.
|
||||
|
||||
In directory with your proto file, run the below command. This will generate
|
||||
|
||||
```bash
|
||||
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative logs.proto
|
||||
```
|
||||
|
||||
## Listener
|
||||
|
||||
### Install dependencies
|
||||
|
||||
```bash
|
||||
go get google.golang.org/gprc
|
||||
go get google.golang.org/protobuf
|
||||
```
|
||||
|
||||
### Implement handlers
|
||||
|
||||
```go
|
||||
// grpc.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"logger/data" // our db models
|
||||
"logger/logs" // our proto files
|
||||
"net"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type LogServer struct {
|
||||
logs.UnimplementedLogServiceServer
|
||||
Models data.Models // db writer
|
||||
}
|
||||
|
||||
// Using code generated from proto
|
||||
func (l *LogServer) WriteLog(ctx context.Context, req *logs.LogRequest) (*logs.LogResponse, error) {
|
||||
input := req.GetLogEntry() // auto generated function
|
||||
|
||||
logEntry := data.LogEntry{
|
||||
Name: input.Name,
|
||||
Data: input.Data,
|
||||
}
|
||||
|
||||
err := l.Models.LogEntry.Insert(logEntry)
|
||||
if err != nil {
|
||||
resp := &logs.LogResponse{
|
||||
Result: "failed",
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
resp := &logs.LogResponse{
|
||||
Result: "logged!",
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (app *Config) gRPCListen() {
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%s", grpcPort))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen to gRPC: %v", err)
|
||||
}
|
||||
|
||||
s := grpc.NewServer()
|
||||
|
||||
logs.RegisterLogServiceServer(s, &LogServer{
|
||||
Models: app.Models,
|
||||
})
|
||||
|
||||
log.Printf("gRPC server listening on port %s", grpcPort)
|
||||
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve gRPC: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Instantiate server
|
||||
|
||||
```go
|
||||
// grpc.go
|
||||
func (app *Config) gRPCListen() {
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%s", grpcPort))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen to gRPC: %v", err)
|
||||
}
|
||||
|
||||
s := grpc.NewServer()
|
||||
|
||||
// auto generated function
|
||||
logs.RegisterLogServiceServer(s, &LogServer{
|
||||
Models: app.Models,
|
||||
})
|
||||
|
||||
log.Printf("gRPC server listening on port %s", grpcPort)
|
||||
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve gRPC: %v", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// main.go
|
||||
go app.grpcLisen()
|
||||
```
|
||||
|
||||
## Sender
|
||||
|
||||
The sender must also have access to the proto files generated by the listener. It would be a good idea to store these in a separate repo so that multiple teams can access them from a central source of truth.
|
||||
|
||||
```go
|
||||
import (
|
||||
"context"
|
||||
"log" // proto
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
func (app *Config) logItemViaGRPC(w http.ResponseWriter, r *http.Request) {
|
||||
var requestPayload RequestPayload
|
||||
|
||||
err := app.readJSON(w, r, &requestPayload)
|
||||
if err != nil {
|
||||
app.errorJSON(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// need to pass some sort of credentials to the server
|
||||
conn, err := grpc.Dial("logger:50001", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
|
||||
if err != nil {
|
||||
app.errorJSON(w, err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// generated in proto
|
||||
client := logs.NewLogServiceClient(conn)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err = client.WriteLog(ctx, &logs.LogRequest{
|
||||
LogEntry: &logs.Log{
|
||||
Name: requestPayload.Log.Name,
|
||||
Data: requestPayload.Log.Data,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
app.errorJSON(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
payload := jsonResponse{
|
||||
Error: false,
|
||||
Message: "logged via gRPC",
|
||||
}
|
||||
|
||||
app.writeJSON(w, http.StatusAccepted, payload)
|
||||
}
|
||||
```
|
||||
357
docs/go/advanced/http.md
Normal file
357
docs/go/advanced/http.md
Normal file
@@ -0,0 +1,357 @@
|
||||
# HTTP
|
||||
|
||||
The below snippets are using the `chi` package for Go.
|
||||
|
||||
### To Install
|
||||
|
||||
```bash
|
||||
go get github.com/go-chi/chi/v5
|
||||
go get github.com/go-chi/chi/v5/middleware
|
||||
go get github.com/go-chi/cors
|
||||
```
|
||||
|
||||
## Basic Implementation
|
||||
|
||||
```go
|
||||
// main.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
_ "github.com/jackc/pgconn"
|
||||
_ "github.com/jackc/pgx/v4"
|
||||
_ "github.com/jackc/pgx/v4/stdlib"
|
||||
)
|
||||
|
||||
const webPort = "80"
|
||||
|
||||
var counts int64
|
||||
|
||||
type Config struct {
|
||||
Client *http.Client
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := Config{
|
||||
Client: &http.Client{},
|
||||
}
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: fmt.Sprintf(":%s", webPort),
|
||||
Handler: app.routes(),
|
||||
}
|
||||
|
||||
err := srv.ListenAndServe()
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// routes.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
)
|
||||
|
||||
func (app *Config) routes() http.Handler {
|
||||
mux := chi.NewRouter()
|
||||
|
||||
mux.Use(cors.Handler(cors.Options{
|
||||
AllowedOrigins: []string{"https://*", "http://*"},
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
||||
ExposedHeaders: []string{"Link"},
|
||||
AllowCredentials: true,
|
||||
MaxAge: 300,
|
||||
}))
|
||||
|
||||
mux.Use(middleware.Heartbeat("/ping"))
|
||||
|
||||
mux.Post("/authenticate", app.Authenticate)
|
||||
|
||||
return mux
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// handlers.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (app *Config) Authenticate(w http.ResponseWriter, r *http.Request) {
|
||||
var requestPayload struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
err := app.readJSON(w, r, &requestPayload)
|
||||
if err != nil {
|
||||
app.errorJSON(w, err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// validate user against db
|
||||
user, err := app.Repo.GetByEmail(requestPayload.Email)
|
||||
if err != nil {
|
||||
app.errorJSON(w, errors.New("invalid credentials"), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
valid, err := app.Repo.PasswordMatches(requestPayload.Password, *user)
|
||||
if err != nil || !valid {
|
||||
app.errorJSON(w, errors.New("invalid credentials"), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// log authentication
|
||||
err = app.logRequest("authentication", fmt.Sprintf("%s logged in", user.Email))
|
||||
if err != nil {
|
||||
app.errorJSON(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Logged authentication request")
|
||||
|
||||
payload := jsonResponse{
|
||||
Error: false,
|
||||
Message: fmt.Sprintf("Logged in user %s", requestPayload.Email),
|
||||
Data: user,
|
||||
}
|
||||
|
||||
app.writeJSON(w, http.StatusAccepted, payload)
|
||||
}
|
||||
|
||||
func (app *Config) logRequest(name, data string) error {
|
||||
var entry struct {
|
||||
Name string `json:"name"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
entry.Name = name
|
||||
entry.Data = data
|
||||
|
||||
jsonData, _ := json.MarshalIndent(entry, "", "\t")
|
||||
|
||||
logURL := "http://logger/log"
|
||||
|
||||
request, err := http.NewRequest("POST", logURL, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = app.Client.Do(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```go
|
||||
// helpers.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type jsonResponse struct {
|
||||
Error bool `json:"error"`
|
||||
Message string `json:"message"`
|
||||
Data any `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
func (app *Config) readJSON(w http.ResponseWriter, r *http.Request, data any) error {
|
||||
maxBytes := 1048576 // one MB
|
||||
|
||||
r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
|
||||
|
||||
dec := json.NewDecoder(r.Body)
|
||||
err := dec.Decode(data)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dec.Decode(&struct{}{})
|
||||
if err != io.EOF {
|
||||
return errors.New("Body must have only a single json value")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *Config) writeJSON(w http.ResponseWriter, status int, data any, headers ...http.Header) error {
|
||||
out, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(headers) > 0 {
|
||||
for key, val := range headers[0] {
|
||||
w.Header()[key] = val
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
_, err = w.Write(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *Config) errorJSON(w http.ResponseWriter, err error, status ...int) error {
|
||||
statusCode := http.StatusBadRequest
|
||||
|
||||
if len(status) > 0 {
|
||||
statusCode = status[0]
|
||||
}
|
||||
|
||||
var payload jsonResponse
|
||||
payload.Error = true
|
||||
payload.Message = err.Error()
|
||||
|
||||
return app.writeJSON(w, statusCode, payload)
|
||||
}
|
||||
```
|
||||
|
||||
## Adding Custom Types to Session
|
||||
|
||||
```go
|
||||
// main.go, before creating session
|
||||
gob.Register(data.User{})
|
||||
|
||||
// to add type to session
|
||||
app.Session.Put(r.Context(), "user", user) // user *data.User
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```go
|
||||
// handlers_test.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type RoundTripFunc func(req *http.Request) *http.Response
|
||||
|
||||
func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return f(req), nil
|
||||
}
|
||||
|
||||
func NewTestClient(fn RoundTripFunc) *http.Client {
|
||||
return &http.Client{
|
||||
Transport: fn,
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Authenticate(t *testing.T) {
|
||||
jsonToReturn := `
|
||||
{
|
||||
"error": false,
|
||||
"message": "some message"
|
||||
}
|
||||
`
|
||||
client := NewTestClient(func(req *http.Request) *http.Response {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(bytes.NewBufferString(jsonToReturn)),
|
||||
Header: make(http.Header),
|
||||
}
|
||||
})
|
||||
testApp.Client = client
|
||||
|
||||
postBody := map[string]interface{}{
|
||||
"email": "me@here.com",
|
||||
"password": "verysecret",
|
||||
}
|
||||
|
||||
body, _ := json.Marshal(postBody)
|
||||
|
||||
req, _ := http.NewRequest("POST", "/authenticate", bytes.NewReader(body))
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
handler := http.HandlerFunc(testApp.Authenticate)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
if rr.Code != http.StatusAccepted {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v",
|
||||
rr.Code, http.StatusAccepted)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// routes_test.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
func Test_routes_exist(t *testing.T) {
|
||||
testApp := Config{}
|
||||
|
||||
testRoutes := testApp.routes()
|
||||
chiRoutes := testRoutes.(chi.Router)
|
||||
|
||||
routes := []string{"/authenticate"}
|
||||
|
||||
for _, route := range routes {
|
||||
routeExists(t, chiRoutes, route)
|
||||
}
|
||||
}
|
||||
|
||||
func routeExists(t *testing.T, routes chi.Router, route string) {
|
||||
found := false
|
||||
_ = chi.Walk(routes, func(method string, foundRoute string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
|
||||
if route == foundRoute {
|
||||
found = true
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if !found {
|
||||
t.Errorf("route %s not found", route)
|
||||
}
|
||||
}
|
||||
```
|
||||
97
docs/go/advanced/rpc.md
Normal file
97
docs/go/advanced/rpc.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# RPC
|
||||
|
||||
RPC stands for Remote Procedure Calls, and allows for external Go microservices to call functions in our application directly.
|
||||
|
||||
## Listener
|
||||
|
||||
To do this, you need to define a RPCServer dummy type, and payload to receive, and a function that gets a pointer to a response string and returns an error.
|
||||
|
||||
```go
|
||||
type RPCServer struct{}
|
||||
|
||||
type RPCPayload struct {
|
||||
Name string
|
||||
Data string
|
||||
}
|
||||
|
||||
func (r *RPCServer) LogInfo(payload RPCPayload, resp *string) error {
|
||||
collection := client.Database("logs").Collection("logs")
|
||||
_, err := collection.InsertOne(context.TODO(), data.LogEntry{
|
||||
Name: payload.Name,
|
||||
Data: payload.Data,
|
||||
})
|
||||
if err != nil {
|
||||
log.Println("error writing to mongo ", err)
|
||||
return err
|
||||
}
|
||||
*resp = "processed payload via RPC: " + payload.Name
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
To instantiate the RPCServer, use the below syntax in your main application file. Basically you define a function that creates the connection and listens on a port. Then, you register your RPC handler type and serve the RPC server.
|
||||
|
||||
```go
|
||||
func (app *Config) rpcListen() error {
|
||||
log.Println("Starting RPC server on port " + RPC_PORT)
|
||||
listen, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%s", RPC_PORT))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer listen.Close()
|
||||
|
||||
for {
|
||||
rpcConn, err := listen.Accept()
|
||||
if err != nil {
|
||||
log.Println("Error accepting connection: ", err)
|
||||
continue
|
||||
}
|
||||
go rpc.ServeConn(rpcConn)
|
||||
}
|
||||
}
|
||||
|
||||
// in function where you start your app...
|
||||
err = rpc.Register(new(RPCServer))
|
||||
go app.rpcListen()
|
||||
```
|
||||
|
||||
## Sender
|
||||
|
||||
To send messages via RPC, you need to create a type for the payload that exactly matches that of the receiver.
|
||||
|
||||
```go
|
||||
type RPCPayload struct {
|
||||
Name string
|
||||
Data string
|
||||
}
|
||||
|
||||
func (app *Config) logItemViaRPC(r http.ResponseWriter, l LogPayload) {
|
||||
client, err := rpc.Dial("tcp", "logger:5001")
|
||||
if err != nil {
|
||||
app.errorJSON(r, err)
|
||||
return
|
||||
}
|
||||
|
||||
payload := RPCPayload{
|
||||
Name: l.Name,
|
||||
Data: l.Data,
|
||||
}
|
||||
|
||||
var resp string
|
||||
|
||||
err = client.Call("RPCServer.LogInfo", payload, &resp)
|
||||
if err != nil {
|
||||
app.errorJSON(r, err)
|
||||
return
|
||||
}
|
||||
|
||||
returnPayload := jsonResponse{
|
||||
Error: false,
|
||||
Message: resp,
|
||||
}
|
||||
|
||||
app.writeJSON(r, http.StatusAccepted, returnPayload)
|
||||
}
|
||||
```
|
||||
59
docs/go/advanced/testing.md
Normal file
59
docs/go/advanced/testing.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Testing
|
||||
|
||||
Test files in go are kept alongside the source code with postfix `_test.go`. Functions beginning with `Test*` that take an argument `(t *testing.T)` will be run as unit tests. A test fails if a condition leading to `t.Error` or `t.Fail` is met.
|
||||
|
||||
```bash
|
||||
# Test everything in current directory
|
||||
go test .
|
||||
|
||||
# Test everything in subdirectories
|
||||
go test ./...
|
||||
|
||||
# See coverage percentage
|
||||
go test -cover .
|
||||
|
||||
# Generate coverage file
|
||||
go test -coverprofile=coverage.out .
|
||||
|
||||
# View code coverage from file
|
||||
go tool cover -html=coverage.out
|
||||
|
||||
# Run individual test
|
||||
go test -run Test_alpha_isPrime
|
||||
|
||||
# Run groups of tests
|
||||
go test -run Test_alpha
|
||||
```
|
||||
|
||||
## Test cases
|
||||
|
||||
Within a test function, you can run multiple related test cases by iterating over a slice of cases with `t.Run`.
|
||||
|
||||
```go
|
||||
func Test_alpha_isPrime(t *testing.T) {
|
||||
primeTests := []struct {
|
||||
name string
|
||||
num int
|
||||
expected bool
|
||||
message string
|
||||
}{
|
||||
{"prime", 7, true, "7 is prime!"},
|
||||
{"not prime", 8, false, "8 is not prime, it is divisible by 2"},
|
||||
{"zero", 0, false, "0 is not prime, by definition!"},
|
||||
{"one", 1, false, "1 is not prime, by definition!"},
|
||||
{"negative", -1, false, "Negative numbers are not prime, by definition!"},
|
||||
}
|
||||
|
||||
for _, tt := range primeTests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual, msg := isPrime(tt.num)
|
||||
if actual != tt.expected {
|
||||
t.Errorf("isPrime(%d): expected %t, actual %t", tt.num, tt.expected, actual)
|
||||
}
|
||||
if msg != tt.message {
|
||||
t.Errorf("isPrime(%d): expected %s, actual %s", tt.num, tt.message, msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
177
docs/go/basics/collection.md
Normal file
177
docs/go/basics/collection.md
Normal file
@@ -0,0 +1,177 @@
|
||||
# Collections
|
||||
|
||||
## Make
|
||||
|
||||
The `make` keyword is used to create slices, maps, and channels that are initialized and ready for use. For example, a slice is a three component object with a pointer to an array, a length, and a capacity. Until these are set, the slice is considered nil. The `make` keyword initialized these variables and makes the slice ready to use. It also returns value of type T instead of a pointer with `new`.
|
||||
|
||||
## Arrays
|
||||
|
||||
Arrays in Go are fixed length and must be declared at initialization.
|
||||
|
||||
```go
|
||||
var a [10]int
|
||||
a[0] = 1
|
||||
```
|
||||
|
||||
## Slices
|
||||
|
||||
A slice is like an array but it does not have a fixed size. You can further slice a slice be specifying a low and high index, inclusive of the first and exclusive of the last.
|
||||
|
||||
```go
|
||||
primes := [6]int{2, 3, 5, 7, 11, 13}
|
||||
|
||||
var s []int = primes[1:4]
|
||||
fmt.Println(s) // 3 5 7
|
||||
s = primes[3:] // 7, 11, 13
|
||||
s = primes[:2] // 2, 3
|
||||
```
|
||||
|
||||
Slices are references to an underlying array, so if you create a slice of an array and edit that slice, you will also edit the underlying array.
|
||||
|
||||
```go
|
||||
names := [4]string{
|
||||
"John",
|
||||
"Paul",
|
||||
"George",
|
||||
"Ringo",
|
||||
}
|
||||
fmt.Println(names) // John Paul George Ringo
|
||||
|
||||
a := names[0:2]
|
||||
b := names[1:3]
|
||||
fmt.Println(a, b) // John Paul, Paul George
|
||||
|
||||
b[0] = "XXX"
|
||||
fmt.Println(a, b) // John XXX, XXX George
|
||||
fmt.Println(names) // John XXX George Ringo
|
||||
```
|
||||
|
||||
### Length vs Capacity
|
||||
|
||||
The length of a slice is how many items are actually stored in the array, and the capacity is how many slots the array has. A slice is considered a nil slice if it has 0 length and capacity and no underlying array.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
s := []int{2, 3, 5, 7, 11, 13}
|
||||
printSlice(s)
|
||||
|
||||
// Slice the slice to give it zero length.
|
||||
s = s[:0]
|
||||
printSlice(s)
|
||||
|
||||
// Extend its length.
|
||||
s = s[:4]
|
||||
printSlice(s)
|
||||
|
||||
// Drop its first two values.
|
||||
s = s[2:]
|
||||
printSlice(s)
|
||||
}
|
||||
|
||||
func printSlice(s []int) {
|
||||
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
|
||||
}
|
||||
|
||||
// Output
|
||||
// len=6 cap=6 [2 3 5 7 11 13]
|
||||
// len=0 cap=6 []
|
||||
// len=4 cap=6 [2 3 5 7]
|
||||
// len=2 cap=4 [5 7]
|
||||
```
|
||||
|
||||
You can create slices with specific length and capacity using `make`.
|
||||
|
||||
```go
|
||||
x := make([]int, 0, 5) // len(x)=0, cap(x)=5
|
||||
|
||||
x = x[:cap(x)] // len(x)=5, cap(x)=5
|
||||
x = x[1:] // len(x)=4, cap(x)=4
|
||||
|
||||
a := make([]int, 5)
|
||||
// a len=5 cap=5 [0 0 0 0 0]
|
||||
|
||||
b := make([]int, 0, 5)
|
||||
// b len=0 cap=5 []
|
||||
|
||||
c := b[:2]
|
||||
// c len=2 cap=5 [0 0]
|
||||
|
||||
d := c[2:5]
|
||||
// d len=3 cap=3 [0 0 0]
|
||||
```
|
||||
|
||||
### Appending
|
||||
|
||||
If the underlying array of the slice is too small to accommodate the new elements, the returned slice will point to a newly allocated array.
|
||||
|
||||
```go
|
||||
var s []int
|
||||
// len=0 cap=0 []
|
||||
|
||||
// append works on nil slices.
|
||||
s = append(s, 0)
|
||||
// len=1 cap=1 [0]
|
||||
|
||||
// The slice grows as needed.
|
||||
s = append(s, 1)
|
||||
// len=2 cap=2 [0 1]
|
||||
|
||||
// We can add more than one element at a time.
|
||||
s = append(s, 2, 3, 4)
|
||||
// len=5 cap=6 [0 1 2 3 4]
|
||||
```
|
||||
|
||||
## Maps
|
||||
|
||||
Maps are key-value pairs. They can be created with `make`.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Vertex struct {
|
||||
Lat, Long float64
|
||||
}
|
||||
|
||||
var m map[string]Vertex // cannot be used
|
||||
|
||||
func main() {
|
||||
m = make(map[string]Vertex) // now can be used
|
||||
m["Bell Labs"] = Vertex{
|
||||
40.68433, -74.39967,
|
||||
}
|
||||
fmt.Println(m["Bell Labs"])
|
||||
|
||||
// Map literal
|
||||
var n = map[string]Vertex{
|
||||
"Bell Labs": Vertex{
|
||||
40.68433, -74.39967,
|
||||
},
|
||||
"Google": Vertex{
|
||||
37.42202, -122.08408,
|
||||
},
|
||||
}
|
||||
|
||||
// Shorthand
|
||||
var o = map[string]Vertex{
|
||||
"Bell Labs": {40.68433, -74.39967},
|
||||
"Google": {37.42202, -122.08408},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Manipulating the map
|
||||
|
||||
```go
|
||||
m[key] = elem
|
||||
elem = m[key]
|
||||
delete(m, key)
|
||||
|
||||
// Check if element exists
|
||||
elem, ok = m[key]
|
||||
```
|
||||
205
docs/go/basics/concurrency.md
Normal file
205
docs/go/basics/concurrency.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# Cocurrency
|
||||
|
||||
One of the strengths of Go is the simplicity of its cocurrency paradigm. A `goroutine` is essentially a thread that gets managed by the runtime.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func say(s string) {
|
||||
for i := 0; i < 5; i++ {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
go say("world")
|
||||
say("hello")
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Channels
|
||||
|
||||
Channels are typed pipelines that you can send and receive values between goroutines. Sending and receiving through a channel block the thread until the otherside is ready.
|
||||
|
||||
```go
|
||||
ch := make(chan int)
|
||||
ch <- v // Send v to channel ch.
|
||||
v := <-ch // Receive from ch, and assign value to v.
|
||||
```
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func sum(s []int, c chan int) {
|
||||
sum := 0
|
||||
for _, v := range s {
|
||||
sum += v
|
||||
}
|
||||
c <- sum // send sum to c
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := []int{7, 2, 8, -9, 4, 0}
|
||||
|
||||
c := make(chan int)
|
||||
go sum(s[:len(s)/2], c)
|
||||
go sum(s[len(s)/2:], c)
|
||||
x, y := <-c, <-c // receive from c
|
||||
|
||||
fmt.Println(x, y, x+y)
|
||||
}
|
||||
```
|
||||
|
||||
### Buffering
|
||||
|
||||
Channels can also be buffered to allow senders to continue sending messages to a channel as long as the buffer is not full. Receivers block when the buffer is empty.
|
||||
|
||||
```go
|
||||
ch := make(chan int, 100)
|
||||
```
|
||||
|
||||
### Closing
|
||||
|
||||
Channels can be closed to denote when no more values are going to be sent through the channel. Receivers can test for a closed channel by receiving a second parameter from the channel. `ok` will be false if the channel is closed. The sender should always close the channel, as sending on a closed channel will cause a panic.
|
||||
|
||||
```go
|
||||
v, ok := <-ch
|
||||
|
||||
close(ch)
|
||||
```
|
||||
|
||||
This can also be used with `range` to automatically receive the values from the channel and break the loop after the channel closes.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func fibonacci(n int, c chan int) {
|
||||
x, y := 0, 1
|
||||
for i := 0; i < n; i++ {
|
||||
c <- x
|
||||
x, y = y, x+y
|
||||
}
|
||||
close(c)
|
||||
}
|
||||
|
||||
func main() {
|
||||
c := make(chan int, 10)
|
||||
go fibonacci(cap(c), c)
|
||||
for i := range c {
|
||||
fmt.Println(i)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Select
|
||||
|
||||
Select is like a switch statement for channels. It blocks until a condition occurs and then runs that condition. If multiple are ready at the same time it will choose randomly. You can add a default case to do something without blocking.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func fibonacci(c, quit chan int) {
|
||||
x, y := 0, 1
|
||||
for {
|
||||
select {
|
||||
case c <- x:
|
||||
x, y = y, x+y
|
||||
case <-quit:
|
||||
fmt.Println("quit")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func bomb() {
|
||||
tick := time.Tick(100 * time.Millisecond)
|
||||
boom := time.After(500 * time.Millisecond)
|
||||
for {
|
||||
select {
|
||||
case <-tick:
|
||||
fmt.Println("tick.")
|
||||
case <-boom:
|
||||
fmt.Println("BOOM!")
|
||||
return
|
||||
default:
|
||||
fmt.Println(" .")
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
c := make(chan int)
|
||||
quit := make(chan int)
|
||||
go func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
fmt.Println(<-c)
|
||||
}
|
||||
quit <- 0
|
||||
}()
|
||||
fibonacci(c, quit)
|
||||
bomb()
|
||||
}
|
||||
```
|
||||
|
||||
## Mutex
|
||||
|
||||
Mutexes can be used to pass variables to multiple goroutines and making sure only one can operate on it at a time.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SafeCounter is safe to use concurrently.
|
||||
type SafeCounter struct {
|
||||
mu sync.Mutex
|
||||
v map[string]int
|
||||
}
|
||||
|
||||
// Inc increments the counter for the given key.
|
||||
func (c *SafeCounter) Inc(key string) {
|
||||
c.mu.Lock()
|
||||
// Lock so only one goroutine at a time can access the map c.v.
|
||||
c.v[key]++
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// Value returns the current value of the counter for the given key.
|
||||
func (c *SafeCounter) Value(key string) int {
|
||||
c.mu.Lock()
|
||||
// Lock so only one goroutine at a time can access the map c.v.
|
||||
defer c.mu.Unlock()
|
||||
return c.v[key]
|
||||
}
|
||||
|
||||
func main() {
|
||||
c := SafeCounter{v: make(map[string]int)}
|
||||
for i := 0; i < 1000; i++ {
|
||||
go c.Inc("somekey")
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
fmt.Println(c.Value("somekey"))
|
||||
}
|
||||
|
||||
```
|
||||
108
docs/go/basics/control.md
Normal file
108
docs/go/basics/control.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# Control Flow
|
||||
|
||||
For the most part, control flow keywords in Go do not require parenthesis `()` for the conditional statements but do require brackets `{}` to contain the logic.
|
||||
|
||||
## For loops
|
||||
|
||||
For loops in Go are similar to C++ in that they have an inital, conditional, and post statement in the declaration. Initial and post statements are optional if not needed. In that case, this becomes Go's `while` loop. You can go even further by dropping the condition all together to create an forever loop.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
sum := 0
|
||||
for i := 0; i < 10; i++ {
|
||||
sum += i
|
||||
}
|
||||
fmt.Println(sum)
|
||||
|
||||
// While
|
||||
sum = 1
|
||||
for sum < 1000 {
|
||||
sum += sum
|
||||
}
|
||||
fmt.Println(sum)
|
||||
|
||||
// Forever loop
|
||||
for {
|
||||
// repeats forever
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can use the `range` keyword to iterate over a slice or map. With a slice, you will get both the index and a copy of the element for each iteration.
|
||||
|
||||
```go
|
||||
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
|
||||
for i, v := range pow { // i or v can be ignored with _
|
||||
fmt.Printf("2**%d = %d\n", i, v)
|
||||
}
|
||||
```
|
||||
|
||||
## If/Else
|
||||
|
||||
If statements in Go can also include short statements to execute before the condition, and variables in these statements are scoped to the if/else block.
|
||||
|
||||
```go
|
||||
func pow(x, n, lim float64) float64 {
|
||||
if v := math.Pow(x, n); v < lim {
|
||||
return v
|
||||
}
|
||||
else if v == lim {
|
||||
fm.Printf("%g == %g\n", v, lim)
|
||||
}
|
||||
else {
|
||||
fmt.Printf("%g >= %g\n", v, lim)
|
||||
}
|
||||
return lim
|
||||
}
|
||||
```
|
||||
|
||||
## Switch
|
||||
|
||||
In Go, only the matching case is run, meaning you do not need to include `break` like you would in C++. The switch case stops evaluating after one of the cases matches, so you can not run multiple cases even if it matches multiple. Switch can also be written with no condition, effectively turning it into a long if/else block.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
switch os := runtime.GOOS; os {
|
||||
case "darwin":
|
||||
fmt.Println("OS X.")
|
||||
case "linux":
|
||||
fmt.Println("Linux.")
|
||||
default:
|
||||
fmt.Printf("%s.\n", os)
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
switch {
|
||||
case t.Hour() < 12:
|
||||
fmt.Println("Good morning!")
|
||||
case t.Hour() < 17:
|
||||
fmt.Println("Good afternoon.")
|
||||
default:
|
||||
fmt.Println("Good evening.")
|
||||
}
|
||||
```
|
||||
|
||||
## Defer
|
||||
|
||||
Defer can be used to delay a function call until the surrounding function returns. Defers can be stacked, and will execute on a last-in, first-out order.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
defer fmt.Println("world")
|
||||
|
||||
fmt.Println("hello")
|
||||
}
|
||||
```
|
||||
113
docs/go/basics/functions.md
Normal file
113
docs/go/basics/functions.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Functions
|
||||
|
||||
Functions are declared with the `func` keyword. They can have zero or more arguments, and the types of the arguments come after the names with a space. Functions can also return multiple values, and this is how error checking is done in Go. You can even instantiate the return values of the function in the function definition (only use in short functions).
|
||||
|
||||
By default, arguments are passed by value. To pass by reference, you need to take a pointer as the argument to modify the reference.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func add(x int, y int) int {
|
||||
return x + y
|
||||
}
|
||||
|
||||
func swap(x, y string) (string, string) {
|
||||
return y, x
|
||||
}
|
||||
|
||||
func split(sum int) (x, y int) {
|
||||
x = sum * 4 / 9
|
||||
y = sum - x
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println(add(42, 13))
|
||||
fmt.Println(swap("foo", "bar"))
|
||||
fmt.Println(split(17))
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Functions can also be saved as variables and passed to other functions.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
func compute(fn func(float64, float64) float64) float64 {
|
||||
return fn(3, 4)
|
||||
}
|
||||
|
||||
func main() {
|
||||
hypot := func(x, y float64) float64 {
|
||||
return math.Sqrt(x*x + y*y)
|
||||
}
|
||||
fmt.Println(hypot(5, 12))
|
||||
|
||||
fmt.Println(compute(hypot))
|
||||
fmt.Println(compute(math.Pow))
|
||||
}
|
||||
```
|
||||
|
||||
## Closures
|
||||
|
||||
Closures are functions that reference variables outside of the function body. In the below example, the inner function of `adder` has access to its own sum.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func adder() func(int) int {
|
||||
sum := 0
|
||||
return func(x int) int {
|
||||
sum += x
|
||||
return sum
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
pos, neg := adder(), adder()
|
||||
for i := 0; i < 10; i++ {
|
||||
fmt.Println(
|
||||
pos(i),
|
||||
neg(-2*i),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 0 0
|
||||
// 1 -2
|
||||
// 3 -6
|
||||
// 6 -12
|
||||
// 10 -20
|
||||
// 15 -30
|
||||
// 21 -42
|
||||
// 28 -56
|
||||
// 36 -72
|
||||
// 45 -90
|
||||
```
|
||||
|
||||
## Generics
|
||||
|
||||
Functions can also take generic types with the below syntax. In the below example the generic type must implement the `comparable` constraint, meaning it has the `==` and `!=` operators.
|
||||
|
||||
```go
|
||||
func Index[T comparable](s []T, x T) int {
|
||||
for i, v := range s {
|
||||
// v and x are type T, which has the comparable
|
||||
// constraint, so we can use == here.
|
||||
if v == x {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
```
|
||||
68
docs/go/basics/packages.md
Normal file
68
docs/go/basics/packages.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Packages
|
||||
|
||||
Every Go program is made up of packages. When importing packages into a file, it is proper to use factored import statements, meaning combining all of the imports within parenthesis. The `main` package symbolizes code that will be run as a binary. Otherwise the package is a library.
|
||||
|
||||
Only names starting with Capital letters are exported from a package (public). Lowercase names will remain private within the package.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Printf("Now you have %g problems.\n", math.Sqrt(7))
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Projects
|
||||
|
||||
Projects can be broken up into subpackages so they can be imported separately. The `internal` directoy can not be imported by someone using your package. Binaries should be kept in the `cmd` folder. Within `cmd` all files and any other binary files should include `package main`. In subpackages like `auth`, `token`, `hash`, and `trace` should have their respective package names.
|
||||
|
||||
```
|
||||
project-root-directory/
|
||||
go.mod
|
||||
modname.go
|
||||
modname_test.go
|
||||
auth/
|
||||
auth.go
|
||||
auth_test.go
|
||||
token/
|
||||
token.go
|
||||
token_test.go
|
||||
hash/
|
||||
hash.go
|
||||
internal/
|
||||
trace/
|
||||
trace.go
|
||||
cmd/
|
||||
prog1/
|
||||
main.go
|
||||
prog2/
|
||||
main.go
|
||||
```
|
||||
|
||||
`go.mod` includes the module name or where the code is located. For example, if the code is kept at `github.com/someuser/modname`, then the file should include:
|
||||
|
||||
```
|
||||
module github.com/someuser/modname
|
||||
```
|
||||
|
||||
In the importing code:
|
||||
|
||||
```go
|
||||
import "github.com/someuser/modname"
|
||||
import "github.com/someuser/modname/auth"
|
||||
import "github.com/someuser/modname/auth/token"
|
||||
import "github.com/someuser/modname/hash"
|
||||
```
|
||||
|
||||
To install the binaries:
|
||||
|
||||
```bash
|
||||
$ go install github.com/someuser/modname/prog1@latest
|
||||
$ go install github.com/someuser/modname/prog2@latest
|
||||
```
|
||||
25
docs/go/basics/pointers.md
Normal file
25
docs/go/basics/pointers.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Pointers
|
||||
|
||||
Pointers, like C++, hold memory addresses of a value. The zero value of a pointer is `nil`. Pointers are declared with `*T` which is a pointer to type T. The `&` operator creates a pointer to its operand. You can access the pointer value with `*` to get and set.
|
||||
|
||||
```go
|
||||
var p *int
|
||||
i := 42
|
||||
p = &i
|
||||
fmt.Println(*p)
|
||||
*p = 21
|
||||
```
|
||||
|
||||
## Structs
|
||||
|
||||
To access values of a struct you are referencing with a pointer, you can use the shorthand of the `.` operator to access the value.
|
||||
|
||||
```go
|
||||
type Vertex struct {
|
||||
X int
|
||||
Y int
|
||||
}
|
||||
p := &Vertex{1,2}
|
||||
fmt.Println(p.Y)
|
||||
p.X = 4
|
||||
```
|
||||
164
docs/go/basics/struct.md
Normal file
164
docs/go/basics/struct.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# Structs
|
||||
|
||||
Structs are collections of fields. They are defined as types and then created using brackets to declare the values. You can also create structs with the `new` keyword which creates a pointer of type T and initializes the object with zero values.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Vertex struct {
|
||||
X int
|
||||
Y int
|
||||
}
|
||||
|
||||
v := Vertex{1, 2}
|
||||
v2 := Vertex{X: 2, Y: 4}
|
||||
fmt.Println(v.X)
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
Go does not have classes. Instead, functions can have `receivers` which relates to an instance of a struct and gets passed to the function. Methods can only be defined with a receiver that is in the same package, including built-in types.
|
||||
|
||||
By default, receivers are passed by value meaning any modifications to the receiver will not stick to the initial object. You can pass pointers as receivers to pass by reference and modify the reference. While functions require you to explicitly pass a pointer or a value, methods will automatically call the pointer method on a normal object and vice versa. In the below example, we call Scale on `v` and not `(%v).Scale(10)`. Pointer receivers also don't require you to copy the object which can be expensive for larger objects.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
type Vertex struct {
|
||||
X, Y float64
|
||||
}
|
||||
|
||||
func (v Vertex) Abs() float64 {
|
||||
return math.Sqrt(v.X*v.X + v.Y*v.Y)
|
||||
}
|
||||
|
||||
func (v *Vertex) Scale(f float64) {
|
||||
v.X = v.X * f
|
||||
v.Y = v.Y * f
|
||||
}
|
||||
|
||||
func main() {
|
||||
v := Vertex{3, 4}
|
||||
fmt.Println(v.Abs()) // 5
|
||||
v.Scale(10)
|
||||
fmt.Println(v.Abs()) // 50
|
||||
}
|
||||
```
|
||||
|
||||
## Interfaces
|
||||
|
||||
Interfaces are definitions of structs that all implement the declared methods. One note is that if the type implements the method with a pointer receiver, then the pointer to that class is considered that interface but the underlying type is not (If M took a \*T, then you would not be able to instantiate an instance of T as I).
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type I interface {
|
||||
M()
|
||||
}
|
||||
|
||||
type T struct {
|
||||
S string
|
||||
}
|
||||
|
||||
// This method means type T implements the interface I,
|
||||
// but we don't need to explicitly declare that it does so.
|
||||
func (t T) M() {
|
||||
fmt.Println(t.S)
|
||||
}
|
||||
|
||||
func main() {
|
||||
var i I = T{"hello"}
|
||||
i.M()
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
You can also check that a pointer is nil within the function to gracefully handle nil pointer exceptions at the function level.
|
||||
|
||||
```go
|
||||
type T struct {
|
||||
S string
|
||||
}
|
||||
|
||||
func (t *T) M() {
|
||||
if t == nil {
|
||||
fmt.Println("<nil>")
|
||||
return
|
||||
}
|
||||
fmt.Println(t.S)
|
||||
}
|
||||
```
|
||||
|
||||
### Empty Interfaces
|
||||
|
||||
Empty interfaces in Go are essentially an `any` type.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
var i interface{}
|
||||
// (<nil>, <nil>)
|
||||
|
||||
i = 42
|
||||
// (42, int)
|
||||
|
||||
i = "hello"
|
||||
// (hello, string)
|
||||
}
|
||||
|
||||
type any interface{}
|
||||
```
|
||||
|
||||
### Type Assertions
|
||||
|
||||
To check if an instance of an interface is actually an instance of an underlying class, you can check with the below syntax.
|
||||
|
||||
```go
|
||||
t := i.(T) // Will panic if not type T
|
||||
|
||||
t, ok := i.(T) // Returns T instance and true if that type, or zero-value of T and false if not
|
||||
|
||||
// With switch
|
||||
switch v := i.(type) {
|
||||
case T:
|
||||
// here v has type T
|
||||
case S:
|
||||
// here v has type S
|
||||
default:
|
||||
// no match; here v has the same type as i
|
||||
}
|
||||
```
|
||||
|
||||
### Common Interfaces
|
||||
|
||||
#### Stringers
|
||||
|
||||
This is an object that has a function to describe itself as a string. Many functions from `fmt` look for this method.
|
||||
|
||||
```go
|
||||
type Stringer interface {
|
||||
String() string
|
||||
}
|
||||
```
|
||||
|
||||
#### Errors
|
||||
|
||||
Return a string when the error occurs.
|
||||
|
||||
```go
|
||||
type error interface {
|
||||
Error() string
|
||||
}
|
||||
```
|
||||
84
docs/go/basics/variables.md
Normal file
84
docs/go/basics/variables.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Variables and Types
|
||||
|
||||
Variables are declared using the `var` keyword or using the initial assignment operator `:=` (only inside functions). The type of the variable comes last, and can be inferred if using the assignment operator. Variables can be at the package or function level. You can also declare constants with the `const` keyword, but cannot use the `:=` operator. Numeric constants can be used for high precision.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
var c, python, java bool
|
||||
|
||||
func main() {
|
||||
var i int
|
||||
j := 3 // int inferred
|
||||
const k = "Can't change this"
|
||||
const Big = 1 << 100 // 1 shifted left 100 times
|
||||
fmt.Println(i, c, python, java)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Types
|
||||
|
||||
The basic types in go are:
|
||||
|
||||
- bool
|
||||
- string
|
||||
- int, int8, int16, int32, int64
|
||||
- uint, uint8, uint16, uint32, uint64, uintptr
|
||||
- byte (alias for uint8)
|
||||
- rune (alias for int32, represents a Unicode code point)
|
||||
- float32, float64
|
||||
- complex64, complex128
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/cmplx"
|
||||
)
|
||||
|
||||
var (
|
||||
ToBe bool = false
|
||||
MaxInt uint64 = 1<<64 - 1
|
||||
z complex128 = cmplx.Sqrt(-5 + 12i)
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Printf("Type: %T Value: %v\n", ToBe, ToBe) // bool, false
|
||||
fmt.Printf("Type: %T Value: %v\n", MaxInt, MaxInt) // uint64, 18446744073709551615
|
||||
fmt.Printf("Type: %T Value: %v\n", z, z) // complex128, (2+3i)
|
||||
}
|
||||
```
|
||||
|
||||
### Default Values (Zero values)
|
||||
|
||||
When not given an initial value, the below types default to:
|
||||
|
||||
- bool - false
|
||||
- string - ""
|
||||
- numeric - 0
|
||||
|
||||
### Type Conversion
|
||||
|
||||
You can cast variables to different types by specifying the type in the below syntax.
|
||||
|
||||
```go
|
||||
var i int = 42
|
||||
var f float64 = float64(i)
|
||||
var u uint = uint(f)
|
||||
```
|
||||
|
||||
### Generic Types
|
||||
|
||||
Types can also be generic to work with multiple different types of arguments.
|
||||
|
||||
```go
|
||||
// Singly linked list
|
||||
type Node[T any] struct {
|
||||
next *Node[T]
|
||||
val T
|
||||
}
|
||||
```
|
||||
@@ -1 +1,11 @@
|
||||
# Go
|
||||
|
||||
Go is a statically-typed, compiled, and open source programming language created by Google.
|
||||
|
||||
## Creating a New Project
|
||||
|
||||
```bash
|
||||
mkdir <project_name>
|
||||
cd <project_name>
|
||||
go mod init gitlab.com/djdietrick/<project_name>
|
||||
```
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
[
|
||||
{
|
||||
"text": "Go Basics",
|
||||
"items": [{ "text": "Introduction", "link": "/go/" }]
|
||||
"items": [
|
||||
{ "text": "Introduction", "link": "/go/" },
|
||||
{ "text": "Packages", "link": "/go/basics/packages" },
|
||||
{ "text": "Variables and Types", "link": "/go/basics/variables" },
|
||||
{ "text": "Functions", "link": "/go/basics/functions" },
|
||||
{ "text": "Control Flow", "link": "/go/basics/control" },
|
||||
{ "text": "Collections", "link": "/go/basics/collection" },
|
||||
{ "text": "Pointers", "link": "/go/basics/pointers" },
|
||||
{ "text": "Structs", "link": "/go/basics/struct" },
|
||||
{ "text": "Concurrency", "link": "/go/basics/concurrency" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Go Advanced",
|
||||
"items": [
|
||||
{"text": "Context", "link": "/go/advanced/context"},
|
||||
{"text": "Flags", "link": "/go/advanced/flags"},
|
||||
{ "text": "Testing", "link": "/go/advanced/testing" },
|
||||
{"text": "HTTP", "link": "/go/advanced/http"},
|
||||
{"text": "RPC", "link": "/go/advanced/rpc"},
|
||||
{"text": "gRPC", "link": "/go/advanced/grpc"},
|
||||
{"text": "Databases", "link": "/go/advanced/db"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -16,3 +16,33 @@ An array is a linear collection of data values that are accessible at numbered i
|
||||
- Removing from end: O(1)
|
||||
- Copying: O(n)
|
||||
- Traversing (including mapping, filtering, etc): O(n)
|
||||
|
||||
## Common Algorithms
|
||||
|
||||
### Binary Search
|
||||
|
||||
Binary search is useful when trying to find values in a sorted array. It works by picking a middle point to check, and then dividing the array in half based on whether the value is too big or small.
|
||||
|
||||
```python
|
||||
def binarySearch(nums, target):
|
||||
"""
|
||||
:type nums: List[int]
|
||||
:type target: int
|
||||
:rtype: int
|
||||
"""
|
||||
if len(nums) == 0:
|
||||
return -1
|
||||
|
||||
left, right = 0, len(nums) - 1
|
||||
while left <= right:
|
||||
mid = (left + right) // 2
|
||||
if nums[mid] == target:
|
||||
return mid
|
||||
elif nums[mid] < target:
|
||||
left = mid + 1
|
||||
else:
|
||||
right = mid - 1
|
||||
|
||||
# End Condition: left > right
|
||||
return -1
|
||||
```
|
||||
|
||||
@@ -31,3 +31,136 @@ The space complexity of a graph is O(v+e) where v is the number of vertices and
|
||||
|
||||
- Depth First Search Traversing: O(v+e)
|
||||
- Bredth First Search Traversing: O(v+e)
|
||||
|
||||
## Searching
|
||||
|
||||
To search a graph, you can use either DFS or BFS.
|
||||
|
||||
```python
|
||||
def DFS(graph, start):
|
||||
visited = set() # keep track of the visited nodes
|
||||
stack = [start] # use a stack to keep track of nodes to visit next
|
||||
|
||||
while stack:
|
||||
node = stack.pop() # get the next node to visit
|
||||
if node not in visited:
|
||||
visited.add(node) # mark the node as visited
|
||||
print(node, end=' ') # visit the node (print its value in this case)
|
||||
stack.extend(graph[node] - visited) # add the node's neighbors to the stack, able to remove seen nodes in python for efficiency
|
||||
|
||||
def BFS(graph, start):
|
||||
visited = set() # Keep track of the nodes that we've visited
|
||||
queue = [start] # Use a queue to implement the BFS
|
||||
|
||||
while queue:
|
||||
node = queue.pop() # Dequeue a node from front of queue
|
||||
if node not in visited:
|
||||
visited.add(node) # Mark the node as visited
|
||||
print(node, end=' ') # Visit the node (print its value in this case)
|
||||
queue.extend(graph[node]) # Enqueue all neighbours
|
||||
|
||||
graph = {
|
||||
'A': ['B', 'C'],
|
||||
'B': ['A', 'D', 'E'],
|
||||
'C': ['A', 'F'],
|
||||
'D': ['B'],
|
||||
'E': ['B', 'F'],
|
||||
'F': ['C', 'E'],
|
||||
}
|
||||
|
||||
DFS(graph, 'A') # Output: A C F E B D
|
||||
BFS(graph, 'A') # Output: A B C D E F
|
||||
```
|
||||
|
||||
## Finding Paths
|
||||
|
||||
The below algorithms find all possible paths from the start to goal. This is assuming all edges have equal weights. If you need to find the shortest path with weighted graphs, you can add the weight to the tuple stored in the stack and find the shortest after all paths have been found.
|
||||
|
||||
```python
|
||||
def dfs_paths(graph, start, goal):
|
||||
stack = [(start, [start])]
|
||||
while stack:
|
||||
(vertex, path) = stack.pop()
|
||||
# Again we are using python to subtract nodes already in the path
|
||||
# You could also check if next is not in the current path instead
|
||||
for next in graph[vertex] - set(path):
|
||||
# if next in path: continue
|
||||
if next == goal:
|
||||
yield path + [next]
|
||||
else:
|
||||
stack.append((next, path + [next]))
|
||||
|
||||
# Will always have shortest path first (if all edges are equal)
|
||||
def bfs_paths(graph, start, goal):
|
||||
queue = [(start, [start])]
|
||||
while queue:
|
||||
(vertex, path) = queue.pop(0)
|
||||
for next in graph[vertex] - set(path):
|
||||
# if next in path: continue
|
||||
if next == goal:
|
||||
yield path + [next]
|
||||
else:
|
||||
queue.append((next, path + [next]))
|
||||
```
|
||||
|
||||
### Finding Shortest Path (Djikstra's Algorithm)
|
||||
|
||||
```python
|
||||
from collections import defaultdict
|
||||
|
||||
class Graph():
|
||||
def __init__(self):
|
||||
"""
|
||||
self.edges is a dict of all possible next nodes
|
||||
e.g. {'X': ['A', 'B', 'C', 'E'], ...}
|
||||
self.weights has all the weights between two nodes,
|
||||
with the two nodes as a tuple as the key
|
||||
e.g. {('X', 'A'): 7, ('X', 'B'): 2, ...}
|
||||
"""
|
||||
self.edges = defaultdict(list)
|
||||
self.weights = {}
|
||||
|
||||
def add_edge(self, from_node, to_node, weight):
|
||||
# Note: assumes edges are bi-directional
|
||||
self.edges[from_node].append(to_node)
|
||||
self.edges[to_node].append(from_node)
|
||||
self.weights[(from_node, to_node)] = weight
|
||||
self.weights[(to_node, from_node)] = weight
|
||||
|
||||
def dijsktra(graph, initial, end):
|
||||
# shortest paths is a dict of nodes
|
||||
# whose value is a tuple of (previous node, weight)
|
||||
shortest_paths = {initial: (None, 0)}
|
||||
current_node = initial
|
||||
visited = set()
|
||||
|
||||
while current_node != end:
|
||||
visited.add(current_node)
|
||||
destinations = graph.edges[current_node]
|
||||
weight_to_current_node = shortest_paths[current_node][1]
|
||||
|
||||
for next_node in destinations:
|
||||
weight = graph.weights[(current_node, next_node)] + weight_to_current_node
|
||||
if next_node not in shortest_paths:
|
||||
shortest_paths[next_node] = (current_node, weight)
|
||||
else:
|
||||
current_shortest_weight = shortest_paths[next_node][1]
|
||||
if current_shortest_weight > weight:
|
||||
shortest_paths[next_node] = (current_node, weight)
|
||||
|
||||
next_destinations = {node: shortest_paths[node] for node in shortest_paths if node not in visited}
|
||||
if not next_destinations:
|
||||
return "Route Not Possible"
|
||||
# next node is the destination with the lowest weight
|
||||
current_node = min(next_destinations, key=lambda k: next_destinations[k][1])
|
||||
|
||||
# Work back through destinations in shortest path
|
||||
path = []
|
||||
while current_node is not None:
|
||||
path.append(current_node)
|
||||
next_node = shortest_paths[current_node][0]
|
||||
current_node = next_node
|
||||
# Reverse path
|
||||
path = path[::-1]
|
||||
return path
|
||||
```
|
||||
|
||||
18
docs/interview/ds/heap.md
Normal file
18
docs/interview/ds/heap.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Heap
|
||||
|
||||
A heap is a specialized tree-based data structure that satisfies the heap property: in a max-heap, for any given node I, the value of I is greater than or equal to the values of its children, while a min-heap has the value of I less than or equal to its children. Heaps are commonly implemented as arrays.
|
||||
|
||||
Common applications include:
|
||||
- Priority queues
|
||||
- Heap sort algorithm
|
||||
- Finding kth largest/smallest elements
|
||||
- Implementing efficient graph algorithms (Dijkstra's)
|
||||
|
||||
## Time Complexities
|
||||
|
||||
- Insert element: O(log n)
|
||||
- Delete element: O(log n)
|
||||
- Get min/max element: O(1)
|
||||
- Build heap from array: O(n)
|
||||
- Heapify (fix heap property): O(log n)
|
||||
- Search for element: O(n)
|
||||
170
docs/interview/ds/patterns.md
Normal file
170
docs/interview/ds/patterns.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# Patterns
|
||||
|
||||
## Prefix Sum
|
||||
|
||||
Useful for finding the sum of elements in a subarray. In this pattern, you would store the sum of the elements up the that point in an array.
|
||||
|
||||
```python
|
||||
array = [1,2,3,4,5,6,7]
|
||||
prefix_sum = [1,3,6,10,15,21,28]
|
||||
|
||||
sum[i,j] = prefix_sum[j] - prefix_sum[i-1]
|
||||
```
|
||||
|
||||
## Two Pointers
|
||||
|
||||
Can be helpful in array problems where you iterate from the beginning and end of the array, moving the pointers towards each other until they meet. You can also start from the middle and work outwards.
|
||||
|
||||
## Sliding Window
|
||||
|
||||
Helpful for finding subarrays that meet a criteria. You can check sums of subarrays by creating a window of length k, check the currently selected elements, and then when moving the window subtract the left-most element and add the new right-most element.
|
||||
|
||||
```python
|
||||
def max_subarray(arr, k):
|
||||
n = len(arr)
|
||||
window_sum = sum(arr[:k])
|
||||
max_sum = window_sum
|
||||
max_start_index = 0
|
||||
for i in range(n-k):
|
||||
window_sum = window_sum - arr[i] + arr[i+k]
|
||||
if window_sum > max_sum:
|
||||
max_sum = window_sum
|
||||
max_start_index = i + 1
|
||||
|
||||
return arr[max_start_index:max_start_index + k], max_sum
|
||||
```
|
||||
|
||||
## Fast and Slow Pointers
|
||||
|
||||
Useful for working with arrays and linked lists. Slow pointer moves 1 node per loop, and the faster moves 2 nodes per loop. This can be used when trying to find a cycle in a linked list, or if trying to find the middle of a linked-list, since when the fast pointer is at the end, the slow pointer is in the middle.
|
||||
|
||||
## Linked List In-Place Reversal
|
||||
|
||||
You can do this by creating 3 pointers: `prev`, `curr`, and `next`.
|
||||
|
||||
```python
|
||||
def reverse_linked_list(head):
|
||||
prev = None
|
||||
curr = head
|
||||
while curr is not None:
|
||||
next = curr.next
|
||||
curr.next = prev
|
||||
prev = curr
|
||||
curr = next
|
||||
return prev
|
||||
```
|
||||
|
||||
## Monotonic Stack
|
||||
|
||||
Useful for finding the next greatest or next smaller element in an array.
|
||||
|
||||
```python
|
||||
# Find the next greater element for each item in the array
|
||||
def next_greater_element(arr):
|
||||
n = len(arr)
|
||||
stack = []
|
||||
result = [-1] * n
|
||||
|
||||
for i in range(n):
|
||||
while stack and arr[i] > arr[stack[-1]]:
|
||||
result[stack.pop()] = arr[i]
|
||||
|
||||
stack.append(i)
|
||||
|
||||
return result
|
||||
|
||||
arr = [1,4,6,3,2,7]
|
||||
result = next_greater_element(arr) # [4,6,7,7,7,-1]
|
||||
```
|
||||
|
||||
## Top K Elements
|
||||
|
||||
This pattern helps find the top K elements in a dataset that satisfy a criteria (greatest, least, etc). This can be done by using a min/max-heap, which only has K slots, where the top element is the next one to check against. Since popping and inserting in a heap is O(logn), then the time complexity of this is O(n\*logk)
|
||||
|
||||
## Overlapping Intervals
|
||||
|
||||
This pattern can be used when solving problems involving integers or ranges where they may overlap and finding intersections. This is done by sorting all intervals by the start, and then checking if the interval overlaps with the previous interval (start of next element is greater than end of previous element).
|
||||
|
||||
```python
|
||||
# Merging Intervals
|
||||
[[1,3], [2,6], [8,10], [15,18]]
|
||||
[[1,6], [8,10], [15,28]]
|
||||
|
||||
# Interval intersection
|
||||
[[0,2], [5,10], [13,23], [24,25]], [[1,5], [8,12], [15,24], [25,26]]
|
||||
[[1,2], [5,5], [8,10], [15,23], [24,24], [25,25]]
|
||||
|
||||
# Insert interval
|
||||
[[1,2], [3,5], [6,7], [8,10], [12,16]], [4,8]
|
||||
[[1,2], [3,10], [12,16]]
|
||||
```
|
||||
|
||||
## Modified Binary Search
|
||||
|
||||
This pattern is helpful when solving problems where you need to find elements in an imperfectly sorted array (nearly sorted, rotated sorted, unknown length, containing duplicates). It can also be helpful finding the first or last occurance of an element, finding the square root of a number, and finding a peak element.
|
||||
|
||||
```python
|
||||
def search_rotated_array(nums, target):
|
||||
left, right = 0, len(nums)-1
|
||||
while left <= right:
|
||||
mid = (left + right) // 2
|
||||
if nums[mid] == target:
|
||||
return mid
|
||||
if nums[left] <= nums[mid]:
|
||||
if nums[left] <= target < nums[mid]:
|
||||
right = mid - 1
|
||||
else:
|
||||
left = mid + 1
|
||||
else:
|
||||
if nums[mid] < target <= nums[right]:
|
||||
left = mid + 1
|
||||
else:
|
||||
right = mid - 1
|
||||
|
||||
return -1
|
||||
|
||||
print(search_rotated_array([4,5,6,7,0,1,2]))
|
||||
```
|
||||
|
||||
## Binary Tree Traversal
|
||||
|
||||
Traversing a tree can use either BFS or DFS, but which one you use can depend on what the use case is.
|
||||
|
||||
- To retrieve values, use DFS inorder traversal.
|
||||
- To create a copy of a tree, use DFS preorder traversal.
|
||||
- To process child nots before the parent (like deleting a tree), use DFS postorder traversal.
|
||||
- If you need to explore all nodes at current level before moving on, use BFS.
|
||||
|
||||
## Matrix Traversal
|
||||
|
||||
Matrix traversal can be seen as graph problems, so you can use DFS or BFS to solve a lot of these problems.
|
||||
|
||||
```python
|
||||
# Number of Islands problem
|
||||
def count_islands(grid):
|
||||
if not grid or not grid[0]: return 0
|
||||
|
||||
rows, cols = len(grid), len(grid[0])
|
||||
islands = 0
|
||||
|
||||
def dfs(i,j):
|
||||
if i < 0 or i >= rows or j < 0 or j >= cols or grid[i][j] != '1':
|
||||
return
|
||||
|
||||
grid[i][j] = '0'
|
||||
dfs(i+1, j)
|
||||
dfs(i-1, j)
|
||||
dfs(i, j+1)
|
||||
dfs(i, j-1)
|
||||
|
||||
for i in range(rows):
|
||||
for j in range(cols):
|
||||
if grid[i][j] == "1":
|
||||
dfs(i,j)
|
||||
islands += 1
|
||||
return islands
|
||||
```
|
||||
|
||||
## Backtracking
|
||||
|
||||
This can be used to find all potential solution paths and backtracking the paths that do not lead to a solution. This can be helpful for finding permutations, combinations, and all possible paths from start to end points.
|
||||
@@ -19,3 +19,39 @@ Each node in a binary tree has at most 2 children. Many operations on a binary
|
||||
### Binary Search Tree
|
||||
|
||||
A binary search tree or BST is a tree where the left child node is always less than the parent, and the right child node is always greater. This allows for very fast searching since it is easy to make a decision on which path to check down next.
|
||||
|
||||
## Searching
|
||||
|
||||
There are two main methods for searching trees: **Breadth First Search** and **Depth First Search**. In BFS, we traverse the tree level by level, and in DFS we traverse sub-tree by sub-tree, going down to the bottom and working our way up. BFS uses a queue (FIFO) and DFS uses a stack (LIFO). Within DFS we can traverse in order (Left-Root-Right), preorder (Root-Left-Right), or postorder (Left-Right-Root).
|
||||
|
||||
```python
|
||||
def BFS(root):
|
||||
if root is None:
|
||||
return
|
||||
|
||||
queue = [root]
|
||||
|
||||
while queue:
|
||||
node = queue.pop() # dequeue a node from the front of the queue
|
||||
print(node.value, end=' ') # visit the node (print its value in this case)
|
||||
|
||||
# Can also be done with n children if not binary tree
|
||||
# enqueue left child
|
||||
if node.left:
|
||||
queue.append(node.left)
|
||||
# enqueue right child
|
||||
if node.right:
|
||||
queue.append(node.right)
|
||||
|
||||
def DFS(node):
|
||||
if node is None:
|
||||
return
|
||||
|
||||
# This example is pre-order, but just rearrange the order in which you check to make it in-order or post-order
|
||||
# Visit the node (print its value in this case)
|
||||
print(node.value, end=' ')
|
||||
# Recursively call DFS on the left child
|
||||
DFS(node.left)
|
||||
# Recursively call DFS on the right child
|
||||
DFS(node.right)
|
||||
```
|
||||
|
||||
181
docs/interview/languages/javascript.md
Normal file
181
docs/interview/languages/javascript.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# JavaScript
|
||||
|
||||
## Arrays
|
||||
|
||||
### Creating
|
||||
|
||||
```javascript
|
||||
let a = [];
|
||||
let a = [1, 2, 3];
|
||||
let a = Array(3).fill(0); // [0,0,0]
|
||||
let a = Array.from({ length: 5 }, (_, i) => i + 1); // [1,2,3,4,5]
|
||||
let a = [...Array(5).keys()]; // [0,1,2,3,4]
|
||||
```
|
||||
|
||||
### Accessing
|
||||
|
||||
```javascript
|
||||
const a = [1, 2, 3];
|
||||
console.log(a[0]); // 1
|
||||
console.log(a.at(-1)); // 3 (last element)
|
||||
```
|
||||
|
||||
### Manipulating
|
||||
|
||||
#### Adding
|
||||
```javascript
|
||||
const a = [];
|
||||
a.push(1); // Add to end
|
||||
a.unshift(2); // Add to beginning
|
||||
a.splice(1, 0, 3); // Insert 3 at index 1
|
||||
[...a, 4]; // Create new array with 4 added
|
||||
```
|
||||
|
||||
#### Removing
|
||||
```javascript
|
||||
const arr = ['bread', 'butter', 'eggs', 'milk'];
|
||||
arr.pop(); // returns and removes 'milk'
|
||||
arr.shift(); // returns and removes 'bread'
|
||||
arr.splice(1, 1); // removes 'butter'
|
||||
```
|
||||
|
||||
#### Slicing
|
||||
```javascript
|
||||
const a = ['spam', 'egg', 'bacon', 'tomato', 'ham', 'lobster'];
|
||||
a.slice(2, 5); // ['bacon', 'tomato', 'ham']
|
||||
a.slice(-3); // ['tomato', 'ham', 'lobster']
|
||||
a.slice(2); // ['bacon', 'tomato', 'ham', 'lobster']
|
||||
[...a]; // Create a copy of an array
|
||||
```
|
||||
|
||||
#### Sorting
|
||||
```javascript
|
||||
const nums = [3, 1, 3, 2, 5];
|
||||
nums.sort(); // [1, 2, 3, 3, 5]
|
||||
nums.reverse(); // [5, 3, 3, 2, 1]
|
||||
[...nums].sort((a, b) => b - a); // Sort descending, creates new array
|
||||
|
||||
const items = [
|
||||
{ name: 'john', grade: 'A', age: 15 },
|
||||
{ name: 'jane', grade: 'B', age: 12 },
|
||||
{ name: 'dave', grade: 'B', age: 10 }
|
||||
];
|
||||
items.sort((a, b) => a.age - b.age);
|
||||
```
|
||||
|
||||
#### Filtering
|
||||
```javascript
|
||||
const odds = [1, 2, 3, 4, 5].filter(x => x % 2 === 1);
|
||||
const evens = Array.from({ length: 20 }, (_, i) => i + 1).filter(x => x % 2 === 0);
|
||||
```
|
||||
|
||||
## Strings
|
||||
|
||||
Strings in JavaScript are immutable but have many built-in methods.
|
||||
|
||||
### Helper Functions
|
||||
|
||||
```javascript
|
||||
const str = 'spam';
|
||||
'I saw spamalot!'.includes(str); // true
|
||||
'I saw The Holy Grail!'.includes(str); // false
|
||||
|
||||
'12345'.repeat(5); // '1234512345123451234512345'
|
||||
|
||||
['John', 'Peter', 'Vicky'].join('#'); // 'John#Peter#Vicky'
|
||||
'Hello, world!'.endsWith('!'); // true
|
||||
'Hello, world!'.startsWith('H'); // true
|
||||
'Hello, world!'.substring(0, 5); // 'Hello'
|
||||
'Hello, world!'.slice(-6); // 'world!'
|
||||
```
|
||||
|
||||
## Objects
|
||||
|
||||
### Creating
|
||||
|
||||
```javascript
|
||||
const obj = {};
|
||||
const obj = { color: 'green', points: 5 };
|
||||
const obj = Object.create(null);
|
||||
const obj = new Object();
|
||||
```
|
||||
|
||||
### Accessing
|
||||
|
||||
```javascript
|
||||
obj.color;
|
||||
obj['color'];
|
||||
obj?.points; // Optional chaining
|
||||
const { color, points } = obj; // Destructuring
|
||||
|
||||
obj.age = 30;
|
||||
Object.assign(obj, { height: 180 }); // Merge properties
|
||||
```
|
||||
|
||||
### Removing
|
||||
|
||||
```javascript
|
||||
delete obj.age;
|
||||
```
|
||||
|
||||
### Iterating
|
||||
|
||||
```javascript
|
||||
Object.keys(obj).forEach(key => {
|
||||
// do something
|
||||
});
|
||||
|
||||
Object.values(obj).forEach(value => {
|
||||
// do something
|
||||
});
|
||||
|
||||
Object.entries(obj).forEach(([key, value]) => {
|
||||
// do something
|
||||
});
|
||||
```
|
||||
|
||||
### Object Methods
|
||||
|
||||
```javascript
|
||||
const keys = Object.keys(obj);
|
||||
const values = Object.values(obj);
|
||||
const entries = Object.entries(obj);
|
||||
const merged = Object.assign({}, obj1, obj2);
|
||||
const clone = { ...obj };
|
||||
|
||||
// Prevent modifications
|
||||
Object.freeze(obj);
|
||||
Object.seal(obj);
|
||||
```
|
||||
|
||||
## Sets and Maps
|
||||
|
||||
### Set
|
||||
|
||||
```javascript
|
||||
const set = new Set();
|
||||
set.add(1);
|
||||
set.has(1); // true
|
||||
set.delete(1);
|
||||
set.clear();
|
||||
|
||||
const unique = [...new Set([1, 2, 2, 3, 3])]; // [1, 2, 3]
|
||||
```
|
||||
|
||||
### Map
|
||||
|
||||
```javascript
|
||||
const map = new Map();
|
||||
map.set('key', 'value');
|
||||
map.get('key');
|
||||
map.has('key');
|
||||
map.delete('key');
|
||||
map.clear();
|
||||
|
||||
// Object to Map
|
||||
const map = new Map(Object.entries(obj));
|
||||
|
||||
// Map to Object
|
||||
const obj = Object.fromEntries(map);
|
||||
```
|
||||
|
||||
209
docs/interview/languages/python.md
Normal file
209
docs/interview/languages/python.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# Python
|
||||
|
||||
## Arrays
|
||||
|
||||
### Creating
|
||||
|
||||
```python
|
||||
a = []
|
||||
a = [1,2,3]
|
||||
a = list(range(1,11)) # 1 ... 10
|
||||
a = [x for x in [3, 4, 5, 6, 7] if x > 5]
|
||||
a = [0] * 3 # [0,0,0]
|
||||
```
|
||||
|
||||
### Accessing
|
||||
|
||||
```python
|
||||
a = [1,2,3]
|
||||
print(a[0]) # 1
|
||||
print(a[-1]) # 3
|
||||
```
|
||||
|
||||
### Manipulating
|
||||
|
||||
#### Adding
|
||||
```python
|
||||
a = []
|
||||
a.append(1)
|
||||
a.extend([9, 11, 13])
|
||||
a += [6,7,8]
|
||||
a.insert(0, 2) # Insert 2 at index 0
|
||||
```
|
||||
|
||||
#### Removing
|
||||
```python
|
||||
li = ['bread', 'butter', 'eggs', 'milk']
|
||||
li.pop() # returns and removes 'milk'
|
||||
li.pop(2) # returns and removes 'eggs'
|
||||
del li[0] # removes 'bread'
|
||||
li.remove('butter')
|
||||
```
|
||||
|
||||
#### Slicing
|
||||
```python
|
||||
# a_list[start:end]
|
||||
# a_list[start:end:step]
|
||||
a = ['spam', 'egg', 'bacon', 'tomato', 'ham', 'lobster']
|
||||
a[2:5] # ['bacon', 'tomato', 'ham']
|
||||
a[-3:] # ['tomato', 'ham', 'lobster']
|
||||
a[-5:-2] # ['egg', 'bacon', 'tomato']
|
||||
a[:4] # ['spam', 'egg', 'bacon', 'tomato']
|
||||
a[2:] # ['bacon', 'tomato', 'ham', 'lobster']
|
||||
a[0:6:2] # ['spam', 'bacon', 'ham']
|
||||
a[6:0:-2] # ['lobster', 'tomato', 'egg']
|
||||
a[::-1] # ['lobster', 'ham', 'tomato', 'bacon', 'egg', 'spam']
|
||||
a[:] # Create a copy of an array
|
||||
```
|
||||
|
||||
#### Sorting
|
||||
```python
|
||||
li = [3, 1, 3, 2, 5]
|
||||
li.sort() # [1, 2, 3, 3, 5]
|
||||
li.reverse() # [5, 3, 3, 2, 1]
|
||||
sorted(li) # returns a sorted copy of li
|
||||
|
||||
li = [
|
||||
('john', 'A', 15),
|
||||
('jane', 'B', 12),
|
||||
('dave', 'B', 10)
|
||||
]
|
||||
sorted(li, key=lambda x: x[2])
|
||||
sorted(li, cmp=lambda x,y: 1 if x<y else -1) # Negative return means second number is "less than"
|
||||
|
||||
```
|
||||
|
||||
#### Filtering
|
||||
```python
|
||||
a = list(filter(lambda x : x % 2 == 1, range(1, 20)))
|
||||
```
|
||||
|
||||
## Strings
|
||||
|
||||
Strings act a lot like arrays/lists in Python, so many of the methods still apply.
|
||||
|
||||
### Helper Functions
|
||||
|
||||
```python
|
||||
s = 'spam'
|
||||
s in 'I saw spamalot!' # True
|
||||
s not in 'I saw The Holy Grail!' # True
|
||||
|
||||
s = '12345' * 5 # '1234512345123451234512345'
|
||||
|
||||
"#".join(["John", "Peter", "Vicky"]) # 'John#Peter#Vicky'
|
||||
"Hello, world!".endswith("!") # True
|
||||
"Hello, world!".startswith("H") # True
|
||||
```
|
||||
|
||||
## Dictionaries
|
||||
|
||||
### Creating
|
||||
|
||||
```python
|
||||
d = {}
|
||||
d = {'color': 'green', 'points', 5}
|
||||
```
|
||||
|
||||
### Accessing
|
||||
|
||||
```python
|
||||
d['color']
|
||||
d.get('points') # Returns None or value of points
|
||||
d.get('points', 0) # Default value
|
||||
|
||||
d['age'] = 30
|
||||
```
|
||||
|
||||
### Removing
|
||||
|
||||
```python
|
||||
del d['age']
|
||||
```
|
||||
|
||||
### Iterating
|
||||
|
||||
```python
|
||||
for key, value in d.items():
|
||||
# do something
|
||||
|
||||
for key in d.keys():
|
||||
# do something
|
||||
|
||||
for value in d.values():
|
||||
# do something
|
||||
```
|
||||
|
||||
### Zipping
|
||||
|
||||
```python
|
||||
group_1 = ['kai', 'abe', 'ada', 'gus', 'zoe']
|
||||
group_2 = ['jen', 'eva', 'dan', 'isa', 'meg']
|
||||
pairings = {name:name_2 for name, name_2 in zip(group_1, group_2)}
|
||||
# {'kai': 'jen', 'abe': 'eva', 'ada': 'dan', 'gus': 'isa', 'zoe': 'meg'}
|
||||
```
|
||||
|
||||
### Sorting
|
||||
|
||||
#### By Key
|
||||
```python
|
||||
my_dict = {'b': 2, 'a': 1, 'c': 3}
|
||||
sorted_dict_by_keys = sorted(my_dict.items())
|
||||
# [('a', 1), ('b', 2), ('c', 3)]
|
||||
```
|
||||
|
||||
#### By Value
|
||||
`dict.items()` returns a tuple of the key value pairs for each iteration, so we can use sorted and use the value of the pair to return a list of the sorted pairs.
|
||||
```python
|
||||
my_dict = {'b': 2, 'a': 1, 'c': 3}
|
||||
sorted_dict_by_values = sorted(my_dict.items(), key=lambda item: item[1])
|
||||
# [('a', 1), ('b', 2), ('c', 3)]
|
||||
```
|
||||
|
||||
#### OrderedDict
|
||||
OrderedDicts preserve the order in which the items were added, whereas normal dicts do not guarantee the order upon iteration. So you can sort the dictionary by key or value, and then pass that into OrderedDict to have a dict-like structure that preserves the inserted order.
|
||||
|
||||
```python
|
||||
from collections import OrderedDict
|
||||
|
||||
my_dict = {'b': 2, 'a': 4, 'c': 3}
|
||||
sorted_dict_by_keys = OrderedDict(sorted(my_dict.items()))
|
||||
for key, value in sorted_dict_by_keys:
|
||||
# do something
|
||||
```
|
||||
|
||||
## Heap
|
||||
|
||||
`heapq` is a module that implements a heap structure on a list of items. It's helpful for finding kth largest/smallest elements and priority queues.
|
||||
|
||||
```python
|
||||
import heapq
|
||||
|
||||
li = [10, 20, 15, 30, 40]
|
||||
|
||||
heapq.heapify(li)
|
||||
# [10, 20, 15, 30, 40]
|
||||
|
||||
# Appending an element
|
||||
heapq.heappush(h, 5)
|
||||
|
||||
# Pop the smallest element from the heap
|
||||
min = heapq.heappop(h)
|
||||
|
||||
# Push a new element (5), then pop the smallest element and return
|
||||
min = heapq.heappushpop(h, 5)
|
||||
|
||||
# Pop the smallest element, and then push new element (5)
|
||||
min = heapq.heapreplace(h, 5)
|
||||
|
||||
# Find the 3 largest elements
|
||||
maxi = heapq.nlargest(3, h)
|
||||
# [40, 30, 20]
|
||||
|
||||
# Find the 3 smallest elements
|
||||
min = heapq.nsmallest(3, h)
|
||||
# [10, 15, 20]
|
||||
|
||||
# Merge heaps
|
||||
h3 = list(heapq.merge(h1, h2))
|
||||
```
|
||||
@@ -17,7 +17,9 @@
|
||||
{"text": "Stacks and Queues", "link": "/interview/ds/stackqueue"},
|
||||
{"text": "Strings", "link": "/interview/ds/string"},
|
||||
{"text": "Graphs", "link": "/interview/ds/graph"},
|
||||
{"text": "Trees", "link": "/interview/ds/tree"}
|
||||
{"text": "Trees", "link": "/interview/ds/tree"},
|
||||
{"text": "Heap", "link": "/interview/ds/heap"},
|
||||
{"text": "Patterns", "link": "/interview/ds/patterns"}
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -48,5 +50,12 @@
|
||||
{"text": "Facade", "link": "/interview/patterns/structure/facade"}
|
||||
]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Languages Specific",
|
||||
"items": [
|
||||
{"text": "Python", "link": "/interview/languages/python"},
|
||||
{"text": "Javascript", "link": "/interview/languages/javascript"}
|
||||
]
|
||||
}
|
||||
]
|
||||
73
docs/java/basics/classes.md
Normal file
73
docs/java/basics/classes.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Classes
|
||||
|
||||
## Generics
|
||||
|
||||
Generics allow you to create a class that can hold many different kinds of data types, and specify an upper bound for what kind of interfaces are allowed to be stored and manipulated.
|
||||
|
||||
```java
|
||||
|
||||
```
|
||||
|
||||
## Nested Classes
|
||||
|
||||
You can also have classes defined within another classes. If the nested class is static, then it can be instantiated from the class itself just like any other static methods. Otherwise, you need an instance of the class to access the nested class.
|
||||
|
||||
### Static
|
||||
|
||||
```java
|
||||
public class Employee {
|
||||
public static class EmployeeComparator <T extends Employee> implements Comparator<Employee> {
|
||||
private String sortType;
|
||||
|
||||
public EmployeeComparator(String sortType) {
|
||||
this.sortType = sortType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Employee o1, Employee o2) {
|
||||
if (sortType == "yearStarted") {
|
||||
// sort by year started
|
||||
}
|
||||
// able to access private variables in Employee, and vice versa
|
||||
return o1.name.compareTo(o2.name);
|
||||
}
|
||||
}
|
||||
|
||||
private int employeeId;
|
||||
private String name;
|
||||
private int yearStarted;
|
||||
}
|
||||
|
||||
public static void main() {
|
||||
List<Employee> employees = new ArrayList<>();
|
||||
|
||||
employees.sort(new Employee.EmployeeComparator<>());
|
||||
}
|
||||
```
|
||||
|
||||
### Non-static
|
||||
|
||||
```java
|
||||
public class StoreEmployee extends Employee {
|
||||
private String store;
|
||||
|
||||
public class StoreComparator <T extends Employee> implements Comparator<StoreEmployee> {
|
||||
public int compare(StoreEmployee o1, StoreEmployee o2) {
|
||||
int result = o1.store.compareTo(o2.store);
|
||||
if (result == 0) {
|
||||
return new Employee.EmployeeComparator<>("yearStarted").compare(o1, o2);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main() {
|
||||
List<StoreEmployee> storeEmployees = new ArrayList<>();
|
||||
|
||||
var genericEmployee = new StoreEmployee();
|
||||
var comparator = genericEmployee.new StoreComparator<>();
|
||||
var comparator = new StoreEmployee().new StoreComparator<>();
|
||||
storeEmployees.sort(comparator);
|
||||
}
|
||||
```
|
||||
51
docs/java/basics/collections.md
Normal file
51
docs/java/basics/collections.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Collections
|
||||
|
||||
Java has a class called `java.util.Collections` that defines all of the built in data types, but also provides some helper methods.
|
||||
|
||||
## Helper Methods
|
||||
|
||||
```java
|
||||
public static void main() {
|
||||
Card[] cardArray = new Card[13];
|
||||
Card aceOfHearts = Card.getFaceCard(Card.Suit.HEART, 'A');
|
||||
Arrays.fill(cardArray, aceOfHearts);
|
||||
Card.printDeck(Arrays.asList(cardArray), "Aces of Hearts", 1);
|
||||
|
||||
List<Card> cards = new ArrayList<>(52);
|
||||
// doesn't add any cards because capacity is 52 but size is 0
|
||||
Collections.fill(cards, aceOfHearts);
|
||||
System.out.println(cards);
|
||||
System.out.println("cards.size() = " + cards.size());
|
||||
|
||||
// Returns a list with n copies
|
||||
List<Card> acesOfHearts = Collections.nCopies(13, aceOfHearts);
|
||||
Card.printDeck(acesOfHearts, "Aces of Hearts", 1);
|
||||
|
||||
Card kingOfClubs = Card.getFaceCard(Card.Suit.CLUB, 'K');
|
||||
List<Card> kingsOfClubs = Collections.nCopies(13, kingOfClubs);
|
||||
Card.printDeck(kingsOfClubs, "Kings of Clubs", 1);
|
||||
|
||||
// adds elements to empty lists
|
||||
Collections.addAll(cards, cardArray);
|
||||
Collections.addAll(cards, cardArray);
|
||||
Card.printDeck(cards, "Card Collection with Aces added", 2);
|
||||
|
||||
// copyies over elements at the same index
|
||||
// in this example we have 26 cards already, so only the first 13 get overwritten
|
||||
Collections.copy(cards, kingsOfClubs);
|
||||
Card.printDeck(cards, "Card Collection with Kings copied", 2);
|
||||
|
||||
Collections.shuffle(cards);
|
||||
Collections.reverse(cards);
|
||||
|
||||
var sortingAlgorithm = Comparator.comparing(Card::rank)
|
||||
.thenComparing(Card::suit);
|
||||
Collections.sort(cards, sortingAlgorithm);
|
||||
|
||||
List<Card> subList = new ArrayList<>(cards.subList(0,4));
|
||||
int subListIndex = Collections.indexOfSubList(cards, subList);
|
||||
|
||||
// Returns true if no elements in second list are found in first list
|
||||
boolean disjoint = Collections.disjoint(cards, subList);
|
||||
}
|
||||
```
|
||||
28
docs/java/basics/controlflow.md
Normal file
28
docs/java/basics/controlflow.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Control Flow
|
||||
|
||||
## Loops
|
||||
|
||||
### For Loop
|
||||
|
||||
```java
|
||||
for(int i = 0; i < 10; i++) {
|
||||
// do something
|
||||
}
|
||||
|
||||
List<String> myStrings = new ArrayList<>(List.of("hello", "world"));
|
||||
for (String s : myStrings) {
|
||||
// do something
|
||||
}
|
||||
for (var s : myStrings) {
|
||||
// do something
|
||||
}
|
||||
```
|
||||
|
||||
#### Interable Interfaces
|
||||
|
||||
Many collections implement the `Iterable` interface, which has a method for each.
|
||||
|
||||
```java
|
||||
List<String> words = new ArrayList<>(List.of("hello", "world"));
|
||||
words.forEach((s) -> System.out.println(s));
|
||||
```
|
||||
36
docs/java/basics/interfaces.md
Normal file
36
docs/java/basics/interfaces.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Interfaces
|
||||
|
||||
## Common Interfaces to Implement
|
||||
|
||||
### Comparator
|
||||
|
||||
Compares two instances of a class.
|
||||
|
||||
```java
|
||||
class StudentGPAComparator implements Comparator<Student> {
|
||||
@Override
|
||||
public int compare(Student o1, Student o2) {
|
||||
return (o1.gpa + o1.name).compareTo(o2.gpa + o2.name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main() {
|
||||
Comparator<Student> gpaSorter = new StudentGPAComparator();
|
||||
Arrays.sort(students, gpaSorter);
|
||||
Arrays.sort(students, gpaSorter.reversed());
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Comparable
|
||||
|
||||
Similar to Comparator, except this is implemented directly on the classes you are comparing. Without specificing a type, you will receive an `Object` to your `compareTo` function.
|
||||
|
||||
```java
|
||||
class Student implements Comparable<Student> {
|
||||
@Override
|
||||
public int compareTo(Student o) {
|
||||
return this.name.compareTo(o.name);
|
||||
}
|
||||
}
|
||||
```
|
||||
94
docs/java/basics/lambda.md
Normal file
94
docs/java/basics/lambda.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Lambdas
|
||||
|
||||
In order to pass a lambda function as an argument, it needs to be passed to a place that expects an interface that is a **functional interface**, or it only has one abstract method. This way the compiler can be sure that the function passed is the correct one for that interface.
|
||||
|
||||
The lambda function can also use variables in the calling scope, as long as the variables are final or effectively final.
|
||||
|
||||
```java
|
||||
@FunctionalInterface
|
||||
public interface Operation<T> {
|
||||
T operate(T a, T b);
|
||||
}
|
||||
|
||||
public static <T> T calculator(Operation<T> function, T v1, T v2) {
|
||||
T result = function.operate(v1, v2);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void main() {
|
||||
List<Person> people = new ArrayList<>();
|
||||
people.sort((o1, o2) -> o1.lastName.compareTo(o2.lastName));
|
||||
|
||||
int result = calculator((a,b) -> a + b, 5, 2);
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Common Functional Interfaces
|
||||
|
||||
These are found in the `java.util.function` package. There are four basic types of functional interfaces.
|
||||
|
||||
- Consumer -> `void accept(T t, U u?)` - Executes code without returning data (BiConsumer takes 2 args)
|
||||
- Function -> `R apply(T t, U u?)` - Return a result of an operation or function
|
||||
- Predicate -> `boolean test(T t, U u?)` - Test if a condition is true or false
|
||||
- Supplier -> `T get()` - Return an instance of something
|
||||
|
||||
### Consumer
|
||||
|
||||
A Consumer interface takes one or two arguments and performs some operation on them without returning anything.
|
||||
|
||||
```java
|
||||
public static <T> void processPoint(T t1, T t2, BiConsumer<T,T> consumer) {
|
||||
consumer.accept(t1, t2);
|
||||
}
|
||||
|
||||
public static void main() {
|
||||
BiConsumer<Double, Double> p1 = (lat, lng) -> System.out.printf("[lat:%.3f, long:%.3f]%n", lat, lng)
|
||||
processPoint(30.245, 50.343, p1);
|
||||
}
|
||||
```
|
||||
|
||||
### Predicate
|
||||
|
||||
```java
|
||||
public static void main() {
|
||||
List<String> names = new ArrayList<>(List.of("Harry", "Ron", "Hermoine"))
|
||||
names.removeIf(s -> s.startsWith("H"));
|
||||
}
|
||||
```
|
||||
|
||||
### Function
|
||||
|
||||
Similar to the Operation interface example above, this interface takes a lambda function and two values, performing some operation on the two with the `apply` method.
|
||||
|
||||
#### Operator
|
||||
|
||||
Operators have the same return and argument types, so only one type `T`. There are both `UnaryOperators` and `BiOperators`.
|
||||
|
||||
```java
|
||||
public static <T> T calculator(BinaryOperator<T> function, T v1, T v2) {
|
||||
T result = function.apply(v1, v2);
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
#### Function
|
||||
|
||||
Functions can have different types for the return values and all of its arguments. There are both `Functions` and `BiFunctions`.
|
||||
|
||||
## Method References
|
||||
|
||||
Method references allow you to pass a static method instead of a lambda function, and java can infer how to pass the arguments and what the return value should be. You pass a method reference by using the class name, two colons, and then the function name.
|
||||
|
||||
```java
|
||||
public static <T> T calculator(BinaryOperator<T> function, T v1, T v2) {
|
||||
T result = function.apply(v1, v2);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void main() {
|
||||
calculator(Integer::sum, 2, 5);
|
||||
calculator(Double::sum, 7.5, 2.5);
|
||||
}
|
||||
```
|
||||
1
docs/java/basics/types.md
Normal file
1
docs/java/basics/types.md
Normal file
@@ -0,0 +1 @@
|
||||
# Types
|
||||
1
docs/java/index.md
Normal file
1
docs/java/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# Java
|
||||
14
docs/java/sidebar.json
Normal file
14
docs/java/sidebar.json
Normal file
@@ -0,0 +1,14 @@
|
||||
[
|
||||
{
|
||||
"text": "Basics",
|
||||
"items": [
|
||||
{ "text": "Introduction", "link": "/java/" },
|
||||
{ "text": "Types", "link": "/java/basics/types" },
|
||||
{ "text": "Control Flow", "link": "/java/basics/controlflow" },
|
||||
{ "text": "Classes", "link": "/java/basics/classes" },
|
||||
{ "text": "Interfaces", "link": "/java/basics/interfaces" },
|
||||
{ "text": "Collections", "link": "/java/basics/collections" },
|
||||
{ "text": "Lambdas", "link": "/java/basics/lambda" }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -3,3 +3,7 @@
|
||||
[Python](/python/)
|
||||
|
||||
[Rust](/rust/)
|
||||
|
||||
[Go](/go/)
|
||||
|
||||
[Java](/java/)
|
||||
27
docs/python/advanced/webscraping.md
Normal file
27
docs/python/advanced/webscraping.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Webscraping
|
||||
|
||||
Python is an amazing language to use for webscraping purposes. One of the ways to do this is with the BeautifulSoup (or bs4) package.
|
||||
|
||||
```python
|
||||
import requests
|
||||
import bs4
|
||||
|
||||
result = requests.get('https://en.wikipedia.org/wiki/Jonas_Salk')
|
||||
|
||||
soup = bs4.BeautifulSoup(result.text,"lxml")
|
||||
|
||||
print(soup.select('title')[0].getText())
|
||||
# soup.select('.some_class')
|
||||
# soup.select('#some_id')
|
||||
|
||||
# Images
|
||||
pic_element = soup.select('.thumbimage')[0]
|
||||
print(pic_element['src'])
|
||||
|
||||
image_link = requests.get('http:' + pic_element['src'])
|
||||
|
||||
# Write image to file
|
||||
f = open('my_img.jpg', 'wb')
|
||||
f.write(image_link.content)
|
||||
f.close()
|
||||
```
|
||||
@@ -25,6 +25,7 @@
|
||||
{"text": "Regex", "link": "/python/advanced/regex"},
|
||||
{"text": "Testing", "link": "/python/advanced/tests"},
|
||||
{"text": "Timing", "link": "/python/advanced/timing"},
|
||||
{"text": "Webscraping", "link": "/python/advanced/webscraping"},
|
||||
{"text": "Zipping", "link": "/python/advanced/zip"}
|
||||
]
|
||||
}
|
||||
|
||||
99
docs/react/components.md
Normal file
99
docs/react/components.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Components
|
||||
|
||||
Components are the building blocks of a React application. They contained logic and templates that are reused throughout your applications. Components are essentially functions that return some JSX that can then be imported and used by parent components.
|
||||
|
||||
```tsx
|
||||
export default function Component() {
|
||||
return <h1>Some component!</h1>;
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
Props provide a way to pass data from a parent component into a child component to somehow influence their behavior. This is a one way flow, meaning changes to these props only flow from the parent to the child, but not the reverse.
|
||||
|
||||
```tsx
|
||||
// Parent
|
||||
function Parent() {
|
||||
return <Child color="red" />;
|
||||
}
|
||||
|
||||
// Child
|
||||
function Child(props) {
|
||||
return <h1>{props.color}</h1>;
|
||||
}
|
||||
// or
|
||||
function Child({ color }) {
|
||||
return <h1>{color}</h1>;
|
||||
}
|
||||
```
|
||||
|
||||
Props however can also be functions, which is how you would handle passing data from the child back to the parent.
|
||||
|
||||
```tsx
|
||||
function Parent() {
|
||||
const onSearch = (term) => {
|
||||
console.log("Searching for", term);
|
||||
};
|
||||
|
||||
return <Child onSubmit={onSearch} />;
|
||||
}
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
function Child({ onSubmit }) {
|
||||
const [term, setTerm] = useState("");
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
onSubmit(term);
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input value={term} onChange={(e) => setTerm(e.target.value)} />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Children Prop
|
||||
|
||||
There is a special prop called `children` which works similarly to slots in Vue. When you use a component and pass in some text or elements within the opening and closing tags, that gets passed as a prop `children` which you can use within your component.
|
||||
|
||||
```tsx
|
||||
function Child({ children }) {
|
||||
return <h1>{children}</h1>;
|
||||
}
|
||||
|
||||
function Parent() {
|
||||
return <Child>This is my children!</Child>;
|
||||
}
|
||||
```
|
||||
|
||||
### With Typescript
|
||||
|
||||
When using Typescript, you can define an interface for your props to improve intelisense in your IDE.
|
||||
|
||||
```tsx
|
||||
interface MyProps {
|
||||
color: string | null; // can be either string or null
|
||||
shade?: string; // optional prop
|
||||
}
|
||||
|
||||
function Child({ color, shade });
|
||||
```
|
||||
|
||||
## Feeding classNames to child components
|
||||
|
||||
To pass classNames to your child components, we can use a package called `classnames` to aggregate multiple classes.
|
||||
|
||||
```tsx
|
||||
import classNames from "classnames";
|
||||
|
||||
function Child({ className }) {
|
||||
const classes = classNames("myclass1 myclass2", className);
|
||||
|
||||
return <div className={classes}>My component with custom classes!</div>;
|
||||
}
|
||||
```
|
||||
78
docs/react/context.md
Normal file
78
docs/react/context.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Context
|
||||
|
||||
Context is a way of sharing state with many child components without having to pass them via props.
|
||||
|
||||
```tsx
|
||||
// context/foo.ts
|
||||
import { createContext, useState } from "react";
|
||||
|
||||
const MyContext = createContext();
|
||||
|
||||
function Provider({ children }) {
|
||||
const [value, setValue] = useState(0);
|
||||
|
||||
const incrementValue = () => {
|
||||
setValue(value + 1);
|
||||
};
|
||||
|
||||
const valueToShare = {
|
||||
value,
|
||||
incrementValue,
|
||||
};
|
||||
|
||||
return (
|
||||
<MyContext.Provider value={valueToShare}>{children}</MyContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export { Provider };
|
||||
export default MyContext;
|
||||
|
||||
// In wrapping component...
|
||||
import { Provider } from "context/foo";
|
||||
|
||||
function Parent() {
|
||||
return (
|
||||
<Provider>
|
||||
<Children />
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
// In consuming component
|
||||
import { useContext } from "react";
|
||||
import { MyContext } from "../context/foo";
|
||||
|
||||
function Child() {
|
||||
const { value, incrementValue } = useContext(MyContext);
|
||||
}
|
||||
```
|
||||
|
||||
## useCallback
|
||||
|
||||
This hook is used to fix some bugs when calling functions stored within contexts from useEffect in a child component. Since useEffect requires the function to be added as a dependency in the second argument, it will rerun that effect whenever that function is called because calling it forces the context to rerender, which in turn creates a new version of that function. So to fix this, we need to use useCallback to create a stable reference to that function.
|
||||
|
||||
```tsx
|
||||
// context/foo.ts
|
||||
import { createContext, useState, useCallback } from "react";
|
||||
|
||||
const MyContext = createContext();
|
||||
|
||||
function Provider({ children }) {
|
||||
const [value, setValue] = useState(0);
|
||||
|
||||
const fetchValue = useCallback(() => {
|
||||
newValue = fetch("localhost:8080");
|
||||
setValue(newValue);
|
||||
}, []);
|
||||
|
||||
const valueToShare = {
|
||||
value,
|
||||
fetchValue,
|
||||
};
|
||||
|
||||
return (
|
||||
<MyContext.Provider value={valueToShare}>{children}</MyContext.Provider>
|
||||
);
|
||||
}
|
||||
```
|
||||
364
docs/react/fetching.md
Normal file
364
docs/react/fetching.md
Normal file
@@ -0,0 +1,364 @@
|
||||
# Data Fetching
|
||||
|
||||
There are a few different ways to fetch data from a server in React using a Redux store. One note is that we should never make API calls within our reducers.
|
||||
|
||||
## React Router
|
||||
|
||||
React Router is a viable option for smaller projects that do not require larger state management. This can be accomplished by using the `loader` property on a route which can be used to fetch data.
|
||||
|
||||
```ts
|
||||
// function for executing the query and returning the results
|
||||
// ex. api/queries/searchPackages.ts
|
||||
export interface PackageSummary {
|
||||
name: string;
|
||||
version: string;
|
||||
description: string;
|
||||
keywords?: string[];
|
||||
}
|
||||
|
||||
interface SearchResponse {
|
||||
objects: {
|
||||
package: {
|
||||
name: string;
|
||||
description: string;
|
||||
version: string;
|
||||
keywords: string[];
|
||||
};
|
||||
}[];
|
||||
}
|
||||
|
||||
export async function searchPackages(term: string): Promise<PackageSummary[]> {
|
||||
const res = await fetch(
|
||||
`https://registry.npmjs.org/-/v1/search?text=${term}&size=10`
|
||||
);
|
||||
const data: SearchResponse = await res.json();
|
||||
|
||||
return data.objects.map(
|
||||
({ package: { name, description, version, keywords } }) => {
|
||||
return {
|
||||
name,
|
||||
description,
|
||||
version,
|
||||
keywords,
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// loader function, ex. pages/search/searchLoader.ts
|
||||
import { searchPackages } from "../../api/queries/searchPackages";
|
||||
import type { PackageSummary } from "../../api/queries/packageSummary";
|
||||
|
||||
export interface SearchLoaderResult {
|
||||
searchResults: PackageSummary[];
|
||||
}
|
||||
|
||||
export async function searchLoader({
|
||||
request, // request includes the url we are currently at in our router, can be parsed for query params
|
||||
params, // named params in the route, ex. /packages/:name would be params.name
|
||||
}: {
|
||||
request: Request;
|
||||
}): Promise<SearchLoaderResult> {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const term = searchParams.get("term");
|
||||
|
||||
if (!term) {
|
||||
throw new Error("Search term must be provided");
|
||||
}
|
||||
|
||||
const results = await searchPackages(term);
|
||||
|
||||
// Good habit to return object, makes it easier to add more data in the future
|
||||
return {
|
||||
searchResults: results,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// router
|
||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||
import Root from "./pages/Root";
|
||||
import HomePage from "./pages/home/HomePage";
|
||||
import { homeLoader } from "./pages/home/homeLoader";
|
||||
import SearchPage from "./pages/search/SearchPage";
|
||||
import { searchLoader } from "./pages/search/searchLoader";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <Root />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <HomePage />,
|
||||
loader: homeLoader,
|
||||
},
|
||||
{
|
||||
path: "/search",
|
||||
element: <SearchPage />,
|
||||
loader: searchLoader,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
```ts
|
||||
// in component
|
||||
import { useLoaderData } from "react-router-dom";
|
||||
import { SearchLoaderResult } from "./searchLoader";
|
||||
|
||||
export default function SearchPage() {
|
||||
const { searchResults } = useLoaderData() as SearchLoaderResult;
|
||||
|
||||
// ... use search results
|
||||
}
|
||||
```
|
||||
|
||||
## Redux Toolkit Queries
|
||||
|
||||
RTKQ is a library that allows you to create APIs to define how you want to query and manipulate your data via requests. It also automatically creates some hooks to let you know when data is loading, fetching, and functions to refetch, as well as hooks to automatically refetch data when some piece of data is changed by one of the API endpoints.
|
||||
|
||||
```ts
|
||||
// store/apis/photosApi.ts
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
import { faker } from "@faker-js/faker";
|
||||
import Album from "../models/Album";
|
||||
import Photo from "../models/Photo";
|
||||
|
||||
const photosApi = createApi({
|
||||
// path in the root state variable
|
||||
reducerPath: "photos",
|
||||
baseQuery: fetchBaseQuery({
|
||||
baseUrl: "http://localhost:3005",
|
||||
}),
|
||||
tagTypes: ["Photo", "AlbumPhotos"],
|
||||
endpoints(builder) {
|
||||
return {
|
||||
fetchPhotos: builder.query({
|
||||
// Create a list of tags that will trigger a refetch if any other endpoint invalidates one of the tags
|
||||
providesTags: (result: Photo[], _, album: Album) => {
|
||||
const tags = result.map((photo) => {
|
||||
return { type: "Photo", id: photo.id };
|
||||
});
|
||||
// Is is also common to use a tag like { type: "Photo", id: "LIST/ALL/*" } to have an entry to invalidate the entire list
|
||||
// However, this will cause a refetch of all Photos lists if there are multiple
|
||||
tags.push({ type: "AlbumPhotos", id: album.id });
|
||||
return tags;
|
||||
},
|
||||
query: (album: Album) => {
|
||||
return {
|
||||
url: "/photos",
|
||||
method: "GET",
|
||||
// appended onto url
|
||||
params: {
|
||||
albumId: album.id,
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
addPhoto: builder.mutation({
|
||||
// triggers refetching for this album
|
||||
invalidatesTags: (_, __, album: Album) => {
|
||||
return [{ type: "AlbumPhotos", id: album.id }];
|
||||
},
|
||||
query: (album: Album) => ({
|
||||
url: "/photos",
|
||||
method: "POST",
|
||||
// request body parameters
|
||||
body: {
|
||||
albumId: album.id,
|
||||
url: faker.image.abstract(150, 150, true),
|
||||
},
|
||||
}),
|
||||
}),
|
||||
removePhoto: builder.mutation({
|
||||
invalidatesTags: (_, __, photo: Photo) => {
|
||||
return [{ type: "Photo", id: photo.id }];
|
||||
},
|
||||
query: (photo: Photo) => ({
|
||||
url: `/photos/${photo.id}`,
|
||||
method: "DELETE",
|
||||
}),
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// Automatically creates functions to access your endpoints
|
||||
export const {
|
||||
useFetchPhotosQuery,
|
||||
useAddPhotoMutation,
|
||||
useRemovePhotoMutation,
|
||||
} = photosApi;
|
||||
export { photosApi };
|
||||
```
|
||||
|
||||
```ts
|
||||
// store/index.ts
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import { setupListeners } from "@reduxjs/toolkit/query";
|
||||
import { photosApi } from "./apis/photosApi";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
[photosApi.reducerPath]: photosApi.reducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) => {
|
||||
return getDefaultMiddleware().concat(photosApi.middleware);
|
||||
},
|
||||
});
|
||||
|
||||
setupListeners(store.dispatch);
|
||||
|
||||
export {
|
||||
useFetchPhotosQuery,
|
||||
useAddPhotoMutation,
|
||||
useRemovePhotoMutation,
|
||||
} from "./apis/photosApi";
|
||||
```
|
||||
|
||||
```tsx
|
||||
// component
|
||||
import { useFetchPhotosQuery, useAddPhotoMutation } from "../store";
|
||||
|
||||
export default function Comp({ album }) {
|
||||
// Automatically loads data on component creation
|
||||
const { data, isLoading, error, isFetching, refetch } =
|
||||
useFetchPhotosQuery(album);
|
||||
|
||||
// Hook to call function when needed
|
||||
// results contains data, isFetching, error, etc and is updated when function is called and request processes
|
||||
const [addPhoto, results] = useAddPhotoMutation();
|
||||
}
|
||||
```
|
||||
|
||||
## Thunks
|
||||
|
||||
Thunks are a vanilla way of using Redux Toolkit to send requests and keep track of what stage those requests are in. These can be listened to in a slice's `extraReducers` to perform state updates. Each thunk has a `pending`, `fulfilled`, and `rejected` state.
|
||||
|
||||
```ts
|
||||
// store/thunks/addUser.ts
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import axios from "axios";
|
||||
import { faker } from "@faker-js/faker";
|
||||
|
||||
const addUser = createAsyncThunk("users/add", async () => {
|
||||
const response = await axios.post("http://localhost:3005/users", {
|
||||
name: faker.name.fullName(),
|
||||
});
|
||||
|
||||
return response.data;
|
||||
});
|
||||
|
||||
export { addUser };
|
||||
```
|
||||
|
||||
```ts
|
||||
// store/slices/user.ts
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { fetchUsers } from "../thunks/fetchUsers";
|
||||
|
||||
const initialState: UserState = {
|
||||
data: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
const usersSlice = createSlice({
|
||||
name: "users",
|
||||
initialState,
|
||||
reducers: {
|
||||
setUsers: (state, action) => {
|
||||
state.data = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
// fetch users
|
||||
builder.addCase(fetchUsers.pending, (state, _) => {
|
||||
state.isLoading = true;
|
||||
state.error = null;
|
||||
});
|
||||
builder.addCase(fetchUsers.fulfilled, (state, action) => {
|
||||
state.isLoading = false;
|
||||
state.data = action.payload;
|
||||
});
|
||||
builder.addCase(fetchUsers.rejected, (state, action) => {
|
||||
state.isLoading = false;
|
||||
state.error = action.error || "An error occurred.";
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const usersReducer = usersSlice.reducer;
|
||||
```
|
||||
|
||||
```ts
|
||||
// store/index.ts
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import { usersReducer } from "./slices/usersSlice";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
users: usersReducer,
|
||||
},
|
||||
});
|
||||
|
||||
// Export thunk function from store index
|
||||
export * from "./thunks/fetchUsers";
|
||||
export * from "./thunks/addUser";
|
||||
export * from "./thunks/deleteUser";
|
||||
```
|
||||
|
||||
### Optional Custom Hook
|
||||
|
||||
```ts
|
||||
// hooks/use-thunk.ts
|
||||
import { AsyncThunk } from "@reduxjs/toolkit";
|
||||
import { useState, useCallback } from "react";
|
||||
import { useAppDispatch } from "./store";
|
||||
|
||||
// Custom hook for handling loading and error states
|
||||
export default function useThunk(
|
||||
thunk: AsyncThunk<any, any | void, {}>
|
||||
): [(arg?: any) => void, boolean, object | null] {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<object | null>(null);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const runThunk = useCallback(
|
||||
(arg?: any) => {
|
||||
setIsLoading(true);
|
||||
dispatch(thunk(arg))
|
||||
.unwrap()
|
||||
.catch((err: object) => setError(err))
|
||||
.finally(() => setIsLoading(false));
|
||||
},
|
||||
[dispatch, thunk]
|
||||
);
|
||||
|
||||
return [runThunk, isLoading, error];
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// use in component
|
||||
import useThunk from "../hooks/use-thunk";
|
||||
import { fetchUsers, addUser } from "../store";
|
||||
|
||||
export default function UsersList() {
|
||||
const [runFetchUsers, isFetching, fetchError] = useThunk(fetchUsers);
|
||||
const [runCreateUser, isCreating, createError] = useThunk(addUser);
|
||||
|
||||
useEffect(() => {
|
||||
runFetchUsers();
|
||||
}, [runFetchUsers]);
|
||||
|
||||
const handleUserAdd = () => {
|
||||
runCreateUser();
|
||||
};
|
||||
}
|
||||
```
|
||||
38
docs/react/index.md
Normal file
38
docs/react/index.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# React
|
||||
|
||||
React is a Javascript/Typescript framework for creating responsive single page applications in the web.
|
||||
|
||||
## Creating an App
|
||||
|
||||
```bash
|
||||
npm create vite@latest <app-name> -- --template=react-ts
|
||||
cd <app-name>
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Adding Tailwindcss (Optional)
|
||||
|
||||
```bash
|
||||
npm install -D tailwindcss postcss autoprefixer
|
||||
npx tailwindcss init -p
|
||||
```
|
||||
|
||||
```js
|
||||
// tailwind.config.js
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
```
|
||||
|
||||
```css
|
||||
/* index.css */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
```
|
||||
86
docs/react/jsx.md
Normal file
86
docs/react/jsx.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# JSX
|
||||
|
||||
## Rendering a List
|
||||
|
||||
```tsx
|
||||
import { Fragment } from "react";
|
||||
|
||||
function Comp() {
|
||||
const items = [
|
||||
{ id: 1, name: "Item 1" },
|
||||
{ id: 2, name: "Item 2" },
|
||||
{ id: 3, name: "Item 3" },
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
{items.map((item) => {
|
||||
return <Fragment key={item.id}>{item.name}</Fragment>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Conditional Rendering
|
||||
|
||||
```tsx
|
||||
function Comp() {
|
||||
const items = [
|
||||
{ id: 1, name: "Item 1", show: true },
|
||||
{ id: 2, name: "Item 2", show: true },
|
||||
{ id: 3, name: "Item 3", show: false },
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
{items.map((item) => {
|
||||
return {item.show && <Fragment key={item.id}>{item.name}</Fragment>};
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Checking if item is valid
|
||||
|
||||
```tsx
|
||||
function Comp() {
|
||||
const items: object[] | null = null;
|
||||
|
||||
return <div>{items?.length || "No items"}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
## Two-way data binding
|
||||
|
||||
In order to have two-way data binding between the component and an input or another child component, you need to make use of the `onChange` event or some other event callback.
|
||||
|
||||
```tsx
|
||||
import { useState } from "react";
|
||||
|
||||
function Comp() {
|
||||
const [value, setValue] = useState("");
|
||||
|
||||
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
|
||||
}
|
||||
```
|
||||
|
||||
## Handling number inputs
|
||||
|
||||
```tsx
|
||||
import { useState } from "react";
|
||||
|
||||
function Comp() {
|
||||
const [value, setValue] = useState(0);
|
||||
|
||||
const handleChange = (e) => {
|
||||
// will be NaN if invalid input or empty
|
||||
newValue = parseInt(e.target.value) || 0;
|
||||
// do something with new value
|
||||
};
|
||||
|
||||
// need || "" or else it will always have an annoying 0 in the input
|
||||
return <input type="number" value={value || ""} onChange={handleChange} />;
|
||||
}
|
||||
```
|
||||
184
docs/react/redux.md
Normal file
184
docs/react/redux.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# Redux
|
||||
|
||||
Redux is a centralized state store that can be accessed from any component in your application. It acts as basically a centralized reducer, where it has a state and an dispatch method to update that state. Redux Toolkit is a library that simplifies creating actions to interact with the store, and is the recommended approach going forward.
|
||||
|
||||
```ts
|
||||
// store/slices/song.ts
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { reset } from "../actions";
|
||||
|
||||
const songsSlice = createSlice({
|
||||
name: "song",
|
||||
initialState: [],
|
||||
reducers: {
|
||||
addSong(state, action) {
|
||||
state.push(action.payload);
|
||||
},
|
||||
removeSong(state, action) {
|
||||
// action.payload === string, the song we want to remove
|
||||
const index = state.indexOf(action.payload);
|
||||
state.splice(index, 1);
|
||||
},
|
||||
},
|
||||
// listen to manual actions not created by this generator
|
||||
extraReducers(builder) {
|
||||
builder.addCase(reset, (state, action) => {
|
||||
return [];
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { addSong, removeSong } = songsSlice.actions;
|
||||
// can also export this as the default
|
||||
export const songsReducer = songsSlice.reducer;
|
||||
```
|
||||
|
||||
```ts
|
||||
// store/actions.ts
|
||||
// Declare common actions shared by multiple reducers
|
||||
import { createAction } from "@reduxjs/toolkit";
|
||||
|
||||
export const reset = createAction("app/reset");
|
||||
```
|
||||
|
||||
```ts
|
||||
// store/index.ts
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import { songsReducer, addSong, removeSong } from "./slices/songsSlice";
|
||||
import { reset } from "./actions";
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
songs: songsReducer,
|
||||
},
|
||||
});
|
||||
|
||||
// Export store and slice actions
|
||||
export { store, reset, addSong, removeSong };
|
||||
```
|
||||
|
||||
```ts
|
||||
// index.ts
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { Provider } from "react-redux";
|
||||
import App from "./App";
|
||||
import { store } from "./store";
|
||||
|
||||
const rootElement = document.getElementById("root");
|
||||
const root = createRoot(rootElement);
|
||||
|
||||
root.render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
);
|
||||
```
|
||||
|
||||
```tsx
|
||||
// components/songs.tsx
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { createRandomSong } from "../data";
|
||||
import { addSong, removeSong } from "../store";
|
||||
|
||||
function SongPlaylist() {
|
||||
// object to call actions
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// fetch state
|
||||
// can also filter/map state within here if you want
|
||||
const songPlaylist = useSelector((state) => {
|
||||
return state.songs;
|
||||
});
|
||||
|
||||
// dispatch actions with some payload
|
||||
const handleSongAdd = (song) => {
|
||||
dispatch(addSong(song));
|
||||
};
|
||||
const handleSongRemove = (song) => {
|
||||
dispatch(removeSong(song));
|
||||
};
|
||||
|
||||
const renderedSongs = songPlaylist.map((song) => {
|
||||
return (
|
||||
<li key={song}>
|
||||
{song}
|
||||
<button
|
||||
onClick={() => handleSongRemove(song)}
|
||||
className="button is-danger"
|
||||
>
|
||||
X
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="content">
|
||||
<div className="table-header">
|
||||
<h3 className="subtitle is-3">Song Playlist</h3>
|
||||
<div className="buttons">
|
||||
<button
|
||||
onClick={() => handleSongAdd(createRandomSong())}
|
||||
className="button is-link"
|
||||
>
|
||||
+ Add Song to Playlist
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul>{renderedSongs}</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SongPlaylist;
|
||||
```
|
||||
|
||||
## Optimize with Typescript
|
||||
|
||||
```ts
|
||||
// store/index.ts
|
||||
export type AppStore = typeof store;
|
||||
export type RootState = ReturnType<AppStore["getState"]>;
|
||||
export type AppDispatch = AppStore["dispatch"];
|
||||
```
|
||||
|
||||
```ts
|
||||
// custom hook
|
||||
import type { TypedUseSelectorHook } from "react-redux";
|
||||
import { useDispatch, useSelector, useStore } from "react-redux";
|
||||
import type { AppDispatch, AppStore, RootState } from "./store";
|
||||
|
||||
// Use throughout your app instead of plain `useDispatch` and `useSelector`
|
||||
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
|
||||
export const useAppSelector = useSelector.withTypes<RootState>();
|
||||
export const useAppStore = useStore.withTypes<AppStore>();
|
||||
```
|
||||
|
||||
```ts
|
||||
// typing your slices
|
||||
import type { RootState } from "../";
|
||||
|
||||
interface CounterState {
|
||||
value: number;
|
||||
}
|
||||
|
||||
// Define the initial state using that type
|
||||
const initialState: CounterState = {
|
||||
value: 0,
|
||||
};
|
||||
|
||||
export const counterSlice = createSlice({
|
||||
name: "counter",
|
||||
// `createSlice` will infer the state type from the `initialState` argument
|
||||
initialState,
|
||||
reducers: {
|
||||
// Use the PayloadAction type to declare the contents of `action.payload`
|
||||
incrementByAmount: (state, action: PayloadAction<number>) => {
|
||||
state.value += action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Other code such as selectors can use the imported `RootState` type
|
||||
export const selectCount = (state: RootState) => state.counter.value;
|
||||
```
|
||||
241
docs/react/routing.md
Normal file
241
docs/react/routing.md
Normal file
@@ -0,0 +1,241 @@
|
||||
# Routing
|
||||
|
||||
## Vanilla Routing with Context
|
||||
|
||||
```tsx
|
||||
// context/navigation.ts
|
||||
import { createContext, useState, useEffect } from "react";
|
||||
|
||||
const NavigationContext = createContext();
|
||||
|
||||
function NavigationProvider({ children }) {
|
||||
const [currentPath, setCurrentPath] = useState(window.location.pathname);
|
||||
|
||||
// handle front and back arrows
|
||||
useEffect(() => {
|
||||
const handler = () => {
|
||||
setCurrentPath(window.location.pathname);
|
||||
};
|
||||
window.addEventListener("popstate", handler);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("popstate", handler);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const navigate = (to) => {
|
||||
window.history.pushState({}, "", to);
|
||||
setCurrentPath(to);
|
||||
};
|
||||
|
||||
return (
|
||||
<NavigationContext.Provider value={{ currentPath, navigate }}>
|
||||
{children}
|
||||
</NavigationContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export { NavigationProvider };
|
||||
export default NavigationContext;
|
||||
```
|
||||
|
||||
```tsx
|
||||
// Route component for conditionally rendering components
|
||||
import useNavigation from "../hooks/use-navigation";
|
||||
|
||||
function Route({ path, children }) {
|
||||
const { currentPath } = useNavigation();
|
||||
|
||||
if (path === currentPath) {
|
||||
return children;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default Route;
|
||||
```
|
||||
|
||||
```tsx
|
||||
// example link component
|
||||
import classNames from "classnames";
|
||||
import useNavigation from "../hooks/use-navigation";
|
||||
|
||||
function Link({ to, children, className, activeClassName }) {
|
||||
const { navigate, currentPath } = useNavigation();
|
||||
|
||||
const classes = classNames(
|
||||
"text-blue-500",
|
||||
className,
|
||||
currentPath === to && activeClassName // apply styling if currently on this page
|
||||
);
|
||||
|
||||
const handleClick = (event) => {
|
||||
// handle opening new tab, use default behavior
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
|
||||
navigate(to);
|
||||
};
|
||||
|
||||
return (
|
||||
<a className={classes} href={to} onClick={handleClick}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export default Link;
|
||||
```
|
||||
|
||||
```tsx
|
||||
// Navbar component
|
||||
import Link from "./Link";
|
||||
|
||||
function NavBar() {
|
||||
const links = [
|
||||
{ label: "Dropdown", path: "/" },
|
||||
{ label: "Accordion", path: "/accordion" },
|
||||
{ label: "Buttons", path: "/buttons" },
|
||||
];
|
||||
|
||||
const renderedLinks = links.map((link) => {
|
||||
return (
|
||||
<Link
|
||||
key={link.label}
|
||||
to={link.path}
|
||||
className="mb-3"
|
||||
activeClassName="font-bold border-l-4 border-blue-500 pl-2"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="sticky top-0 overflow-y-scroll flex flex-col items-start">
|
||||
{renderedLinks}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default NavBar;
|
||||
```
|
||||
|
||||
```tsx
|
||||
// App.ts
|
||||
import Sidebar from "./components/Sidebar";
|
||||
import Route from "./components/Route";
|
||||
import AccordionPage from "./pages/AccordionPage";
|
||||
import DropdownPage from "./pages/DropdownPage";
|
||||
import ButtonPage from "./pages/ButtonPage";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="container mx-auto grid grid-cols-6 gap-4 mt-4">
|
||||
<Sidebar />
|
||||
<div className="col-span-5">
|
||||
<Route path="/accordion">
|
||||
<AccordionPage />
|
||||
</Route>
|
||||
<Route path="/">
|
||||
<DropdownPage />
|
||||
</Route>
|
||||
<Route path="/buttons">
|
||||
<ButtonPage />
|
||||
</Route>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
## Using React Router library
|
||||
|
||||
Documentation [here](https://reactrouter.com/home).
|
||||
|
||||
```tsx
|
||||
// main.tsx
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||
|
||||
import HomePage from "./pages/HomePage";
|
||||
import NotFoundPage from "./pages/NotFoundPage";
|
||||
import ProfilePage from "./pages/ProfilePage";
|
||||
import ProfilesPage from "./pages/ProfilesPage";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <HomePage />,
|
||||
errorElement: <NotFoundPage />,
|
||||
},
|
||||
{
|
||||
path: "/profiles",
|
||||
element: <ProfilesPage />,
|
||||
children: [
|
||||
{
|
||||
path: "/profiles/:profileId",
|
||||
element: <ProfilePage />,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<RouterProvider router={router} />
|
||||
</React.StrictMode>
|
||||
);
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ProfilesPage.tsx with child component
|
||||
import { NavLink, Outlet } from "react-router-dom";
|
||||
|
||||
export default function ProfilesPage() {
|
||||
const profiles = [1, 2, 3, 4, 5];
|
||||
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
{profiles.map((profile) => (
|
||||
// NavLink allows you to apply styling if the link is active
|
||||
<NavLink
|
||||
key={profile}
|
||||
to={`/profiles/${profile}`}
|
||||
className={({ isActive }) => {
|
||||
return isActive ? "text-primary-700" : "";
|
||||
}}
|
||||
>
|
||||
Profile {profile}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
<!-- Outlet displays child component links if active -->
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ProfilePage component using query params
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
export default function ProfilePage() {
|
||||
const params = useParams<{ profileId: string }>();
|
||||
return (
|
||||
<div>
|
||||
<h1>Profile Page {params.profileId}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
15
docs/react/sidebar.json
Normal file
15
docs/react/sidebar.json
Normal file
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"text": "React Basics",
|
||||
"items": [
|
||||
{ "text": "Introduction", "link": "/react/" },
|
||||
{ "text": "JSX", "link": "/react/jsx" },
|
||||
{ "text": "Components", "link": "/react/components" },
|
||||
{ "text": "State", "link": "/react/state" },
|
||||
{ "text": "Context", "link": "/react/context" },
|
||||
{ "text": "Routing", "link": "/react/routing" },
|
||||
{ "text": "Redux", "link": "/react/redux" },
|
||||
{ "text": "Data Fetching", "link": "/react/fetching" }
|
||||
]
|
||||
}
|
||||
]
|
||||
175
docs/react/state.md
Normal file
175
docs/react/state.md
Normal file
@@ -0,0 +1,175 @@
|
||||
# State and Events
|
||||
|
||||
State allows you to have the user interace with your application, update some data under the hood, and have that reflected in the page.
|
||||
|
||||
## Events
|
||||
|
||||
Events means how you handle some user actions in your application. A full list of possible events can be found [here](https://react.dev/reference/react-dom/components/common#). Essentially, you define functions that are called when certain actions are performed on elements in your page.
|
||||
|
||||
```tsx
|
||||
function App() {
|
||||
const handleClick = () => {
|
||||
console.log("button clicked");
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={handleClick}>Click me</button>;
|
||||
<button
|
||||
onClick={() => {
|
||||
console.log("Inline function click");
|
||||
}}
|
||||
>
|
||||
Click me too!
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## useState
|
||||
|
||||
State tells your application to rerender the page when something changes in your application.
|
||||
|
||||
```tsx
|
||||
import { useState } from "react";
|
||||
|
||||
function App() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
const handleClick = () => {
|
||||
setCount(count + 1);
|
||||
};
|
||||
|
||||
return <button onClick={handleClick} />;
|
||||
}
|
||||
```
|
||||
|
||||
## useReducer
|
||||
|
||||
A Reducer is a way of combining multiple state variables into one object. This allows you to reduce the number of rerenders if multiple pieces of state are changing at the same time. `useReducer` takes a `reducer` function and an initial state. This `reducer` function will run whenever we call `dispatch`, and the argument to `dispatch` will be passed as an argument `action` along with the current state. The `action` argument is usually an object which tells the reducer what type of update to do. Whatever is returned from this function will be the new state. The `reducer` function cannot include async/await, promises, requests, etc. It is only for updating the state. You should not update the state directly unless you are using Immer, which is a library that automatically returns the new state for you.
|
||||
|
||||
```tsx
|
||||
import produce from "immer";
|
||||
import { useReducer } from "react";
|
||||
|
||||
// Use constants for action to prevent typos
|
||||
const INCREMENT_COUNT = "increment";
|
||||
const SET_VALUE_TO_ADD = "change_value_to_add";
|
||||
const DECREMENT_COUNT = "decrement";
|
||||
const ADD_VALUE_TO_COUNT = "add_value_to_count";
|
||||
|
||||
const reducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case INCREMENT_COUNT:
|
||||
state.count = state.count + 1;
|
||||
return;
|
||||
// If not using immer, you would need to do something like...
|
||||
return {
|
||||
...state,
|
||||
count: state.count + 1,
|
||||
};
|
||||
case DECREMENT_COUNT:
|
||||
state.count = state.count - 1;
|
||||
return;
|
||||
case ADD_VALUE_TO_COUNT:
|
||||
state.count = state.count + state.valueToAdd;
|
||||
state.valueToAdd = 0;
|
||||
return;
|
||||
case SET_VALUE_TO_ADD:
|
||||
state.valueToAdd = action.payload;
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
function CounterPage({ initialCount }) {
|
||||
// wrapper reducer with produce to use immer
|
||||
const [state, dispatch] = useReducer(produce(reducer), {
|
||||
count: initialCount,
|
||||
valueToAdd: 0,
|
||||
});
|
||||
|
||||
const increment = () => {
|
||||
dispatch({
|
||||
type: INCREMENT_COUNT,
|
||||
});
|
||||
};
|
||||
const decrement = () => {
|
||||
dispatch({
|
||||
type: DECREMENT_COUNT,
|
||||
});
|
||||
};
|
||||
const handleChange = (event) => {
|
||||
const value = parseInt(event.target.value) || 0;
|
||||
|
||||
dispatch({
|
||||
type: SET_VALUE_TO_ADD,
|
||||
payload: value,
|
||||
});
|
||||
};
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
dispatch({
|
||||
type: ADD_VALUE_TO_COUNT,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default CounterPage;
|
||||
```
|
||||
|
||||
### Binding inputs
|
||||
|
||||
```tsx
|
||||
function Input({ onSubmit }) {
|
||||
const [text, setText] = useState("");
|
||||
|
||||
const handleChange = (event) => {
|
||||
setText(event.target.value);
|
||||
};
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
onSubmit(text);
|
||||
setText("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input className="input" value={title} onChange={handleChange} />
|
||||
<button className="button">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## useEffect
|
||||
|
||||
useEffect is a function that runs when the application is initially rendered and sometimes when it is rerendered. The arrow function provided is always called on the initial render, and if a variable passed in the second argument is changed in the rerender, then the arrow function is called again. If no second argument is passed, then the function is called on every rerender.
|
||||
|
||||
```tsx
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
function Comp() {
|
||||
useEffect(() => {
|
||||
console.log("Run on initial render");
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Run on every render");
|
||||
});
|
||||
|
||||
const [text, setText] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Run when 'text' is changed");
|
||||
}, [text]);
|
||||
}
|
||||
```
|
||||
|
||||
The only thing we can return from useEffect is a function, and this function gets called before the next time the useEffect function is run, so from the second render on.
|
||||
@@ -7,6 +7,11 @@ Test functions are defined with the `#[test]` attribute. Within the tests, we u
|
||||
For unit tests, its best to write your tests in the file that it is testing but contain it with a `mod tests` with the `cfg(test)` attribute. This will exclude it from the final executable or library. Unit tests also allow you to test private functions, meaning ones that are not `pub`.
|
||||
|
||||
```rust
|
||||
#[allow(dead_code)]
|
||||
pub fn greeting(_name: &str) -> String {
|
||||
String::from("Hello!")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*; // Use this to call functions in the current file
|
||||
@@ -33,7 +38,31 @@ mod tests {
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn greater_than_100() {
|
||||
// Something illegal
|
||||
panic!("I'm supposed to panic")
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Expected text")]
|
||||
fn expect_panic_message() {
|
||||
panic!("Expected text");
|
||||
}
|
||||
|
||||
// Ignore test
|
||||
// Can still be run on demand
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn expensive_test() {
|
||||
// code that takes an hour to run
|
||||
}
|
||||
|
||||
// Return a result to allow for the ? operator to be used to cause test failures
|
||||
#[test]
|
||||
fn result_test() -> Result<(), String> {
|
||||
if 2 + 2 == 4 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(String::from("two plus two does not equal four"))
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -91,7 +91,7 @@ fn some_function<T, U>(t: &T, u: &U) -> i32
|
||||
|
||||
## Returning types that implement traits
|
||||
|
||||
You can only use a trait as a return value if you are returning a single type.
|
||||
You can only use a trait as a return value if you are returning a single type. For example, you can not have one path that returns a NewsArticle and one that returns a Tweet.
|
||||
|
||||
```rust
|
||||
fn returns_summarizable() -> impl Summary {
|
||||
|
||||
@@ -17,8 +17,11 @@ if num < 3 {
|
||||
|
||||
```rust
|
||||
let config_max = Some(3u8);
|
||||
let age: Result<u8, _> = "34".parse();
|
||||
if let Some(max) = config_max {
|
||||
println!("The maximum is configured to be {}", max);
|
||||
} else if let Ok(age) = age {
|
||||
println!("Age is {}", age);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -113,4 +116,19 @@ fn plus_one(x: Option<i32>) -> Option<i32> {
|
||||
}
|
||||
}
|
||||
|
||||
// Or
|
||||
let x = 1;
|
||||
match x {
|
||||
1 | 2 => println!("one or two"),
|
||||
3 => println!("three"),
|
||||
_ => println!("anything"),
|
||||
}
|
||||
|
||||
// Range
|
||||
let x = 5;
|
||||
match x {
|
||||
1..=5 => println!("one through five"),
|
||||
_ => println!("something else"),
|
||||
}
|
||||
|
||||
```
|
||||
@@ -82,6 +82,21 @@ let _two = b[1];
|
||||
// let n = a[10];
|
||||
```
|
||||
|
||||
## Result
|
||||
|
||||
Results allow you to handle when something returns either a value or error. If it has a valid value, it will be of type `Ok<T>`, otherwise it will be of type `Err<E>`. See the `errors` section for more into on Results.
|
||||
|
||||
## Option
|
||||
|
||||
Options are similar to Results in that they can contain a conditional type. It can be either of type `None` or type `Some<T>`.
|
||||
|
||||
```rust
|
||||
enum Option<T> {
|
||||
None,
|
||||
Some(T),
|
||||
}
|
||||
```
|
||||
|
||||
## Type Aliases
|
||||
|
||||
```rust
|
||||
|
||||
7
docs/vue/index.md
Normal file
7
docs/vue/index.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Vue
|
||||
|
||||
## Creating a New Project
|
||||
|
||||
```bash
|
||||
npm create vue@latest
|
||||
```
|
||||
8
docs/vue/sidebar.json
Normal file
8
docs/vue/sidebar.json
Normal file
@@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"text": "Vue Basics",
|
||||
"items": [
|
||||
{ "text": "Introduction", "link": "/vue/" }
|
||||
]
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user