diff --git a/exercise1/problem1/main.go b/exercise1/problem1/main.go index dfca465c..0aa86e4c 100644 --- a/exercise1/problem1/main.go +++ b/exercise1/problem1/main.go @@ -1,3 +1,18 @@ package main -func addUp() {} +import "fmt" + +func addUp(n int) int { + sum := 0 + for i := 1; i <= n; i++ { + sum += i + } + return sum +} + +func main() { + fmt.Println(addUp(4)) // 10 + fmt.Println(addUp(13)) // 91 + fmt.Println(addUp(600)) // 180300 +} + diff --git a/exercise1/problem10/main.go b/exercise1/problem10/main.go index 04ec3430..ca2b9416 100644 --- a/exercise1/problem10/main.go +++ b/exercise1/problem10/main.go @@ -1,3 +1,20 @@ package main -func sum() {} +import ( + "fmt" + "strconv" +) + +func sum(a string, b string) (string, error) { + num1, err1 := strconv.Atoi(a) + if err1 != nil { + return "", fmt.Errorf("string: %s cannot be converted", a) + } + + num2, err2 := strconv.Atoi(b) + if err2 != nil { + return "", fmt.Errorf("string: %s cannot be converted", b) + } + result := num1 + num2 + return strconv.Itoa(result), nil +} diff --git a/exercise1/problem2/main.go b/exercise1/problem2/main.go index 2ca540b8..1967046c 100644 --- a/exercise1/problem2/main.go +++ b/exercise1/problem2/main.go @@ -1,3 +1,9 @@ package main -func binary() {} +import ( + "strconv" +) + +func binary(num int) string { + return strconv.FormatInt(int64(num), 2) +} diff --git a/exercise1/problem3/main.go b/exercise1/problem3/main.go index d346641a..c7d52ff0 100644 --- a/exercise1/problem3/main.go +++ b/exercise1/problem3/main.go @@ -1,3 +1,9 @@ package main -func numberSquares() {} +func numberSquares(n int) int { + sum := 0 + for i := 1; i <= n; i++ { + sum += i * i + } + return sum +} diff --git a/exercise1/problem4/main.go b/exercise1/problem4/main.go index 74af9044..fc1b3319 100644 --- a/exercise1/problem4/main.go +++ b/exercise1/problem4/main.go @@ -1,3 +1,15 @@ package main -func detectWord() {} +import ( + "unicode" +) + +func detectWord(crowd string) string { + var result string + for _, char := range crowd { + if unicode.IsLower(char) { + result += string(char) + } + } + return result +} diff --git a/exercise1/problem5/main.go b/exercise1/problem5/main.go index c5a804c9..918e5b22 100644 --- a/exercise1/problem5/main.go +++ b/exercise1/problem5/main.go @@ -1,3 +1,7 @@ package main -func potatoes() {} +import "strings" + +func potatoes(str string) int { + return strings.Count(str, "potato") +} diff --git a/exercise1/problem6/main.go b/exercise1/problem6/main.go index 06043890..a161f8c8 100644 --- a/exercise1/problem6/main.go +++ b/exercise1/problem6/main.go @@ -1,3 +1,22 @@ package main -func emojify() {} +import ( + "strings" +) + +func emojify(str string) string { + if strings.Contains(str, "smile") { + str = strings.ReplaceAll(str, "smile", "🙂") + } + if strings.Contains(str, "grin") { + str = strings.ReplaceAll(str, "grin", "😀") + } + if strings.Contains(str, "sad") { + str = strings.ReplaceAll(str, "sad", "😥") + } + if strings.Contains(str, "mad") { + str = strings.ReplaceAll(str, "mad", "😠") + + } + return str +} diff --git a/exercise1/problem7/main.go b/exercise1/problem7/main.go index 57c99b5c..1564e4aa 100644 --- a/exercise1/problem7/main.go +++ b/exercise1/problem7/main.go @@ -1,3 +1,16 @@ package main -func highestDigit() {} +import "strconv" + +func highestDigit(x int) int { + ans := '0' + s := strconv.Itoa(x) + for _, c := range s { + if c > ans { + ans = c + } + } + maxDigitInt, _ := strconv.Atoi(string(ans)) + + return maxDigitInt +} diff --git a/exercise1/problem8/main.go b/exercise1/problem8/main.go index 97fa0dae..e3f4e14c 100644 --- a/exercise1/problem8/main.go +++ b/exercise1/problem8/main.go @@ -1,3 +1,14 @@ package main -func countVowels() {} +import "strings" + +func countVowels(s string) int { + count := 0 + vowels := "aeiou" + for _, c := range s { + if strings.Contains(vowels, string(c)) { + count++ + } + } + return count +} diff --git a/exercise1/problem9/main.go b/exercise1/problem9/main.go index e8c84a54..12df71bc 100644 --- a/exercise1/problem9/main.go +++ b/exercise1/problem9/main.go @@ -1,7 +1,13 @@ package main -func bitwiseAND() {} +func bitwiseAND(x int, y int) int { + return x & y +} -func bitwiseOR() {} +func bitwiseOR(x int, y int) int { + return x | y +} -func bitwiseXOR() {} +func bitwiseXOR(x int, y int) int { + return x ^ y +} diff --git a/exercise2/problem1/problem1.go b/exercise2/problem1/problem1.go index 4763006c..a8a121f8 100644 --- a/exercise2/problem1/problem1.go +++ b/exercise2/problem1/problem1.go @@ -1,4 +1,13 @@ package problem1 -func isChangeEnough() { +func isChangeEnough(changes [4]int, total float32) bool { + + quarter := float32(changes[0]) * 0.25 + dime := float32(changes[1]) * 0.10 + nickel := float32(changes[2]) * 0.05 + penny := float32(changes[3]) * 0.01 + + ChangeEnough := quarter + dime + nickel + penny + + return ChangeEnough >= total } diff --git a/exercise2/problem10/problem10.go b/exercise2/problem10/problem10.go index 7142a022..4c51b8b3 100644 --- a/exercise2/problem10/problem10.go +++ b/exercise2/problem10/problem10.go @@ -1,3 +1,23 @@ package problem10 -func factory() {} +import "sync" + +func factory() (map[string]int, func(string) func(int)) { + brands := make(map[string]int) + var mu sync.Mutex + + makeBrand := func(brand string) func(int) { + mu.Lock() + if _, exists := brands[brand]; !exists { + brands[brand] = 0 + } + mu.Unlock() + return func(increment int) { + mu.Lock() + brands[brand] += increment + mu.Unlock() + } + } + + return brands, makeBrand +} diff --git a/exercise2/problem11/problem11.go b/exercise2/problem11/problem11.go index 33988711..b0127207 100644 --- a/exercise2/problem11/problem11.go +++ b/exercise2/problem11/problem11.go @@ -1,3 +1,14 @@ package problem11 -func removeDups() {} +func removeDups[T comparable](inp []T) []T { + seen := make(map[T]struct{}) + var result []T + + for _, v := range inp { + if _, exists := seen[v]; !exists { + seen[v] = struct{}{} + result = append(result, v) + } + } + return result +} diff --git a/exercise2/problem12/problem12.go b/exercise2/problem12/problem12.go index 4c1ae327..c0818e8a 100644 --- a/exercise2/problem12/problem12.go +++ b/exercise2/problem12/problem12.go @@ -1,3 +1,12 @@ package problem11 -func keysAndValues() {} +func keysAndValues[K comparable, V any](m map[K]V) ([]K, []V) { + keys := make([]K, 0, len(m)) + values := make([]V, 0, len(m)) + + for k, v := range m { + keys = append(keys, k) + values = append(values, v) + } + return keys, values +} diff --git a/exercise2/problem2/problem2.go b/exercise2/problem2/problem2.go index fdb199f0..2ca3c1e3 100644 --- a/exercise2/problem2/problem2.go +++ b/exercise2/problem2/problem2.go @@ -1,4 +1,17 @@ package problem2 -func capitalize() { +import ( + "strings" +) + +func capitalize(names []string) []string { + Upper := make([]string, len(names)) + for i, name := range names { + if len(name) > 0 { + Upper[i] = strings.ToUpper(string(name[0])) + strings.ToLower(name[1:]) + } else { + Upper[i] = name + } + } + return Upper } diff --git a/exercise2/problem3/problem3.go b/exercise2/problem3/problem3.go index f183fafb..a235ed6c 100644 --- a/exercise2/problem3/problem3.go +++ b/exercise2/problem3/problem3.go @@ -9,5 +9,24 @@ const ( lr dir = "lr" ) -func diagonalize() { +func diagonalize(n int, d dir) [][]int { + matrix := make([][]int, n) + for i := range matrix { + matrix[i] = make([]int, n) + } + for i := 0; i < n; i++ { + for j := 0; j < n; j++ { + switch d { + case ul: + matrix[i][j] = i + j + case ur: + matrix[i][j] = i + (n - 1 - j) + case ll: + matrix[i][j] = (n - 1 - i) + j + case lr: + matrix[i][j] = (n - 1 - i) + (n - 1 - j) + } + } + } + return matrix } diff --git a/exercise2/problem4/problem4.go b/exercise2/problem4/problem4.go index 1f680a4d..d9d6c7b1 100644 --- a/exercise2/problem4/problem4.go +++ b/exercise2/problem4/problem4.go @@ -1,4 +1,14 @@ package problem4 -func mapping() { +import ( + "strings" +) + +func mapping(inp []string) map[string]string { + m := make(map[string]string) + + for _, letter := range inp { + m[letter] = strings.ToUpper(letter) + } + return m } diff --git a/exercise2/problem5/problem5.go b/exercise2/problem5/problem5.go index 43fb96a4..c032e7d6 100644 --- a/exercise2/problem5/problem5.go +++ b/exercise2/problem5/problem5.go @@ -1,4 +1,20 @@ package problem5 -func products() { +import "sort" + +func products(catalog map[string]int, minPrice int) []string { + var result []string + for product, price := range catalog { + if price >= minPrice { + result = append(result, product) + } + } + + sort.Slice(result, func(i, j int) bool { + if catalog[result[i]] == catalog[result[j]] { + return result[i] < result[j] + } + return catalog[result[i]] > catalog[result[j]] + }) + return result } diff --git a/exercise2/problem6/problem6.go b/exercise2/problem6/problem6.go index 89fc5bfe..4d0f421d 100644 --- a/exercise2/problem6/problem6.go +++ b/exercise2/problem6/problem6.go @@ -1,4 +1,14 @@ package problem6 -func sumOfTwo() { +func sumOfTwo(a []int, b []int, sum int) bool { + complements := make(map[int]bool) + for _, numA := range a { + complements[sum-numA] = true + } + for _, numB := range b { + if complements[numB] { + return true + } + } + return false } diff --git a/exercise2/problem7/problem7.go b/exercise2/problem7/problem7.go index 32514209..79f51068 100644 --- a/exercise2/problem7/problem7.go +++ b/exercise2/problem7/problem7.go @@ -1,4 +1,5 @@ package problem7 -func swap() { +func swap(a, b *int) { + *a, *b = *b, *a } diff --git a/exercise2/problem8/problem8.go b/exercise2/problem8/problem8.go index 9389d3b0..481e8a21 100644 --- a/exercise2/problem8/problem8.go +++ b/exercise2/problem8/problem8.go @@ -1,16 +1,14 @@ package problem8 func simplify(list []string) map[string]int { - var indMap map[string]int - - indMap = make(map[string]int) - load(&indMap, &list) + indMap := make(map[string]int) + load(&indMap, list) return indMap } -func load(m *map[string]int, students *[]string) { - for i, name := range *students { +func load(m *map[string]int, students []string) { + for i, name := range students { (*m)[name] = i } } diff --git a/exercise2/problem9/problem9.go b/exercise2/problem9/problem9.go index fc96d21a..6f8a6487 100644 --- a/exercise2/problem9/problem9.go +++ b/exercise2/problem9/problem9.go @@ -1,3 +1,12 @@ package problem9 -func factory() {} +func factory(multiple int) func(...int) []int { + return func(numbers ...int) []int { + result := make([]int, len(numbers)) + + for i, num := range numbers { + result[i] = num * multiple + } + return result + } +} diff --git a/exercise3/problem1/problem1.go b/exercise3/problem1/problem1.go index d45605c6..2a626579 100644 --- a/exercise3/problem1/problem1.go +++ b/exercise3/problem1/problem1.go @@ -1,3 +1,37 @@ package problem1 -type Queue struct{} +import ( + "errors" +) + +type Queue struct { + elements []any +} + +func (q *Queue) Enqueue(element any) { + q.elements = append(q.elements, element) +} + +func (q *Queue) Dequeue() (any, error) { + if q.IsEmpty() { + return nil, errors.New("queue is empty") + } + element := q.elements[0] + q.elements = q.elements[1:] + return element, nil +} + +func (q *Queue) Peek() (any, error) { + if q.IsEmpty() { + return nil, errors.New("queue is empty") + } + return q.elements[0], nil +} + +func (q *Queue) Size() int { + return len(q.elements) +} + +func (q *Queue) IsEmpty() bool { + return len(q.elements) == 0 +} diff --git a/exercise3/problem2/problem2.go b/exercise3/problem2/problem2.go index e9059889..a571abd3 100644 --- a/exercise3/problem2/problem2.go +++ b/exercise3/problem2/problem2.go @@ -1,3 +1,37 @@ package problem2 -type Stack struct{} +import ( + "errors" +) + +type Stack struct { + elements []interface{} +} + +func (s *Stack) Push(element interface{}) { + s.elements = append(s.elements, element) +} + +func (s *Stack) Pop() (interface{}, error) { + if len(s.elements) == 0 { + return nil, errors.New("Stack is empty") + } + element := s.elements[len(s.elements)-1] + s.elements = s.elements[:len(s.elements)-1] + return element, nil +} + +func (s *Stack) Peek() (interface{}, error) { + if len(s.elements) == 0 { + return nil, errors.New("Stack is empty") + } + return s.elements[len(s.elements)-1], nil +} + +func (s *Stack) Size() int { + return len(s.elements) +} + +func (s *Stack) IsEmpty() bool { + return len(s.elements) == 0 +} diff --git a/exercise3/problem3/problem3.go b/exercise3/problem3/problem3.go index d8d79ac0..b8cf724e 100644 --- a/exercise3/problem3/problem3.go +++ b/exercise3/problem3/problem3.go @@ -1,3 +1,96 @@ package problem3 -type Set struct{} +import ( + "errors" +) + +type Set struct { + Elements map[any]struct{} +} + +func NewSet() *Set { + var set Set + set.Elements = make(map[any]struct{}) + return &set +} + +func (s *Set) Add(elem any) { + s.Elements[elem] = struct{}{} +} + +func (s *Set) Remove(elem any) error { + if _, exists := s.Elements[elem]; !exists { + return errors.New("Stack is empty") + } + delete(s.Elements, elem) + return nil +} + +func (s *Set) IsEmpty() bool { + return len(s.Elements) == 0 +} +func (s *Set) Size() int { + return len(s.Elements) +} +func (s *Set) List() []any { + list := make([]any, 0, len(s.Elements)) + for elem := range s.Elements { + list = append(list, elem) + } + return list +} +func (s *Set) Has(elem any) bool { + _, exists := s.Elements[elem] + return exists +} + +func (s *Set) Copy() *Set { + newSet := NewSet() + for elem := range s.Elements { + newSet.Elements[elem] = struct{}{} + } + return newSet +} +func (s *Set) Difference(other *Set) *Set { + difference := NewSet() + for elem := range s.Elements { + if !other.Has(elem) { + difference.Add(elem) + } + } + return difference +} + +func (s *Set) IsSubset(other *Set) bool { + if s.Size() > other.Size() { + return false + } + return s.Difference(other).Size() == 0 + +} + +func Union(sets ...*Set) *Set { + unionSet := NewSet() + + for _, set := range sets { + for elem := range set.Elements { + unionSet.Add(elem) + } + } + return unionSet +} + +func Intersect(sets ...*Set) *Set { + if len(sets) == 0 { + return NewSet() + } + intersection := sets[0].Copy() + for _, set := range sets[1:] { + for elem := range intersection.Elements { + if !set.Has(elem) { + intersection.Remove(elem) + } + } + } + return intersection +} diff --git a/exercise3/problem4/problem4.go b/exercise3/problem4/problem4.go index ebf78147..c5b98fdc 100644 --- a/exercise3/problem4/problem4.go +++ b/exercise3/problem4/problem4.go @@ -1,3 +1,94 @@ package problem4 -type LinkedList struct{} +import "fmt" + +type Element[T comparable] struct { + value T + next *Element[T] +} + +type LinkedList[T comparable] struct { + head *Element[T] + tail *Element[T] + size int +} + +func (ll *LinkedList[T]) Add(el *Element[T]) { + if ll.head == nil { + ll.head = el + ll.tail = el + } else { + ll.tail.next = el + ll.tail = el + } + ll.size++ +} + +func (ll *LinkedList[T]) Insert(el *Element[T], index int) error { + if index < 0 || index > ll.size { + return fmt.Errorf("index out of range") + } + if index == 0 { + el.next = ll.head + ll.head = el + } else { + current := ll.head + for i := 0; i < index-1; i++ { + current = current.next + } + el.next = current.next + current.next = el + } + ll.size++ + return nil +} + +func (ll *LinkedList[T]) Delete(el *Element[T]) error { + if ll.head == nil { + return fmt.Errorf("list is empty") + } + if ll.head.value == el.value { + ll.head = ll.head.next + ll.size-- + return nil + } + current := ll.head + for current.next != nil { + if current.next.value == el.value { + current.next = current.next.next + ll.size-- + return nil + } + current = current.next + } + return fmt.Errorf("element not found") +} + +func (ll *LinkedList[T]) Find(value T) (*Element[T], error) { + current := ll.head + for current != nil { + if current.value == value { + return current, nil + } + current = current.next + } + return nil, fmt.Errorf("element not found") +} + +func (ll *LinkedList[T]) List() []T { + result := make([]T, ll.size) + current := ll.head + for i := 0; i < ll.size; i++ { + result[i] = current.value + current = current.next + } + return result +} + +func (ll *LinkedList[T]) IsEmpty() bool { + return ll.size == 0 +} + +func (ll *LinkedList[T]) Size() int { + return ll.size +} diff --git a/exercise3/problem5/problem5.go b/exercise3/problem5/problem5.go index 4177599f..094e6b80 100644 --- a/exercise3/problem5/problem5.go +++ b/exercise3/problem5/problem5.go @@ -1,3 +1,16 @@ package problem5 -type Person struct{} +type Person struct { + name string + age int +} + +func (p *Person) compareAge(other *Person) string { + if p.age < other.age { + return other.name + " is older than me." + } else if p.age > other.age { + return other.name + " is younger than me." + } else { + return other.name + " is the same age as me." + } +} diff --git a/exercise3/problem6/problem6.go b/exercise3/problem6/problem6.go index 4e8d1af8..be6b6f68 100644 --- a/exercise3/problem6/problem6.go +++ b/exercise3/problem6/problem6.go @@ -1,7 +1,24 @@ package problem6 -type Animal struct{} +type Animal struct { + name string + legsNum int +} -type Insect struct{} +type Insect struct { + name string + legsNum int +} -func sumOfAllLegsNum() {} +func sumOfAllLegsNum(animals ...interface{}) int { + totalLegsNum := 0 + for _, animal := range animals { + switch a := animal.(type) { + case *Animal: + totalLegsNum += a.legsNum + case *Insect: + totalLegsNum += a.legsNum + } + } + return totalLegsNum +} diff --git a/exercise3/problem7/problem7.go b/exercise3/problem7/problem7.go index 26887151..fda2d5f8 100644 --- a/exercise3/problem7/problem7.go +++ b/exercise3/problem7/problem7.go @@ -1,10 +1,54 @@ package problem7 +type Account interface { + Withdraw(amount int) + SendPackage(to string) +} type BankAccount struct { + name string + balance int +} + +func (a *BankAccount) Withdraw(amount int) { + a.balance -= amount +} +func (a *BankAccount) SendPackage(to string) { + } type FedexAccount struct { + name string + packages []string +} + +func (f *FedexAccount) Withdraw(amount int) { + +} +func (f *FedexAccount) SendPackage(to string) { + f.packages = append(f.packages, f.name+" send package to "+to) } type KazPostAccount struct { + name string + balance int + packages []string +} + +func (k *KazPostAccount) Withdraw(amount int) { + k.balance -= amount +} +func (k *KazPostAccount) SendPackage(to string) { + k.packages = append(k.packages, k.name+" send package to "+to) +} + +func withdrawMoney(amount int, accounts ...Account) { + for _, account := range accounts { + account.Withdraw(amount) + } +} + +func sendPackagesTo(to string, accounts ...Account) { + for _, account := range accounts { + account.SendPackage(to) + } } diff --git a/exercise4/bot/main.go b/exercise4/bot/main.go index 64f9e0a3..9f980203 100644 --- a/exercise4/bot/main.go +++ b/exercise4/bot/main.go @@ -2,20 +2,105 @@ package main import ( "context" + "encoding/json" + "fmt" + "log" + "net/http" "os" "os/signal" - "syscall" + "time" +) + +type GameState struct { + Board [9]string `json:"board"` + Current string `json:"current"` +} + +var ( + port = os.Getenv("PORT") + url string ) func main() { - ctx := context.Background() + if port == "" { + log.Fatal("PORT environment variable is required") + } + + url = fmt.Sprintf("http://localhost:%s", port) - ready := startServer() - <-ready + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() - // TODO after server start + go func() { + if err := startPing(); err != nil { + log.Fatalf("Failed to ping judge: %v", err) + } + }() stop := make(chan os.Signal, 1) - signal.Notify(stop, os.Interrupt, syscall.SIGTERM) - <-stop // Wait for SIGINT or SIGTERM + signal.Notify(stop, os.Interrupt) + + <-stop // Wait for interrupt signal to stop the bot +} + +func startPing() error { + for { + resp, err := http.Get(url + "/ping") + if err != nil { + return fmt.Errorf("ping error: %w", err) + } + if resp.StatusCode == http.StatusOK { + fmt.Println("Successfully connected to the judge.") + break + } + resp.Body.Close() + time.Sleep(1 * time.Second) // Retry every second + } + + return handleGame() +} + +func handleGame() error { + for { + resp, err := http.Get(url + "/game") + if err != nil { + return fmt.Errorf("failed to get game state: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + var state GameState + if err := json.NewDecoder(resp.Body).Decode(&state); err != nil { + return fmt.Errorf("failed to decode game state: %w", err) + } + + if state.Current == "" { + // Game has ended + fmt.Println("Game ended.") + return nil + } + + move, err := makeMove(state) + if err != nil { + return fmt.Errorf("failed to make move: %w", err) + } + + _, err = http.Post(url+"/move", "application/json", move) + if err != nil { + return fmt.Errorf("failed to send move: %w", err) + } + } + + time.Sleep(1 * time.Second) // Poll every second + } +} + +func makeMove(state GameState) (json.RawMessage, error) { + // Implement your logic for making a move based on the current board state + for i, cell := range state.Board { + if cell == "" { + return json.Marshal(map[string]int{"move": i}) + } + } + return nil, fmt.Errorf("no valid moves available") } diff --git a/exercise4/bot/server.go b/exercise4/bot/server.go index e6760ec5..2ee6730a 100644 --- a/exercise4/bot/server.go +++ b/exercise4/bot/server.go @@ -32,8 +32,12 @@ func startServer() <-chan struct{} { list := &readyListener{Listener: listener, ready: ready} srv := &http.Server{ IdleTimeout: 2 * time.Minute, + Handler: http.NewServeMux(), } - + mux := srv.Handler.(*http.ServeMux) + mux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "pong") + }) go func() { err := srv.Serve(list) if !errors.Is(err, http.ErrServerClosed) { diff --git a/exercise5/problem1/problem1.go b/exercise5/problem1/problem1.go index 4f514fab..5ceb3f21 100644 --- a/exercise5/problem1/problem1.go +++ b/exercise5/problem1/problem1.go @@ -1,9 +1,14 @@ package problem1 +import "sync" + func incrementConcurrently(num int) int { + var wg sync.WaitGroup + wg.Add(1) go func() { + defer wg.Done() num++ }() - + wg.Wait() return num } diff --git a/exercise5/problem2/problem2.go b/exercise5/problem2/problem2.go index 16d38e1d..523aab13 100644 --- a/exercise5/problem2/problem2.go +++ b/exercise5/problem2/problem2.go @@ -1,5 +1,10 @@ package problem2 +import ( + "runtime" + "sync" +) + // add - sequential code to add numbers, don't update it, just to illustrate concept func add(numbers []int) int64 { var sum int64 @@ -11,6 +16,31 @@ func add(numbers []int) int64 { func addConcurrently(numbers []int) int64 { var sum int64 + numCores := runtime.NumCPU() + partSize := len(numbers) / numCores + var wg sync.WaitGroup + var mu sync.Mutex + + for i := 0; i < numCores; i++ { + wg.Add(1) + + start := i * partSize + end := start + partSize + if i == numCores-1 { + end = len(numbers) + } + go func(part []int) { + defer wg.Done() + var partSum int64 + for _, n := range part { + partSum += int64(n) + } + mu.Lock() + sum += partSum + mu.Unlock() + }(numbers[start:end]) + } + wg.Wait() return sum } diff --git a/exercise5/problem3/problem3.go b/exercise5/problem3/problem3.go index e085a51a..ec7d8a50 100644 --- a/exercise5/problem3/problem3.go +++ b/exercise5/problem3/problem3.go @@ -1,11 +1,11 @@ package problem3 func sum(a, b int) int { - var c int + c := make(chan int) go func(a, b int) { - c = a + b + c <- a + b }(a, b) - return c + return <-c } diff --git a/exercise5/problem4/problem4.go b/exercise5/problem4/problem4.go index b5899ddf..4418359a 100644 --- a/exercise5/problem4/problem4.go +++ b/exercise5/problem4/problem4.go @@ -4,6 +4,7 @@ func iter(ch chan<- int, nums []int) { for _, n := range nums { ch <- n } + close(ch) } func sum(nums []int) int { @@ -11,9 +12,10 @@ func sum(nums []int) int { go iter(ch, nums) - var sum int + var total int + for n := range ch { - sum += n + total += n } - return sum + return total } diff --git a/exercise5/problem5/problem5.go b/exercise5/problem5/problem5.go index ac192c58..3827c3c2 100644 --- a/exercise5/problem5/problem5.go +++ b/exercise5/problem5/problem5.go @@ -1,8 +1,23 @@ package problem5 -func producer() {} +import ( + "strings" +) -func consumer() {} +func producer(words []string, ch chan<- string) { + for _, word := range words { + ch <- word + } + close(ch) +} + +func consumer(ch <-chan string) string { + var result []string + for word := range ch { + result = append(result, word) + } + return strings.Join(result, " ") +} func send( words []string, diff --git a/exercise5/problem6/problem6.go b/exercise5/problem6/problem6.go index e1beea87..4ce14c48 100644 --- a/exercise5/problem6/problem6.go +++ b/exercise5/problem6/problem6.go @@ -2,8 +2,31 @@ package problem6 type pipe func(in <-chan int) <-chan int -var multiplyBy2 pipe = func() {} +var multiplyBy2 pipe = func(in <-chan int) <-chan int { + out := make(chan int) + go func() { + defer close(out) + for v := range in { + out <- v * 2 + } + }() + return out +} -var add5 pipe = func() {} +var add5 pipe = func(in <-chan int) <-chan int { + out := make(chan int) + go func() { + defer close(out) + for v := range in { + out <- v + 5 + } + }() + return out +} -func piper(in <-chan int, pipes []pipe) <-chan int {} +func piper(in <-chan int, pipes []pipe) <-chan int { + for _, p := range pipes { + in = p(in) + } + return in +} diff --git a/exercise5/problem7/problem7.go b/exercise5/problem7/problem7.go index c3c1d0c9..43cf19c1 100644 --- a/exercise5/problem7/problem7.go +++ b/exercise5/problem7/problem7.go @@ -1,3 +1,23 @@ package problem7 -func multiplex(ch1 <-chan string, ch2 <-chan string) []string {} +func multiplex(ch1 <-chan string, ch2 <-chan string) []string { + var result []string + + for ch1 != nil || ch2 != nil { + select { + case val, ok := <-ch1: + if ok { + result = append(result, val) + } else { + ch1 = nil + } + case val, ok := <-ch2: + if ok { + result = append(result, val) + } else { + ch2 = nil + } + } + } + return result +} diff --git a/exercise5/problem8/problem8.go b/exercise5/problem8/problem8.go index 3e951b3b..cde2d620 100644 --- a/exercise5/problem8/problem8.go +++ b/exercise5/problem8/problem8.go @@ -4,4 +4,11 @@ import ( "time" ) -func withTimeout(ch <-chan string, ttl time.Duration) string {} +func withTimeout(ch <-chan string, ttl time.Duration) string { + select { + case msg := <-ch: + return msg + case <-time.After(ttl): + return "timeout" + } +} diff --git a/exercise6/problem1/problem1.go b/exercise6/problem1/problem1.go index ee453b24..41cb56af 100644 --- a/exercise6/problem1/problem1.go +++ b/exercise6/problem1/problem1.go @@ -1,9 +1,34 @@ package problem1 +import "sync" + type bankAccount struct { blnc int + mu sync.Mutex } func newAccount(blnc int) *bankAccount { - return &bankAccount{blnc} + return &bankAccount{blnc: blnc} +} + +func (b *bankAccount) deposit(amount int) { + b.mu.Lock() + defer b.mu.Unlock() + b.blnc += amount +} + +func (b *bankAccount) withdraw(amount int) bool { + b.mu.Lock() + defer b.mu.Unlock() + if amount > b.blnc { + return false + } + b.blnc -= amount + return true +} + +func (b *bankAccount) balance() int { + b.mu.Lock() + defer b.mu.Unlock() + return b.blnc } diff --git a/exercise6/problem2/problem2.go b/exercise6/problem2/problem2.go index 97e02368..798d1ef3 100644 --- a/exercise6/problem2/problem2.go +++ b/exercise6/problem2/problem2.go @@ -1,6 +1,7 @@ package problem2 import ( + "sync" "time" ) @@ -8,13 +9,33 @@ var readDelay = 10 * time.Millisecond type bankAccount struct { blnc int + mu sync.Mutex } func newAccount(blnc int) *bankAccount { - return &bankAccount{blnc} + return &bankAccount{blnc: blnc} +} + +func (b *bankAccount) deposit(amount int) { + b.mu.Lock() + defer b.mu.Unlock() + b.blnc += amount +} + +func (b *bankAccount) withdraw(amount int) bool { + b.mu.Lock() + defer b.mu.Unlock() + if amount > b.blnc { + return false + } + b.blnc -= amount + return true } func (b *bankAccount) balance() int { + b.mu.Lock() + currentBalance := b.blnc + b.mu.Unlock() time.Sleep(readDelay) - return 0 + return currentBalance } diff --git a/exercise6/problem3/problem3.go b/exercise6/problem3/problem3.go index b34b90bb..acb946e2 100644 --- a/exercise6/problem3/problem3.go +++ b/exercise6/problem3/problem3.go @@ -1,5 +1,7 @@ package problem3 +import "sync/atomic" + type counter struct { val int64 } @@ -9,3 +11,15 @@ func newCounter() *counter { val: 0, } } + +func (c *counter) inc() { + atomic.AddInt64(&c.val, 1) +} + +func (c *counter) dec() { + atomic.AddInt64(&c.val, -1) +} + +func (c *counter) value() int64 { + return atomic.LoadInt64(&c.val) +} diff --git a/exercise6/problem4/problem4.go b/exercise6/problem4/problem4.go index 793449c9..1525f7eb 100644 --- a/exercise6/problem4/problem4.go +++ b/exercise6/problem4/problem4.go @@ -1,31 +1,54 @@ package problem4 import ( + "sync" "time" ) -func worker(id int, _ *[]string, ch chan<- int) { - // TODO wait for shopping list to be completed - ch <- id +func worker(id int, cond *sync.Cond, shoppingList *[]string, notified *int, done *bool, wg *sync.WaitGroup) { + defer wg.Done() + + cond.L.Lock() + for !*done { + cond.Wait() + } + if len(*shoppingList) > 0 && *notified == 0 { + *notified = id + } + cond.L.Unlock() } -func updateShopList(shoppingList *[]string) { +func updateShopList(shoppingList *[]string, cond *sync.Cond, done *bool) { time.Sleep(10 * time.Millisecond) - - *shoppingList = append(*shoppingList, "apples") - *shoppingList = append(*shoppingList, "milk") - *shoppingList = append(*shoppingList, "bake soda") + *shoppingList = append(*shoppingList, "apples", "milk", "baking soda") + cond.L.Lock() + *done = true + cond.Broadcast() + cond.L.Unlock() } func notifyOnShopListUpdate(shoppingList *[]string, numWorkers int) <-chan int { - notifier := make(chan int) - - for i := range numWorkers { - go worker(i+1, shoppingList, notifier) - time.Sleep(time.Millisecond) // order matters + notifier := make(chan int, 1) + var notified int + done := false + mutex := sync.Mutex{} + cond := sync.NewCond(&mutex) + var wg sync.WaitGroup + + for i := 0; i < numWorkers; i++ { + wg.Add(1) + go worker(i+1, cond, shoppingList, ¬ified, &done, &wg) + time.Sleep(time.Millisecond) } - go updateShopList(shoppingList) + go func() { + updateShopList(shoppingList, cond, &done) + wg.Wait() + if notified != 0 { + notifier <- notified + } + close(notifier) + }() return notifier } diff --git a/exercise6/problem5/problem5.go b/exercise6/problem5/problem5.go index 8e4a1703..5cb89b68 100644 --- a/exercise6/problem5/problem5.go +++ b/exercise6/problem5/problem5.go @@ -5,7 +5,7 @@ import ( ) func worker(id int, shoppingList *[]string, ch chan<- int) { - // TODO wait for shopping list to be completed + // Wait for the shopping list to be completed ch <- id } @@ -20,7 +20,8 @@ func updateShopList(shoppingList *[]string) { func notifyOnShopListUpdate(shoppingList *[]string, numWorkers int) <-chan int { notifier := make(chan int) - for i := range numWorkers { + // Iterate using numWorkers as the loop limit + for i := 0; i < numWorkers; i++ { go worker(i+1, shoppingList, notifier) time.Sleep(time.Millisecond) // order matters } diff --git a/exercise6/problem6/problem6.go b/exercise6/problem6/problem6.go index 0c1122b9..8263ea03 100644 --- a/exercise6/problem6/problem6.go +++ b/exercise6/problem6/problem6.go @@ -6,6 +6,7 @@ import ( func runTasks(init func()) { var wg sync.WaitGroup + var once sync.Once for range 10 { wg.Add(1) @@ -13,7 +14,7 @@ func runTasks(init func()) { defer wg.Done() //TODO: modify so that load function gets called only once. - init() + once.Do(init) }() } wg.Wait() diff --git a/exercise6/problem7/problem7.go b/exercise6/problem7/problem7.go index ef49497b..4a0a34f9 100644 --- a/exercise6/problem7/problem7.go +++ b/exercise6/problem7/problem7.go @@ -3,19 +3,22 @@ package problem7 import ( "fmt" "math/rand" + "sync" "time" ) func task() { start := time.Now() var t *time.Timer - t = time.AfterFunc( - randomDuration(), - func() { - fmt.Println(time.Now().Sub(start)) - t.Reset(randomDuration()) - }, - ) + var mu sync.Mutex + t = time.AfterFunc(randomDuration(), func() { + mu.Lock() + defer mu.Unlock() + + fmt.Println(time.Now().Sub(start)) + t.Reset(randomDuration()) + }) + time.Sleep(5 * time.Second) } diff --git a/exercise6/problem8/problem8.go b/exercise6/problem8/problem8.go index 949eb2d2..67573d47 100644 --- a/exercise6/problem8/problem8.go +++ b/exercise6/problem8/problem8.go @@ -1,3 +1,23 @@ package problem8 -func multiplex(chs []<-chan string) []string {} +func multiplex(chs []<-chan string) []string { + out := make([]string, 0) + done := make(chan struct{}) + var remaining = len(chs) + + for _, ch := range chs { + go func(c <-chan string) { + for msg := range c { + out = append(out, msg) + } + remaining-- + if remaining == 0 { + close(done) + } + }(ch) + } + + <-done + + return out +} diff --git a/exercise7/blogging-platform/go.mod b/exercise7/blogging-platform/go.mod index ca16e703..528508f6 100644 --- a/exercise7/blogging-platform/go.mod +++ b/exercise7/blogging-platform/go.mod @@ -3,3 +3,21 @@ module github.com/talgat-ruby/exercises-go/exercise7/blogging-platform go 1.23.3 require github.com/lib/pq v1.10.9 + +require ( + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/gofiber/fiber v1.14.6 // indirect + github.com/gofiber/fiber/v2 v2.52.6 // indirect + github.com/gofiber/utils v0.0.10 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/schema v1.1.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.51.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + golang.org/x/sys v0.28.0 // indirect +) diff --git a/exercise7/blogging-platform/go.sum b/exercise7/blogging-platform/go.sum index aeddeae3..b679ca93 100644 --- a/exercise7/blogging-platform/go.sum +++ b/exercise7/blogging-platform/go.sum @@ -1,2 +1,56 @@ +github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4= +github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/gofiber/fiber v1.14.6 h1:QRUPvPmr8ijQuGo1MgupHBn8E+wW0IKqiOvIZPtV70o= +github.com/gofiber/fiber v1.14.6/go.mod h1:Yw2ekF1YDPreO9V6TMYjynu94xRxZBdaa8X5HhHsjCM= +github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= +github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/gofiber/utils v0.0.10 h1:3Mr7X7JdCUo7CWf/i5sajSaDmArEDtti8bM1JUVso2U= +github.com/gofiber/utils v0.0.10/go.mod h1:9J5aHFUIjq0XfknT4+hdSMG6/jzfaAgCu4HEbWDeBlo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY= +github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= +github.com/klauspost/compress v1.10.7 h1:7rix8v8GpI3ZBb0nSozFRgbtXKv+hOe+qfEpZqybrAg= +github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.16.0 h1:9zAqOYLl8Tuy3E5R6ckzGDJ1g8+pw15oQp2iL9Jl6gQ= +github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA= +github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= +github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6JcmhJgXi5E4b8Wg84ihbmUKp/GvSPEzc= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/exercise7/blogging-platform/internal/api/api.go b/exercise7/blogging-platform/internal/api/api.go new file mode 100644 index 00000000..7d6eac59 --- /dev/null +++ b/exercise7/blogging-platform/internal/api/api.go @@ -0,0 +1,162 @@ +package api + +import ( + "context" + "net/http" + "strconv" + "strings" + "time" + + "github.com/gofiber/fiber/v2" + "github.com/talgat-ruby/exercises-go/exercise7/blogging-platform/internal/db" + "github.com/talgat-ruby/exercises-go/exercise7/blogging-platform/internal/models" +) + +type API struct { + app *fiber.App + db *db.DB +} + +func New() *API { + return &API{ + app: fiber.New(), + } +} + +func (a *API) Start(ctx context.Context) error { + a.routes() + return a.app.Listen(":8080") +} + +func (a *API) routes() { + a.app.Get("/", a.handleRoot) + a.app.Post("/posts", a.createPost) + a.app.Put("/posts/:id", a.updatePost) + a.app.Delete("/posts/:id", a.deletePost) + a.app.Get("/posts/:id", a.getPost) + a.app.Get("/posts", a.getAllPosts) +} +func (a *API) handleRoot(c *fiber.Ctx) error { + return c.SendString("Welcome to the Blogging Platform!") +} + +// CRUD Handlers +func (a *API) createPost(c *fiber.Ctx) error { + var post struct { + Title string `json:"title"` + Content string `json:"content"` + Category string `json:"category"` + Tags []string `json:"tags"` + } + + if err := c.BodyParser(&post); err != nil { + return c.Status(fiber.StatusBadRequest).SendString("Invalid data") + } + + // Вставка нового поста в базу данных + query := `INSERT INTO posts (title, content, category, tags) + VALUES ($1, $2, $3, $4) RETURNING id, created_at, updated_at` + var newPost struct { + ID int `json:"id"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + } + + err := a.db.QueryRow(query, post.Title, post.Content, post.Category, post.Tags).Scan(&newPost.ID, &newPost.CreatedAt, &newPost.UpdatedAt) + if err != nil { + return c.Status(fiber.StatusInternalServerError).SendString("Failed to create post") + } + + return c.Status(fiber.StatusCreated).JSON(newPost) +} + +func (a *API) updatePost(c *fiber.Ctx) error { + + id := c.Params("id") + var post struct { + Title string `json:"title"` + Content string `json:"content"` + Category string `json:"category"` + Tags []string `json:"tags"` + } + + if err := c.BodyParser(&post); err != nil { + return c.Status(fiber.StatusBadRequest).SendString("Invalid data") + } + + // Обновление поста в базе данных + query := `UPDATE posts + SET title = $1, content = $2, category = $3, tags = $4, updated_at = CURRENT_TIMESTAMP + WHERE id = $5` + _, err := a.db.Exec(query, post.Title, post.Content, post.Category, post.Tags, id) + if err != nil { + return c.Status(fiber.StatusInternalServerError).SendString("Failed to update post") + } + + return c.Status(fiber.StatusOK).SendString("Post updated successfully") +} + +func (a *API) deletePost(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "Invalid ID"}) + } + + if err := a.db.DeletePostByID(id); err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to delete post"}) + } + + return c.SendStatus(http.StatusNoContent) +} + +func (a *API) getPost(c *fiber.Ctx) error { + + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "Invalid ID"}) + } + + post := models.Post{ + ID: id, + Title: "Sample Post", + Content: "Sample Content", + Category: "Technology", + Tags: []string{"Sample", "Tech"}, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + return c.Status(http.StatusOK).JSON(post) +} + +func (a *API) getAllPosts(c *fiber.Ctx) error { + term := c.Query("term") + + posts := []models.Post{ + { + ID: 1, + Title: "Sample Post", + Content: "Sample Content", + Category: "Technology", + Tags: []string{"Sample", "Tech"}, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, + } + + if term != "" { + + term = strings.ToLower(term) + filtered := []models.Post{} + for _, post := range posts { + if strings.Contains(strings.ToLower(post.Title), term) || + strings.Contains(strings.ToLower(post.Content), term) || + strings.Contains(strings.ToLower(post.Category), term) { + filtered = append(filtered, post) + } + } + return c.Status(http.StatusOK).JSON(filtered) + } + + return c.Status(http.StatusOK).JSON(posts) +} diff --git a/exercise7/blogging-platform/internal/db/db.go b/exercise7/blogging-platform/internal/db/db.go new file mode 100644 index 00000000..bc57eeee --- /dev/null +++ b/exercise7/blogging-platform/internal/db/db.go @@ -0,0 +1,38 @@ +package db + +import ( + "database/sql" + "fmt" + "os" + + _ "github.com/lib/pq" +) + +// Создаем собственный тип DB, который будет оберткой для *sql.DB +type DB struct { + *sql.DB +} + +// Функция для создания нового подключения к базе данных с типом DB +func New() (*DB, error) { + connStr := fmt.Sprintf( + "host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", + os.Getenv("DB_HOST"), + os.Getenv("DB_PORT"), + os.Getenv("DB_USER"), + os.Getenv("DB_PASSWORD"), + os.Getenv("DB_NAME"), + ) + sqlDB, err := sql.Open("postgres", connStr) + if err != nil { + return nil, err + } + return &DB{sqlDB}, nil +} + +// Функция для удаления поста по ID +func (d *DB) DeletePostByID(id int) error { + // Логика удаления записи из базы данных по id + _, err := d.Exec("DELETE FROM posts WHERE id = $1", id) + return err +} diff --git a/exercise7/blogging-platform/internal/models/post.go b/exercise7/blogging-platform/internal/models/post.go new file mode 100644 index 00000000..3e567070 --- /dev/null +++ b/exercise7/blogging-platform/internal/models/post.go @@ -0,0 +1,13 @@ +package models + +import "time" + +type Post struct { + ID int `json:"id"` + Title string `json:"title"` + Content string `json:"content"` + Category string `json:"category"` + Tags []string `json:"tags"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/exercise7/blogging-platform/migrations/0001_create_posts_table.sql b/exercise7/blogging-platform/migrations/0001_create_posts_table.sql new file mode 100644 index 00000000..aa8d8203 --- /dev/null +++ b/exercise7/blogging-platform/migrations/0001_create_posts_table.sql @@ -0,0 +1,9 @@ +CREATE TABLE posts ( + id SERIAL PRIMARY KEY, + title VARCHAR(255) NOT NULL, + content TEXT NOT NULL, + category VARCHAR(100), + tags TEXT[], + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file