├── .gitignore
├── README.md
├── day1
├── README.md
└── javascript
│ ├── 01-variables.js
│ ├── 02-controlStatements.js
│ ├── 03-arrays.js
│ └── 04-functions.js
├── day10
├── README.md
├── controllers
│ └── login_api.go
├── db
│ ├── db.go
│ ├── init.sql
│ ├── player_db.go
│ └── session_db.go
├── go.mod
├── go.sum
├── main.go
├── models
│ ├── game.go
│ ├── player.go
│ └── session.go
├── router
│ └── router.go
└── utils
│ └── utils.go
├── day11
├── controllers
│ ├── game_api.go
│ └── login_api.go
├── db
│ ├── db.go
│ ├── game_db.go
│ ├── init.sql
│ ├── player_db.go
│ └── session_db.go
├── go.mod
├── go.sum
├── main.go
├── models
│ ├── game.go
│ ├── player.go
│ └── session.go
├── router
│ └── router.go
└── utils
│ └── utils.go
├── day12
├── README.md
├── client
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ └── vite.svg
│ ├── src
│ │ ├── App.css
│ │ ├── App.jsx
│ │ ├── components
│ │ │ ├── GameBoard.css
│ │ │ ├── GameBoard.jsx
│ │ │ ├── Stats.css
│ │ │ └── Stats.jsx
│ │ ├── data
│ │ │ ├── GameData.js
│ │ │ └── GameLogic.js
│ │ ├── index.css
│ │ ├── main.jsx
│ │ ├── models
│ │ │ ├── Game.js
│ │ │ └── Player.js
│ │ ├── pages
│ │ │ ├── Game.jsx
│ │ │ ├── Login.jsx
│ │ │ └── Signup.jsx
│ │ └── utils
│ │ │ ├── GameRequests.js
│ │ │ └── LoginRequests.js
│ └── vite.config.js
└── server
│ ├── controllers
│ ├── game_api.go
│ └── login_api.go
│ ├── db
│ ├── db.go
│ ├── game_db.go
│ ├── init.sql
│ ├── player_db.go
│ └── session_db.go
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ ├── models
│ ├── game.go
│ ├── player.go
│ └── session.go
│ ├── router
│ └── router.go
│ └── utils
│ └── utils.go
├── day2
├── README.md
├── javascript
│ ├── 01-classes-and-objects.js
│ ├── 02-module1.mjs
│ └── 03-module2.mjs
└── react
│ ├── .gitignore
│ ├── index.html
│ ├── package.json
│ ├── public
│ └── vite.svg
│ ├── src
│ ├── App.jsx
│ ├── components
│ │ ├── Hello.jsx
│ │ └── HelloProps.jsx
│ ├── index.css
│ └── main.jsx
│ └── vite.config.js
├── day3
├── README.md
├── part1
│ ├── .gitignore
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── vite.svg
│ ├── src
│ │ ├── App.jsx
│ │ ├── components
│ │ │ └── YouShallPass.jsx
│ │ ├── index.css
│ │ └── main.jsx
│ └── vite.config.js
├── part2
│ ├── .gitignore
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── vite.svg
│ ├── src
│ │ ├── App.jsx
│ │ ├── index.css
│ │ └── main.jsx
│ └── vite.config.js
└── part3
│ ├── .gitignore
│ ├── index.html
│ ├── package.json
│ ├── public
│ └── vite.svg
│ ├── src
│ ├── App.jsx
│ ├── index.css
│ └── main.jsx
│ └── vite.config.js
├── day4
├── students
│ ├── .gitignore
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── vite.svg
│ ├── src
│ │ ├── App.css
│ │ ├── App.jsx
│ │ ├── classes
│ │ │ └── Student.js
│ │ ├── componets
│ │ │ └── Students.jsx
│ │ ├── index.css
│ │ └── main.jsx
│ └── vite.config.js
└── tictactoe_part1
│ ├── .gitignore
│ ├── index.html
│ ├── package.json
│ ├── public
│ └── vite.svg
│ ├── src
│ ├── App.css
│ ├── App.jsx
│ ├── components
│ │ ├── GameBoard.css
│ │ ├── GameBoard.jsx
│ │ └── Stats.jsx
│ ├── data
│ │ ├── GameData.js
│ │ └── GameLogic.js
│ ├── index.css
│ └── main.jsx
│ └── vite.config.js
├── day5
├── .gitignore
├── README.md
├── index.html
├── package.json
├── public
│ └── vite.svg
├── src
│ ├── App.css
│ ├── App.jsx
│ ├── components
│ │ ├── GameBoard.css
│ │ ├── GameBoard.jsx
│ │ ├── Stats.css
│ │ └── Stats.jsx
│ ├── data
│ │ ├── GameData.js
│ │ └── GameLogic.js
│ ├── index.css
│ └── main.jsx
└── vite.config.js
├── day6
├── 01-variables.go
├── 02-controlStatements.go
├── 03-arrays.go
├── 04-maps.go
├── 05-functions.go
├── 06-objects.go
├── 07-errorHandling
│ ├── Main.java
│ ├── go.mod
│ └── main.go
└── README.md
├── day7
├── README.md
├── queries.sql
└── rest-apis
│ ├── APIs Invocations.postman_collection.json
│ ├── Pizza API.postman_collection.json
│ ├── bmi-api
│ └── main.go
│ └── pizza-api
│ └── main.go
├── day8
├── README.md
├── db
│ ├── db.go
│ ├── init.sql
│ └── player_db.go
├── go.mod
├── go.sum
├── main.go
└── models
│ └── player.go
└── day9
├── README.md
├── controllers
└── login_api.go
├── db
├── db.go
├── init.sql
└── player_db.go
├── go.mod
├── go.sum
├── main.go
├── models
├── player.go
└── session.go
└── router
└── router.go
/.gitignore:
--------------------------------------------------------------------------------
1 | ./**/node_modules/
2 | ./**/go.sum
3 | ./**/**/go.sum
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Learn Web Development by Building a Multiplayer Game
2 |
3 | ### Day 1:
4 |
5 | * Agenda
6 | * Introduction to Frontend web development
7 | * Introduction to React
8 | * NodeJS, NPM, and VS Code Installation
9 |
10 | ### Day 2:
11 |
12 | * Final Game Preview
13 | * Array recap
14 | * JS classes
15 | * JS objects
16 | * Create Vite Project
17 | * Introduction to React
18 | * React components (definition, props, styles)
19 |
20 | ### Day 3:
21 |
22 | * Conditional components display in React
23 | * Simple todo list
24 | * Value binding
25 | * React states
26 | * Display array's elements as react components
27 |
28 | ### Day 4:
29 |
30 | * Game's logic
31 | * Data encapsulation
32 | * Logic encapsulation
33 | * Game’s initial view
34 | * Objects as props
35 | * Update object’s value/s from inside of the component
36 |
37 | ### Day 5:
38 |
39 | * Styling...
40 |
41 | ### Day 6:
42 |
43 | * Go, VS Code, and Postman Installation
44 | * Introduction to Go
45 | * variables, if, switch, for, functions, literal functions
46 | * arrays, maps, structs, interfaces, packages
47 |
48 | ### Day 7:
49 |
50 | * How the final game will look like
51 | * REST APIs
52 | * Structure of a REST API (request and response)
53 | * HTTP methods
54 | * Type of bodies
55 | * JSON
56 | * Examples
57 | * Relational Databases and MySQL(MariaDB) introduction
58 | * MariaDB installation
59 | * CRUD oprtations
60 | * create a table
61 | * create a new record
62 | * retrive a record
63 | * update a record
64 | * delete a record
65 |
66 | ### Day 8:
67 |
68 | * Database operations in Go
69 | * Writing a helper struct to wrap database operations for the player model
70 | * Viewing and handling database errors
71 |
72 | ### Day 9:
73 |
74 | * Utilizing databases in REST APIs
75 | * Creating player login REST API
76 | * Hashing a password
77 |
78 | ### Day 10:
79 |
80 | * Finalizing player login REST API
81 | * Creating game data model
82 |
83 | ### Day 11:
84 |
85 | * Creating game's database wrapper
86 | * Creating game's RESS API controller
87 |
88 | ### Day 12:
89 |
90 | * React Router
91 | * Using fetch to make REST API requests to the backend
92 | * Finalizing
93 |
94 |
95 |
--------------------------------------------------------------------------------
/day1/README.md:
--------------------------------------------------------------------------------
1 | # Day 1's Links
2 |
3 | - NodeJS
4 | `https://nodejs.org/en/download/`
5 | - Visual Studio Code (VS Code)
6 | `https://code.visualstudio.com/`
7 | - React VS Code Extension
8 | `https://marketplace.visualstudio.com/items?itemName=dsznajder.es7-react-js-snippets`
9 | - Vite
10 | `https://vitejs.dev/guide/`
11 |
--------------------------------------------------------------------------------
/day1/javascript/01-variables.js:
--------------------------------------------------------------------------------
1 | let x = 12;
2 | let y = x + 8;
3 | console.log("y = ", y);
4 |
5 | let z = 12.4;
6 | console.log("z + x = ", z + x);
7 |
8 | let name = "Ali";
9 | console.log("Hello,", name);
10 |
11 | let firstName = "Ali";
12 | let lastName = "Al-Homsi";
13 | let fullName = `${firstName} ${lastName}`;
14 | console.log("Hello", fullName);
15 |
16 | let canIDrive = false;
17 | console.log("Am I old enough to drive?");
18 |
--------------------------------------------------------------------------------
/day1/javascript/02-controlStatements.js:
--------------------------------------------------------------------------------
1 | let shallIPass = true;
2 | if (shallIPass) {
3 | console.log("You shall pass");
4 | } else {
5 | console.log("You shall not pass");
6 | }
7 |
8 | let x = 12,
9 | y = 10,
10 | z = 13;
11 |
12 | if (x > y && x > z) {
13 | console.log("x is the greatest");
14 | } else if (y > x && y > z) {
15 | console.log("y is the greatest");
16 | } else if (z > x && z > y) {
17 | console.log("z is the greatest");
18 | }
19 |
20 | let mark = "B";
21 | switch (mark) {
22 | case "A":
23 | console.log("big nerd");
24 | break;
25 | case "B":
26 | console.log("nerd");
27 | break;
28 | case "C":
29 | console.log("ain't much");
30 | break;
31 | default:
32 | console.log("I don't know what is that!");
33 | }
34 |
35 | for (let i = 1; i <= 10; i++) {
36 | console.log(i);
37 | }
38 |
39 | let count = 10;
40 | while (count > 0) {
41 | console.log(count);
42 | count--;
43 | }
44 |
--------------------------------------------------------------------------------
/day1/javascript/03-arrays.js:
--------------------------------------------------------------------------------
1 | let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
2 | console.log("all array's elements", arr);
3 |
4 | console.log("element by element");
5 | for (let i = 0; i < arr.length; i++) {
6 | console.log(arr[i]);
7 | }
8 |
9 | arr[0] = 99;
10 | console.log(arr);
11 |
12 | console.log("using array's forEach method");
13 | arr.forEach((element) => console.log(element));
14 |
15 | let doubleVals = arr.map((element) => {
16 | return element * 2;
17 | });
18 | console.log(doubleVals);
19 |
20 | console.log(
21 | arr.findIndex((element) => {
22 | return element === 99;
23 | })
24 | );
25 |
--------------------------------------------------------------------------------
/day1/javascript/04-functions.js:
--------------------------------------------------------------------------------
1 | function nothing() {
2 | console.log("nothing");
3 | }
4 |
5 | nothing();
6 |
7 | function greet(name) {
8 | console.log(`Hello, ${name}`);
9 | }
10 |
11 | greet("Jaber");
12 |
13 | function square(n) {
14 | return n * n;
15 | }
16 |
17 | console.log(square(4));
18 |
19 | const nothing2 = () => {
20 | console.log("nothing2");
21 | };
22 |
23 | nothing2();
24 |
25 | const add = (x, y, z) => {
26 | return x + y + z;
27 | };
28 |
29 | console.log(add(1, 2, 3));
30 |
--------------------------------------------------------------------------------
/day10/README.md:
--------------------------------------------------------------------------------
1 | # Day 10:
2 |
3 | - Install `uuid` for Go:
4 | - Open terminal or CMD inside a project
5 | - Run `go get -u github.com/google/uuid`
6 | - Check `controllers/login_api.go` for more details about the hash
7 |
--------------------------------------------------------------------------------
/day10/controllers/login_api.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 | "strings"
8 | "tictactoe/db"
9 | "tictactoe/models"
10 |
11 | "github.com/google/uuid"
12 | "golang.org/x/crypto/bcrypt"
13 | )
14 |
15 | type LoginAPI struct {
16 | playerDB *db.PlayerDB
17 | sessionDB *db.SessionDB
18 | }
19 |
20 | func NewLoginAPI(playerDB *db.PlayerDB, sessionDB *db.SessionDB) *LoginAPI {
21 | return &LoginAPI{
22 | playerDB: playerDB,
23 | sessionDB: sessionDB,
24 | }
25 | }
26 |
27 | type reqBody struct {
28 | Username string `json:"username"`
29 | Password string `json:"password"`
30 | }
31 |
32 | func (l *LoginAPI) HandleSignup(resp http.ResponseWriter, req *http.Request) {
33 | var requestBody reqBody
34 |
35 | err := json.NewDecoder(req.Body).Decode(&requestBody)
36 | if err != nil {
37 | resp.WriteHeader(http.StatusBadRequest)
38 | return
39 | }
40 |
41 | hashed, err := bcrypt.GenerateFromPassword([]byte(requestBody.Password), bcrypt.MinCost)
42 | if err != nil {
43 | resp.WriteHeader(http.StatusInternalServerError)
44 | return
45 | }
46 |
47 | requestBody.Password = string(hashed)
48 |
49 | player, err := l.playerDB.Create(models.Player{
50 | Username: requestBody.Username,
51 | Password: requestBody.Password,
52 | })
53 | if err != nil {
54 | resp.WriteHeader(http.StatusInternalServerError)
55 | return
56 | }
57 |
58 | resp.Header().Set("Content-Type", "application/json")
59 | json.NewEncoder(resp).Encode(player)
60 | }
61 |
62 | func (l *LoginAPI) HandleLogin(resp http.ResponseWriter, req *http.Request) {
63 | var requestBody reqBody
64 |
65 | err := json.NewDecoder(req.Body).Decode(&requestBody)
66 | if err != nil {
67 | resp.WriteHeader(http.StatusBadRequest)
68 | fmt.Fprintln(resp, "👎👎 your request has an incorrect form 👎👎")
69 | return
70 | }
71 |
72 | player, err := l.playerDB.Get(requestBody.Username)
73 | if err != nil {
74 | resp.WriteHeader(http.StatusUnauthorized)
75 | fmt.Fprintln(resp, "username is incorrect 👎")
76 | return
77 | }
78 |
79 | err = bcrypt.CompareHashAndPassword([]byte(player.Password), []byte(requestBody.Password))
80 | if err != nil {
81 | resp.WriteHeader(http.StatusUnauthorized)
82 | fmt.Fprintln(resp, "password is incorrect 👎")
83 | return
84 | }
85 |
86 | session := models.Session{
87 | Username: player.Username,
88 | Token: strings.ReplaceAll(uuid.NewString(), "-", ""),
89 | }
90 |
91 | err = l.sessionDB.Create(session)
92 | if err != nil {
93 | resp.WriteHeader(http.StatusInternalServerError)
94 | fmt.Fprintln(resp, "something went wrong ☹️")
95 | return
96 | }
97 |
98 | resp.Header().Set("Authorization", session.Token)
99 | fmt.Fprintln(resp, "ok 👍")
100 | }
101 |
102 | func (l *LoginAPI) HandleTokenLogin(resp http.ResponseWriter, req *http.Request) {
103 | token := req.Header.Get("Authorization")
104 |
105 | session, err := l.sessionDB.Get(token)
106 | if err != nil {
107 | resp.WriteHeader(http.StatusUnauthorized)
108 | fmt.Fprintln(resp, "invalid token 👎")
109 | return
110 | }
111 |
112 | player, err := l.playerDB.Get(session.Username)
113 | if err != nil {
114 | resp.WriteHeader(http.StatusInternalServerError)
115 | fmt.Fprintln(resp, "something went wrong")
116 | return
117 | }
118 |
119 | player.Password = ""
120 |
121 | resp.Header().Set("Content-Type", "application/json")
122 | json.NewEncoder(resp).Encode(player)
123 | }
124 |
--------------------------------------------------------------------------------
/day10/db/db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "database/sql"
5 |
6 | _ "github.com/go-sql-driver/mysql"
7 | )
8 |
9 | var connectionInstance *sql.DB = nil
10 |
11 | func GetInstance() *sql.DB {
12 | if connectionInstance == nil {
13 | var err error
14 | // DB_USERNAME is root by default
15 | // DB_PASSWORD is what you've set when installing the database
16 | // DB_NAME is the database you've created using `CREATE DATABASE whateverName`
17 | // connectionInstance, err = sql.Open("mysql", "DB_USERNAME:DB_PASSWORD@tcp(localhost:3306)/DB_NAME?parseTime=True")
18 | connectionInstance, err = sql.Open("mysql", "root:1234@tcp(localhost:3306)/ttt?parseTime=True")
19 | if err != nil {
20 | panic(err)
21 | }
22 | }
23 | return connectionInstance
24 | }
25 |
--------------------------------------------------------------------------------
/day10/db/init.sql:
--------------------------------------------------------------------------------
1 | CREATE DATABASE ttt;
2 |
3 | CREATE TABLE IF NOT EXISTS ttt.players (
4 | id INTEGER AUTO_INCREMENT,
5 | username VARCHAR(60) UNIQUE NOT NULL,
6 | password VARCHAR(200),
7 | created_at TIMESTAMP,
8 | updated_at TIMESTAMP,
9 | PRIMARY KEY (id)
10 | );
11 |
12 | CREATE TABLE IF NOT EXISTS ttt.sessions (
13 | id INTEGER AUTO_INCREMENT,
14 | username VARCHAR(60),
15 | token VARCHAR(32) UNIQUE,
16 | PRIMARY KEY (id)
17 | );
18 |
19 | CREATE TABLE IF NOT EXISTS ttt.game (
20 | id INTEGER AUTO_INCREMENT,
21 | x_player_id INTEGER,
22 | o_player_id INTEGER,
23 | x_score INTEGER,
24 | o_score INTEGER,
25 | cells VARCHAR(20),
26 | current_player_id INTEGER,
27 | PRIMARY KEY (id),
28 | FOREIGN KEY (x_player_id) REFERENCES ttt.players(id),
29 | FOREIGN KEY (current_player_id) REFERENCES ttt.players(id)
30 | );
31 |
--------------------------------------------------------------------------------
/day10/db/player_db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import "tictactoe/models"
4 |
5 | type PlayerDB struct{}
6 |
7 | func (p *PlayerDB) Create(player models.Player) (models.Player, error) {
8 | result, err := GetInstance().Exec(
9 | "INSERT INTO ttt.players (username, password, created_at) VALUES (?, ?, current_timestamp)",
10 | player.Username, player.Password)
11 | if err != nil {
12 | return models.Player{}, err
13 | }
14 |
15 | playerId, err := result.LastInsertId()
16 | if err != nil {
17 | return models.Player{}, err
18 | }
19 |
20 | player.Id = int(playerId)
21 |
22 | return player, nil
23 | }
24 |
25 | func (p *PlayerDB) Get(username string) (models.Player, error) {
26 | var player models.Player
27 | err := GetInstance().
28 | QueryRow("SELECT id, username, password FROM ttt.players WHERE username=?", username).
29 | Scan(&player.Id, &player.Username, &player.Password)
30 |
31 | if err != nil {
32 | return models.Player{}, err
33 | }
34 |
35 | return player, nil
36 | }
37 |
38 | func (p *PlayerDB) Update(username string, player models.Player) error {
39 | _, err := GetInstance().Exec("UPDATE ttt.players SET username=?, password=? WHERE username=?", player.Username, player.Password, username)
40 | if err != nil {
41 | return err
42 | }
43 | return nil
44 | }
45 |
46 | func (p *PlayerDB) Delete(username string) error {
47 | _, err := GetInstance().Exec("DELETE FROM ttt.players WHERE username=?", username)
48 | if err != nil {
49 | return err
50 | }
51 | return nil
52 | }
--------------------------------------------------------------------------------
/day10/db/session_db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import "tictactoe/models"
4 |
5 | type SessionDB struct{}
6 |
7 | func (s *SessionDB) Create(session models.Session) error {
8 | _, err := GetInstance().Exec("INSERT INTO ttt.sessions (username, token) VALUES (?, ?)", session.Username, session.Token)
9 | if err != nil {
10 | return err
11 | }
12 | return nil
13 | }
14 |
15 | func (s *SessionDB) Get(token string) (models.Session, error) {
16 | var session models.Session
17 |
18 | err := GetInstance().
19 | QueryRow("SELECT username, token FROM ttt.sessions WHERE token=?", token).
20 | Scan(&session.Username, &session.Token)
21 | if err != nil {
22 | return models.Session{}, err
23 | }
24 |
25 | return session, nil
26 | }
27 |
--------------------------------------------------------------------------------
/day10/go.mod:
--------------------------------------------------------------------------------
1 | module tictactoe
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/go-sql-driver/mysql v1.7.0 // indirect
7 | github.com/google/uuid v1.3.0 // indirect
8 | golang.org/x/crypto v0.6.0 // indirect
9 | )
10 |
--------------------------------------------------------------------------------
/day10/go.sum:
--------------------------------------------------------------------------------
1 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
2 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
3 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
4 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
5 | golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
6 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
7 |
--------------------------------------------------------------------------------
/day10/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "tictactoe/router"
4 |
5 | func main() {
6 | router.StartServer()
7 | }
8 |
--------------------------------------------------------------------------------
/day10/models/game.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "strings"
7 | "tictactoe/utils"
8 | )
9 |
10 | type Game struct {
11 | Id int `json:"id"`
12 | XPlayerId int `json:"xPlayerId"`
13 | OPlayerId int `json:"oPlayerId"`
14 | XScore int `json:"xScore"`
15 | OScore int `json:"oScore"`
16 | Cells string `json:"cells"`
17 | CurrentPlayerId int `json:"currentPlayerId"`
18 | }
19 |
20 | func (g *Game) resetGame() {
21 | g.Cells = "1 2 3 4 5 6 7 8 9"
22 | g.CurrentPlayerId = g.XPlayerId
23 | }
24 |
25 | func (g *Game) switchTurns() {
26 | if g.CurrentPlayerId == g.XPlayerId {
27 | g.CurrentPlayerId = g.OPlayerId
28 | } else if g.CurrentPlayerId == g.OPlayerId {
29 | g.CurrentPlayerId = g.XPlayerId
30 | }
31 | }
32 |
33 | func (g *Game) MarkCell(cellNumber int) error {
34 | if !g.isCellNumberInBounds(cellNumber) {
35 | return errors.New("cell number is not in bound")
36 | }
37 |
38 | cells := strings.Split(g.Cells, " ")
39 | if g.areAllCellsOccupied(cells) {
40 | return errors.New("all game's cells are occupied")
41 | }
42 |
43 | if g.isCellOccupied(cells, cellNumber) {
44 | return errors.New("the requested cell: " + fmt.Sprint(cellNumber) + " is occupied with: " + cells[cellNumber])
45 | }
46 |
47 | currentPlayer := g.getCurrentPlayerSymbol()
48 | cells[cellNumber] = currentPlayer
49 | g.switchTurns()
50 |
51 | return nil
52 | }
53 |
54 | func (g *Game) CheckWinnerAndUpdateScore() {
55 | cells := strings.Split(g.Cells, " ")
56 |
57 | if g.areAllCellsOccupied(cells) {
58 | g.resetGame()
59 | return
60 | }
61 |
62 | winner := utils.CheckWinner(cells)
63 | switch winner {
64 | case "X":
65 | g.XScore++
66 | case "O":
67 | g.OScore++
68 | }
69 |
70 | if len(winner) > 0 {
71 | g.resetGame()
72 | }
73 | }
74 |
75 | func (g *Game) isCellOccupied(cells []string, cellNumber int) bool {
76 | return cells[cellNumber] != "X" && cells[cellNumber] != "O"
77 | }
78 |
79 | func (g *Game) areAllCellsOccupied(cells []string) bool {
80 | for index, _ := range cells {
81 | if !g.isCellOccupied(cells, index) {
82 | return false
83 | }
84 | }
85 | return true
86 | }
87 |
88 | func (g *Game) isCellNumberInBounds(cellNumber int) bool {
89 | return cellNumber < 9 && cellNumber >= 0
90 | }
91 |
92 | func (g *Game) getCurrentPlayerSymbol() string {
93 | if g.CurrentPlayerId == g.XPlayerId {
94 | return "X"
95 | } else if g.CurrentPlayerId == g.OPlayerId {
96 | return "O"
97 | }
98 | return ""
99 | }
100 |
--------------------------------------------------------------------------------
/day10/models/player.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "time"
4 |
5 | type Player struct {
6 | Id int `json:"id"`
7 | Username string `json:"username"`
8 | Password string `json:"password"`
9 | CreatedAt time.Time `json:"-"`
10 | UpdatedAt time.Time `json:"-"`
11 | }
12 |
--------------------------------------------------------------------------------
/day10/models/session.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type Session struct {
4 | Username string `json:"-"`
5 | Token string `json:"-"`
6 | }
7 |
--------------------------------------------------------------------------------
/day10/router/router.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "log"
5 | "net/http"
6 | "tictactoe/controllers"
7 | "tictactoe/db"
8 | )
9 |
10 | func StartServer() {
11 | playerDB := new(db.PlayerDB)
12 | sessionDB := new(db.SessionDB)
13 |
14 | loginAPI := controllers.NewLoginAPI(playerDB, sessionDB)
15 |
16 | router := http.NewServeMux()
17 | router.HandleFunc("/player/login", loginAPI.HandleLogin)
18 | router.HandleFunc("/player/signup", loginAPI.HandleSignup)
19 | router.HandleFunc("/player/token", loginAPI.HandleTokenLogin)
20 |
21 | log.Println("started server at http://localhost:8080")
22 | http.ListenAndServe(":8080", router)
23 | }
24 |
--------------------------------------------------------------------------------
/day10/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | func CheckWinner(cells []string) string {
4 | if checkIfPlayerWon(cells, "X") {
5 | return "X"
6 | } else if checkIfPlayerWon(cells, "O") {
7 | return "O"
8 | }
9 | return ""
10 | }
11 |
12 | func checkIfPlayerWon(cells []string, playerType string) bool {
13 | var firstDiagonal = cells[0] == cells[4] && cells[4] == cells[8] && cells[0] == playerType
14 |
15 | var secondDiagonal = cells[2] == cells[4] && cells[4] == cells[6] && cells[2] == playerType
16 |
17 | var firstRow = cells[0] == cells[1] && cells[1] == cells[2] && cells[1] == playerType
18 |
19 | var secondRow = cells[3] == cells[4] && cells[4] == cells[5] && cells[3] == playerType
20 |
21 | var thirdRow = cells[6] == cells[7] && cells[7] == cells[8] && cells[6] == playerType
22 |
23 | var firstColumn = cells[0] == cells[3] && cells[3] == cells[6] && cells[0] == playerType
24 |
25 | var secondColumn = cells[1] == cells[4] && cells[4] == cells[7] && cells[1] == playerType
26 |
27 | var thirdColumn = cells[2] == cells[5] && cells[5] == cells[8] && cells[8] == playerType
28 |
29 | return firstColumn ||
30 | secondColumn ||
31 | thirdColumn ||
32 | firstRow ||
33 | secondRow ||
34 | thirdRow ||
35 | firstDiagonal ||
36 | secondDiagonal
37 | }
38 |
--------------------------------------------------------------------------------
/day11/controllers/game_api.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 | "strconv"
8 | "tictactoe/db"
9 | "tictactoe/models"
10 | )
11 |
12 | type GameAPI struct {
13 | gameDB *db.GameDB
14 | playerDB *db.PlayerDB
15 | sessionDB *db.SessionDB
16 | }
17 |
18 | func NewGameAPI(gameDB *db.GameDB, playerDB *db.PlayerDB, sessionDB *db.SessionDB) *GameAPI {
19 | return &GameAPI{
20 | gameDB: gameDB,
21 | playerDB: playerDB,
22 | sessionDB: sessionDB,
23 | }
24 | }
25 |
26 | func (g *GameAPI) setupResponseHeaders(resp http.ResponseWriter) {
27 | resp.Header().Set("Content-Type", "application/json")
28 | }
29 |
30 | func (g *GameAPI) HandleNewGame(resp http.ResponseWriter, req *http.Request) {
31 | token := req.Header.Get("Authorization")
32 | if len(token) == 0 {
33 | resp.WriteHeader(http.StatusUnauthorized)
34 | return
35 | }
36 |
37 | player, err := g.fetchPlayerUsingToken(token)
38 | if err != nil {
39 | resp.WriteHeader(http.StatusUnauthorized)
40 | return
41 | }
42 |
43 | game := models.Game{
44 | XPlayerId: player.Id,
45 | }
46 |
47 | newGame, err := g.gameDB.Create(game)
48 | if err != nil {
49 | resp.WriteHeader(http.StatusInternalServerError)
50 | return
51 | }
52 |
53 | g.setupResponseHeaders(resp)
54 | json.NewEncoder(resp).Encode(newGame)
55 | }
56 |
57 | func (g *GameAPI) HandleJoinGame(resp http.ResponseWriter, req *http.Request) {
58 | gameId := req.URL.Query().Get("id")
59 | if len(gameId) == 0 {
60 | resp.WriteHeader(http.StatusBadRequest)
61 | return
62 | }
63 |
64 | intGameId, err := strconv.Atoi(gameId)
65 | if len(gameId) == 0 {
66 | resp.WriteHeader(http.StatusBadRequest)
67 | return
68 | }
69 |
70 | token := req.Header.Get("Authorization")
71 | if len(token) == 0 {
72 | resp.WriteHeader(http.StatusUnauthorized)
73 | return
74 | }
75 |
76 | player, err := g.fetchPlayerUsingToken(token)
77 | if err != nil {
78 | resp.WriteHeader(http.StatusUnauthorized)
79 | return
80 | }
81 |
82 | game, err := g.gameDB.Get(intGameId)
83 | if err != nil {
84 | fmt.Println(err)
85 | resp.WriteHeader(http.StatusNotFound)
86 | return
87 | }
88 |
89 | if game.OPlayerId != 0 {
90 | resp.WriteHeader(http.StatusBadRequest)
91 | fmt.Fprintln(resp, "THIS GAME IS FULLY OCCUPIED!!!")
92 | return
93 | }
94 |
95 | game.OPlayerId = player.Id
96 |
97 | err = g.gameDB.Update(game.Id, game)
98 | if err != nil {
99 | resp.WriteHeader(http.StatusInternalServerError)
100 | return
101 | }
102 |
103 | g.setupResponseHeaders(resp)
104 | json.NewEncoder(resp).Encode(game)
105 | }
106 |
107 | func (g *GameAPI) HandlePlayTurn(resp http.ResponseWriter, req *http.Request) {
108 | var reqBody struct {
109 | GameId int `json:"gameId"`
110 | CellNumber int `json:"cellNumber"`
111 | }
112 |
113 | err := json.NewDecoder(req.Body).Decode(&reqBody)
114 | if err != nil {
115 | resp.WriteHeader(http.StatusBadRequest)
116 | return
117 | }
118 |
119 | token := req.Header.Get("Authorization")
120 | if len(token) == 0 {
121 | resp.WriteHeader(http.StatusUnauthorized)
122 | return
123 | }
124 |
125 | player, err := g.fetchPlayerUsingToken(token)
126 | if err != nil {
127 | resp.WriteHeader(http.StatusUnauthorized)
128 | return
129 | }
130 |
131 | game, err := g.gameDB.Get(reqBody.GameId)
132 | if err != nil {
133 | resp.WriteHeader(http.StatusInternalServerError)
134 | return
135 | }
136 |
137 | if player.Id != game.CurrentPlayerId {
138 | resp.WriteHeader(http.StatusBadRequest)
139 | fmt.Fprintln(resp, "this player can't play right now")
140 | return
141 | }
142 |
143 | err = game.MarkCell(reqBody.CellNumber)
144 | if err != nil {
145 | resp.WriteHeader(http.StatusBadRequest)
146 | fmt.Fprintln(resp, err.Error())
147 | return
148 | }
149 |
150 | game.CheckWinnerAndUpdateScore()
151 |
152 | err = g.gameDB.Update(game.Id, game)
153 | if err != nil {
154 | resp.WriteHeader(http.StatusInternalServerError)
155 | return
156 | }
157 |
158 | g.setupResponseHeaders(resp)
159 | json.NewEncoder(resp).Encode(game)
160 | }
161 |
162 | func (g *GameAPI) HandleGetGame(resp http.ResponseWriter, req *http.Request) {
163 | gameId := req.URL.Query().Get("id")
164 | if len(gameId) == 0 {
165 | resp.WriteHeader(http.StatusBadRequest)
166 | return
167 | }
168 |
169 | intGameId, err := strconv.Atoi(gameId)
170 | if len(gameId) == 0 {
171 | resp.WriteHeader(http.StatusBadRequest)
172 | return
173 | }
174 |
175 | token := req.Header.Get("Authorization")
176 | if len(token) == 0 {
177 | resp.WriteHeader(http.StatusUnauthorized)
178 | return
179 | }
180 |
181 | player, err := g.fetchPlayerUsingToken(token)
182 | if err != nil {
183 | resp.WriteHeader(http.StatusUnauthorized)
184 | return
185 | }
186 |
187 | game, err := g.gameDB.Get(intGameId)
188 | if err != nil {
189 | resp.WriteHeader(http.StatusInternalServerError)
190 | return
191 | }
192 |
193 | if player.Id != game.XPlayerId && player.Id != game.OPlayerId {
194 | resp.WriteHeader(http.StatusUnauthorized)
195 | fmt.Fprintln(resp, "this player does not belong to this game!")
196 | return
197 | }
198 |
199 | g.setupResponseHeaders(resp)
200 | json.NewEncoder(resp).Encode(game)
201 | }
202 |
203 | func (g *GameAPI) fetchPlayerUsingToken(token string) (models.Player, error) {
204 | session, err := g.sessionDB.Get(token)
205 | if err != nil {
206 | return models.Player{}, err
207 | }
208 |
209 | player, err := g.playerDB.Get(session.Username)
210 | if err != nil {
211 | return models.Player{}, err
212 | }
213 |
214 | return player, nil
215 | }
216 |
--------------------------------------------------------------------------------
/day11/controllers/login_api.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 | "strings"
8 | "tictactoe/db"
9 | "tictactoe/models"
10 |
11 | "github.com/google/uuid"
12 | "golang.org/x/crypto/bcrypt"
13 | )
14 |
15 | type LoginAPI struct {
16 | playerDB *db.PlayerDB
17 | sessionDB *db.SessionDB
18 | }
19 |
20 | func NewLoginAPI(playerDB *db.PlayerDB, sessionDB *db.SessionDB) *LoginAPI {
21 | return &LoginAPI{
22 | playerDB: playerDB,
23 | sessionDB: sessionDB,
24 | }
25 | }
26 |
27 | type reqBody struct {
28 | Username string `json:"username"`
29 | Password string `json:"password"`
30 | }
31 |
32 | func (l *LoginAPI) HandleSignup(resp http.ResponseWriter, req *http.Request) {
33 | var requestBody reqBody
34 |
35 | err := json.NewDecoder(req.Body).Decode(&requestBody)
36 | if err != nil {
37 | resp.WriteHeader(http.StatusBadRequest)
38 | return
39 | }
40 |
41 | hashed, err := bcrypt.GenerateFromPassword([]byte(requestBody.Password), bcrypt.MinCost)
42 | if err != nil {
43 | resp.WriteHeader(http.StatusInternalServerError)
44 | return
45 | }
46 |
47 | requestBody.Password = string(hashed)
48 |
49 | player, err := l.playerDB.Create(models.Player{
50 | Username: requestBody.Username,
51 | Password: requestBody.Password,
52 | })
53 | if err != nil {
54 | resp.WriteHeader(http.StatusInternalServerError)
55 | return
56 | }
57 |
58 | resp.Header().Set("Content-Type", "application/json")
59 | json.NewEncoder(resp).Encode(player)
60 | }
61 |
62 | func (l *LoginAPI) HandleLogin(resp http.ResponseWriter, req *http.Request) {
63 | var requestBody reqBody
64 |
65 | err := json.NewDecoder(req.Body).Decode(&requestBody)
66 | if err != nil {
67 | resp.WriteHeader(http.StatusBadRequest)
68 | fmt.Fprintln(resp, "👎👎 your request has an incorrect form 👎👎")
69 | return
70 | }
71 |
72 | player, err := l.playerDB.Get(requestBody.Username)
73 | if err != nil {
74 | resp.WriteHeader(http.StatusUnauthorized)
75 | fmt.Fprintln(resp, "username is incorrect 👎")
76 | return
77 | }
78 |
79 | err = bcrypt.CompareHashAndPassword([]byte(player.Password), []byte(requestBody.Password))
80 | if err != nil {
81 | resp.WriteHeader(http.StatusUnauthorized)
82 | fmt.Fprintln(resp, "password is incorrect 👎")
83 | return
84 | }
85 |
86 | session := models.Session{
87 | Username: player.Username,
88 | Token: strings.ReplaceAll(uuid.NewString(), "-", ""),
89 | }
90 |
91 | err = l.sessionDB.Create(session)
92 | if err != nil {
93 | resp.WriteHeader(http.StatusInternalServerError)
94 | fmt.Fprintln(resp, "something went wrong ☹️")
95 | return
96 | }
97 |
98 | resp.Header().Set("Authorization", session.Token)
99 | fmt.Fprintln(resp, "ok 👍")
100 | }
101 |
102 | func (l *LoginAPI) HandleTokenLogin(resp http.ResponseWriter, req *http.Request) {
103 | token := req.Header.Get("Authorization")
104 |
105 | session, err := l.sessionDB.Get(token)
106 | if err != nil {
107 | resp.WriteHeader(http.StatusUnauthorized)
108 | fmt.Fprintln(resp, "invalid token 👎")
109 | return
110 | }
111 |
112 | player, err := l.playerDB.Get(session.Username)
113 | if err != nil {
114 | resp.WriteHeader(http.StatusInternalServerError)
115 | fmt.Fprintln(resp, "something went wrong")
116 | return
117 | }
118 |
119 | player.Password = ""
120 |
121 | resp.Header().Set("Content-Type", "application/json")
122 | json.NewEncoder(resp).Encode(player)
123 | }
124 |
--------------------------------------------------------------------------------
/day11/db/db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "database/sql"
5 |
6 | _ "github.com/go-sql-driver/mysql"
7 | )
8 |
9 | var connectionInstance *sql.DB = nil
10 |
11 | func GetInstance() *sql.DB {
12 | if connectionInstance == nil {
13 | var err error
14 | // DB_USERNAME is root by default
15 | // DB_PASSWORD is what you've set when installing the database
16 | // DB_NAME is the database you've created using `CREATE DATABASE whateverName`
17 | // connectionInstance, err = sql.Open("mysql", "DB_USERNAME:DB_PASSWORD@tcp(localhost:3306)/DB_NAME?parseTime=True")
18 | connectionInstance, err = sql.Open("mysql", "root:1234@tcp(localhost:3306)/ttt?parseTime=True")
19 | if err != nil {
20 | panic(err)
21 | }
22 | }
23 | return connectionInstance
24 | }
25 |
--------------------------------------------------------------------------------
/day11/db/game_db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import "tictactoe/models"
4 |
5 | type GameDB struct{}
6 |
7 | func (g *GameDB) Create(game models.Game) (models.Game, error) {
8 | result, err := GetInstance().Exec(
9 | "INSERT INTO ttt.games (x_player_id, o_player_id, x_score, o_score, current_player_id, cells) VALUES (?, ?, ?, ?, ?, ?)",
10 | game.XPlayerId, 0, 0, 0, game.XPlayerId, "1 2 3 4 5 6 7 8 9")
11 | if err != nil {
12 | return models.Game{}, err
13 | }
14 |
15 | lastId, err := result.LastInsertId()
16 | if err != nil {
17 | return models.Game{}, err
18 | }
19 |
20 | game.Id = int(lastId)
21 | game.Cells = "1 2 3 4 5 6 7 8 9"
22 | game.CurrentPlayerId = game.XPlayerId
23 | game.XScore = 0
24 | game.OScore = 0
25 |
26 | return game, nil
27 | }
28 |
29 | func (g *GameDB) Get(id int) (models.Game, error) {
30 | var game models.Game
31 | err := GetInstance().
32 | QueryRow("SELECT * FROM ttt.games WHERE id=?", id).
33 | Scan(&game.Id, &game.XPlayerId, &game.OPlayerId, &game.XScore, &game.OScore,
34 | &game.Cells, &game.CurrentPlayerId)
35 | if err != nil {
36 | return models.Game{}, err
37 | }
38 |
39 | return game, nil
40 | }
41 |
42 | func (p *GameDB) Update(id int, game models.Game) error {
43 | _, err := GetInstance().Exec(
44 | "UPDATE ttt.games SET cells=?, x_score=?, o_score=?, current_player_id=?, o_player_id=?",
45 | game.Cells, game.XScore, game.OScore, game.CurrentPlayerId, game.OPlayerId)
46 | if err != nil {
47 | return err
48 | }
49 | return nil
50 | }
51 |
52 | func (p *GameDB) Delete(id int) error {
53 | _, err := GetInstance().Exec("DELETE FROM ttt.games WHERE id=?", id)
54 | if err != nil {
55 | return err
56 | }
57 | return nil
58 | }
59 |
--------------------------------------------------------------------------------
/day11/db/init.sql:
--------------------------------------------------------------------------------
1 | CREATE DATABASE ttt;
2 |
3 | CREATE TABLE IF NOT EXISTS ttt.players (
4 | id INTEGER AUTO_INCREMENT,
5 | username VARCHAR(60) UNIQUE NOT NULL,
6 | password VARCHAR(200),
7 | created_at TIMESTAMP,
8 | updated_at TIMESTAMP,
9 | PRIMARY KEY (id)
10 | );
11 |
12 | CREATE TABLE IF NOT EXISTS ttt.sessions (
13 | id INTEGER AUTO_INCREMENT,
14 | username VARCHAR(60),
15 | token VARCHAR(32) UNIQUE,
16 | PRIMARY KEY (id)
17 | );
18 |
19 | CREATE TABLE IF NOT EXISTS ttt.game (
20 | id INTEGER AUTO_INCREMENT,
21 | x_player_id INTEGER,
22 | o_player_id INTEGER,
23 | x_score INTEGER,
24 | o_score INTEGER,
25 | cells VARCHAR(20),
26 | current_player_id INTEGER,
27 | PRIMARY KEY (id),
28 | FOREIGN KEY (x_player_id) REFERENCES ttt.players(id),
29 | FOREIGN KEY (current_player_id) REFERENCES ttt.players(id)
30 | );
31 |
--------------------------------------------------------------------------------
/day11/db/player_db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import "tictactoe/models"
4 |
5 | type PlayerDB struct{}
6 |
7 | func (p *PlayerDB) Create(player models.Player) (models.Player, error) {
8 | result, err := GetInstance().Exec(
9 | "INSERT INTO ttt.players (username, password, created_at) VALUES (?, ?, current_timestamp)",
10 | player.Username, player.Password)
11 | if err != nil {
12 | return models.Player{}, err
13 | }
14 |
15 | playerId, err := result.LastInsertId()
16 | if err != nil {
17 | return models.Player{}, err
18 | }
19 |
20 | player.Id = int(playerId)
21 |
22 | return player, nil
23 | }
24 |
25 | func (p *PlayerDB) Get(username string) (models.Player, error) {
26 | var player models.Player
27 | err := GetInstance().
28 | QueryRow("SELECT id, username, password FROM ttt.players WHERE username=?", username).
29 | Scan(&player.Id, &player.Username, &player.Password)
30 |
31 | if err != nil {
32 | return models.Player{}, err
33 | }
34 |
35 | return player, nil
36 | }
37 |
38 | func (p *PlayerDB) Update(username string, player models.Player) error {
39 | _, err := GetInstance().Exec("UPDATE ttt.players SET username=?, password=? WHERE username=?", player.Username, player.Password, username)
40 | if err != nil {
41 | return err
42 | }
43 | return nil
44 | }
45 |
46 | func (p *PlayerDB) Delete(username string) error {
47 | _, err := GetInstance().Exec("DELETE FROM ttt.players WHERE username=?", username)
48 | if err != nil {
49 | return err
50 | }
51 | return nil
52 | }
--------------------------------------------------------------------------------
/day11/db/session_db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import "tictactoe/models"
4 |
5 | type SessionDB struct{}
6 |
7 | func (s *SessionDB) Create(session models.Session) error {
8 | _, err := GetInstance().Exec("INSERT INTO ttt.sessions (username, token) VALUES (?, ?)", session.Username, session.Token)
9 | if err != nil {
10 | return err
11 | }
12 | return nil
13 | }
14 |
15 | func (s *SessionDB) Get(token string) (models.Session, error) {
16 | var session models.Session
17 |
18 | err := GetInstance().
19 | QueryRow("SELECT username, token FROM ttt.sessions WHERE token=?", token).
20 | Scan(&session.Username, &session.Token)
21 | if err != nil {
22 | return models.Session{}, err
23 | }
24 |
25 | return session, nil
26 | }
27 |
--------------------------------------------------------------------------------
/day11/go.mod:
--------------------------------------------------------------------------------
1 | module tictactoe
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/go-sql-driver/mysql v1.7.0 // indirect
7 | github.com/google/uuid v1.3.0 // indirect
8 | golang.org/x/crypto v0.6.0 // indirect
9 | )
10 |
--------------------------------------------------------------------------------
/day11/go.sum:
--------------------------------------------------------------------------------
1 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
2 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
3 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
4 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
5 | golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
6 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
7 |
--------------------------------------------------------------------------------
/day11/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "tictactoe/router"
4 |
5 | func main() {
6 | router.StartServer()
7 | }
8 |
--------------------------------------------------------------------------------
/day11/models/game.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "strings"
7 | "tictactoe/utils"
8 | )
9 |
10 | type Game struct {
11 | Id int `json:"id"`
12 | XPlayerId int `json:"xPlayerId"`
13 | OPlayerId int `json:"oPlayerId"`
14 | XScore int `json:"xScore"`
15 | OScore int `json:"oScore"`
16 | Cells string `json:"cells"`
17 | CurrentPlayerId int `json:"currentPlayerId"`
18 | }
19 |
20 | func (g *Game) resetGame() {
21 | g.Cells = "1 2 3 4 5 6 7 8 9"
22 | g.CurrentPlayerId = g.XPlayerId
23 | }
24 |
25 | func (g *Game) switchTurns() {
26 | if g.CurrentPlayerId == g.XPlayerId {
27 | g.CurrentPlayerId = g.OPlayerId
28 | } else if g.CurrentPlayerId == g.OPlayerId {
29 | g.CurrentPlayerId = g.XPlayerId
30 | }
31 | }
32 |
33 | func (g *Game) MarkCell(cellNumber int) error {
34 | if !g.isCellNumberInBounds(cellNumber) {
35 | return errors.New("cell number is not in bound")
36 | }
37 |
38 | cells := strings.Split(g.Cells, " ")
39 | if g.areAllCellsOccupied(cells) {
40 | return errors.New("all game's cells are occupied")
41 | }
42 |
43 | if g.isCellOccupied(cells, cellNumber) {
44 | return errors.New("the requested cell: " + fmt.Sprint(cellNumber) + " is occupied with: " + cells[cellNumber])
45 | }
46 |
47 | currentPlayer := g.getCurrentPlayerSymbol()
48 | cells[cellNumber] = currentPlayer
49 | g.Cells = strings.Join(cells, " ")
50 |
51 | g.switchTurns()
52 |
53 | return nil
54 | }
55 |
56 | func (g *Game) CheckWinnerAndUpdateScore() {
57 | cells := strings.Split(g.Cells, " ")
58 |
59 | if g.areAllCellsOccupied(cells) {
60 | g.resetGame()
61 | return
62 | }
63 |
64 | winner := utils.CheckWinner(cells)
65 | switch winner {
66 | case "X":
67 | g.XScore++
68 | case "O":
69 | g.OScore++
70 | }
71 |
72 | if len(winner) > 0 {
73 | g.resetGame()
74 | }
75 | }
76 |
77 | func (g *Game) isCellOccupied(cells []string, cellNumber int) bool {
78 | return cells[cellNumber] == "X" || cells[cellNumber] == "O"
79 | }
80 |
81 | func (g *Game) areAllCellsOccupied(cells []string) bool {
82 | for index, _ := range cells {
83 | if !g.isCellOccupied(cells, index) {
84 | return false
85 | }
86 | }
87 | return true
88 | }
89 |
90 | func (g *Game) isCellNumberInBounds(cellNumber int) bool {
91 | return cellNumber < 9 && cellNumber >= 0
92 | }
93 |
94 | func (g *Game) getCurrentPlayerSymbol() string {
95 | if g.CurrentPlayerId == g.XPlayerId {
96 | return "X"
97 | } else if g.CurrentPlayerId == g.OPlayerId {
98 | return "O"
99 | }
100 | return ""
101 | }
102 |
--------------------------------------------------------------------------------
/day11/models/player.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "time"
4 |
5 | type Player struct {
6 | Id int `json:"id"`
7 | Username string `json:"username"`
8 | Password string `json:"password"`
9 | CreatedAt time.Time `json:"-"`
10 | UpdatedAt time.Time `json:"-"`
11 | }
12 |
--------------------------------------------------------------------------------
/day11/models/session.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type Session struct {
4 | Username string `json:"-"`
5 | Token string `json:"-"`
6 | }
7 |
--------------------------------------------------------------------------------
/day11/router/router.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "log"
5 | "net/http"
6 | "tictactoe/controllers"
7 | "tictactoe/db"
8 | )
9 |
10 | func StartServer() {
11 | playerDB := new(db.PlayerDB)
12 | sessionDB := new(db.SessionDB)
13 | gameDB := new(db.GameDB)
14 |
15 | loginAPI := controllers.NewLoginAPI(playerDB, sessionDB)
16 | gameAPI := controllers.NewGameAPI(gameDB, playerDB, sessionDB)
17 |
18 | router := http.NewServeMux()
19 | router.HandleFunc("/player/login", loginAPI.HandleLogin)
20 | router.HandleFunc("/player/signup", loginAPI.HandleSignup)
21 | router.HandleFunc("/player/token", loginAPI.HandleTokenLogin)
22 |
23 | router.HandleFunc("/game/new", gameAPI.HandleNewGame)
24 | router.HandleFunc("/game/join", gameAPI.HandleJoinGame)
25 | router.HandleFunc("/game/play", gameAPI.HandlePlayTurn)
26 | router.HandleFunc("/game/get", gameAPI.HandleGetGame)
27 |
28 | log.Println("started server at http://localhost:8080")
29 | http.ListenAndServe(":8080", router)
30 | }
31 |
--------------------------------------------------------------------------------
/day11/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | func CheckWinner(cells []string) string {
4 | if checkIfPlayerWon(cells, "X") {
5 | return "X"
6 | } else if checkIfPlayerWon(cells, "O") {
7 | return "O"
8 | }
9 | return ""
10 | }
11 |
12 | func checkIfPlayerWon(cells []string, playerType string) bool {
13 | var firstDiagonal = cells[0] == cells[4] && cells[4] == cells[8] && cells[0] == playerType
14 |
15 | var secondDiagonal = cells[2] == cells[4] && cells[4] == cells[6] && cells[2] == playerType
16 |
17 | var firstRow = cells[0] == cells[1] && cells[1] == cells[2] && cells[1] == playerType
18 |
19 | var secondRow = cells[3] == cells[4] && cells[4] == cells[5] && cells[3] == playerType
20 |
21 | var thirdRow = cells[6] == cells[7] && cells[7] == cells[8] && cells[6] == playerType
22 |
23 | var firstColumn = cells[0] == cells[3] && cells[3] == cells[6] && cells[0] == playerType
24 |
25 | var secondColumn = cells[1] == cells[4] && cells[4] == cells[7] && cells[1] == playerType
26 |
27 | var thirdColumn = cells[2] == cells[5] && cells[5] == cells[8] && cells[8] == playerType
28 |
29 | return firstColumn ||
30 | secondColumn ||
31 | thirdColumn ||
32 | firstRow ||
33 | secondRow ||
34 | thirdRow ||
35 | firstDiagonal ||
36 | secondDiagonal
37 | }
38 |
--------------------------------------------------------------------------------
/day12/README.md:
--------------------------------------------------------------------------------
1 | # Day 12:
2 |
3 | ### React Router
4 | Install React's Router by running
5 |
6 | ```bash
7 | npm install react-router-dom
8 | ```
9 |
10 | inside the client's directory
11 |
12 |
13 | ### Middleware definition example:
14 |
15 | ```go
16 |
17 | // handler is the rest api handler that we want to do a process before the request reaches it
18 | func middlewareName(handler http.HandlerFunc) http.HandlerFunc {
19 | // return a rest api handler function that has the processing
20 | // where every statement before `handler.ServeHTTP(resp, req)` is concidered as middleware processing
21 | return func(resp http.ResponseWriter, req *http.Request) {
22 | // here we're setting some required http headers so that fetch can work properly
23 | resp.Header().Set("Access-Control-Allow-Origin", "*")
24 | resp.Header().Set("Access-Control-Allow-Headers", "Accept,Content-Type,Authorization")
25 | resp.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
26 | resp.Header().Set("Content-Type", "application/json")
27 |
28 | if req.Method != http.MethodOptions {
29 | handler.ServeHTTP(resp, req)
30 | }
31 | }
32 | }
33 | ```
34 |
--------------------------------------------------------------------------------
/day12/client/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/day12/client/README.md:
--------------------------------------------------------------------------------
1 | # Day 5's links:
2 |
3 | * Empty Chracter `https://emptycharacter.com/`
4 |
--------------------------------------------------------------------------------
/day12/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Tic Tac Toe
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/day12/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tictactoe",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0",
14 | "react-router-dom": "^6.8.2"
15 | },
16 | "devDependencies": {
17 | "@types/react": "^18.0.27",
18 | "@types/react-dom": "^18.0.10",
19 | "@vitejs/plugin-react": "^3.1.0",
20 | "vite": "^4.1.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/day12/client/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/day12/client/src/App.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day12/client/src/App.css
--------------------------------------------------------------------------------
/day12/client/src/App.jsx:
--------------------------------------------------------------------------------
1 | import "./App.css";
2 | import { createBrowserRouter, RouterProvider } from "react-router-dom";
3 | import Game from "./pages/Game";
4 | import Login from "./pages/Login";
5 | import Signup from "./pages/Signup";
6 |
7 | function App() {
8 | const router = createBrowserRouter([
9 | { path: "/", element: },
10 | { path: "/login", element: },
11 | { path: "/signup", element: },
12 | ]);
13 |
14 | return (
15 |
16 |
17 |
18 | );
19 | }
20 |
21 | export default App;
22 |
--------------------------------------------------------------------------------
/day12/client/src/components/GameBoard.css:
--------------------------------------------------------------------------------
1 | .cell {
2 | display: inline;
3 | }
4 |
5 | button.cell {
6 | font-size: 40px;
7 | font-weight: bold;
8 | background-color: #000000;
9 | color: #ffffff;
10 | border: 1px #ababab solid;
11 |
12 | width: 140px;
13 | height: 140px;
14 |
15 | padding: 30px;
16 | }
17 |
18 | .board {
19 | }
20 |
21 | .bigContainer {
22 | position: absolute;
23 | top: 50%;
24 | left: 50%;
25 | transform: translateX(-50%) translateY(-50%);
26 | }
27 |
28 | .playerStatus {
29 | color: #ffffff;
30 | text-align: center;
31 | }
32 |
--------------------------------------------------------------------------------
/day12/client/src/components/GameBoard.jsx:
--------------------------------------------------------------------------------
1 | import GameData from "../data/GameData";
2 | import Game from "../models/Game";
3 | import GameRequests from "../utils/GameRequests";
4 | import LoginRequests from "../utils/LoginRequests";
5 | import "./GameBoard.css";
6 | import Stats from "./Stats";
7 | import { useEffect } from "react";
8 | import { useState } from "react";
9 | import Player from "../models/Player";
10 |
11 | function GameBoard({ game, setGame }) {
12 | const [player, setPlayer] = useState(new Player());
13 |
14 | const play = async (cellNumber) => {
15 | await GameRequests.playTurn({
16 | cellNumber: cellNumber,
17 | gameId: game.id,
18 | }).then(async (resp) => {
19 | if (resp.ok) {
20 | setGame(new Game(await resp.json()));
21 | return;
22 | }
23 | window.alert(await resp.text());
24 | });
25 | };
26 |
27 | useEffect(() => {
28 | (async () => {
29 | setPlayer(await LoginRequests.tokenLogin());
30 | })();
31 |
32 | const interval = setInterval(async () => {
33 | setGame(await GameRequests.getGame(game.id));
34 | }, 1000);
35 |
36 | return () => clearInterval(interval);
37 | }, []);
38 |
39 | const getCurrentPlayer = () => {
40 | switch (game.currentPlayerId) {
41 | case game.xPlayerId:
42 | return "X";
43 | case game.oPlayerId:
44 | return "O";
45 | }
46 | return "";
47 | };
48 |
49 | const isPlayerPlaying = () => {
50 | return player.id === game.currentPlayerId;
51 | };
52 |
53 | return (
54 |
55 |
56 | {isPlayerPlaying() ? "Your turn" : "Wait for your opponent"}
57 |
58 |
59 | {game.cells.map((cell, index) => {
60 | return (
61 |
62 |
71 | {(index + 1) % 3 === 0 &&
}
72 |
73 | );
74 | })}
75 |
76 |
77 |
83 |
84 |
85 | );
86 | }
87 |
88 | export default GameBoard;
89 |
--------------------------------------------------------------------------------
/day12/client/src/components/Stats.css:
--------------------------------------------------------------------------------
1 | span {
2 | color: #ffffff;
3 | font-weight: 900;
4 | font-size: 25px;
5 | text-align: center;
6 | display: inline-block;
7 | width: 100%;
8 | }
9 |
10 | .container {
11 | display: flex;
12 | justify-content: space-between;
13 | margin-left: 20px;
14 | margin-right: 20px;
15 | margin-top: 20px;
16 | }
17 |
--------------------------------------------------------------------------------
/day12/client/src/components/Stats.jsx:
--------------------------------------------------------------------------------
1 | import "./Stats.css";
2 |
3 | function Stats({ xScore, oScore, turn, gameId }) {
4 | return (
5 |
6 |
7 | Player X
8 |
9 | {xScore}
10 |
11 |
12 |
13 | Player O
14 |
15 | {oScore}
16 |
17 |
18 |
19 | Turn
20 |
21 | {turn}
22 |
23 |
24 |
25 | Game Id
26 |
27 | {gameId}
28 |
29 |
30 | );
31 | }
32 |
33 | export default Stats;
34 |
--------------------------------------------------------------------------------
/day12/client/src/data/GameData.js:
--------------------------------------------------------------------------------
1 | import GameLogic from "./GameLogic";
2 |
3 | class GameData {
4 | cells;
5 | turn;
6 | xScore;
7 | oScore;
8 |
9 | constructor(copiedData) {
10 | if (copiedData !== undefined && copiedData !== null) {
11 | this.cells = copiedData.cells;
12 | this.turn = copiedData.turn;
13 | this.xScore = copiedData.xScore;
14 | this.oScore = copiedData.oScore;
15 | return;
16 | }
17 | this.cells = ["1", "2", "3", "4", "5", "6", "7", "8", "9"];
18 | this.turn = "X";
19 | this.xScore = 0;
20 | this.oScore = 0;
21 | }
22 |
23 | resetGame() {
24 | this.cells = ["1", "2", "3", "4", "5", "6", "7", "8", "9"];
25 | this.turn = "X";
26 | }
27 |
28 | markCell(cellNumber) {
29 | if (this.isCellNumberInBounds(cellNumber)) return;
30 |
31 | if (this.isCellOccupied(cellNumber)) return;
32 |
33 | switch (this.turn) {
34 | case "X":
35 | this.cells[cellNumber] = "X";
36 | break;
37 | case "O":
38 | this.cells[cellNumber] = "O";
39 | break;
40 | }
41 |
42 | if (this.areAllCellsOccupied()) {
43 | this.resetGame();
44 | return;
45 | }
46 |
47 | this.switchTurn();
48 | }
49 |
50 | isCellNumberInBounds(cellNumber) {
51 | return cellNumber < 0 || cellNumber > this.cells.length - 1;
52 | }
53 |
54 | isCellOccupied(cellNumber) {
55 | return this.cells[cellNumber] === "X" || this.cells[cellNumber] === "O";
56 | }
57 |
58 | areAllCellsOccupied() {
59 | for (let i = 0; i < this.cells.length; i++) {
60 | if (!this.isCellOccupied(i)) {
61 | return false;
62 | }
63 | }
64 | return true;
65 | }
66 |
67 | switchTurn() {
68 | switch (this.turn) {
69 | case "X":
70 | this.turn = "O";
71 | break;
72 | case "O":
73 | this.turn = "X";
74 | break;
75 | }
76 | }
77 |
78 | checkWinner() {
79 | let winner = GameLogic.checkWinner(this.cells);
80 | switch (winner) {
81 | case "X":
82 | this.xScore++;
83 | this.resetGame();
84 | return "X";
85 | case "O":
86 | this.oScore++;
87 | this.resetGame();
88 | return "O";
89 | }
90 | }
91 | }
92 |
93 | export default GameData;
94 |
--------------------------------------------------------------------------------
/day12/client/src/data/GameLogic.js:
--------------------------------------------------------------------------------
1 | class GameLogic {
2 | static checkWinner(cells) {
3 | if (this.checkIfPlayerWon(cells, "X")) {
4 | return "X";
5 | } else if (this.checkIfPlayerWon(cells, "O")) {
6 | return "O";
7 | }
8 | return "";
9 | }
10 |
11 | static checkIfPlayerWon(cells, playerType) {
12 | let firstDiagonal =
13 | cells[0] === cells[4] && cells[4] === cells[8] && cells[0] === playerType;
14 |
15 | let secondDiagonal =
16 | cells[2] === cells[4] && cells[4] === cells[6] && cells[2] === playerType;
17 |
18 | let firstRow =
19 | cells[0] === cells[1] && cells[1] === cells[2] && cells[1] === playerType;
20 |
21 | let secondRow =
22 | cells[3] === cells[4] && cells[4] === cells[5] && cells[3] === playerType;
23 |
24 | let thirdRow =
25 | cells[6] === cells[7] && cells[7] === cells[8] && cells[6] === playerType;
26 |
27 | let firstColumn =
28 | cells[0] === cells[3] && cells[3] === cells[6] && cells[0] === playerType;
29 |
30 | let secondColumn =
31 | cells[1] === cells[4] && cells[4] === cells[7] && cells[1] === playerType;
32 |
33 | let thirdColumn =
34 | cells[2] === cells[5] && cells[5] === cells[8] && cells[8] === playerType;
35 |
36 | return (
37 | firstColumn ||
38 | secondColumn ||
39 | thirdColumn ||
40 | firstRow ||
41 | secondRow ||
42 | thirdRow ||
43 | firstDiagonal ||
44 | secondDiagonal
45 | );
46 | }
47 | }
48 |
49 | export default GameLogic;
50 |
--------------------------------------------------------------------------------
/day12/client/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #000000;
3 | }
4 |
--------------------------------------------------------------------------------
/day12/client/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 | import './index.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root')).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/day12/client/src/models/Game.js:
--------------------------------------------------------------------------------
1 | class Game {
2 | id;
3 | xPlayerId;
4 | oPlayerId;
5 | xScore;
6 | oScore;
7 | cells;
8 | currentPlayerId;
9 |
10 | constructor(copiedGame) {
11 | if (copiedGame) {
12 | this.id = copiedGame.id;
13 | this.xPlayerId = copiedGame.xPlayerId;
14 | this.oPlayerId = copiedGame.oPlayerId;
15 | this.xScore = copiedGame.xScore;
16 | this.oScore = copiedGame.oScore;
17 | this.cells = copiedGame.cells.split(" ");
18 | this.currentPlayerId = copiedGame.currentPlayerId;
19 | return;
20 | }
21 |
22 | this.id = 0;
23 | this.xPlayerId = 0;
24 | this.oPlayerId = 0;
25 | this.xScore = 0;
26 | this.oScore = 0;
27 | this.cells = ["1", "2", "3", "4", "5", "6", "7", "8", "9"];
28 | this.currentPlayerId = 0;
29 | }
30 | }
31 |
32 | export default Game;
33 |
--------------------------------------------------------------------------------
/day12/client/src/models/Player.js:
--------------------------------------------------------------------------------
1 | class Player {
2 | id;
3 | username;
4 | password;
5 |
6 | constructor(copiedPlayer) {
7 | if (copiedPlayer) {
8 | this.id = copiedPlayer.id;
9 | this.username = copiedPlayer.username;
10 | this.password = copiedPlayer.password;
11 | return;
12 | }
13 | this.id = 0;
14 | this.username = "";
15 | this.password = "";
16 | }
17 | }
18 |
19 | export default Player;
20 |
--------------------------------------------------------------------------------
/day12/client/src/pages/Game.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import GameRequests from "../utils/GameRequests";
3 | import { default as GameData } from "../models/Game";
4 | import GameBoard from "../components/GameBoard";
5 | import LoginRequests from "../utils/LoginRequests";
6 | import { useNavigate } from "react-router-dom";
7 |
8 | function Game() {
9 | const [gameData, setGameData] = useState(new GameData());
10 |
11 | const [joinGameId, setJoinGameId] = useState(0);
12 |
13 | const navigate = useNavigate();
14 |
15 | useEffect(() => {
16 | (async () => {
17 | const player = await LoginRequests.tokenLogin();
18 | if (!player || !player.id) {
19 | navigate("/login");
20 | }
21 | })();
22 | }, []);
23 |
24 | const joinGameIdOnChangeHandler = (event) => {
25 | setJoinGameId(parseInt(event.target.value));
26 | };
27 |
28 | const joinGame = async () => {
29 | setGameData(await GameRequests.joinGame(joinGameId));
30 | };
31 |
32 | const newGame = async () => {
33 | setGameData(await GameRequests.newGame());
34 | };
35 |
36 | return (
37 |
38 | {gameData.id === 0 && (
39 |
40 |
46 |
47 |
48 |
49 |
50 | )}
51 | {gameData.id !== 0 && (
52 |
53 |
54 |
55 | )}
56 |
57 | );
58 | }
59 |
60 | export default Game;
61 |
--------------------------------------------------------------------------------
/day12/client/src/pages/Login.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import Player from "../models/Player";
3 | import LoginRequests from "../utils/LoginRequests";
4 | import { useNavigate } from "react-router-dom";
5 |
6 | function Login() {
7 | const navigate = useNavigate();
8 | const [player, setPlayer] = useState(new Player());
9 |
10 | const usernameOnChangeHandler = (event) => {
11 | player.username = event.target.value;
12 | setPlayer(new Player(player));
13 | };
14 |
15 | const passwordOnChangeHandler = (event) => {
16 | player.password = event.target.value;
17 | setPlayer(new Player(player));
18 | };
19 |
20 | const login = async () => {
21 | await LoginRequests.login(player);
22 | console.log(player);
23 | navigate("/");
24 | };
25 |
26 | return (
27 |
28 |
34 |
35 |
41 |
42 |
43 |
44 | );
45 | }
46 |
47 | export default Login;
48 |
--------------------------------------------------------------------------------
/day12/client/src/pages/Signup.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import Player from "../models/Player";
3 | import LoginRequests from "../utils/LoginRequests";
4 | import { useNavigate } from "react-router-dom";
5 |
6 | function Signup() {
7 | const navigate = useNavigate();
8 | const [player, setPlayer] = useState(new Player());
9 |
10 | const usernameOnChangeHandler = (event) => {
11 | player.username = event.target.value;
12 | setPlayer(new Player(player));
13 | };
14 |
15 | const passwordOnChangeHandler = (event) => {
16 | player.password = event.target.value;
17 | setPlayer(new Player(player));
18 | };
19 |
20 | const signup = async () => {
21 | await LoginRequests.signup(player);
22 | console.log(player);
23 | navigate("/login");
24 | };
25 |
26 | return (
27 |
28 |
34 |
35 |
41 |
42 |
43 |
44 | );
45 | }
46 |
47 | export default Signup;
48 |
--------------------------------------------------------------------------------
/day12/client/src/utils/GameRequests.js:
--------------------------------------------------------------------------------
1 | import Game from "../models/Game";
2 |
3 | class GameRequests {
4 | static async newGame() {
5 | return await fetch("http://localhost:8080/game/new", {
6 | method: "GET",
7 | mode: "cors",
8 | headers: {
9 | Authorization: localStorage.getItem("token"),
10 | },
11 | })
12 | .then((resp) => {
13 | return resp.json();
14 | })
15 | .then((game) => {
16 | return new Game(game);
17 | })
18 | .catch((err) => console.log(err));
19 | }
20 |
21 | static async joinGame(gameId) {
22 | return await fetch(`http://localhost:8080/game/join?id=${gameId}`, {
23 | method: "GET",
24 | mode: "cors",
25 | headers: {
26 | Authorization: localStorage.getItem("token"),
27 | },
28 | })
29 | .then((resp) => {
30 | return resp.json();
31 | })
32 | .then((game) => {
33 | return new Game(game);
34 | })
35 | .catch((err) => console.log(err));
36 | }
37 |
38 | static async playTurn(reqBody) {
39 | return fetch("http://localhost:8080/game/play", {
40 | method: "POST",
41 | mode: "cors",
42 | headers: {
43 | Authorization: localStorage.getItem("token"),
44 | },
45 | body: JSON.stringify(reqBody),
46 | });
47 | }
48 |
49 | static async getGame(id) {
50 | return await fetch(`http://localhost:8080/game/get?id=${id}`, {
51 | method: "GET",
52 | mode: "cors",
53 | headers: {
54 | Authorization: localStorage.getItem("token"),
55 | },
56 | })
57 | .then((resp) => {
58 | return resp.json();
59 | })
60 | .then((game) => {
61 | return new Game(game);
62 | })
63 | .catch((err) => console.log(err));
64 | }
65 | }
66 |
67 | export default GameRequests;
68 |
--------------------------------------------------------------------------------
/day12/client/src/utils/LoginRequests.js:
--------------------------------------------------------------------------------
1 | import Player from "../models/Player";
2 |
3 | class LoginRequests {
4 | static async login(player) {
5 | return await fetch("http://localhost:8080/player/login", {
6 | method: "POST",
7 | mode: "cors",
8 | body: JSON.stringify(player),
9 | })
10 | .then((resp) => {
11 | return resp.json();
12 | })
13 | .then((data) => {
14 | localStorage.setItem("token", data["token"]);
15 | })
16 | .catch((err) => console.log(err));
17 | }
18 |
19 | static async signup(player) {
20 | return await fetch("http://localhost:8080/player/signup", {
21 | method: "POST",
22 | mode: "cors",
23 | body: JSON.stringify(player),
24 | })
25 | .then((resp) => {})
26 | .catch((err) => console.log(err));
27 | }
28 |
29 | static async tokenLogin() {
30 | return await fetch("http://localhost:8080/player/token", {
31 | method: "GET",
32 | mode: "cors",
33 | headers: { Authorization: localStorage.getItem("token") },
34 | })
35 | .then((resp) => {
36 | return resp.json();
37 | })
38 | .then((player) => {
39 | return new Player(player);
40 | })
41 | .catch((err) => console.log(err));
42 | }
43 | }
44 |
45 | export default LoginRequests;
46 |
--------------------------------------------------------------------------------
/day12/client/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/day12/server/controllers/game_api.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 | "strconv"
8 | "tictactoe/db"
9 | "tictactoe/models"
10 | )
11 |
12 | type GameAPI struct {
13 | gameDB *db.GameDB
14 | playerDB *db.PlayerDB
15 | sessionDB *db.SessionDB
16 | }
17 |
18 | func NewGameAPI(gameDB *db.GameDB, playerDB *db.PlayerDB, sessionDB *db.SessionDB) *GameAPI {
19 | return &GameAPI{
20 | gameDB: gameDB,
21 | playerDB: playerDB,
22 | sessionDB: sessionDB,
23 | }
24 | }
25 |
26 | func (g *GameAPI) HandleNewGame(resp http.ResponseWriter, req *http.Request) {
27 | token := req.Header.Get("Authorization")
28 | if len(token) == 0 {
29 | resp.WriteHeader(http.StatusUnauthorized)
30 | return
31 | }
32 |
33 | player, err := g.fetchPlayerUsingToken(token)
34 | if err != nil {
35 | resp.WriteHeader(http.StatusUnauthorized)
36 | return
37 | }
38 |
39 | game := models.Game{
40 | XPlayerId: player.Id,
41 | }
42 |
43 | newGame, err := g.gameDB.Create(game)
44 | if err != nil {
45 | resp.WriteHeader(http.StatusInternalServerError)
46 | return
47 | }
48 |
49 | json.NewEncoder(resp).Encode(newGame)
50 | }
51 |
52 | func (g *GameAPI) HandleJoinGame(resp http.ResponseWriter, req *http.Request) {
53 | gameId := req.URL.Query().Get("id")
54 | if len(gameId) == 0 {
55 | resp.WriteHeader(http.StatusBadRequest)
56 | return
57 | }
58 |
59 | intGameId, err := strconv.Atoi(gameId)
60 | if len(gameId) == 0 {
61 | resp.WriteHeader(http.StatusBadRequest)
62 | return
63 | }
64 |
65 | token := req.Header.Get("Authorization")
66 | if len(token) == 0 {
67 | resp.WriteHeader(http.StatusUnauthorized)
68 | return
69 | }
70 |
71 | player, err := g.fetchPlayerUsingToken(token)
72 | if err != nil {
73 | resp.WriteHeader(http.StatusUnauthorized)
74 | return
75 | }
76 |
77 | game, err := g.gameDB.Get(intGameId)
78 | if err != nil {
79 | fmt.Println(err)
80 | resp.WriteHeader(http.StatusNotFound)
81 | return
82 | }
83 |
84 | if game.OPlayerId != 0 {
85 | resp.WriteHeader(http.StatusBadRequest)
86 | fmt.Fprintln(resp, "THIS GAME IS FULLY OCCUPIED!!!")
87 | return
88 | }
89 |
90 | game.OPlayerId = player.Id
91 |
92 | err = g.gameDB.Update(game.Id, game)
93 | if err != nil {
94 | resp.WriteHeader(http.StatusInternalServerError)
95 | return
96 | }
97 |
98 | json.NewEncoder(resp).Encode(game)
99 | }
100 |
101 | func (g *GameAPI) HandlePlayTurn(resp http.ResponseWriter, req *http.Request) {
102 | var reqBody struct {
103 | GameId int `json:"gameId"`
104 | CellNumber int `json:"cellNumber"`
105 | }
106 |
107 | err := json.NewDecoder(req.Body).Decode(&reqBody)
108 | if err != nil {
109 | resp.WriteHeader(http.StatusBadRequest)
110 | return
111 | }
112 |
113 | token := req.Header.Get("Authorization")
114 | if len(token) == 0 {
115 | resp.WriteHeader(http.StatusUnauthorized)
116 | return
117 | }
118 |
119 | player, err := g.fetchPlayerUsingToken(token)
120 | if err != nil {
121 | resp.WriteHeader(http.StatusUnauthorized)
122 | return
123 | }
124 |
125 | game, err := g.gameDB.Get(reqBody.GameId)
126 | if err != nil {
127 | resp.WriteHeader(http.StatusInternalServerError)
128 | return
129 | }
130 |
131 | if player.Id != game.CurrentPlayerId {
132 | resp.WriteHeader(http.StatusBadRequest)
133 | fmt.Fprintln(resp, "this player can't play right now")
134 | return
135 | }
136 |
137 | err = game.MarkCell(reqBody.CellNumber)
138 | if err != nil {
139 | resp.WriteHeader(http.StatusBadRequest)
140 | fmt.Fprintln(resp, err.Error())
141 | return
142 | }
143 |
144 | game.CheckWinnerAndUpdateScore()
145 |
146 | err = g.gameDB.Update(game.Id, game)
147 | if err != nil {
148 | resp.WriteHeader(http.StatusInternalServerError)
149 | return
150 | }
151 |
152 | json.NewEncoder(resp).Encode(game)
153 | }
154 |
155 | func (g *GameAPI) HandleGetGame(resp http.ResponseWriter, req *http.Request) {
156 | gameId := req.URL.Query().Get("id")
157 | if len(gameId) == 0 {
158 | resp.WriteHeader(http.StatusBadRequest)
159 | return
160 | }
161 |
162 | intGameId, err := strconv.Atoi(gameId)
163 | if len(gameId) == 0 {
164 | resp.WriteHeader(http.StatusBadRequest)
165 | return
166 | }
167 |
168 | token := req.Header.Get("Authorization")
169 | if len(token) == 0 {
170 | resp.WriteHeader(http.StatusUnauthorized)
171 | return
172 | }
173 |
174 | player, err := g.fetchPlayerUsingToken(token)
175 | if err != nil {
176 | resp.WriteHeader(http.StatusUnauthorized)
177 | return
178 | }
179 |
180 | game, err := g.gameDB.Get(intGameId)
181 | if err != nil {
182 | resp.WriteHeader(http.StatusInternalServerError)
183 | return
184 | }
185 |
186 | if player.Id != game.XPlayerId && player.Id != game.OPlayerId {
187 | resp.WriteHeader(http.StatusUnauthorized)
188 | fmt.Fprintln(resp, "this player does not belong to this game!")
189 | return
190 | }
191 |
192 | json.NewEncoder(resp).Encode(game)
193 | }
194 |
195 | func (g *GameAPI) fetchPlayerUsingToken(token string) (models.Player, error) {
196 | session, err := g.sessionDB.Get(token)
197 | if err != nil {
198 | return models.Player{}, err
199 | }
200 |
201 | player, err := g.playerDB.Get(session.Username)
202 | if err != nil {
203 | return models.Player{}, err
204 | }
205 |
206 | return player, nil
207 | }
208 |
--------------------------------------------------------------------------------
/day12/server/controllers/login_api.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 | "strings"
8 | "tictactoe/db"
9 | "tictactoe/models"
10 |
11 | "github.com/google/uuid"
12 | "golang.org/x/crypto/bcrypt"
13 | )
14 |
15 | type LoginAPI struct {
16 | playerDB *db.PlayerDB
17 | sessionDB *db.SessionDB
18 | }
19 |
20 | func NewLoginAPI(playerDB *db.PlayerDB, sessionDB *db.SessionDB) *LoginAPI {
21 | return &LoginAPI{
22 | playerDB: playerDB,
23 | sessionDB: sessionDB,
24 | }
25 | }
26 |
27 | type reqBody struct {
28 | Username string `json:"username"`
29 | Password string `json:"password"`
30 | }
31 |
32 | func (l *LoginAPI) HandleSignup(resp http.ResponseWriter, req *http.Request) {
33 | var requestBody reqBody
34 |
35 | err := json.NewDecoder(req.Body).Decode(&requestBody)
36 | if err != nil {
37 | resp.WriteHeader(http.StatusBadRequest)
38 | return
39 | }
40 |
41 | hashed, err := bcrypt.GenerateFromPassword([]byte(requestBody.Password), bcrypt.MinCost)
42 | if err != nil {
43 | resp.WriteHeader(http.StatusInternalServerError)
44 | return
45 | }
46 |
47 | requestBody.Password = string(hashed)
48 |
49 | player, err := l.playerDB.Create(models.Player{
50 | Username: requestBody.Username,
51 | Password: requestBody.Password,
52 | })
53 | if err != nil {
54 | resp.WriteHeader(http.StatusInternalServerError)
55 | return
56 | }
57 |
58 | player.Password = ""
59 |
60 | json.NewEncoder(resp).Encode(player)
61 | }
62 |
63 | func (l *LoginAPI) HandleLogin(resp http.ResponseWriter, req *http.Request) {
64 | var requestBody reqBody
65 |
66 | err := json.NewDecoder(req.Body).Decode(&requestBody)
67 | if err != nil {
68 | resp.WriteHeader(http.StatusBadRequest)
69 | fmt.Fprintln(resp, "👎👎 your request has an incorrect form 👎👎")
70 | return
71 | }
72 |
73 | player, err := l.playerDB.Get(requestBody.Username)
74 | if err != nil {
75 | resp.WriteHeader(http.StatusUnauthorized)
76 | fmt.Fprintln(resp, "username is incorrect 👎")
77 | return
78 | }
79 |
80 | err = bcrypt.CompareHashAndPassword([]byte(player.Password), []byte(requestBody.Password))
81 | if err != nil {
82 | resp.WriteHeader(http.StatusUnauthorized)
83 | fmt.Fprintln(resp, "password is incorrect 👎")
84 | return
85 | }
86 |
87 | session := models.Session{
88 | Username: player.Username,
89 | Token: strings.ReplaceAll(uuid.NewString(), "-", ""),
90 | }
91 |
92 | err = l.sessionDB.Create(session)
93 | if err != nil {
94 | resp.WriteHeader(http.StatusInternalServerError)
95 | fmt.Fprintln(resp, "something went wrong ☹️")
96 | return
97 | }
98 |
99 | json.NewEncoder(resp).Encode(map[string]any{
100 | "token": session.Token,
101 | })
102 | }
103 |
104 | func (l *LoginAPI) HandleTokenLogin(resp http.ResponseWriter, req *http.Request) {
105 | token := req.Header.Get("Authorization")
106 |
107 | session, err := l.sessionDB.Get(token)
108 | if err != nil {
109 | resp.WriteHeader(http.StatusUnauthorized)
110 | fmt.Fprintln(resp, "invalid token 👎")
111 | return
112 | }
113 |
114 | player, err := l.playerDB.Get(session.Username)
115 | if err != nil {
116 | resp.WriteHeader(http.StatusInternalServerError)
117 | fmt.Fprintln(resp, "something went wrong")
118 | return
119 | }
120 |
121 | player.Password = ""
122 |
123 | resp.Header().Set("Content-Type", "application/json")
124 | json.NewEncoder(resp).Encode(player)
125 | }
126 |
--------------------------------------------------------------------------------
/day12/server/db/db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "database/sql"
5 |
6 | _ "github.com/go-sql-driver/mysql"
7 | )
8 |
9 | var connectionInstance *sql.DB = nil
10 |
11 | func GetInstance() *sql.DB {
12 | if connectionInstance == nil {
13 | var err error
14 | // DB_USERNAME is root by default
15 | // DB_PASSWORD is what you've set when installing the database
16 | // DB_NAME is the database you've created using `CREATE DATABASE whateverName`
17 | // connectionInstance, err = sql.Open("mysql", "DB_USERNAME:DB_PASSWORD@tcp(localhost:3306)/DB_NAME?parseTime=True")
18 | connectionInstance, err = sql.Open("mysql", "root:1234@tcp(localhost:3306)/ttt?parseTime=True")
19 | if err != nil {
20 | panic(err)
21 | }
22 | }
23 | return connectionInstance
24 | }
25 |
--------------------------------------------------------------------------------
/day12/server/db/game_db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import "tictactoe/models"
4 |
5 | type GameDB struct{}
6 |
7 | func (g *GameDB) Create(game models.Game) (models.Game, error) {
8 | result, err := GetInstance().Exec(
9 | "INSERT INTO ttt.games (x_player_id, o_player_id, x_score, o_score, current_player_id, cells) VALUES (?, ?, ?, ?, ?, ?)",
10 | game.XPlayerId, 0, 0, 0, game.XPlayerId, "1 2 3 4 5 6 7 8 9")
11 | if err != nil {
12 | return models.Game{}, err
13 | }
14 |
15 | lastId, err := result.LastInsertId()
16 | if err != nil {
17 | return models.Game{}, err
18 | }
19 |
20 | game.Id = int(lastId)
21 | game.Cells = "1 2 3 4 5 6 7 8 9"
22 | game.CurrentPlayerId = game.XPlayerId
23 | game.XScore = 0
24 | game.OScore = 0
25 |
26 | return game, nil
27 | }
28 |
29 | func (g *GameDB) Get(id int) (models.Game, error) {
30 | var game models.Game
31 | err := GetInstance().
32 | QueryRow("SELECT * FROM ttt.games WHERE id=?", id).
33 | Scan(&game.Id, &game.XPlayerId, &game.OPlayerId, &game.XScore, &game.OScore,
34 | &game.Cells, &game.CurrentPlayerId)
35 | if err != nil {
36 | return models.Game{}, err
37 | }
38 |
39 | return game, nil
40 | }
41 |
42 | func (p *GameDB) Update(id int, game models.Game) error {
43 | _, err := GetInstance().Exec(
44 | "UPDATE ttt.games SET cells=?, x_score=?, o_score=?, current_player_id=?, o_player_id=?",
45 | game.Cells, game.XScore, game.OScore, game.CurrentPlayerId, game.OPlayerId)
46 | if err != nil {
47 | return err
48 | }
49 | return nil
50 | }
51 |
52 | func (p *GameDB) Delete(id int) error {
53 | _, err := GetInstance().Exec("DELETE FROM ttt.games WHERE id=?", id)
54 | if err != nil {
55 | return err
56 | }
57 | return nil
58 | }
59 |
--------------------------------------------------------------------------------
/day12/server/db/init.sql:
--------------------------------------------------------------------------------
1 | CREATE DATABASE ttt;
2 |
3 | CREATE TABLE IF NOT EXISTS ttt.players (
4 | id INTEGER AUTO_INCREMENT,
5 | username VARCHAR(60) UNIQUE NOT NULL,
6 | password VARCHAR(200),
7 | created_at TIMESTAMP,
8 | updated_at TIMESTAMP,
9 | PRIMARY KEY (id)
10 | );
11 |
12 | CREATE TABLE IF NOT EXISTS ttt.sessions (
13 | id INTEGER AUTO_INCREMENT,
14 | username VARCHAR(60),
15 | token VARCHAR(32) UNIQUE,
16 | PRIMARY KEY (id)
17 | );
18 |
19 | CREATE TABLE IF NOT EXISTS ttt.game (
20 | id INTEGER AUTO_INCREMENT,
21 | x_player_id INTEGER,
22 | o_player_id INTEGER,
23 | x_score INTEGER,
24 | o_score INTEGER,
25 | cells VARCHAR(20),
26 | current_player_id INTEGER,
27 | PRIMARY KEY (id),
28 | FOREIGN KEY (x_player_id) REFERENCES ttt.players(id),
29 | FOREIGN KEY (current_player_id) REFERENCES ttt.players(id)
30 | );
31 |
--------------------------------------------------------------------------------
/day12/server/db/player_db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import "tictactoe/models"
4 |
5 | type PlayerDB struct{}
6 |
7 | func (p *PlayerDB) Create(player models.Player) (models.Player, error) {
8 | result, err := GetInstance().Exec(
9 | "INSERT INTO ttt.players (username, password, created_at) VALUES (?, ?, current_timestamp)",
10 | player.Username, player.Password)
11 | if err != nil {
12 | return models.Player{}, err
13 | }
14 |
15 | playerId, err := result.LastInsertId()
16 | if err != nil {
17 | return models.Player{}, err
18 | }
19 |
20 | player.Id = int(playerId)
21 |
22 | return player, nil
23 | }
24 |
25 | func (p *PlayerDB) Get(username string) (models.Player, error) {
26 | var player models.Player
27 | err := GetInstance().
28 | QueryRow("SELECT id, username, password FROM ttt.players WHERE username=?", username).
29 | Scan(&player.Id, &player.Username, &player.Password)
30 |
31 | if err != nil {
32 | return models.Player{}, err
33 | }
34 |
35 | return player, nil
36 | }
37 |
38 | func (p *PlayerDB) Update(username string, player models.Player) error {
39 | _, err := GetInstance().Exec("UPDATE ttt.players SET username=?, password=? WHERE username=?", player.Username, player.Password, username)
40 | if err != nil {
41 | return err
42 | }
43 | return nil
44 | }
45 |
46 | func (p *PlayerDB) Delete(username string) error {
47 | _, err := GetInstance().Exec("DELETE FROM ttt.players WHERE username=?", username)
48 | if err != nil {
49 | return err
50 | }
51 | return nil
52 | }
--------------------------------------------------------------------------------
/day12/server/db/session_db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import "tictactoe/models"
4 |
5 | type SessionDB struct{}
6 |
7 | func (s *SessionDB) Create(session models.Session) error {
8 | _, err := GetInstance().Exec("INSERT INTO ttt.sessions (username, token) VALUES (?, ?)", session.Username, session.Token)
9 | if err != nil {
10 | return err
11 | }
12 | return nil
13 | }
14 |
15 | func (s *SessionDB) Get(token string) (models.Session, error) {
16 | var session models.Session
17 |
18 | err := GetInstance().
19 | QueryRow("SELECT username, token FROM ttt.sessions WHERE token=?", token).
20 | Scan(&session.Username, &session.Token)
21 | if err != nil {
22 | return models.Session{}, err
23 | }
24 |
25 | return session, nil
26 | }
27 |
--------------------------------------------------------------------------------
/day12/server/go.mod:
--------------------------------------------------------------------------------
1 | module tictactoe
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/go-sql-driver/mysql v1.7.0 // indirect
7 | github.com/google/uuid v1.3.0 // indirect
8 | golang.org/x/crypto v0.6.0 // indirect
9 | )
10 |
--------------------------------------------------------------------------------
/day12/server/go.sum:
--------------------------------------------------------------------------------
1 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
2 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
3 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
4 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
5 | golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
6 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
7 |
--------------------------------------------------------------------------------
/day12/server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "tictactoe/router"
4 |
5 | func main() {
6 | router.StartServer()
7 | }
8 |
--------------------------------------------------------------------------------
/day12/server/models/game.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "strings"
7 | "tictactoe/utils"
8 | )
9 |
10 | type Game struct {
11 | Id int `json:"id"`
12 | XPlayerId int `json:"xPlayerId"`
13 | OPlayerId int `json:"oPlayerId"`
14 | XScore int `json:"xScore"`
15 | OScore int `json:"oScore"`
16 | Cells string `json:"cells"`
17 | CurrentPlayerId int `json:"currentPlayerId"`
18 | }
19 |
20 | func (g *Game) resetGame() {
21 | g.Cells = "1 2 3 4 5 6 7 8 9"
22 | g.CurrentPlayerId = g.XPlayerId
23 | }
24 |
25 | func (g *Game) switchTurns() {
26 | if g.CurrentPlayerId == g.XPlayerId {
27 | g.CurrentPlayerId = g.OPlayerId
28 | } else if g.CurrentPlayerId == g.OPlayerId {
29 | g.CurrentPlayerId = g.XPlayerId
30 | }
31 | }
32 |
33 | func (g *Game) MarkCell(cellNumber int) error {
34 | if !g.isCellNumberInBounds(cellNumber) {
35 | return errors.New("cell number is not in bound")
36 | }
37 |
38 | cells := strings.Split(g.Cells, " ")
39 | if g.areAllCellsOccupied(cells) {
40 | return errors.New("all game's cells are occupied")
41 | }
42 |
43 | if g.isCellOccupied(cells, cellNumber) {
44 | return errors.New("the requested cell: " + fmt.Sprint(cellNumber) + " is occupied with: " + cells[cellNumber])
45 | }
46 |
47 | currentPlayer := g.getCurrentPlayerSymbol()
48 | cells[cellNumber] = currentPlayer
49 | g.Cells = strings.Join(cells, " ")
50 |
51 | g.switchTurns()
52 |
53 | return nil
54 | }
55 |
56 | func (g *Game) CheckWinnerAndUpdateScore() {
57 | cells := strings.Split(g.Cells, " ")
58 |
59 | if g.areAllCellsOccupied(cells) {
60 | g.resetGame()
61 | return
62 | }
63 |
64 | winner := utils.CheckWinner(cells)
65 | switch winner {
66 | case "X":
67 | g.XScore++
68 | case "O":
69 | g.OScore++
70 | }
71 |
72 | if len(winner) > 0 {
73 | g.resetGame()
74 | }
75 | }
76 |
77 | func (g *Game) isCellOccupied(cells []string, cellNumber int) bool {
78 | return cells[cellNumber] == "X" || cells[cellNumber] == "O"
79 | }
80 |
81 | func (g *Game) areAllCellsOccupied(cells []string) bool {
82 | for index, _ := range cells {
83 | if !g.isCellOccupied(cells, index) {
84 | return false
85 | }
86 | }
87 | return true
88 | }
89 |
90 | func (g *Game) isCellNumberInBounds(cellNumber int) bool {
91 | return cellNumber < 9 && cellNumber >= 0
92 | }
93 |
94 | func (g *Game) getCurrentPlayerSymbol() string {
95 | if g.CurrentPlayerId == g.XPlayerId {
96 | return "X"
97 | } else if g.CurrentPlayerId == g.OPlayerId {
98 | return "O"
99 | }
100 | return ""
101 | }
102 |
--------------------------------------------------------------------------------
/day12/server/models/player.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "time"
4 |
5 | type Player struct {
6 | Id int `json:"id"`
7 | Username string `json:"username"`
8 | Password string `json:"password"`
9 | CreatedAt time.Time `json:"-"`
10 | UpdatedAt time.Time `json:"-"`
11 | }
12 |
--------------------------------------------------------------------------------
/day12/server/models/session.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type Session struct {
4 | Username string `json:"-"`
5 | Token string `json:"-"`
6 | }
7 |
--------------------------------------------------------------------------------
/day12/server/router/router.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "log"
5 | "net/http"
6 | "tictactoe/controllers"
7 | "tictactoe/db"
8 | )
9 |
10 | func ignoreOptionsMethod(handler http.HandlerFunc) http.HandlerFunc {
11 | return func(resp http.ResponseWriter, req *http.Request) {
12 | resp.Header().Set("Access-Control-Allow-Origin", "*")
13 | resp.Header().Set("Access-Control-Allow-Headers", "Accept,Content-Type,Authorization")
14 | resp.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
15 | resp.Header().Set("Content-Type", "application/json")
16 |
17 | if req.Method != http.MethodOptions {
18 | handler.ServeHTTP(resp, req)
19 | }
20 | }
21 | }
22 |
23 | func StartServer() {
24 | playerDB := new(db.PlayerDB)
25 | sessionDB := new(db.SessionDB)
26 | gameDB := new(db.GameDB)
27 |
28 | loginAPI := controllers.NewLoginAPI(playerDB, sessionDB)
29 | gameAPI := controllers.NewGameAPI(gameDB, playerDB, sessionDB)
30 |
31 | router := http.NewServeMux()
32 | router.HandleFunc("/player/login", ignoreOptionsMethod(loginAPI.HandleLogin))
33 | router.HandleFunc("/player/signup", ignoreOptionsMethod(loginAPI.HandleSignup))
34 | router.HandleFunc("/player/token", ignoreOptionsMethod(loginAPI.HandleTokenLogin))
35 |
36 | router.HandleFunc("/game/new", ignoreOptionsMethod(gameAPI.HandleNewGame))
37 | router.HandleFunc("/game/join", ignoreOptionsMethod(gameAPI.HandleJoinGame))
38 | router.HandleFunc("/game/play", ignoreOptionsMethod(gameAPI.HandlePlayTurn))
39 | router.HandleFunc("/game/get", ignoreOptionsMethod(gameAPI.HandleGetGame))
40 |
41 | log.Println("started server at http://localhost:8080")
42 | http.ListenAndServe(":8080", router)
43 | }
44 |
--------------------------------------------------------------------------------
/day12/server/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | func CheckWinner(cells []string) string {
4 | if checkIfPlayerWon(cells, "X") {
5 | return "X"
6 | } else if checkIfPlayerWon(cells, "O") {
7 | return "O"
8 | }
9 | return ""
10 | }
11 |
12 | func checkIfPlayerWon(cells []string, playerType string) bool {
13 | var firstDiagonal = cells[0] == cells[4] && cells[4] == cells[8] && cells[0] == playerType
14 |
15 | var secondDiagonal = cells[2] == cells[4] && cells[4] == cells[6] && cells[2] == playerType
16 |
17 | var firstRow = cells[0] == cells[1] && cells[1] == cells[2] && cells[1] == playerType
18 |
19 | var secondRow = cells[3] == cells[4] && cells[4] == cells[5] && cells[3] == playerType
20 |
21 | var thirdRow = cells[6] == cells[7] && cells[7] == cells[8] && cells[6] == playerType
22 |
23 | var firstColumn = cells[0] == cells[3] && cells[3] == cells[6] && cells[0] == playerType
24 |
25 | var secondColumn = cells[1] == cells[4] && cells[4] == cells[7] && cells[1] == playerType
26 |
27 | var thirdColumn = cells[2] == cells[5] && cells[5] == cells[8] && cells[8] == playerType
28 |
29 | return firstColumn ||
30 | secondColumn ||
31 | thirdColumn ||
32 | firstRow ||
33 | secondRow ||
34 | thirdRow ||
35 | firstDiagonal ||
36 | secondDiagonal
37 | }
38 |
--------------------------------------------------------------------------------
/day2/README.md:
--------------------------------------------------------------------------------
1 | # Day 2
2 |
3 | ## Create React Project
4 | 1. Create skeleton project
5 |
6 | ```bash
7 | npm create vite@latest PROJECT_NAME -- --template react
8 | ```
9 |
10 | 2. Install project's dependencies
11 |
12 | ```bash
13 | cd PROJECT_NAME
14 | npm install
15 | ```
16 |
17 | 3. Run the project
18 | ```bash
19 | npm run dev
20 | ```
21 |
22 | 4. Open `localhost:5173` in your web browser
23 |
--------------------------------------------------------------------------------
/day2/javascript/01-classes-and-objects.js:
--------------------------------------------------------------------------------
1 | let obj = {
2 | name: "Jaber",
3 | phone: "+962791234567",
4 | };
5 |
6 | console.log("whole object", obj);
7 |
8 | console.log("name", obj.name);
9 |
10 | obj.name = "Ali";
11 | console.log("name", obj.name);
12 |
13 | class Student {
14 | id;
15 | name;
16 | gpa;
17 | courses;
18 | /**
19 | * @param{string} id
20 | * @param{string} name
21 | * @param{number} gpa
22 | * @param{Array} courses
23 | */
24 | constructor (id, name, gpa, courses) {
25 | this.id = id;
26 | this.name = name;
27 | this.gpa = gpa;
28 | this.courses = courses;
29 | }
30 | }
31 | let ali = new Student("1", "Ali", 90, ["C++", "Discrete"]);
32 | let jaber = new Student("2", "Jaber", 70, ["C++", "Discrete", "English"]);
33 |
34 | console.log(ali);
35 | console.log(jaber);
36 |
--------------------------------------------------------------------------------
/day2/javascript/02-module1.mjs:
--------------------------------------------------------------------------------
1 | export function exp2(power) {
2 | return 1 << power;
3 | }
4 |
5 | export default class Car {
6 | model;
7 | speed;
8 | constructor(model, speed) {
9 | this.model = model;
10 | this.speed = speed;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/day2/javascript/03-module2.mjs:
--------------------------------------------------------------------------------
1 | import Car, { exp2 } from "./module1.mjs";
2 |
3 | console.log(exp2(3));
4 |
5 | const nissan = new Car("Nissan", 200);
6 | console.log(nissan);
7 |
--------------------------------------------------------------------------------
/day2/react/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/day2/react/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/day2/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tectactoe_",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite --host",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0"
14 | },
15 | "devDependencies": {
16 | "@types/react": "^18.0.27",
17 | "@types/react-dom": "^18.0.10",
18 | "@vitejs/plugin-react": "^3.1.0",
19 | "vite": "^4.1.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/day2/react/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/day2/react/src/App.jsx:
--------------------------------------------------------------------------------
1 | import Hello from "./components/Hello";
2 | import HelloProps from "./components/HelloProps";
3 |
4 | function App() {
5 | return (
6 |
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default App;
14 |
--------------------------------------------------------------------------------
/day2/react/src/components/Hello.jsx:
--------------------------------------------------------------------------------
1 | export default function Hello() {
2 | return (
3 |
6 | );
7 | }
8 |
--------------------------------------------------------------------------------
/day2/react/src/components/HelloProps.jsx:
--------------------------------------------------------------------------------
1 | export default function HelloProps({ name }) {
2 | return (
3 |
6 | );
7 | }
8 |
--------------------------------------------------------------------------------
/day2/react/src/index.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day2/react/src/index.css
--------------------------------------------------------------------------------
/day2/react/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 | import './index.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root')).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/day2/react/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/day3/README.md:
--------------------------------------------------------------------------------
1 | # Day 3
2 |
3 | ### Prettier extention for code formatting
4 |
5 | `https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode`
6 |
7 | then press `Alt+Shift+f` in VS Code when you need to format your code.
8 |
--------------------------------------------------------------------------------
/day3/part1/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/day3/part1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/day3/part1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tectactoe_",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite --host",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0"
14 | },
15 | "devDependencies": {
16 | "@types/react": "^18.0.27",
17 | "@types/react-dom": "^18.0.10",
18 | "@vitejs/plugin-react": "^3.1.0",
19 | "vite": "^4.1.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/day3/part1/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/day3/part1/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import YouShallPass from "./components/YouShallPass";
3 |
4 | function App() {
5 | const [pass, setPass] = useState(false);
6 |
7 | const togglePass = () => {
8 | setPass(!pass);
9 | };
10 |
11 | return (
12 |
13 |
14 |
15 | {pass && }
16 |
17 | );
18 | }
19 |
20 | export default App;
21 |
--------------------------------------------------------------------------------
/day3/part1/src/components/YouShallPass.jsx:
--------------------------------------------------------------------------------
1 | export default function YouShallPass() {
2 | return (
3 |
6 | );
7 | }
8 |
--------------------------------------------------------------------------------
/day3/part1/src/index.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day3/part1/src/index.css
--------------------------------------------------------------------------------
/day3/part1/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 | import './index.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root')).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/day3/part1/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/day3/part2/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/day3/part2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/day3/part2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tectactoe_",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite --host",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0"
14 | },
15 | "devDependencies": {
16 | "@types/react": "^18.0.27",
17 | "@types/react-dom": "^18.0.10",
18 | "@vitejs/plugin-react": "^3.1.0",
19 | "vite": "^4.1.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/day3/part2/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/day3/part2/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | function App() {
4 | const [counter, setCounter] = useState(0);
5 |
6 | const increaseCounter = () => {
7 | setCounter(counter + 1);
8 | };
9 |
10 | return (
11 |
12 |
Your counter is {counter}
13 |
14 |
15 | );
16 | }
17 |
18 | export default App;
19 |
--------------------------------------------------------------------------------
/day3/part2/src/index.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day3/part2/src/index.css
--------------------------------------------------------------------------------
/day3/part2/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 | import './index.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root')).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/day3/part2/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/day3/part3/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/day3/part3/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/day3/part3/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tectactoe_",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite --host",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0"
14 | },
15 | "devDependencies": {
16 | "@types/react": "^18.0.27",
17 | "@types/react-dom": "^18.0.10",
18 | "@vitejs/plugin-react": "^3.1.0",
19 | "vite": "^4.1.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/day3/part3/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/day3/part3/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | function App() {
4 | const [list, setList] = useState([]);
5 | const [todo, setTodo] = useState("");
6 |
7 | const addTodo = () => {
8 | list.push(todo);
9 | setTodo("");
10 | };
11 |
12 | const onChangeHandler = (e) => {
13 | setTodo(e.target.value);
14 | };
15 |
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
23 | {list.map((todo) => {
24 | return - {todo}
;
25 | })}
26 |
27 |
28 | );
29 | }
30 |
31 | export default App;
32 |
--------------------------------------------------------------------------------
/day3/part3/src/index.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day3/part3/src/index.css
--------------------------------------------------------------------------------
/day3/part3/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 | import './index.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root')).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/day3/part3/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/day4/students/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/day4/students/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Intro
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/day4/students/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "intro",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0"
14 | },
15 | "devDependencies": {
16 | "@types/react": "^18.0.27",
17 | "@types/react-dom": "^18.0.10",
18 | "@vitejs/plugin-react": "^3.1.0",
19 | "vite": "^4.1.0"
20 | }
21 | }
--------------------------------------------------------------------------------
/day4/students/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/day4/students/src/App.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day4/students/src/App.css
--------------------------------------------------------------------------------
/day4/students/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import "./App.css";
3 | import Student from "./classes/Student";
4 | import Students from "./componets/Students";
5 |
6 | function App() {
7 | const [arr, setStudents] = useState([
8 | new Student("1", "Ali", 90),
9 | new Student("2", "Jaber", 70),
10 | new Student("3", "Omar", 80.3),
11 | ]);
12 |
13 | return (
14 |
15 |
16 |
17 | );
18 | }
19 |
20 | export default App;
21 |
--------------------------------------------------------------------------------
/day4/students/src/classes/Student.js:
--------------------------------------------------------------------------------
1 | class Student {
2 | id;
3 | name;
4 | gpa;
5 |
6 | constructor (id, name, gpa) {
7 | this.id = id;
8 | this.name = name;
9 | this.gpa = gpa;
10 | }
11 | }
12 |
13 | export default Student;
--------------------------------------------------------------------------------
/day4/students/src/componets/Students.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import Student from "../classes/Student";
3 |
4 | function Students({ students, setStudents }) {
5 | const [id, setId] = useState("");
6 | const [name, setName] = useState("");
7 | const [gpa, setGpa] = useState(0.0);
8 |
9 | const idOnchangeHandler = (e) => {
10 | setId(e.target.value);
11 | };
12 |
13 | const nameOnchangeHandler = (e) => {
14 | setName(e.target.value);
15 | };
16 |
17 | const gpaOnchangeHandler = (e) => {
18 | setGpa(parseFloat(e.target.value));
19 | };
20 |
21 | const addStudent = () => {
22 | students.push(new Student(id, name, gpa));
23 | setStudents(students.flat());
24 | };
25 |
26 | const removeStudent = (id) => {
27 | const newArr = students.filter((el) => {
28 | return el.id !== id;
29 | });
30 |
31 | // const newArr = [];
32 | // for (let i = 0; i < students.length; i++) {
33 | // if (students[i].id !== id) {
34 | // newArr.push(students[i]);
35 | // }
36 | // }
37 |
38 | setStudents(newArr);
39 | };
40 |
41 | return (
42 |
92 | );
93 | }
94 |
95 | export default Students;
96 |
--------------------------------------------------------------------------------
/day4/students/src/index.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day4/students/src/index.css
--------------------------------------------------------------------------------
/day4/students/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 | import './index.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root')).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/day4/students/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/day4/tictactoe_part1/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/day4/tictactoe_part1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Tic Tac Toe
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/day4/tictactoe_part1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tictactoe",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0"
14 | },
15 | "devDependencies": {
16 | "@types/react": "^18.0.27",
17 | "@types/react-dom": "^18.0.10",
18 | "@vitejs/plugin-react": "^3.1.0",
19 | "vite": "^4.1.0"
20 | }
21 | }
--------------------------------------------------------------------------------
/day4/tictactoe_part1/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/day4/tictactoe_part1/src/App.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day4/tictactoe_part1/src/App.css
--------------------------------------------------------------------------------
/day4/tictactoe_part1/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import './App.css'
3 | import GameBoard from './components/GameBoard'
4 | import GameData from './data/GameData';
5 |
6 | function App() {
7 | const [gameData, setGameData] = useState(new GameData());
8 |
9 | return (
10 |
11 |
12 |
13 | )
14 | }
15 |
16 | export default App
17 |
--------------------------------------------------------------------------------
/day4/tictactoe_part1/src/components/GameBoard.css:
--------------------------------------------------------------------------------
1 | .cell {
2 | display: inline;
3 | }
4 |
5 | button.cell {
6 | font-size: 40px;
7 | padding: 15px;
8 | }
--------------------------------------------------------------------------------
/day4/tictactoe_part1/src/components/GameBoard.jsx:
--------------------------------------------------------------------------------
1 | import GameData from "../data/GameData";
2 | import "./GameBoard.css"
3 | import Stats from "./Stats";
4 |
5 | function GameBoard({ game, setGame }) {
6 |
7 | const play = (cellNumber) => {
8 | game.markCell(cellNumber);
9 | game.checkWinner();
10 | setGame(new GameData(game));
11 | }
12 |
13 | return (
14 |
15 |
16 |
17 |
18 | {game.cells.map((cell, index) => {
19 | return (
20 |
21 |
22 | {((index + 1) % 3 === 0) &&
}
23 |
24 | );
25 | })}
26 |
27 | );
28 | }
29 |
30 | export default GameBoard;
31 |
--------------------------------------------------------------------------------
/day4/tictactoe_part1/src/components/Stats.jsx:
--------------------------------------------------------------------------------
1 | function Stats({ xScore, oScore, turn }) {
2 | return
3 |
Player X's Score: {xScore}
4 |
Player O's Score: {oScore}
5 |
Current Player: {turn}
6 |
7 | }
8 |
9 | export default Stats;
--------------------------------------------------------------------------------
/day4/tictactoe_part1/src/data/GameData.js:
--------------------------------------------------------------------------------
1 | import GameLogic from "./GameLogic";
2 |
3 | class GameData {
4 | cells;
5 | turn;
6 | xScore;
7 | oScore;
8 |
9 | constructor(copiedData) {
10 | if (copiedData !== undefined && copiedData !== null) {
11 | this.cells = copiedData.cells;
12 | this.turn = copiedData.turn;
13 | this.xScore = copiedData.xScore;
14 | this.oScore = copiedData.oScore;
15 | return;
16 | }
17 | this.cells = ["1", "2", "3", "4", "5", "6", "7", "8", "9"];
18 | this.turn = "X";
19 | this.xScore = 0;
20 | this.oScore = 0;
21 | }
22 |
23 | resetGame() {
24 | this.cells = ["1", "2", "3", "4", "5", "6", "7", "8", "9"];
25 | this.turn = "X";
26 | }
27 |
28 | markCell(cellNumber) {
29 | if (this.isCellNumberInBounds(cellNumber))
30 | return;
31 |
32 | if (this.isCellOccupied(cellNumber))
33 | return;
34 |
35 | switch (this.turn) {
36 | case "X":
37 | this.cells[cellNumber] = "X";
38 | break;
39 | case "O":
40 | this.cells[cellNumber] = "O";
41 | break;
42 | }
43 |
44 | if (this.areAllCellsOccupied()) {
45 | this.resetGame();
46 | return;
47 | }
48 |
49 | this.switchTurn();
50 | }
51 |
52 | isCellNumberInBounds(cellNumber) {
53 | return cellNumber < 0 || cellNumber > this.cells.length-1;
54 | }
55 |
56 | isCellOccupied(cellNumber) {
57 | return this.cells[cellNumber] === "X" || this.cells[cellNumber] === "O"
58 | }
59 |
60 | areAllCellsOccupied() {
61 | for (let i = 0; i < this.cells.length; i++) {
62 | if (!this.isCellOccupied(i)) {
63 | return false;
64 | }
65 | }
66 | return true;
67 | }
68 |
69 | switchTurn() {
70 | switch (this.turn) {
71 | case "X":
72 | this.turn = "O";
73 | break;
74 | case "O":
75 | this.turn = "X";
76 | break;
77 | }
78 | }
79 |
80 | checkWinner() {
81 | let winner = GameLogic.checkWinner(this.cells);
82 | switch (winner) {
83 | case "X":
84 | this.xScore++;
85 | this.resetGame();
86 | return "X";
87 | case "O":
88 | this.oScore++;
89 | this.resetGame();
90 | return "O";
91 | }
92 | }
93 | }
94 |
95 | export default GameData;
--------------------------------------------------------------------------------
/day4/tictactoe_part1/src/data/GameLogic.js:
--------------------------------------------------------------------------------
1 | class GameLogic {
2 | static checkWinner(cells) {
3 | if (this.checkIfPlayerWon(cells, "X")) {
4 | return "X";
5 | } else if (this.checkIfPlayerWon(cells, "O")) {
6 | return "O";
7 | }
8 | return "";
9 | }
10 |
11 | static checkIfPlayerWon(cells, playerType) {
12 | let firstDiagonal =
13 | cells[0] === cells[4] && cells[4] === cells[8] && cells[0] === playerType;
14 |
15 | let secondDiagonal =
16 | cells[2] === cells[4] && cells[4] === cells[6] && cells[2] === playerType;
17 |
18 | let firstRow =
19 | cells[0] === cells[1] && cells[1] === cells[2] && cells[1] === playerType;
20 |
21 | let secondRow =
22 | cells[3] === cells[4] && cells[4] === cells[5] && cells[3] === playerType;
23 |
24 | let thirdRow =
25 | cells[6] === cells[7] && cells[7] === cells[8] && cells[6] === playerType;
26 |
27 | let firstColumn =
28 | cells[0] === cells[3] && cells[3] === cells[6] && cells[0] === playerType;
29 |
30 | let secondColumn =
31 | cells[1] === cells[4] && cells[4] === cells[7] && cells[1] === playerType;
32 |
33 | let thirdColumn =
34 | cells[2] === cells[5] && cells[5] === cells[8] && cells[8] === playerType;
35 |
36 | return (
37 | firstColumn ||
38 | secondColumn ||
39 | thirdColumn ||
40 | firstRow ||
41 | secondRow ||
42 | thirdRow ||
43 | firstDiagonal ||
44 | secondDiagonal
45 | );
46 | }
47 | }
48 |
49 | export default GameLogic;
50 |
--------------------------------------------------------------------------------
/day4/tictactoe_part1/src/index.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day4/tictactoe_part1/src/index.css
--------------------------------------------------------------------------------
/day4/tictactoe_part1/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 | import './index.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root')).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/day4/tictactoe_part1/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/day5/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/day5/README.md:
--------------------------------------------------------------------------------
1 | # Day 5's links:
2 |
3 | * Empty Chracter `https://emptycharacter.com/`
4 |
--------------------------------------------------------------------------------
/day5/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Tic Tac Toe
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/day5/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tictactoe",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0"
14 | },
15 | "devDependencies": {
16 | "@types/react": "^18.0.27",
17 | "@types/react-dom": "^18.0.10",
18 | "@vitejs/plugin-react": "^3.1.0",
19 | "vite": "^4.1.0"
20 | }
21 | }
--------------------------------------------------------------------------------
/day5/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/day5/src/App.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day5/src/App.css
--------------------------------------------------------------------------------
/day5/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import "./App.css";
3 | import GameBoard from "./components/GameBoard";
4 | import GameData from "./data/GameData";
5 |
6 | function App() {
7 | const [gameData, setGameData] = useState(new GameData());
8 |
9 | return (
10 |
11 |
12 |
13 | );
14 | }
15 |
16 | export default App;
17 |
--------------------------------------------------------------------------------
/day5/src/components/GameBoard.css:
--------------------------------------------------------------------------------
1 | .cell {
2 | display: inline;
3 | }
4 |
5 | button.cell {
6 | font-size: 40px;
7 | font-weight: bold;
8 | background-color: #000000;
9 | color: #ffffff;
10 | border: 1px #ababab solid;
11 |
12 | width: 120px;
13 | height: 120px;
14 |
15 | padding: 30px;
16 | }
17 |
18 | .board {
19 | }
20 |
21 | .bigContainer {
22 | position: absolute;
23 | top: 50%;
24 | left: 50%;
25 | transform: translateX(-50%) translateY(-50%);
26 | }
27 |
--------------------------------------------------------------------------------
/day5/src/components/GameBoard.jsx:
--------------------------------------------------------------------------------
1 | import GameData from "../data/GameData";
2 | import "./GameBoard.css";
3 | import Stats from "./Stats";
4 |
5 | function GameBoard({ game, setGame }) {
6 | const play = (cellNumber) => {
7 | game.markCell(cellNumber);
8 | game.checkWinner();
9 | setGame(new GameData(game));
10 | };
11 |
12 | return (
13 |
14 |
15 | {game.cells.map((cell, index) => {
16 | return (
17 |
18 |
27 | {(index + 1) % 3 === 0 &&
}
28 |
29 | );
30 | })}
31 |
32 |
33 |
34 |
35 |
36 | );
37 | }
38 |
39 | export default GameBoard;
40 |
--------------------------------------------------------------------------------
/day5/src/components/Stats.css:
--------------------------------------------------------------------------------
1 | span {
2 | color: #ffffff;
3 | font-weight: 900;
4 | font-size: 25px;
5 | text-align: center;
6 | display: inline-block;
7 | width: 100%;
8 | }
9 |
10 | .container {
11 | display: flex;
12 | justify-content: space-between;
13 | margin-left: 20px;
14 | margin-right: 20px;
15 | margin-top: 20px;
16 | }
17 |
--------------------------------------------------------------------------------
/day5/src/components/Stats.jsx:
--------------------------------------------------------------------------------
1 | import "./Stats.css";
2 |
3 | function Stats({ xScore, oScore, turn }) {
4 | return (
5 |
6 |
7 | Player X
8 |
9 | {xScore}
10 |
11 |
12 |
13 | Player O
14 |
15 | {oScore}
16 |
17 |
18 |
19 | Turn
20 |
21 | {turn}
22 |
23 |
24 | );
25 | }
26 |
27 | export default Stats;
28 |
--------------------------------------------------------------------------------
/day5/src/data/GameData.js:
--------------------------------------------------------------------------------
1 | import GameLogic from "./GameLogic";
2 |
3 | class GameData {
4 | cells;
5 | turn;
6 | xScore;
7 | oScore;
8 |
9 | constructor(copiedData) {
10 | if (copiedData !== undefined && copiedData !== null) {
11 | this.cells = copiedData.cells;
12 | this.turn = copiedData.turn;
13 | this.xScore = copiedData.xScore;
14 | this.oScore = copiedData.oScore;
15 | return;
16 | }
17 | this.cells = ["1", "2", "3", "4", "5", "6", "7", "8", "9"];
18 | this.turn = "X";
19 | this.xScore = 0;
20 | this.oScore = 0;
21 | }
22 |
23 | resetGame() {
24 | this.cells = ["1", "2", "3", "4", "5", "6", "7", "8", "9"];
25 | this.turn = "X";
26 | }
27 |
28 | markCell(cellNumber) {
29 | if (this.isCellNumberInBounds(cellNumber))
30 | return;
31 |
32 | if (this.isCellOccupied(cellNumber))
33 | return;
34 |
35 | switch (this.turn) {
36 | case "X":
37 | this.cells[cellNumber] = "X";
38 | break;
39 | case "O":
40 | this.cells[cellNumber] = "O";
41 | break;
42 | }
43 |
44 | if (this.areAllCellsOccupied()) {
45 | this.resetGame();
46 | return;
47 | }
48 |
49 | this.switchTurn();
50 | }
51 |
52 | isCellNumberInBounds(cellNumber) {
53 | return cellNumber < 0 || cellNumber > this.cells.length-1;
54 | }
55 |
56 | isCellOccupied(cellNumber) {
57 | return this.cells[cellNumber] === "X" || this.cells[cellNumber] === "O"
58 | }
59 |
60 | areAllCellsOccupied() {
61 | for (let i = 0; i < this.cells.length; i++) {
62 | if (!this.isCellOccupied(i)) {
63 | return false;
64 | }
65 | }
66 | return true;
67 | }
68 |
69 | switchTurn() {
70 | switch (this.turn) {
71 | case "X":
72 | this.turn = "O";
73 | break;
74 | case "O":
75 | this.turn = "X";
76 | break;
77 | }
78 | }
79 |
80 | checkWinner() {
81 | let winner = GameLogic.checkWinner(this.cells);
82 | switch (winner) {
83 | case "X":
84 | this.xScore++;
85 | this.resetGame();
86 | return "X";
87 | case "O":
88 | this.oScore++;
89 | this.resetGame();
90 | return "O";
91 | }
92 | }
93 | }
94 |
95 | export default GameData;
--------------------------------------------------------------------------------
/day5/src/data/GameLogic.js:
--------------------------------------------------------------------------------
1 | class GameLogic {
2 | static checkWinner(cells) {
3 | if (this.checkIfPlayerWon(cells, "X")) {
4 | return "X";
5 | } else if (this.checkIfPlayerWon(cells, "O")) {
6 | return "O";
7 | }
8 | return "";
9 | }
10 |
11 | static checkIfPlayerWon(cells, playerType) {
12 | let firstDiagonal =
13 | cells[0] === cells[4] && cells[4] === cells[8] && cells[0] === playerType;
14 |
15 | let secondDiagonal =
16 | cells[2] === cells[4] && cells[4] === cells[6] && cells[2] === playerType;
17 |
18 | let firstRow =
19 | cells[0] === cells[1] && cells[1] === cells[2] && cells[1] === playerType;
20 |
21 | let secondRow =
22 | cells[3] === cells[4] && cells[4] === cells[5] && cells[3] === playerType;
23 |
24 | let thirdRow =
25 | cells[6] === cells[7] && cells[7] === cells[8] && cells[6] === playerType;
26 |
27 | let firstColumn =
28 | cells[0] === cells[3] && cells[3] === cells[6] && cells[0] === playerType;
29 |
30 | let secondColumn =
31 | cells[1] === cells[4] && cells[4] === cells[7] && cells[1] === playerType;
32 |
33 | let thirdColumn =
34 | cells[2] === cells[5] && cells[5] === cells[8] && cells[8] === playerType;
35 |
36 | return (
37 | firstColumn ||
38 | secondColumn ||
39 | thirdColumn ||
40 | firstRow ||
41 | secondRow ||
42 | thirdRow ||
43 | firstDiagonal ||
44 | secondDiagonal
45 | );
46 | }
47 | }
48 |
49 | export default GameLogic;
50 |
--------------------------------------------------------------------------------
/day5/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #000000;
3 | }
4 |
--------------------------------------------------------------------------------
/day5/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 | import './index.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root')).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/day5/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/day6/01-variables.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | var x = 12
7 | var y int = x + 8
8 | fmt.Println("y = ", y)
9 |
10 | var z = 12.4
11 | fmt.Println("z + x =", (z + float64(x)))
12 |
13 | var name = "Ali"
14 | fmt.Println("Hello,", name)
15 |
16 | var firstName = "Ali"
17 | var lastName = "Al-Homsi"
18 | var fullName = fmt.Sprintf("%s %s", firstName, lastName)
19 | fmt.Println("Hello", fullName)
20 |
21 | canIDrive := false
22 | fmt.Println("Am I old enough to drive?", canIDrive)
23 | }
24 |
--------------------------------------------------------------------------------
/day6/02-controlStatements.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | var shallIPass = true
7 | if shallIPass {
8 | fmt.Println("You shall pass")
9 | } else {
10 | fmt.Println("You shall not pass")
11 | }
12 |
13 | x, y, z := 12, 10, 13
14 |
15 | if x > y && x > z {
16 | fmt.Println("x is the greatest")
17 | } else if y > x && y > z {
18 | fmt.Println("y is the greatest")
19 | } else if z > x && z > y {
20 | fmt.Println("z is the greatest")
21 | }
22 |
23 | var mark = "B"
24 | switch mark {
25 | case "A":
26 | fmt.Println("big nerd")
27 | case "B":
28 | fmt.Println("nerd")
29 | case "C":
30 | fmt.Println("ain't much")
31 | default:
32 | fmt.Println("I don't know what is that!")
33 | }
34 |
35 | for i := 1; i <= 10; i++ {
36 | fmt.Println(i)
37 | }
38 |
39 | var count = 10
40 | for count > 0 {
41 | fmt.Println(count)
42 | count--
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/day6/03-arrays.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | var arr = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
7 | fmt.Println("all array's elements", arr)
8 |
9 | fmt.Println("element by element")
10 | // using counter loop
11 | for i := 0; i < len(arr); i++ {
12 | fmt.Println(arr[i])
13 | }
14 |
15 | // using for range
16 | for index, value := range arr {
17 | fmt.Println(index, value)
18 | }
19 |
20 | arr[0] = 99
21 | fmt.Println(arr)
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/day6/04-maps.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | mp := make(map[string]int)
7 | mp["square"] = 4
8 | mp["pentagon"] = 5
9 | mp["triangle"] = 3
10 |
11 | // using println statement
12 | fmt.Println(mp)
13 |
14 | // using for range
15 | for key, value := range mp {
16 | fmt.Println(key, value)
17 | }
18 |
19 | // delete an element
20 | delete(mp, "pentagon")
21 | fmt.Println(mp)
22 |
23 | fmt.Println("length of the map:", len(mp))
24 | }
25 |
--------------------------------------------------------------------------------
/day6/05-functions.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func nothing() {
6 | fmt.Println("nothing")
7 | }
8 |
9 | func greet(name string) {
10 | fmt.Println(`Hello, ${name}`)
11 | }
12 |
13 | func square(n int) int {
14 | return n * n
15 | }
16 |
17 | var nothing2 = func() {
18 | fmt.Println("nothing2")
19 | }
20 |
21 | var add = func(x, y, z int) int {
22 | return x + y + z
23 | }
24 |
25 | func main() {
26 | nothing()
27 | greet("Jaber")
28 | fmt.Println(square(4))
29 | nothing2()
30 | fmt.Println(add(1, 2, 3))
31 | }
32 |
--------------------------------------------------------------------------------
/day6/06-objects.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | )
7 |
8 | type Student struct {
9 | Name string
10 | Phone string
11 | Gpa float64
12 | }
13 |
14 | func (s *Student) ConvertPhoneNumberToInteger() int64 {
15 | numberWithoutPlus := s.Phone[1:]
16 |
17 | intNumber, err := strconv.ParseInt(numberWithoutPlus, 10, 64)
18 | if err != nil {
19 | panic(err)
20 | }
21 |
22 | return intNumber
23 | }
24 |
25 | func main() {
26 | ali := Student{Name: "Ali Al-Homsi", Phone: "+962791234567", Gpa: 90.2}
27 | fmt.Println("ali's data", ali)
28 | }
29 |
--------------------------------------------------------------------------------
/day6/07-errorHandling/Main.java:
--------------------------------------------------------------------------------
1 | class Main {
2 | public static double sqrt(int n) throws Exception {
3 | if (n < 0) {
4 | throw new Exception("can't take square root of a negative number");
5 | }
6 | return Math.sqrt(n);
7 | }
8 |
9 | public static void main(String[] args) {
10 | try {
11 | double value = sqrt(-12);
12 | } catch (Exception err) {
13 | System.out.println(err.getMessage());
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/day6/07-errorHandling/go.mod:
--------------------------------------------------------------------------------
1 | module x
2 |
3 | go 1.20
4 |
--------------------------------------------------------------------------------
/day6/07-errorHandling/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "math"
7 | )
8 |
9 | func sqrt(n int) (float64, error) {
10 | if n < 0 {
11 | return 0.0, errors.New("can't take square root of a negative number")
12 | }
13 |
14 | return math.Sqrt(float64(n)), nil
15 | }
16 |
17 | func main() {
18 | value, err := sqrt(-12)
19 | if err != nil {
20 | fmt.Println(err.Error())
21 | return
22 | }
23 |
24 | fmt.Println(value)
25 | }
26 |
--------------------------------------------------------------------------------
/day6/README.md:
--------------------------------------------------------------------------------
1 | # Day 6's links:
2 |
3 | * Go download: `https://go.dev/dl/`
4 | * Tour link: `https://go.dev/tour/welcome/1`
5 | * Go VSCode extension: `https://marketplace.visualstudio.com/items?itemName=golang.Go`
6 |
--------------------------------------------------------------------------------
/day7/README.md:
--------------------------------------------------------------------------------
1 | # Day 7:
2 |
3 | ## Running an API's server:
4 |
5 | - Open the API's directory, e.g. `pizza-api`
6 | - Run `go run main.go` in terminal or CMD
7 |
8 | ## Importing a Postman collection:
9 |
10 | - Open Postman
11 | - Go to File -> Import -> Choose Files
12 | - Select the collection that you want to import, e.g. `Pizza API.postman_collection.json`
13 | - Click on import
14 | - Use the requests from the imported collection
15 |
16 | ## Links:
17 |
18 | - Postman: `https://www.postman.com/downloads/?utm_source=postman-home`
19 | - Open Weather API Docs `https://openweathermap.org/api`
20 | - BITCOIN API Docs `https://binance-docs.github.io/apidocs/spot/en/#exchange-information`
21 | - MariaDB: `https://mariadb.org/download`
22 | - Download Props:
23 | - Version: 10.9.5
24 | - Architecture: x86_64
25 | - OS: depends on what you use
26 |
27 |
--------------------------------------------------------------------------------
/day7/queries.sql:
--------------------------------------------------------------------------------
1 | -- create tictactoe table named ttt
2 | CREATE DATABASE ttt;
3 |
4 | -- create students table
5 | CREATE TABLE IF NOT EXISTS ttt.students (
6 | id INTEGER AUTO_INCREMENT,
7 | name VARCHAR(50),
8 | student_id VARCHAR(10),
9 | gpa FLOAT,
10 | PRIMARY KEY (id)
11 | );
12 |
13 | -- retrive all students from database with all the attributes
14 | SELECT * FROM ttt.students;
15 |
16 | -- retrive a single student with id with all the attributes
17 | SELECT * FROM ttt.students WHERE id = 1;
18 |
19 | -- retrive a single student with id with only name and gpa attributes
20 | SELECT (name, gpa) gpa FROM ttt.students WHERE id = 1;
21 |
22 | -- update the student with id=1 and set the name to Mohammad
23 | UPDATE ttt.students SET name='Mohammad' WHERE id=1;
24 |
25 | -- delete the student with id=2
26 | DELETE FROM ttt.students WHERE id=2;
27 |
28 | -- delete all of the students
29 | DELETE FROM ttt.students;
--------------------------------------------------------------------------------
/day7/rest-apis/APIs Invocations.postman_collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "info": {
3 | "_postman_id": "2cf8da4f-e0f4-45d5-904f-0192c083243d",
4 | "name": "APIs Invocations",
5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
6 | "_exporter_id": "22901882"
7 | },
8 | "item": [
9 | {
10 | "name": "Weather",
11 | "request": {
12 | "method": "GET",
13 | "header": [],
14 | "url": {
15 | "raw": "https://api.openweathermap.org/data/2.5/weather?q=Amman&appid=API_TOKEN_HERE",
16 | "protocol": "https",
17 | "host": [
18 | "api",
19 | "openweathermap",
20 | "org"
21 | ],
22 | "path": [
23 | "data",
24 | "2.5",
25 | "weather"
26 | ],
27 | "query": [
28 | {
29 | "key": "q",
30 | "value": "Amman"
31 | },
32 | {
33 | "key": "appid",
34 | "value": "2fb1078b1ae351cef587e0b7a1e479f0"
35 | }
36 | ]
37 | }
38 | },
39 | "response": []
40 | },
41 | {
42 | "name": "BITCOIN",
43 | "request": {
44 | "method": "GET",
45 | "header": [],
46 | "url": {
47 | "raw": "https://api.binance.com/api/v3/depth?symbol=BTCUSDT",
48 | "protocol": "https",
49 | "host": [
50 | "api",
51 | "binance",
52 | "com"
53 | ],
54 | "path": [
55 | "api",
56 | "v3",
57 | "depth"
58 | ],
59 | "query": [
60 | {
61 | "key": "symbol",
62 | "value": "BTCUSDT"
63 | }
64 | ]
65 | }
66 | },
67 | "response": []
68 | },
69 | {
70 | "name": "BMI",
71 | "request": {
72 | "method": "GET",
73 | "header": [],
74 | "url": {
75 | "raw": "http://localhost:8080/bmi/calc?height=1.74&weight=71.5",
76 | "protocol": "http",
77 | "host": [
78 | "localhost"
79 | ],
80 | "port": "8080",
81 | "path": [
82 | "bmi",
83 | "calc"
84 | ],
85 | "query": [
86 | {
87 | "key": "height",
88 | "value": "1.74"
89 | },
90 | {
91 | "key": "weight",
92 | "value": "71.5"
93 | }
94 | ]
95 | }
96 | },
97 | "response": []
98 | },
99 | {
100 | "name": "BMI List",
101 | "request": {
102 | "method": "GET",
103 | "header": [],
104 | "url": {
105 | "raw": "http://localhost:8080/bmi/all",
106 | "protocol": "http",
107 | "host": [
108 | "localhost"
109 | ],
110 | "port": "8080",
111 | "path": [
112 | "bmi",
113 | "all"
114 | ]
115 | }
116 | },
117 | "response": []
118 | }
119 | ]
120 | }
121 |
--------------------------------------------------------------------------------
/day7/rest-apis/Pizza API.postman_collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "info": {
3 | "_postman_id": "bdd346bf-6a23-431f-9821-ff4f55cf3175",
4 | "name": "Pizza API",
5 | "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json",
6 | "_exporter_id": "22901882"
7 | },
8 | "item": [
9 | {
10 | "name": "Add",
11 | "request": {
12 | "method": "POST",
13 | "header": [],
14 | "body": {
15 | "mode": "raw",
16 | "raw": "{\n \"type\": \"Margarita\",\n \"extraCheese\": true,\n \"size\": \"medium\",\n \"price\": 3.5\n}",
17 | "options": {
18 | "raw": {
19 | "language": "json"
20 | }
21 | }
22 | },
23 | "url": "http://localhost:8081/pizza/add"
24 | },
25 | "response": []
26 | },
27 | {
28 | "name": "Get Single",
29 | "request": {
30 | "method": "GET",
31 | "header": [],
32 | "url": {
33 | "raw": "http://localhost:8081/pizza/get?type=Margarita",
34 | "protocol": "http",
35 | "host": [
36 | "localhost"
37 | ],
38 | "port": "8081",
39 | "path": [
40 | "pizza",
41 | "get"
42 | ],
43 | "query": [
44 | {
45 | "key": "type",
46 | "value": "Margarita"
47 | }
48 | ]
49 | }
50 | },
51 | "response": []
52 | },
53 | {
54 | "name": "Get All",
55 | "request": {
56 | "method": "GET",
57 | "header": [],
58 | "url": "http://localhost:8081/pizza/get-all"
59 | },
60 | "response": []
61 | },
62 | {
63 | "name": "Update",
64 | "request": {
65 | "method": "PUT",
66 | "header": [],
67 | "body": {
68 | "mode": "raw",
69 | "raw": "{\n \"type\": \"Margarita\",\n \"extraCheese\": false,\n \"size\": \"medium\",\n \"price\": 3\n}",
70 | "options": {
71 | "raw": {
72 | "language": "json"
73 | }
74 | }
75 | },
76 | "url": {
77 | "raw": "http://localhost:8081/pizza/update?type=Margarita",
78 | "protocol": "http",
79 | "host": [
80 | "localhost"
81 | ],
82 | "port": "8081",
83 | "path": [
84 | "pizza",
85 | "update"
86 | ],
87 | "query": [
88 | {
89 | "key": "type",
90 | "value": "Margarita"
91 | }
92 | ]
93 | }
94 | },
95 | "response": []
96 | },
97 | {
98 | "name": "Delete",
99 | "request": {
100 | "method": "DELETE",
101 | "header": [],
102 | "url": {
103 | "raw": "http://localhost:8081/pizza/delete?type=Margarita",
104 | "protocol": "http",
105 | "host": [
106 | "localhost"
107 | ],
108 | "port": "8081",
109 | "path": [
110 | "pizza",
111 | "delete"
112 | ],
113 | "query": [
114 | {
115 | "key": "type",
116 | "value": "Margarita"
117 | }
118 | ]
119 | }
120 | },
121 | "response": []
122 | }
123 | ]
124 | }
--------------------------------------------------------------------------------
/day7/rest-apis/bmi-api/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 | "strconv"
7 | )
8 |
9 | func calculateBMI(height, wight float64) (string, float64) {
10 | bmi := wight / (height * height)
11 | var index string
12 |
13 | if bmi < 18.5 {
14 | index = "Underweight"
15 | } else if bmi >= 18.5 && bmi <= 24.9 {
16 | index = "Normal Weight"
17 | } else if bmi >= 25 && bmi <= 29.9 {
18 | index = "Overweight"
19 | } else {
20 | index = "Obesity"
21 | }
22 |
23 | return index, bmi
24 | }
25 |
26 | // handleCalculateBMI calculates Body Mass Index, and returns a response
27 | // with BMI, and status based on that index.
28 | func handleCalculateBMI(res http.ResponseWriter, req *http.Request) {
29 | height, _ := strconv.ParseFloat(req.URL.Query().Get("height"), 64)
30 | weight, _ := strconv.ParseFloat(req.URL.Query().Get("weight"), 64)
31 |
32 | index, bmi := calculateBMI(height, weight)
33 |
34 | res.Header().Set("Content-Type", "application/json")
35 | res.Header().Set("Access-Control-Allow-Origin", "*")
36 | json.NewEncoder(res).Encode(map[string]any{
37 | "index": index,
38 | "bmi": bmi,
39 | })
40 | }
41 |
42 | // handleListBMI returns a response with a map with BMI categories
43 | func handleListBMI(res http.ResponseWriter, req *http.Request) {
44 | res.Header().Set("Content-Type", "application/json")
45 | res.Header().Set("Access-Control-Allow-Origin", "*")
46 | json.NewEncoder(res).Encode(map[string]string{
47 | "Underweight": "< 18.5",
48 | "Normal weight": "18.5 - 24.9",
49 | "Overweight": "25 - 29.9",
50 | "Obesity": "> 30",
51 | })
52 | }
53 |
54 | func main() {
55 | router := http.NewServeMux()
56 | router.HandleFunc("/bmi/calc", handleCalculateBMI)
57 | router.HandleFunc("/bmi/all", handleListBMI)
58 |
59 | http.ListenAndServe(":8080", router)
60 | }
61 |
--------------------------------------------------------------------------------
/day7/rest-apis/pizza-api/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 | )
7 |
8 | var pizzas = make(map[string]Pizza)
9 |
10 | type Pizza struct {
11 | Type string `json:"type"`
12 | ExtraCheese bool `json:"extraCheese"`
13 | Size string `json:"size"`
14 | Price float64 `json:"price"`
15 | }
16 |
17 | func handleGetPizza(res http.ResponseWriter, req *http.Request) {
18 | _type := req.URL.Query().Get("type")
19 | pizza, ok := pizzas[_type]
20 | if ok {
21 | json.NewEncoder(res).Encode(pizza)
22 | return
23 | }
24 |
25 | // res.WriteHeader(404)
26 | res.WriteHeader(http.StatusNotFound)
27 | }
28 |
29 | func handleGetPizzas(res http.ResponseWriter, req *http.Request) {
30 | pizzasArr := make([]Pizza, 0)
31 |
32 | for _, pizza := range pizzas {
33 | pizzasArr = append(pizzasArr, pizza)
34 | }
35 |
36 | json.NewEncoder(res).Encode(pizzasArr)
37 | }
38 |
39 | func handleAddPizza(res http.ResponseWriter, req *http.Request) {
40 | var pizza Pizza
41 | err := json.NewDecoder(req.Body).Decode(&pizza)
42 | if err != nil {
43 | res.WriteHeader(http.StatusBadRequest)
44 | return
45 | }
46 |
47 | pizzas[pizza.Type] = pizza
48 | }
49 |
50 | func handleUpdatePizza(res http.ResponseWriter, req *http.Request) {
51 | _type := req.URL.Query().Get("type")
52 |
53 | _, exists := pizzas[_type]
54 | if len(_type) == 0 || !exists {
55 | res.WriteHeader(http.StatusBadRequest)
56 | return
57 | }
58 |
59 | var pizza Pizza
60 | err := json.NewDecoder(req.Body).Decode(&pizza)
61 | if err != nil {
62 | res.WriteHeader(http.StatusBadRequest)
63 | return
64 | }
65 |
66 | pizzas[_type] = pizza
67 | }
68 |
69 | func handleDeletePizza(res http.ResponseWriter, req *http.Request) {
70 | _type := req.URL.Query().Get("type")
71 |
72 | _, exists := pizzas[_type]
73 | if len(_type) == 0 || !exists {
74 | res.WriteHeader(http.StatusBadRequest)
75 | return
76 | }
77 |
78 | delete(pizzas, _type)
79 | }
80 |
81 | func main() {
82 | router := http.NewServeMux()
83 | router.HandleFunc("/pizza/get", handleGetPizza)
84 | router.HandleFunc("/pizza/get-all", handleGetPizzas)
85 | router.HandleFunc("/pizza/add", handleAddPizza)
86 | router.HandleFunc("/pizza/update", handleUpdatePizza)
87 | router.HandleFunc("/pizza/delete", handleDeletePizza)
88 |
89 | http.ListenAndServe(":8081", router)
90 | }
91 |
--------------------------------------------------------------------------------
/day8/README.md:
--------------------------------------------------------------------------------
1 | # Day 8:
2 |
3 | - Install `mariadb` database driver for Go:
4 | - Open terminal or CMD inside a project
5 | - Run `go get -u github.com/go-sql-driver/mysql`
6 | - Make a databse connection like in the file `db/db.go`
7 |
8 | - Using MariaDB CMD client (Windows)
9 | - Open start
10 | - Search for `MariaDB`
11 | - You'll find a program called `MySQL Client (MariaDB 10.9 (x64))`
12 | - Open it, enter your database password
13 | - Run your SQL queries
14 |
15 | - Using MariaDB CMD client (Linux & MacOS)
16 | - Open terminal
17 | - Run `mariadb -u root -p`
18 | - Enter your database password
19 | - Run your SQL queries
20 |
21 |
--------------------------------------------------------------------------------
/day8/db/db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "database/sql"
5 |
6 | _ "github.com/go-sql-driver/mysql"
7 | )
8 |
9 | var connectionInstance *sql.DB = nil
10 |
11 | func GetInstance() *sql.DB {
12 | if connectionInstance == nil {
13 | var err error
14 | // DB_USERNAME is root by default
15 | // DB_PASSWORD is what you've set when installing the database
16 | // DB_NAME is the database you've created using `CREATE DATABASE whateverName`
17 | // connectionInstance, err = sql.Open("mysql", "DB_USERNAME:DB_PASSWORD@tcp(localhost:3306)/DB_NAME?parseTime=True")
18 | connectionInstance, err = sql.Open("mysql", "root:1234@tcp(localhost:3306)/ttt?parseTime=True")
19 | if err != nil {
20 | panic(err)
21 | }
22 | }
23 | return connectionInstance
24 | }
25 |
--------------------------------------------------------------------------------
/day8/db/init.sql:
--------------------------------------------------------------------------------
1 | CREATE DATABASE ttt;
2 |
3 | CREATE TABLE IF NOT EXISTS ttt.players (
4 | id INTEGER AUTO_INCREMENT,
5 | username VARCHAR(60) UNIQUE NOT NULL,
6 | password VARCHAR(200),
7 | created_at TIMESTAMP,
8 | updated_at TIMESTAMP,
9 | PRIMARY KEY (id)
10 | );
--------------------------------------------------------------------------------
/day8/db/player_db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import "tictactoe/models"
4 |
5 | type PlayerDB struct{}
6 |
7 | func (p *PlayerDB) Create(player models.Player) (models.Player, error) {
8 | result, err := GetInstance().Exec(
9 | "INSERT INTO ttt.players (username, password, created_at) VALUES (?, ?, current_timestamp)",
10 | player.Username, player.Password)
11 | if err != nil {
12 | return models.Player{}, err
13 | }
14 |
15 | playerId, err := result.LastInsertId()
16 | if err != nil {
17 | return models.Player{}, err
18 | }
19 |
20 | player.Id = int(playerId)
21 |
22 | return player, nil
23 | }
24 |
25 | func (p *PlayerDB) Get(username string) (models.Player, error) {
26 | var player models.Player
27 | err := GetInstance().
28 | QueryRow("SELECT id, username, password FROM ttt.players WHERE username=?", username).
29 | Scan(&player.Id, &player.Username, &player.Password)
30 |
31 | if err != nil {
32 | return models.Player{}, err
33 | }
34 |
35 | return player, nil
36 | }
37 |
38 | func (p *PlayerDB) Update(username string, player models.Player) error {
39 | _, err := GetInstance().Exec("UPDATE ttt.players SET username=?, password=? WHERE username=?", player.Username, player.Password, username)
40 | if err != nil {
41 | return err
42 | }
43 | return nil
44 | }
45 |
46 | func (p *PlayerDB) Delete(username string) error {
47 | _, err := GetInstance().Exec("DELETE FROM ttt.players WHERE username=?", username)
48 | if err != nil {
49 | return err
50 | }
51 | return nil
52 | }
--------------------------------------------------------------------------------
/day8/go.mod:
--------------------------------------------------------------------------------
1 | module tictactoe
2 |
3 | go 1.20
4 |
5 | require github.com/go-sql-driver/mysql v1.7.0 // indirect
6 |
--------------------------------------------------------------------------------
/day8/go.sum:
--------------------------------------------------------------------------------
1 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
2 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
3 |
--------------------------------------------------------------------------------
/day8/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "tictactoe/db"
5 | )
6 |
7 | func main() {
8 | playerDB := new(db.PlayerDB)
9 | err := playerDB.Delete("Laith")
10 | if err != nil {
11 | panic(err)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/day8/models/player.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "time"
4 |
5 | type Player struct {
6 | Id int
7 | Username string
8 | Password string
9 | CreatedAt time.Time
10 | UpdatedAt time.Time
11 | }
12 |
--------------------------------------------------------------------------------
/day9/README.md:
--------------------------------------------------------------------------------
1 | # Day 9:
2 |
3 | - Install `bcrypt` hash for Go:
4 | - Open terminal or CMD inside a project
5 | - Run `go get -u golang.org/x/crypto/bcrypt`
6 | - Check `controllers/login_api.go` for more details about the hash
7 |
--------------------------------------------------------------------------------
/day9/controllers/login_api.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 | "tictactoe/db"
7 | "tictactoe/models"
8 |
9 | "golang.org/x/crypto/bcrypt"
10 | )
11 |
12 | type LoginAPI struct {
13 | playerDB *db.PlayerDB
14 | }
15 |
16 | func NewLoginAPI(playerDB *db.PlayerDB) *LoginAPI {
17 | return &LoginAPI{
18 | playerDB: playerDB,
19 | }
20 | }
21 |
22 | type reqBody struct {
23 | Username string `json:"username"`
24 | Password string `json:"password"`
25 | }
26 |
27 | func (l *LoginAPI) HandleSignup(resp http.ResponseWriter, req *http.Request) {
28 | var requestBody reqBody
29 |
30 | err := json.NewDecoder(req.Body).Decode(&requestBody)
31 | if err != nil {
32 | resp.WriteHeader(http.StatusBadRequest)
33 | return
34 | }
35 |
36 | hashed, err := bcrypt.GenerateFromPassword([]byte(requestBody.Password), bcrypt.MinCost)
37 | if err != nil {
38 | resp.WriteHeader(http.StatusInternalServerError)
39 | return
40 | }
41 |
42 | requestBody.Password = string(hashed)
43 |
44 | player, err := l.playerDB.Create(models.Player{
45 | Username: requestBody.Username,
46 | Password: requestBody.Password,
47 | })
48 | if err != nil {
49 | resp.WriteHeader(http.StatusInternalServerError)
50 | return
51 | }
52 |
53 | resp.Header().Set("Content-Type", "application/json")
54 | json.NewEncoder(resp).Encode(player)
55 | }
56 |
57 | func (l *LoginAPI) HandleLogin(resp http.ResponseWriter, req *http.Request) {
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/day9/db/db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "database/sql"
5 |
6 | _ "github.com/go-sql-driver/mysql"
7 | )
8 |
9 | var connectionInstance *sql.DB = nil
10 |
11 | func GetInstance() *sql.DB {
12 | if connectionInstance == nil {
13 | var err error
14 | // DB_USERNAME is root by default
15 | // DB_PASSWORD is what you've set when installing the database
16 | // DB_NAME is the database you've created using `CREATE DATABASE whateverName`
17 | // connectionInstance, err = sql.Open("mysql", "DB_USERNAME:DB_PASSWORD@tcp(localhost:3306)/DB_NAME?parseTime=True")
18 | connectionInstance, err = sql.Open("mysql", "root:1234@tcp(localhost:3306)/ttt?parseTime=True")
19 | if err != nil {
20 | panic(err)
21 | }
22 | }
23 | return connectionInstance
24 | }
25 |
--------------------------------------------------------------------------------
/day9/db/init.sql:
--------------------------------------------------------------------------------
1 | CREATE DATABASE ttt;
2 |
3 | CREATE TABLE IF NOT EXISTS ttt.players (
4 | id INTEGER AUTO_INCREMENT,
5 | username VARCHAR(60) UNIQUE NOT NULL,
6 | password VARCHAR(200),
7 | created_at TIMESTAMP,
8 | updated_at TIMESTAMP,
9 | PRIMARY KEY (id)
10 | );
11 |
12 | CREATE TABLE IF NOT EXISTS ttt.sessions (
13 | id INTEGER AUTO_INCREMENT,
14 | username VARCHAR(60),
15 | token VARCHAR(32),
16 | PRIMARY KEY (id)
17 | );
--------------------------------------------------------------------------------
/day9/db/player_db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import "tictactoe/models"
4 |
5 | type PlayerDB struct{}
6 |
7 | func (p *PlayerDB) Create(player models.Player) (models.Player, error) {
8 | result, err := GetInstance().Exec(
9 | "INSERT INTO ttt.players (username, password, created_at) VALUES (?, ?, current_timestamp)",
10 | player.Username, player.Password)
11 | if err != nil {
12 | return models.Player{}, err
13 | }
14 |
15 | playerId, err := result.LastInsertId()
16 | if err != nil {
17 | return models.Player{}, err
18 | }
19 |
20 | player.Id = int(playerId)
21 |
22 | return player, nil
23 | }
24 |
25 | func (p *PlayerDB) Get(username string) (models.Player, error) {
26 | var player models.Player
27 | err := GetInstance().
28 | QueryRow("SELECT id, username, password FROM ttt.players WHERE username=?", username).
29 | Scan(&player.Id, &player.Username, &player.Password)
30 |
31 | if err != nil {
32 | return models.Player{}, err
33 | }
34 |
35 | return player, nil
36 | }
37 |
38 | func (p *PlayerDB) Update(username string, player models.Player) error {
39 | _, err := GetInstance().Exec("UPDATE ttt.players SET username=?, password=? WHERE username=?", player.Username, player.Password, username)
40 | if err != nil {
41 | return err
42 | }
43 | return nil
44 | }
45 |
46 | func (p *PlayerDB) Delete(username string) error {
47 | _, err := GetInstance().Exec("DELETE FROM ttt.players WHERE username=?", username)
48 | if err != nil {
49 | return err
50 | }
51 | return nil
52 | }
--------------------------------------------------------------------------------
/day9/go.mod:
--------------------------------------------------------------------------------
1 | module tictactoe
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/go-sql-driver/mysql v1.7.0 // indirect
7 | golang.org/x/crypto v0.6.0 // indirect
8 | )
9 |
--------------------------------------------------------------------------------
/day9/go.sum:
--------------------------------------------------------------------------------
1 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
2 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
3 | golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
4 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
5 |
--------------------------------------------------------------------------------
/day9/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "tictactoe/router"
5 | )
6 |
7 | func main() {
8 | router.StartServer()
9 | }
10 |
--------------------------------------------------------------------------------
/day9/models/player.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "time"
4 |
5 | type Player struct {
6 | Id int `json:"id"`
7 | Username string `json:"username"`
8 | Password string `json:"password"`
9 | CreatedAt time.Time `json:"-"`
10 | UpdatedAt time.Time `json:"-"`
11 | }
12 |
--------------------------------------------------------------------------------
/day9/models/session.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type Session struct {
4 | Username string `json:"-"`
5 | Token string `json:"-"`
6 | }
--------------------------------------------------------------------------------
/day9/router/router.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "net/http"
5 | "tictactoe/controllers"
6 | "tictactoe/db"
7 | )
8 |
9 | func StartServer() {
10 | playerDB := new(db.PlayerDB)
11 |
12 | loginAPI := controllers.NewLoginAPI(playerDB)
13 |
14 | router := http.NewServeMux()
15 | router.HandleFunc("/player/login", loginAPI.HandleLogin)
16 | router.HandleFunc("/player/signup", loginAPI.HandleSignup)
17 |
18 | http.ListenAndServe(":8080", router)
19 | }
20 |
--------------------------------------------------------------------------------