diff --git a/exercise1/problem1/main.go b/exercise1/problem1/main.go index dfca465c..f30038f0 100644 --- a/exercise1/problem1/main.go +++ b/exercise1/problem1/main.go @@ -1,3 +1,9 @@ package main -func addUp() {} +func addUp(num int) int { + out := 0 + for i := 1; i <= num; i++ { + out += i + } + return out +} diff --git a/exercise1/problem10/main.go b/exercise1/problem10/main.go index 04ec3430..fba5fb72 100644 --- a/exercise1/problem10/main.go +++ b/exercise1/problem10/main.go @@ -1,3 +1,52 @@ package main -func sum() {} +import ( + "errors" +) + +func sum(left, right string) (string, error) { + leftNum, err := stringToNumber(left) + if err != nil { + return "", err + } + rightNum, err := stringToNumber(right) + if err != nil { + return "", err + } + return numberToString(leftNum + rightNum), nil +} + +func numberToString(num int) string { + out := make([]rune, 0) + for num > 0 { + r := rune(num%10 + '0') + out = append([]rune{r}, out...) + num = num / 10 + } + return string(out) +} + +func stringToNumber(s string) (int, error) { + out := 0 + length := len(s) + for i, c := range s { + n := runeToNumber(c) + if n > 9 || n < 0 { + return 0, errors.New("NaN") + } + out += n * powerTen(length-i-1) + } + return out, nil +} + +func runeToNumber(r rune) int { + return int(r) - '0' +} + +func powerTen(pow int) int { + out := 1 + for range pow { + out *= 10 + } + return out +} diff --git a/exercise1/problem2/main.go b/exercise1/problem2/main.go index 2ca540b8..80cd52aa 100644 --- a/exercise1/problem2/main.go +++ b/exercise1/problem2/main.go @@ -1,3 +1,20 @@ package main -func binary() {} +func binary(num int) string { + if num == 0 { + return "0" + } + out := make([]rune, 0) + for i := 31; i >= 0; i-- { + diff := num - 1<= 'a' && l <= 'z' { + out = append(out, l) + } + } + return string(out) +} diff --git a/exercise1/problem5/main.go b/exercise1/problem5/main.go index c5a804c9..f3dc4424 100644 --- a/exercise1/problem5/main.go +++ b/exercise1/problem5/main.go @@ -1,3 +1,19 @@ package main -func potatoes() {} +func potatoes(word string) int { + buffer := []rune("potato") + count := 0 + bufferInd := 0 + for _, ch := range word { + if ch == buffer[bufferInd] { + bufferInd++ + if bufferInd == len(buffer) { + count++ + bufferInd = 0 + } + } else { + bufferInd = 0 + } + } + return count +} diff --git a/exercise1/problem6/main.go b/exercise1/problem6/main.go index 06043890..680b7d0f 100644 --- a/exercise1/problem6/main.go +++ b/exercise1/problem6/main.go @@ -1,3 +1,52 @@ package main -func emojify() {} +type wordStruct struct { + word []rune + emoji string +} + +func emojify(word string) string { + buffer := []wordStruct{ + {word: []rune("smile"), emoji: "πŸ™‚"}, + {word: []rune("grin"), emoji: "πŸ˜€"}, + {word: []rune("sad"), emoji: "πŸ˜₯"}, + {word: []rune("mad"), emoji: "😠"}, + } + for _, ws := range buffer { + word = replaceWord(word, &ws) + } + return word +} + +func replaceWord(str string, ws *wordStruct) string { + buffer := []rune(str) + wordInd := 0 + start := make([]int, 0) + tmp := -1 + for i, ch := range buffer { + if ch == ws.word[wordInd] { + if wordInd == 0 { + tmp = i + } + wordInd++ + if wordInd == len(ws.word) { + wordInd = 0 + start = append(start, tmp) + } + } else { + wordInd = 0 + } + } + + if len(start) == 0 { + return str + } + + out := make([]rune, 0) + for _, i := range start { + out = append(out, buffer[:i]...) + out = append(out, []rune(ws.emoji)...) + out = append(out, buffer[i+len(ws.word):]...) + } + return string(out) +} diff --git a/exercise1/problem7/main.go b/exercise1/problem7/main.go index 57c99b5c..2e9ea4c3 100644 --- a/exercise1/problem7/main.go +++ b/exercise1/problem7/main.go @@ -1,3 +1,13 @@ package main -func highestDigit() {} +func highestDigit(num int) int { + highest := 0 + for num > 0 { + rem := num % 10 + if rem > highest { + highest = rem + } + num = num / 10 + } + return highest +} diff --git a/exercise1/problem8/main.go b/exercise1/problem8/main.go index 97fa0dae..6828bdf4 100644 --- a/exercise1/problem8/main.go +++ b/exercise1/problem8/main.go @@ -1,3 +1,18 @@ package main -func countVowels() {} +func countVowels(word string) int { + count := 0 + for _, ch := range word { + if isVowel(ch) { + count++ + } + } + return count +} +func isVowel(char rune) bool { + if char == 'a' || char == 'e' || char == 'i' || char == 'o' || char == 'u' || + char == 'A' || char == 'E' || char == 'I' || char == 'O' || char == 'U' { + return true + } + return false +} diff --git a/exercise1/problem9/main.go b/exercise1/problem9/main.go index e8c84a54..64f74a97 100644 --- a/exercise1/problem9/main.go +++ b/exercise1/problem9/main.go @@ -1,7 +1,13 @@ package main -func bitwiseAND() {} +func bitwiseAND(a, b int) int { + return a & b +} -func bitwiseOR() {} +func bitwiseOR(a, b int) int { + return a | b +} -func bitwiseXOR() {} +func bitwiseXOR(a, b int) int { + return a ^ b +} diff --git a/exercise2/problem1/problem1.go b/exercise2/problem1/problem1.go index 4763006c..f90d9df4 100644 --- a/exercise2/problem1/problem1.go +++ b/exercise2/problem1/problem1.go @@ -1,4 +1,18 @@ package problem1 -func isChangeEnough() { +func isChangeEnough(coins [4]int, amount float32) bool { + total := int(amount * 100) + nominal := []int{25, 10, 5, 1} + for i, num := range coins { + if num == 0 { + continue + } + if needed := total / nominal[i]; num >= needed { + total -= needed * nominal[i] + } else { + total -= num * nominal[i] + } + } + + return total == 0 } diff --git a/exercise2/problem10/problem10.go b/exercise2/problem10/problem10.go index 7142a022..6e38a383 100644 --- a/exercise2/problem10/problem10.go +++ b/exercise2/problem10/problem10.go @@ -1,3 +1,11 @@ package problem10 -func factory() {} +func factory() (map[string]int, func(str string) func(int)) { + res := make(map[string]int) + return res, func(str string) func(int) { + res[str] = 0 + return func(in int) { + res[str] += in + } + } +} diff --git a/exercise2/problem11/problem11.go b/exercise2/problem11/problem11.go index 33988711..b5e95f30 100644 --- a/exercise2/problem11/problem11.go +++ b/exercise2/problem11/problem11.go @@ -1,3 +1,13 @@ package problem11 -func removeDups() {} +func removeDups[T any](array []T) []T { + out := make([]T, 0, len(array)) + filter := make(map[any]struct{}) + for _, val := range array { + if _, ok := filter[val]; !ok { + filter[val] = struct{}{} + out = append(out, val) + } + } + return out +} diff --git a/exercise2/problem12/problem12.go b/exercise2/problem12/problem12.go index 4c1ae327..076f6980 100644 --- a/exercise2/problem12/problem12.go +++ b/exercise2/problem12/problem12.go @@ -1,3 +1,21 @@ package problem11 -func keysAndValues() {} +func keysAndValues[T string | int, S any](m map[T]S) ([]T, []S) { + left := make([]T, 0, len(m)) + right := make([]S, 0, len(m)) + for k, v := range m { + left, right = appendWithSorting(left, right, k, v) + } + return left, right +} + +func appendWithSorting[T string | int, S any](left []T, right []S, key T, val S) ([]T, []S) { + for i, v := range left { + if key <= v { + left = append(left[:i], append([]T{key}, left[i:]...)...) + right = append(right[:i], append([]S{val}, right[i:]...)...) + return left, right + } + } + return append(left, key), append(right, val) +} diff --git a/exercise2/problem2/problem2.go b/exercise2/problem2/problem2.go index fdb199f0..4f035d2a 100644 --- a/exercise2/problem2/problem2.go +++ b/exercise2/problem2/problem2.go @@ -1,4 +1,18 @@ package problem2 -func capitalize() { +func capitalize(words []string) []string { + out := make([]string, len(words)) + for i, word := range words { + runeArray := make([]rune, len(word)) + for j, r := range word { + if j == 0 && (r >= 'a' && r <= 'z') { + r = r - 'a' + 'A' + } else if j != 0 && (r >= 'A' && r <= 'Z') { + r = r - 'A' + 'a' + } + runeArray[j] = r + } + out[i] = string(runeArray) + } + return out } diff --git a/exercise2/problem3/problem3.go b/exercise2/problem3/problem3.go index f183fafb..74d4f191 100644 --- a/exercise2/problem3/problem3.go +++ b/exercise2/problem3/problem3.go @@ -9,5 +9,21 @@ const ( lr dir = "lr" ) -func diagonalize() { +func diagonalize(size int, d dir) [][]int { + out := make([][]int, size) + for i := 0; i < size; i++ { + out[i] = make([]int, size) + for j := 0; j < size; j++ { + if d == ul { + out[i][j] = i + j + } else if d == ur { + out[i][j] = size + i - j - 1 + } else if d == ll { + out[i][j] = size - i + j - 1 + } else { + out[i][j] = 2*size - i - j - 2 + } + } + } + return out } diff --git a/exercise2/problem4/problem4.go b/exercise2/problem4/problem4.go index 1f680a4d..2548cda4 100644 --- a/exercise2/problem4/problem4.go +++ b/exercise2/problem4/problem4.go @@ -1,4 +1,11 @@ package problem4 -func mapping() { +func mapping(words []string) map[string]string { + out := make(map[string]string) + for _, word := range words { + runes := []rune(word) + runes[0] = runes[0] - 'a' + 'A' + out[word] = string(runes) + } + return out } diff --git a/exercise2/problem5/problem5.go b/exercise2/problem5/problem5.go index 43fb96a4..ecc17567 100644 --- a/exercise2/problem5/problem5.go +++ b/exercise2/problem5/problem5.go @@ -1,4 +1,46 @@ package problem5 -func products() { +func products(m map[string]int, min int) []string { + out := make([]string, 0, len(m)) + intAr := make([]int, 0, len(m)) + for s, i := range m { + if i > min { + intAr, out = appendWithSorting(intAr, out, i, s) + } + } + return out +} + +func appendWithSorting(left []int, right []string, key int, val string) ([]int, []string) { + for i, v := range left { + if key > v || (key == v && compareStrings(val, right[i]) < 0) { + left = append(left[:i], append([]int{key}, left[i:]...)...) + right = append(right[:i], append([]string{val}, right[i:]...)...) + return left, right + } + } + return append(left, key), append(right, val) +} + +func compareStrings(a, b string) int { + var minLen int + if len(a) < len(b) { + minLen = len(a) + } else { + minLen = len(b) + } + for i := 0; i < minLen; i++ { + if a[i] > b[i] { + return 1 + } else if a[i] < b[i] { + return -1 + } + } + if len(a) < len(b) { + return -1 + } else if len(a) > len(b) { + return 1 + } else { + return 0 + } } diff --git a/exercise2/problem6/problem6.go b/exercise2/problem6/problem6.go index 89fc5bfe..80bc22e1 100644 --- a/exercise2/problem6/problem6.go +++ b/exercise2/problem6/problem6.go @@ -1,4 +1,14 @@ package problem6 -func sumOfTwo() { +func sumOfTwo(left, right []int, sum int) bool { + mapper := make(map[int]struct{}) + for _, l := range left { + mapper[sum-l] = struct{}{} + } + for _, r := range right { + if _, ok := mapper[r]; ok { + 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..ed01931c 100644 --- a/exercise2/problem8/problem8.go +++ b/exercise2/problem8/problem8.go @@ -1,16 +1,15 @@ 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 { - (*m)[name] = i +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..b5bb3f60 100644 --- a/exercise2/problem9/problem9.go +++ b/exercise2/problem9/problem9.go @@ -1,3 +1,10 @@ package problem9 -func factory() {} +func factory(num int) func(...int) []int { + return func(a ...int) []int { + for i := 0; i < len(a); i++ { + a[i] = a[i] * num + } + return a + } +} diff --git a/exercise3/problem1/problem1.go b/exercise3/problem1/problem1.go index d45605c6..56906838 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 { + array []any + size int +} + +func (q *Queue) Enqueue(val any) { + q.array = append(q.array, val) + q.size++ +} +func (q *Queue) Dequeue() (any, error) { + if q.size <= 0 { + return nil, errors.New("queue is empty") + } + val := q.array[0] + q.array = q.array[1:] + q.size-- + return val, nil +} + +func (q *Queue) Size() int { + return q.size +} + +func (q *Queue) Peek() (any, error) { + if q.size == 0 { + return nil, errors.New("queue is empty") + } + return q.array[0], nil +} + +func (q *Queue) IsEmpty() bool { + return q.size == 0 +} diff --git a/exercise3/problem2/problem2.go b/exercise3/problem2/problem2.go index e9059889..4d05ed09 100644 --- a/exercise3/problem2/problem2.go +++ b/exercise3/problem2/problem2.go @@ -1,3 +1,38 @@ package problem2 -type Stack struct{} +import "errors" + +type Stack struct { + arr []any + size int +} + +func (s *Stack) Push(val any) { + s.arr = append(s.arr, val) + s.size++ +} + +func (s *Stack) Pop() (any, error) { + if s.size <= 0 { + return nil, errors.New("stack is empty") + } + val := s.arr[s.size-1] + s.arr = s.arr[:s.size-1] + s.size-- + return val, nil +} + +func (s *Stack) Size() int { + return s.size +} + +func (s *Stack) IsEmpty() bool { + return s.size == 0 +} + +func (s *Stack) Peek() (any, error) { + if s.size <= 0 { + return nil, errors.New("stack is empty") + } + return s.arr[s.size-1], nil +} diff --git a/exercise3/problem3/problem3.go b/exercise3/problem3/problem3.go index d8d79ac0..ad73599b 100644 --- a/exercise3/problem3/problem3.go +++ b/exercise3/problem3/problem3.go @@ -1,3 +1,103 @@ package problem3 -type Set struct{} +type Set struct { + m map[any]struct{} + size int +} + +func NewSet() *Set { + return &Set{make(map[any]struct{}), 0} +} + +func (set *Set) Add(x any) { + if !set.Has(x) { + set.m[x] = struct{}{} + set.size++ + } +} + +func (set *Set) Size() int { + return set.size +} + +func (set *Set) Has(x any) bool { + if _, ok := set.m[x]; ok { + return true + } + return false +} + +func (set *Set) IsEmpty() bool { + return set.size == 0 +} + +func (set *Set) Remove(x any) { + if set.Has(x) { + delete(set.m, x) + set.size-- + } +} + +func (set *Set) List() []any { + out := make([]any, set.size) + var j int + for i := range set.m { + out[j] = i + j++ + } + return out +} + +func (set *Set) Copy() *Set { + out := make(map[any]struct{}) + for x := range set.m { + out[x] = struct{}{} + } + return &Set{m: out, size: set.size} +} + +func Union(sets ...*Set) *Set { + m := map[any]struct{}{} + for _, set := range sets { + for x := range set.m { + m[x] = struct{}{} + } + } + return &Set{m: m, size: len(m)} +} + +func Intersect(sets ...*Set) *Set { + out := Union(sets...) + for x := range out.m { + for _, y := range sets { + if !y.Has(x) { + out.Remove(x) + } + } + } + return out +} + +func (set *Set) Difference(other *Set) *Set { + m := map[any]struct{}{} + for x := range set.m { + if !other.Has(x) { + m[x] = struct{}{} + } + } + for x := range other.m { + if !set.Has(x) { + m[x] = struct{}{} + } + } + return &Set{m: m, size: len(m)} +} + +func (set *Set) IsSubset(other *Set) bool { + for x := range set.m { + if !other.Has(x) { + return false + } + } + return true +} diff --git a/exercise3/problem4/problem4.go b/exercise3/problem4/problem4.go index ebf78147..b79ca744 100644 --- a/exercise3/problem4/problem4.go +++ b/exercise3/problem4/problem4.go @@ -1,3 +1,101 @@ package problem4 -type LinkedList struct{} +import "errors" + +type LinkedList[T comparable] struct { + head *Element[T] + tail *Element[T] + size int +} + +type Element[T comparable] struct { + value T + next *Element[T] +} + +func (l *LinkedList[T]) Add(el *Element[T]) { + if l.head == nil { + l.head = el + l.tail = el + l.size++ + return + } + l.tail.next = el + l.tail = el + l.size++ +} + +func (l *LinkedList[T]) Insert(el *Element[T], ind int) error { + if el == nil { + return errors.New("element is nil") + } + if l.size <= ind { + return errors.New("index out of range") + } + l.size++ + if ind == 0 { + el.next = l.head + l.head = el + return nil + } + + cur := l.head + for range ind - 1 { + cur = cur.next + } + el.next = cur.next + cur.next = el + return nil +} + +func (l *LinkedList[T]) IsEmpty() bool { + return l.size == 0 +} + +func (l *LinkedList[T]) Delete(el *Element[T]) error { + if el == nil { + return errors.New("element is nil") + } + cur := l.head + if cur.value == el.value { + l.head = l.head.next + l.size-- + return nil + } + for i := 1; i < l.size; i++ { + prev := cur + cur = cur.next + if prev.value == el.value { + prev.next = cur.next + l.size-- + return nil + } + } + + return errors.New("element not found") +} + +func (l *LinkedList[T]) Find(val T) (*Element[T], error) { + cur := l.head + for i := 0; i < l.size; i++ { + if cur.value == val { + return cur, nil + } + cur = cur.next + } + return nil, errors.New("element not found") +} + +func (l *LinkedList[T]) List() []*Element[T] { + out := make([]*Element[T], l.size) + cur := l.head + for i := 0; i < l.size; i++ { + out[i] = cur + cur = cur.next + } + return out +} + +func (l *LinkedList[T]) Size() int { + return l.size +} diff --git a/exercise3/problem5/problem5.go b/exercise3/problem5/problem5.go index 4177599f..464175b7 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(secondP *Person) string { + if secondP.age > p.age { + return secondP.name + " is older than me." + } else if secondP.age < p.age { + return secondP.name + " is younger than me." + } else { + return secondP.name + " is the same age as me." + } +} diff --git a/exercise3/problem6/problem6.go b/exercise3/problem6/problem6.go index 4e8d1af8..8a2f50b8 100644 --- a/exercise3/problem6/problem6.go +++ b/exercise3/problem6/problem6.go @@ -1,7 +1,31 @@ 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 (a *Animal) Legs() int { + return a.legsNum +} + +func (i *Insect) Legs() int { + return i.legsNum +} + +type HasLegs interface { + Legs() int +} + +func sumOfAllLegsNum(legs ...HasLegs) int { + var out int + for _, leg := range legs { + out += leg.Legs() + } + return out +} diff --git a/exercise3/problem7/problem7.go b/exercise3/problem7/problem7.go index 26887151..a76ba94a 100644 --- a/exercise3/problem7/problem7.go +++ b/exercise3/problem7/problem7.go @@ -1,10 +1,66 @@ package problem7 type BankAccount struct { + name string + balance int } type FedexAccount struct { + name string + packages []string } type KazPostAccount struct { + name string + balance int + packages []string +} + +func (b *BankAccount) getName() string { + return b.name +} + +func (b *FedexAccount) getName() string { + return b.name +} + +func (b *KazPostAccount) getName() string { + return b.name +} + +func (b *BankAccount) withdraw(amount int) { + b.balance -= amount +} + +func (b *KazPostAccount) withdraw(amount int) { + b.balance -= amount +} + +func (b *FedexAccount) send(receiver string, name string) { + b.packages = append(b.packages, name+" send package to "+receiver) +} + +func (b *KazPostAccount) send(receiver string, name string) { + b.packages = append(b.packages, name+" send package to "+receiver) +} + +type HasBalance interface { + withdraw(amount int) +} + +type HasPackages interface { + send(receiver string, name string) + getName() string +} + +func withdrawMoney(amount int, people ...HasBalance) { + for _, p := range people { + p.withdraw(amount) + } +} + +func sendPackagesTo(receiver string, people ...HasPackages) { + for _, p := range people { + p.send(receiver, p.getName()) + } } diff --git a/exercise4/bot/internal/api/handler/handler.go b/exercise4/bot/internal/api/handler/handler.go new file mode 100644 index 00000000..28e84831 --- /dev/null +++ b/exercise4/bot/internal/api/handler/handler.go @@ -0,0 +1,15 @@ +package handler + +import ( + "github.com/talgat-ruby/exercises-go/exercise4/bot/internal/model" +) + +type Handler struct { + game *model.Game +} + +func New() *Handler { + return &Handler{ + game: model.NewGame(), + } +} diff --git a/exercise4/bot/internal/api/handler/harakiri.go b/exercise4/bot/internal/api/handler/harakiri.go new file mode 100644 index 00000000..539c9624 --- /dev/null +++ b/exercise4/bot/internal/api/handler/harakiri.go @@ -0,0 +1,25 @@ +package handler + +import ( + "log/slog" + "net/http" + "os" + "time" +) + +func (h *Handler) Harakiri(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := slog.With( + "handler", "Harakiri", + "path", r.URL.Path, + ) + log.InfoContext( + ctx, + "performing noble art of sepuku...", + ) + _, _ = w.Write([]byte("Banzaaaaaai...")) + go func() { + time.Sleep(1 * time.Second) + os.Exit(1) + }() +} diff --git a/exercise4/bot/internal/api/handler/move.go b/exercise4/bot/internal/api/handler/move.go new file mode 100644 index 00000000..1679c0e3 --- /dev/null +++ b/exercise4/bot/internal/api/handler/move.go @@ -0,0 +1,33 @@ +package handler + +import ( + "encoding/json" + "github.com/talgat-ruby/exercises-go/exercise4/bot/internal/dto" + "log/slog" + "net/http" +) + +func (h *Handler) Move(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := slog.With( + "handler", "Move", + "path", r.URL.Path, + ) + log.InfoContext( + ctx, + "move", + ) + moveRequest := dto.RequestMove{} + err := json.NewDecoder(r.Body).Decode(&moveRequest) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + } + + ind := moveRequest.Board.CalculateNewPosition(moveRequest.Token) + w.Header().Set("Content-Type", "application/json") + moveResponse := dto.ResponseMove{Index: ind} + err = json.NewEncoder(w).Encode(moveResponse) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} diff --git a/exercise4/bot/internal/api/handler/ping.go b/exercise4/bot/internal/api/handler/ping.go new file mode 100644 index 00000000..e8c0ecc9 --- /dev/null +++ b/exercise4/bot/internal/api/handler/ping.go @@ -0,0 +1,20 @@ +package handler + +import ( + "log/slog" + "net/http" +) + +func (h *Handler) Ping(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := slog.With( + "handler", "Ping", + "path", r.URL.Path, + ) + + log.InfoContext( + ctx, + "pinged", + ) + w.WriteHeader(http.StatusOK) +} diff --git a/exercise4/bot/internal/api/router/router.go b/exercise4/bot/internal/api/router/router.go new file mode 100644 index 00000000..bde8e8a3 --- /dev/null +++ b/exercise4/bot/internal/api/router/router.go @@ -0,0 +1,18 @@ +package router + +import ( + "net/http" + + "github.com/talgat-ruby/exercises-go/exercise4/bot/internal/api/handler" +) + +func New() *http.ServeMux { + han := handler.New() + mux := http.NewServeMux() + + mux.Handle("GET /ping", http.HandlerFunc(han.Ping)) + mux.Handle("GET /harakiri", http.HandlerFunc(han.Harakiri)) + mux.Handle("POST /move", http.HandlerFunc(han.Move)) + + return mux +} diff --git a/exercise4/bot/internal/config/api.go b/exercise4/bot/internal/config/api.go new file mode 100644 index 00000000..9ffed754 --- /dev/null +++ b/exercise4/bot/internal/config/api.go @@ -0,0 +1,58 @@ +package config + +import ( + "context" + "errors" + "fmt" + "github.com/talgat-ruby/exercises-go/exercise4/bot/internal/api/router" + "github.com/talgat-ruby/exercises-go/exercise4/bot/internal/constants" + "log" + "log/slog" + "net" + "net/http" +) + +type Api struct { + srv *http.Server + port string + ctx context.Context +} + +func (api *Api) Start(preInitFun func()) { + r := router.New() + ctx := context.Background() + // start up HTTP server + api.port = constants.PORT + api.srv = &http.Server{ + Addr: fmt.Sprintf(":%s", api.port), + Handler: r, + BaseContext: func(_ net.Listener) context.Context { + return ctx + }, + } + api.ctx = ctx + + slog.InfoContext( + api.ctx, + "starting service", + "port", api.port, + ) + + go preInitFun() + + if err := api.srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Fatal(api.ctx, "service error", "error", err) + } + +} + +func (api *Api) Stop() { + slog.InfoContext( + api.ctx, + "closing service", + "port", api.port, + ) + if err := api.srv.Shutdown(api.ctx); err != nil { + log.Fatal(api.ctx, "server shutdown error", "error", err) + } +} diff --git a/exercise4/bot/internal/config/config.go b/exercise4/bot/internal/config/config.go new file mode 100644 index 00000000..649f5e79 --- /dev/null +++ b/exercise4/bot/internal/config/config.go @@ -0,0 +1,13 @@ +package config + +type Config struct { + api *Api +} + +func New() *Config { + return &Config{api: &Api{}} +} + +func (c *Config) GetApi() *Api { + return c.api +} diff --git a/exercise4/bot/internal/constants/client.go b/exercise4/bot/internal/constants/client.go new file mode 100644 index 00000000..d0c6b77c --- /dev/null +++ b/exercise4/bot/internal/constants/client.go @@ -0,0 +1,8 @@ +package constants + +import "os" + +var NAME = os.Getenv("NAME") +var PORT = os.Getenv("PORT") +var URL = "localhost:" + PORT +var FullUrl = "http://" + URL diff --git a/exercise4/bot/internal/constants/server.go b/exercise4/bot/internal/constants/server.go new file mode 100644 index 00000000..78fb3eff --- /dev/null +++ b/exercise4/bot/internal/constants/server.go @@ -0,0 +1,7 @@ +package constants + +import "os" + +const JoinPath string = "/join" + +var Host = os.Getenv("HOST") diff --git a/exercise4/bot/internal/dto/request.go b/exercise4/bot/internal/dto/request.go new file mode 100644 index 00000000..91e77905 --- /dev/null +++ b/exercise4/bot/internal/dto/request.go @@ -0,0 +1,15 @@ +package dto + +import ( + "github.com/talgat-ruby/exercises-go/exercise4/bot/internal/model" +) + +type RequestJoin struct { + Name string `json:"name"` + URL string `json:"url"` +} + +type RequestMove struct { + Board *model.Board `json:"board"` + Token model.Token `json:"token"` +} diff --git a/exercise4/bot/internal/dto/response.go b/exercise4/bot/internal/dto/response.go new file mode 100644 index 00000000..578fd47c --- /dev/null +++ b/exercise4/bot/internal/dto/response.go @@ -0,0 +1,5 @@ +package dto + +type ResponseMove struct { + Index int `json:"index"` +} diff --git a/exercise4/bot/internal/game/join.go b/exercise4/bot/internal/game/join.go new file mode 100644 index 00000000..76308c44 --- /dev/null +++ b/exercise4/bot/internal/game/join.go @@ -0,0 +1,51 @@ +package game + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + constants2 "github.com/talgat-ruby/exercises-go/exercise4/bot/internal/constants" + "github.com/talgat-ruby/exercises-go/exercise4/bot/internal/dto" + "log/slog" + "net/http" + "time" +) + +func JoinGame() error { + + // timeout after 5 sec + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + body := dto.RequestJoin{Name: constants2.NAME, URL: constants2.FullUrl} + + jsonData, err := json.Marshal(body) + if err != nil { + return fmt.Errorf("failed to marshal join request: %w", err) + } + + req, err := http.NewRequestWithContext( + ctx, + http.MethodPost, + constants2.Host+constants2.JoinPath, + bytes.NewBuffer(jsonData), + ) + + if err != nil { + return fmt.Errorf("error creating join request %w", err) + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return fmt.Errorf("error making join request %w", err) + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return fmt.Errorf("failed making join request %d - %s", resp.StatusCode, http.StatusText(resp.StatusCode)) + } + + slog.Info("successfully joined the game") + + return nil +} diff --git a/exercise4/bot/internal/model/board.go b/exercise4/bot/internal/model/board.go new file mode 100644 index 00000000..c974038c --- /dev/null +++ b/exercise4/bot/internal/model/board.go @@ -0,0 +1,182 @@ +package model + +import "fmt" + +type Token string + +const ( + TokenEmpty Token = " " + TokenX Token = "x" + TokenO Token = "o" +) + +const ( + Cols = 3 + Rows = 3 +) + +var motion int + +type Board [Cols * Rows]Token + +func (b *Board) CalculateNewPosition(token Token) int { + ind := b.calculateNewPosition(token) + b.logMove(ind, token) + return ind +} + +func (b *Board) logMove(pos int, token Token) { + b[pos] = token + fmt.Println(b.String()) +} + +func (b *Board) String() string { + str := fmt.Sprintf( + ` + %s|%s|%s + ----- + %s|%s|%s + ----- + %s|%s|%s +`, + b[0], + b[1], + b[2], + b[3], + b[4], + b[5], + b[6], + b[7], + b[8], + ) + + return str +} + +func (b *Board) calculateNewPosition(token Token) int { + // Π£Π²Π΅Π»ΠΈΡ‡ΠΈΠ²Π°Π΅ΠΌ счётчик Ρ…ΠΎΠ΄ΠΎΠ² + motion++ + + // ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ количСство Π·Π°ΠΏΠΎΠ»Π½Π΅Π½Π½Ρ‹Ρ… ячССк Π½Π° доскС + count := 0 + for _, p := range b { + if p != TokenEmpty { + count++ + } + } + + // Если Π½Π° доскС Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΎΠ΄ΠΈΠ½ Ρ…ΠΎΠ΄ ΠΈ Ρ†Π΅Π½Ρ‚Ρ€Π°Π»ΡŒΠ½Π°Ρ ячСйка пуста, ставим Ρ‚ΠΎΠΊΠ΅Π½ Π² Ρ†Π΅Π½Ρ‚Ρ€ + if count == 1 && b[4] == TokenEmpty { + return 4 + } + + // ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ всС Π²ΠΎΠ·ΠΌΠΎΠΆΠ½Ρ‹Π΅ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Ρ‹ Π²Ρ‹ΠΈΠ³Ρ€Ρ‹ΡˆΠ½Ρ‹Ρ… ΠΊΠΎΠΌΠ±ΠΈΠ½Π°Ρ†ΠΈΠΉ + alloptions := b.allWin() + // Находим ΠΎΠΏΡ‚ΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹Π΅ Ρ…ΠΎΠ΄Ρ‹ для Ρ‚Π΅ΠΊΡƒΡ‰Π΅Π³ΠΎ ΠΈΠ³Ρ€ΠΎΠΊΠ° + optmoves := optimalMoves(alloptions) + + // ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ, Π΅ΡΡ‚ΡŒ Π»ΠΈ Π²Ρ‹ΠΈΠ³Ρ€Ρ‹ΡˆΠ½Ρ‹Π΅ ΠΈΠ»ΠΈ Π·Π°Ρ‰ΠΈΡ‚Π½Ρ‹Π΅ Ρ…ΠΎΠ΄Ρ‹ + if len(optmoves) > 0 { + // Если Π΅ΡΡ‚ΡŒ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ Π²Ρ‹ΠΈΠ³Ρ€Π°Ρ‚ΡŒ, Π²Ρ‹Π±ΠΈΡ€Π°Π΅ΠΌ этот Ρ…ΠΎΠ΄. + // Если Π½Π΅Ρ‚, Π²Ρ‹Π±ΠΈΡ€Π°Π΅ΠΌ Ρ…ΠΎΠ΄ для Π·Π°Ρ‰ΠΈΡ‚Ρ‹. + emptyToken := findEmptySlotForToken(optmoves, token) + return emptyToken + } + + // Если Π½Π΅Ρ‚ Π²Ρ‹ΠΈΠ³Ρ€Ρ‹ΡˆΠ½Ρ‹Ρ… ΠΈΠ»ΠΈ Π·Π°Ρ‰ΠΈΡ‚Π½Ρ‹Ρ… Ρ…ΠΎΠ΄ΠΎΠ², ΠΈΡ‰Π΅ΠΌ пустыС ΡƒΠ³Π»Ρ‹ + emtyCorner := b.findEmptyCorners() + return emtyCorner +} + +func (b *Board) allWin() []map[int]Token { + // ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½Ρ‹Π΅ Π²Ρ‹ΠΈΠ³Ρ€Ρ‹ΡˆΠ½Ρ‹Π΅ ΠΊΠΎΠΌΠ±ΠΈΠ½Π°Ρ†ΠΈΠΈ + winCombinations := [][]int{ + {0, 1, 2}, // Π“ΠΎΡ€ΠΈΠ·ΠΎΠ½Ρ‚Π°Π»ΡŒΠ½Ρ‹Π΅ Π»ΠΈΠ½ΠΈΠΈ + {3, 4, 5}, + {6, 7, 8}, + {0, 3, 6}, // Π’Π΅Ρ€Ρ‚ΠΈΠΊΠ°Π»ΡŒΠ½Ρ‹Π΅ Π»ΠΈΠ½ΠΈΠΈ + {1, 4, 7}, + {2, 5, 8}, + {0, 4, 8}, // Π”ΠΈΠ°Π³ΠΎΠ½Π°Π»ΠΈ + {2, 4, 6}, + } + + // Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ массив для хранСния всСх Π²Ρ‹ΠΈΠ³Ρ€Ρ‹ΡˆΠ½Ρ‹Ρ… ΠΊΠΎΠΌΠ±ΠΈΠ½Π°Ρ†ΠΈΠΉ + alloptions := []map[int]Token{} + + // ЗаполняСм массив Π²Ρ‹ΠΈΠ³Ρ€Ρ‹ΡˆΠ½Ρ‹Ρ… ΠΊΠΎΠΌΠ±ΠΈΠ½Π°Ρ†ΠΈΠΉ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠΌΠΈ значСниями с доски + for _, combination := range winCombinations { + option := make(map[int]Token) + for _, index := range combination { + option[index] = b[index] + } + alloptions = append(alloptions, option) + } + + return alloptions +} + +func optimalMoves(alloptions []map[int]Token) []map[int]Token { + // Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ массив для хранСния ΠΎΠΏΡ‚ΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹Ρ… Ρ…ΠΎΠ΄ΠΎΠ² + optmoves := []map[int]Token{} + + // ΠŸΠ΅Ρ€Π΅Π±ΠΈΡ€Π°Π΅ΠΌ всС Π²Ρ‹ΠΈΠ³Ρ€Ρ‹ΡˆΠ½Ρ‹Π΅ ΠΊΠΎΠΌΠ±ΠΈΠ½Π°Ρ†ΠΈΠΈ + for _, option := range alloptions { + a := []Token{} + for _, value := range option { + a = append(a, value) + } + + // ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ условия для ΠΏΠΎΠ±Π΅Π΄Ρ‹ ΠΈΠ»ΠΈ Π·Π°Ρ‰ΠΈΡ‚Ρ‹ + if (a[0] == a[1] && a[0] != TokenEmpty && a[2] == TokenEmpty) || + (a[0] == a[2] && a[0] != TokenEmpty && a[1] == TokenEmpty) || + (a[1] == a[2] && a[1] != TokenEmpty && a[0] == TokenEmpty) { + // ДобавляСм Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠΌΠ±ΠΈΠ½Π°Ρ†ΠΈΡŽ Π² ΠΎΠΏΡ‚ΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹Π΅ Ρ…ΠΎΠ΄Ρ‹ + optmoves = append(optmoves, option) + } + } + return optmoves +} + +func findEmptySlotForToken(variants []map[int]Token, token Token) int { + var emptyIndex int + + // ΠŸΠ΅Ρ€Π΅Π±ΠΈΡ€Π°Π΅ΠΌ всС Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Ρ‹ + for _, variant := range variants { + hasToken := false + + // ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ, Π΅ΡΡ‚ΡŒ Π»ΠΈ Ρ‚ΠΎΠΊΠ΅Π½ ΠΈ Π·Π°ΠΏΠΎΠΌΠΈΠ½Π°Π΅ΠΌ индСкс пустой ячСйки + for key, value := range variant { + if value == token { + hasToken = true + } else if value == TokenEmpty { + emptyIndex = key + } + } + + // Если Π½Π°ΠΉΠ΄Π΅Π½ Ρ‚ΠΎΠΊΠ΅Π½, Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌ индСкс пустой ячСйки + if hasToken { + return emptyIndex + } + } + + return emptyIndex // Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌ индСкс пустой ячСйки +} + +func (b *Board) findEmptyCorners() int { + // ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ индСксы ΡƒΠ³Π»ΠΎΠ²Ρ‹Ρ… ячССк + corners := []int{0, 2, 6, 8} + + // ΠŸΠ΅Ρ€Π΅Π±ΠΈΡ€Π°Π΅ΠΌ ΡƒΠ³Π»ΠΎΠ²Ρ‹Π΅ ячСйки + for _, index := range corners { + if b[index] == TokenEmpty { + // ΠŸΡ€ΠΎΠΏΡƒΡΠΊΠ°Π΅ΠΌ индСкс 2, Ссли Motion == 2 + if index == 2 && motion == 2 { + continue + } + return index // Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌ индСкс пустой ΡƒΠ³Π»ΠΎΠ²ΠΎΠΉ ячСйки + } + } + + return -1 // Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌ -1, Ссли всС ΡƒΠ³Π»ΠΎΠ²Ρ‹Π΅ ячСйки заняты +} \ No newline at end of file diff --git a/exercise4/bot/internal/model/game.go b/exercise4/bot/internal/model/game.go new file mode 100644 index 00000000..2c66c305 --- /dev/null +++ b/exercise4/bot/internal/model/game.go @@ -0,0 +1,11 @@ +package model + +type Game struct { + State string `json:"state"` +} + +func NewGame() *Game { + return &Game{ + State: "start", + } +} diff --git a/exercise4/bot/main.go b/exercise4/bot/main.go index 64f9e0a3..2c38ebca 100644 --- a/exercise4/bot/main.go +++ b/exercise4/bot/main.go @@ -1,21 +1,27 @@ package main import ( - "context" + "fmt" + "github.com/talgat-ruby/exercises-go/exercise4/bot/internal/config" + "github.com/talgat-ruby/exercises-go/exercise4/bot/internal/constants" + "github.com/talgat-ruby/exercises-go/exercise4/bot/internal/game" + "log/slog" "os" - "os/signal" - "syscall" ) func main() { - ctx := context.Background() - - ready := startServer() - <-ready - - // TODO after server start + apiConfig := config.New().GetApi() + preInitFun := preInitFunc(apiConfig.Stop) + apiConfig.Start(preInitFun) +} - stop := make(chan os.Signal, 1) - signal.Notify(stop, os.Interrupt, syscall.SIGTERM) - <-stop // Wait for SIGINT or SIGTERM +func preInitFunc(internalModifier func()) func() { + return func() { + err := game.JoinGame() + if err != nil { + slog.Error(fmt.Sprintf("could not join game with host %s: %s", constants.Host, err)) + internalModifier() + os.Exit(1) + } + } } diff --git a/exercise4/bot/server.go b/exercise4/bot/server.go deleted file mode 100644 index e6760ec5..00000000 --- a/exercise4/bot/server.go +++ /dev/null @@ -1,45 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "net" - "net/http" - "os" - "sync" - "time" -) - -type readyListener struct { - net.Listener - ready chan struct{} - once sync.Once -} - -func (l *readyListener) Accept() (net.Conn, error) { - l.once.Do(func() { close(l.ready) }) - return l.Listener.Accept() -} - -func startServer() <-chan struct{} { - ready := make(chan struct{}) - - listener, err := net.Listen("tcp", fmt.Sprintf(":%s", os.Getenv("PORT"))) - if err != nil { - panic(err) - } - - list := &readyListener{Listener: listener, ready: ready} - srv := &http.Server{ - IdleTimeout: 2 * time.Minute, - } - - go func() { - err := srv.Serve(list) - if !errors.Is(err, http.ErrServerClosed) { - panic(err) - } - }() - - return ready -} diff --git a/exercise5/problem1/problem1.go b/exercise5/problem1/problem1.go index 4f514fab..f67f3af0 100644 --- a/exercise5/problem1/problem1.go +++ b/exercise5/problem1/problem1.go @@ -1,9 +1,10 @@ package problem1 func incrementConcurrently(num int) int { + ch := make(chan int) go func() { - num++ + ch <- num + 1 }() - return num + return <-ch } diff --git a/exercise5/problem2/problem2.go b/exercise5/problem2/problem2.go index 16d38e1d..14b6f88e 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 @@ -10,7 +15,34 @@ func add(numbers []int) int64 { } func addConcurrently(numbers []int) int64 { + numCores := runtime.NumCPU() + runtime.GOMAXPROCS(numCores) + + chunkSize := len(numbers) / numCores + results := make([]int64, numCores) + + var wg sync.WaitGroup var sum int64 + for i := 0; i < numCores; i++ { + wg.Add(1) + + start := i * chunkSize + end := start + chunkSize + if i == numCores-1 { + end = len(numbers) + } + + go func(i, start, end int) { + defer wg.Done() + results[i] = add(numbers[start:end]) + }(i, start, end) + } + + wg.Wait() + + for _, s := range results { + sum += s + } return sum } diff --git a/exercise5/problem3/problem3.go b/exercise5/problem3/problem3.go index e085a51a..600a3d29 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 + ch := make(chan int) go func(a, b int) { - c = a + b + ch <- a + b }(a, b) - return c + return <-ch } diff --git a/exercise5/problem4/problem4.go b/exercise5/problem4/problem4.go index b5899ddf..6c05ac2e 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 { diff --git a/exercise5/problem5/problem5.go b/exercise5/problem5/problem5.go index ac192c58..3f84bfaa 100644 --- a/exercise5/problem5/problem5.go +++ b/exercise5/problem5/problem5.go @@ -1,8 +1,19 @@ package problem5 -func producer() {} +func producer(words []string, channel chan<- string) { + for _, word := range words { + channel <- word + } + close(channel) +} -func consumer() {} +func consumer(channel <-chan string) string { + out := "" + for word := range channel { + out += word + " " + } + return out[:len(out)-1] +} func send( words []string, diff --git a/exercise5/problem6/problem6.go b/exercise5/problem6/problem6.go index e1beea87..65b3bf2a 100644 --- a/exercise5/problem6/problem6.go +++ b/exercise5/problem6/problem6.go @@ -2,8 +2,28 @@ package problem6 type pipe func(in <-chan int) <-chan int -var multiplyBy2 pipe = func() {} +var multiplyBy2 pipe = func(in <-chan int) <-chan int { + return channelIterator(in, func(n int) int { return n * 2 }) +} -var add5 pipe = func() {} +var add5 pipe = func(in <-chan int) <-chan int { + return channelIterator(in, func(n int) int { return n + 5 }) +} -func piper(in <-chan int, pipes []pipe) <-chan int {} +func channelIterator(in <-chan int, f func(n int) int) <-chan int { + out := make(chan int) + go func() { + for n := range in { + out <- f(n) + } + close(out) + }() + return out +} + +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..4631d0f4 100644 --- a/exercise5/problem7/problem7.go +++ b/exercise5/problem7/problem7.go @@ -1,3 +1,24 @@ package problem7 -func multiplex(ch1 <-chan string, ch2 <-chan string) []string {} +func multiplex(ch1 <-chan string, ch2 <-chan string) []string { + out := make([]string, 0) + + for ch1 != nil || ch2 != nil { + select { + case val, ok := <-ch1: + if ok { + out = append(out, val) + } else { + ch1 = nil + } + case val, ok := <-ch2: + if ok { + out = append(out, val) + } else { + ch2 = nil + } + } + } + + return out +} diff --git a/exercise5/problem8/problem8.go b/exercise5/problem8/problem8.go index 3e951b3b..210cb17b 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 val := <-ch: + return val + case <-time.After(ttl): + return "timeout" + } +} diff --git a/exercise6/problem1/problem1.go b/exercise6/problem1/problem1.go index ee453b24..b67fd64c 100644 --- a/exercise6/problem1/problem1.go +++ b/exercise6/problem1/problem1.go @@ -1,9 +1,29 @@ 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 (acc *bankAccount) deposit(amount int) { + if amount > 0 { + acc.mu.Lock() + defer acc.mu.Unlock() + acc.blnc += amount + } +} + +func (acc *bankAccount) withdraw(amount int) { + + if amount <= acc.blnc { + acc.mu.Lock() + defer acc.mu.Unlock() + acc.blnc -= amount + } } diff --git a/exercise6/problem2/problem2.go b/exercise6/problem2/problem2.go index 97e02368..4d93e66a 100644 --- a/exercise6/problem2/problem2.go +++ b/exercise6/problem2/problem2.go @@ -1,6 +1,7 @@ package problem2 import ( + "sync" "time" ) @@ -8,13 +9,32 @@ 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) balance() int { - time.Sleep(readDelay) - return 0 +func (acc *bankAccount) balance() int { + acc.mu.Lock() + defer acc.mu.Unlock() + return acc.blnc +} + +func (acc *bankAccount) deposit(amount int) { + if amount > 0 { + acc.mu.Lock() + defer acc.mu.Unlock() + acc.blnc += amount + } +} + +func (acc *bankAccount) withdraw(amount int) { + + if amount <= acc.blnc { + acc.mu.Lock() + defer acc.mu.Unlock() + acc.blnc -= amount + } } 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..b9e29b46 100644 --- a/exercise6/problem4/problem4.go +++ b/exercise6/problem4/problem4.go @@ -1,12 +1,16 @@ package problem4 import ( + "sync" "time" ) -func worker(id int, _ *[]string, ch chan<- int) { +func worker(id int, _ *[]string, ch chan<- int, once *sync.Once) { // TODO wait for shopping list to be completed - ch <- id + once.Do(func() { + ch <- id + }) + } func updateShopList(shoppingList *[]string) { @@ -19,9 +23,10 @@ func updateShopList(shoppingList *[]string) { func notifyOnShopListUpdate(shoppingList *[]string, numWorkers int) <-chan int { notifier := make(chan int) + var once sync.Once for i := range numWorkers { - go worker(i+1, shoppingList, notifier) + go worker(i+1, shoppingList, notifier, &once) time.Sleep(time.Millisecond) // order matters } diff --git a/exercise6/problem5/problem5.go b/exercise6/problem5/problem5.go index 8e4a1703..37b47bc1 100644 --- a/exercise6/problem5/problem5.go +++ b/exercise6/problem5/problem5.go @@ -1,31 +1,36 @@ package problem5 import ( + "sync" "time" ) -func worker(id int, shoppingList *[]string, ch chan<- int) { +func worker(id int, _ *[]string, ch chan<- int, wg *sync.WaitGroup) { // TODO wait for shopping list to be completed + wg.Wait() ch <- id } -func updateShopList(shoppingList *[]string) { +func updateShopList(shoppingList *[]string, wg *sync.WaitGroup) { time.Sleep(10 * time.Millisecond) *shoppingList = append(*shoppingList, "apples") *shoppingList = append(*shoppingList, "milk") *shoppingList = append(*shoppingList, "bake soda") + defer wg.Done() } func notifyOnShopListUpdate(shoppingList *[]string, numWorkers int) <-chan int { notifier := make(chan int) + var wg sync.WaitGroup + wg.Add(1) for i := range numWorkers { - go worker(i+1, shoppingList, notifier) + go worker(i+1, shoppingList, notifier, &wg) time.Sleep(time.Millisecond) // order matters } - go updateShopList(shoppingList) + go updateShopList(shoppingList, &wg) return notifier } diff --git a/exercise6/problem6/problem6.go b/exercise6/problem6/problem6.go index 0c1122b9..3158acb6 100644 --- a/exercise6/problem6/problem6.go +++ b/exercise6/problem6/problem6.go @@ -6,14 +6,14 @@ import ( func runTasks(init func()) { var wg sync.WaitGroup + var once sync.Once for range 10 { wg.Add(1) go 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..4d726ed5 100644 --- a/exercise6/problem7/problem7.go +++ b/exercise6/problem7/problem7.go @@ -8,12 +8,9 @@ import ( func task() { start := time.Now() - var t *time.Timer - t = time.AfterFunc( - randomDuration(), - func() { + time.AfterFunc( + randomDuration(), func() { 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..dfb9b24d 100644 --- a/exercise6/problem8/problem8.go +++ b/exercise6/problem8/problem8.go @@ -1,3 +1,26 @@ package problem8 -func multiplex(chs []<-chan string) []string {} +func multiplex(chs []<-chan string) []string { + out := make([]string, 0) + openChannels := len(chs) + + for openChannels > 0 { + for i, ch := range chs { + if ch == nil { + continue + } + + select { + case val, ok := <-ch: + if ok { + out = append(out, val) + } else { + chs[i] = nil + openChannels-- + } + default: + } + } + } + return out +} diff --git a/exercise7/blogging-platform/config/app.properties b/exercise7/blogging-platform/config/app.properties new file mode 100644 index 00000000..4ed6b38c --- /dev/null +++ b/exercise7/blogging-platform/config/app.properties @@ -0,0 +1,7 @@ +DB_HOST=localhost +DB_PORT=5432 +DB_USER=postgres +DB_PASSWORD= +DB_NAME=posts_go +DB_SSLMODE=disable +API_PORT=8080 diff --git a/exercise7/blogging-platform/internal/api/api.go b/exercise7/blogging-platform/internal/api/api.go new file mode 100644 index 00000000..337a2940 --- /dev/null +++ b/exercise7/blogging-platform/internal/api/api.go @@ -0,0 +1,41 @@ +package api + +import ( + "context" + "fmt" + "github.com/talgat-ruby/exercises-go/exercise7/blogging-platform/internal/api/router" + "github.com/talgat-ruby/exercises-go/exercise7/blogging-platform/internal/service" + "log" + "net" + "net/http" +) + +type API struct { + port string + mux *http.ServeMux +} + +func New(cfg map[string]string, service *service.PostService) *API { + return &API{ + port: cfg["API_PORT"], + mux: router.SetupRouter(service), + } +} + +func (a *API) Start(ctx context.Context) error { + server := &http.Server{ + Addr: fmt.Sprintf(":%s", a.port), + Handler: a.mux, + BaseContext: func(_ net.Listener) context.Context { + return ctx + }, + } + + go func() { + <-ctx.Done() + _ = server.Shutdown(context.Background()) + }() + + log.Printf("API server is running on port %s", a.port) + return server.ListenAndServe() +} diff --git a/exercise7/blogging-platform/internal/api/handlers/handlers.go b/exercise7/blogging-platform/internal/api/handlers/handlers.go new file mode 100644 index 00000000..9cb0c3b4 --- /dev/null +++ b/exercise7/blogging-platform/internal/api/handlers/handlers.go @@ -0,0 +1,118 @@ +package handlers + +import ( + "encoding/json" + "fmt" + "github.com/talgat-ruby/exercises-go/exercise7/blogging-platform/internal/model" + "github.com/talgat-ruby/exercises-go/exercise7/blogging-platform/internal/service" + "github.com/talgat-ruby/exercises-go/exercise7/blogging-platform/pkg/httputils/response" + "net/http" + "strconv" +) + +type Handler struct { + service *service.PostService +} + +func New(service *service.PostService) *Handler { + return &Handler{ + service: service, + } +} + +func (h *Handler) GetPosts(w http.ResponseWriter, r *http.Request) { + term := r.URL.Query().Get("term") + var posts []model.Post + var err error + if term != "" { + posts, err = h.service.GetPost(term) + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) + return + } else if posts == nil || len(posts) == 0 { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + } else { + posts, err = h.service.GetPosts() + if err != nil { + http.Error(w, "Failed to retrieve posts", http.StatusInternalServerError) + return + } + } + + if errJson := response.JSON(w, http.StatusOK, posts); errJson != nil { + http.Error(w, "Error marshaling response", http.StatusInternalServerError) + } +} + +func (h *Handler) CreatePost(w http.ResponseWriter, r *http.Request) { + var post model.Post + if err := json.NewDecoder(r.Body).Decode(&post); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + createdPost, err := h.service.CreatePost(&post) + if err != nil { + http.Error(w, "Failed to create post", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + if err := json.NewEncoder(w).Encode(createdPost); err != nil { + http.Error(w, "Failed to write response", http.StatusInternalServerError) + } +} + +func (h *Handler) UpdatePost(w http.ResponseWriter, r *http.Request) { + postIDStr := r.URL.Path[len("/posts/"):] + postID, err := strconv.Atoi(postIDStr) + if err != nil { + http.Error(w, "Invalid post ID", http.StatusBadRequest) + return + } + + var post model.Post + if err := json.NewDecoder(r.Body).Decode(&post); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + post.ID = postID + updatedPost, err := h.service.UpdatePost(&post) + if err != nil { + if err.Error() == "post not found" { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + http.Error(w, "Failed to update post", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(updatedPost); err != nil { + http.Error(w, "Failed to write response", http.StatusInternalServerError) + } +} + +func (h *Handler) DeletePost(w http.ResponseWriter, r *http.Request) { + postIDStr := r.URL.Path[len("/posts/"):] + postID, err := strconv.Atoi(postIDStr) + if err != nil { + http.Error(w, "Invalid post ID", http.StatusBadRequest) + return + } + if err := h.service.DeletePost(postID); err != nil { + if err.Error() == "post not found" { + http.Error(w, err.Error(), http.StatusNotFound) + } else { + http.Error(w, "Failed to delete post", http.StatusInternalServerError) + } + return + } + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, "Post with ID %d deleted successfully", postID) +} diff --git a/exercise7/blogging-platform/internal/api/handlers/ping.go b/exercise7/blogging-platform/internal/api/handlers/ping.go new file mode 100644 index 00000000..2224e448 --- /dev/null +++ b/exercise7/blogging-platform/internal/api/handlers/ping.go @@ -0,0 +1,20 @@ +package handlers + +import ( + "log/slog" + "net/http" +) + +func Ping(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := slog.With( + "handler", "Ping", + "path", r.URL.Path, + ) + + log.InfoContext( + ctx, + "pinged", + ) + w.WriteHeader(http.StatusOK) +} diff --git a/exercise7/blogging-platform/internal/api/router/router.go b/exercise7/blogging-platform/internal/api/router/router.go new file mode 100644 index 00000000..acc35950 --- /dev/null +++ b/exercise7/blogging-platform/internal/api/router/router.go @@ -0,0 +1,22 @@ +package router + +import ( + "net/http" + + "github.com/talgat-ruby/exercises-go/exercise7/blogging-platform/internal/api/handlers" + "github.com/talgat-ruby/exercises-go/exercise7/blogging-platform/internal/service" +) + +func SetupRouter(service *service.PostService) *http.ServeMux { + mux := http.NewServeMux() + + handler := handlers.New(service) + + mux.Handle("GET /ping", http.HandlerFunc(handlers.Ping)) + mux.Handle("GET /posts", http.HandlerFunc(handler.GetPosts)) + mux.Handle("POST /posts", http.HandlerFunc(handler.CreatePost)) + mux.Handle("PUT /posts/", http.HandlerFunc(handler.UpdatePost)) + mux.Handle("DELETE /posts/", http.HandlerFunc(handler.DeletePost)) + + return mux +} diff --git a/exercise7/blogging-platform/internal/config/config.go b/exercise7/blogging-platform/internal/config/config.go new file mode 100644 index 00000000..7d5bbd67 --- /dev/null +++ b/exercise7/blogging-platform/internal/config/config.go @@ -0,0 +1,46 @@ +package config + +import ( + "bufio" + "fmt" + "log" + "os" + "strings" +) + +// LoadConfig loads the properties from the given file into a map[string]string. +func LoadConfig(filePath string) (map[string]string, error) { + file, err := os.Open(filePath) + if err != nil { + return nil, fmt.Errorf("failed to open configuration file: %w", err) + } + defer func(file *os.File) { + _ = file.Close() + }(file) + + configMap := make(map[string]string) + scanner := bufio.NewScanner(file) + + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + + parts := strings.SplitN(line, "=", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("invalid configuration line: %s", line) + } + + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + configMap[key] = value + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error reading configuration file: %w", err) + } + + log.Println("Config loaded successfully.") + return configMap, nil +} diff --git a/exercise7/blogging-platform/internal/db/config.go b/exercise7/blogging-platform/internal/db/config.go new file mode 100644 index 00000000..16953233 --- /dev/null +++ b/exercise7/blogging-platform/internal/db/config.go @@ -0,0 +1,75 @@ +package db + +import ( + "context" + "database/sql" + "fmt" + "log" + "log/slog" + + _ "github.com/lib/pq" +) + +func InitDatabase(cfg map[string]string, ctx context.Context) *sql.DB { + // Setup the connection to the database + dbConn, err := setupConnection(cfg) + if err != nil { + slog.ErrorContext(ctx, "db initialize error", "service", "db", "error", err) + panic(err) // Panic on connection error + } + + // Perform database migration + errMigration := migrate(dbConn) + if errMigration != nil { + slog.ErrorContext(ctx, "db migration error", "service", "db", "error", err) + panic(errMigration) // Panic on migration error + } + + // Return the initialized connection + return dbConn +} + +func migrate(db *sql.DB) error { + // Simple migration script to create the posts table if it doesn't exist + migrationScript := ` + CREATE TABLE IF NOT EXISTS posts ( + id SERIAL PRIMARY KEY, + title VARCHAR(255) NOT NULL, + content TEXT NOT NULL, + category VARCHAR(100) NOT NULL, + tags TEXT[], -- Using an array of text for tags + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); +` + _, err := db.Exec(migrationScript) + if err != nil { + return fmt.Errorf("failed to initialize the database: %w", err) + } + + log.Println("Database migration performed successfully.") + return nil +} + +func setupConnection(cfg map[string]string) (*sql.DB, error) { + // Construct the DSN (Data Source Name) + dsn := fmt.Sprintf( + "host=%s port=%s user=%s password=%s dbname=%s sslmode=%s", + cfg["DB_HOST"], cfg["DB_PORT"], cfg["DB_USER"], + cfg["DB_PASSWORD"], cfg["DB_NAME"], cfg["DB_SSLMODE"], + ) + + // Open the database connection + db, err := sql.Open("postgres", dsn) + if err != nil { + return nil, fmt.Errorf("failed to connect to database: %w", err) + } + + // Check if the connection is valid + if err := db.Ping(); err != nil { + return nil, fmt.Errorf("failed to ping database: %w", err) + } + + log.Println("Database initialized successfully.") + return db, nil +} diff --git a/exercise7/blogging-platform/internal/model/post.go b/exercise7/blogging-platform/internal/model/post.go new file mode 100644 index 00000000..9ae97914 --- /dev/null +++ b/exercise7/blogging-platform/internal/model/post.go @@ -0,0 +1,13 @@ +package model + +import "time" + +type Post struct { + ID int `json:"id"` // ID is the primary key for the blog post + Title string `json:"title"` // Title is the title of the blog post + Content string `json:"content"` // Content is the main content of the blog post + Category string `json:"category"` // Category is the category under which the blog post falls + Tags []string `json:"tags"` // Tags are associated tags for the blog post + CreatedAt time.Time `json:"createdAt"` // CreatedAt is the timestamp when the post was created + UpdatedAt time.Time `json:"updatedAt"` // UpdatedAt is the timestamp when the post was last updated +} diff --git a/exercise7/blogging-platform/internal/service/service.go b/exercise7/blogging-platform/internal/service/service.go new file mode 100644 index 00000000..fd82c807 --- /dev/null +++ b/exercise7/blogging-platform/internal/service/service.go @@ -0,0 +1,157 @@ +package service + +import ( + "database/sql" + "errors" + "fmt" + "github.com/talgat-ruby/exercises-go/exercise7/blogging-platform/internal/model" + "strings" + "time" +) + +type PostService struct { + db *sql.DB +} + +func NewPostService(db *sql.DB) *PostService { + return &PostService{ + db: db, + } +} + +func (ps *PostService) GetPosts() ([]model.Post, error) { + rows, err := ps.db.Query("SELECT id, title, content, category, tags, created_at, updated_at FROM posts") + if err != nil { + return nil, fmt.Errorf("failed to retrieve posts: %w", err) + } + defer rows.Close() + + var posts []model.Post + for rows.Next() { + var post model.Post + var tags string + if err := rows.Scan(&post.ID, &post.Title, &post.Content, &post.Category, &tags, &post.CreatedAt, &post.UpdatedAt); err != nil { + return nil, fmt.Errorf("failed to scan post: %w", err) + } + + // Convert comma-separated tags string to a slice + post.Tags = splitTags(tags) + + posts = append(posts, post) + } + + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("error iterating over posts: %w", err) + } + + return posts, nil +} + +func (ps *PostService) GetPost(term string) ([]model.Post, error) { + // Construct the query with a wildcard search if a term is provided + query := `SELECT id, title, content, category, tags, created_at, updated_at + FROM posts WHERE title ILIKE $1 OR content ILIKE $1 OR category ILIKE $1` + + // Execute the query + rows, err := ps.db.Query(query, "%"+term+"%") + if err != nil { + return nil, fmt.Errorf("failed to retrieve posts: %w", err) + } + defer rows.Close() + + var posts []model.Post + for rows.Next() { + var post model.Post + var tags string + if err := rows.Scan(&post.ID, &post.Title, &post.Content, &post.Category, &tags, &post.CreatedAt, &post.UpdatedAt); err != nil { + return nil, fmt.Errorf("failed to scan post: %w", err) + } + + // Convert comma-separated tags string to a slice + post.Tags = splitTags(tags) + + posts = append(posts, post) + } + + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("error iterating over posts: %w", err) + } + + return posts, nil +} + +// CreatePost inserts a new post into the database +func (ps *PostService) CreatePost(post *model.Post) (*model.Post, error) { + query := ` + INSERT INTO posts (title, content, category, tags) + VALUES ($1, $2, $3, $4) + RETURNING id, created_at, updated_at + ` + + tags := joinTags(post.Tags) + + err := ps.db.QueryRow(query, post.Title, post.Content, post.Category, tags). + Scan(&post.ID, &post.CreatedAt, &post.UpdatedAt) + if err != nil { + return nil, fmt.Errorf("failed to create post: %w", err) + } + + return post, nil +} + +// UpdatePost updates an existing post in the database +func (ps *PostService) UpdatePost(post *model.Post) (*model.Post, error) { + updated := time.Now() + post.UpdatedAt = updated + query := ` + UPDATE posts + SET title = $1, content = $2, category = $3, tags = $4, updated_at = $5 + WHERE id = $6 + RETURNING created_at + ` + + tags := joinTags(post.Tags) + + err := ps.db.QueryRow(query, post.Title, post.Content, post.Category, tags, updated, post.ID). + Scan(&post.CreatedAt) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("post not found") + } + return nil, fmt.Errorf("failed to update post: %w", err) + } + + return post, nil +} + +// DeletePost deletes a post by its ID from the database +func (ps *PostService) DeletePost(id int) error { + result, err := ps.db.Exec("DELETE FROM posts WHERE id = $1", id) + if err != nil { + return fmt.Errorf("failed to delete post: %w", err) + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("failed to retrieve the number of deleted rows: %w", err) + } + + if rowsAffected == 0 { + return fmt.Errorf("post not found") + } + return nil +} + +// Helper function to split the comma-separated tags string into a slice of strings +func splitTags(tags string) []string { + if tags == "" { + return []string{} + } + tags = tags[1 : len(tags)-1] + return strings.Split(tags, ",") // Split tags by commas +} + +// Helper function to join a slice of strings into a comma-separated tags string +func joinTags(tags []string) string { + return "{" + strings.Join(tags, ",") + "}" // Join tags into a single comma-separated string +} diff --git a/exercise7/blogging-platform/main.go b/exercise7/blogging-platform/main.go index 1ffa1477..cd31a7dd 100644 --- a/exercise7/blogging-platform/main.go +++ b/exercise7/blogging-platform/main.go @@ -2,44 +2,38 @@ package main import ( "context" + "github.com/talgat-ruby/exercises-go/exercise7/blogging-platform/internal/service" "log/slog" "os" "os/signal" "github.com/talgat-ruby/exercises-go/exercise7/blogging-platform/internal/api" + "github.com/talgat-ruby/exercises-go/exercise7/blogging-platform/internal/config" "github.com/talgat-ruby/exercises-go/exercise7/blogging-platform/internal/db" ) func main() { ctx, cancel := context.WithCancel(context.Background()) - // db - _, err := db.New() + cfg, err := config.LoadConfig("config/app.properties") if err != nil { - slog.ErrorContext( - ctx, - "initialize service error", - "service", "db", - "error", err, - ) + slog.ErrorContext(ctx, "failed to load configuration", "error", err) panic(err) } - // api - a := api.New() + dbConn := db.InitDatabase(cfg, ctx) + + postService := service.NewPostService(dbConn) + + a := api.New(cfg, postService) if err := a.Start(ctx); err != nil { - slog.ErrorContext( - ctx, - "initialize service error", - "service", "api", - "error", err, - ) + slog.ErrorContext(ctx, "initialize service error", "service", "api", "error", err) panic(err) } go func() { - shutdown := make(chan os.Signal, 1) // Create channel to signify s signal being sent - signal.Notify(shutdown, os.Interrupt) // When an interrupt is sent, notify the channel + shutdown := make(chan os.Signal, 1) + signal.Notify(shutdown, os.Interrupt) sig := <-shutdown slog.WarnContext(ctx, "signal received - shutting down...", "signal", sig)