├── .github
└── workflows
│ └── ci.yaml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── backend
├── Dockerfile
├── Makefile
├── cmd
│ └── app
│ │ └── main.go
├── config.yaml
├── go.mod
├── go.sum
├── internal
│ ├── config
│ │ └── config.go
│ ├── requesters
│ │ ├── reddit_requester.go
│ │ ├── requester.go
│ │ ├── requester_container.go
│ │ ├── social_network_requester.go
│ │ └── telegram_requester.go
│ ├── server
│ │ └── server.go
│ └── sharedData
│ │ └── config.go
└── test
│ └── social_network_requester_test.go
├── docker-compose.yaml
├── frontend
├── .gitignore
├── Dockerfile
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
└── src
│ ├── App.css
│ ├── App.jsx
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── reportWebVitals.js
│ └── setupTests.js
├── go.work
└── go.work.sum
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 |
11 | jobs:
12 |
13 | build:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v3
17 |
18 | - name: Build
19 | run: make run
20 |
21 | test:
22 | runs-on: ubuntu-latest
23 | steps:
24 | - uses: actions/checkout@v3
25 |
26 | - name: Test
27 | run: |
28 | cd backend/test
29 | go test .
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # System
2 | .DS_Store
3 |
4 | # IDE
5 | .fleet/
6 | .idea/
7 | .vscode/
8 |
9 | # Binaries
10 | *.o
11 | bin/
12 | build/
13 | target/
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Oleg Sidorenkov (olegdayo)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | cd backend && GOOS=linux GOARCH=amd64 make build
3 |
4 | run: build
5 | sudo docker compose up --force-recreate --build -d
6 |
7 | check:
8 | cd checkout && make check
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GotchaPage project
2 |
3 | [ci]: https://github.com/olegdayo/gotcha-page/actions/workflows/ci.yaml/badge.svg
4 |
5 | ![CI][ci]
6 |
7 | ### About
8 | _GotchaPage_ is a web application which allows to get list of pages with the given nickname from different social networks.
9 |
10 | Available social networks:
11 | - [ ] Discord
12 | - [x] Facebook
13 | - [x] GitHub
14 | - [x] GitLab
15 | - [x] Instagram
16 | - [ ] Reddit
17 | - [ ] Telegram
18 | - [x] VK
19 | - [x] YouTube
20 |
--------------------------------------------------------------------------------
/backend/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:22.04
2 |
3 | ADD ./bin/app /app
4 | ADD ./config.yaml /config.yaml
5 |
6 | CMD ["./app"]
7 |
--------------------------------------------------------------------------------
/backend/Makefile:
--------------------------------------------------------------------------------
1 | CURRDIR=$(shell pwd)
2 | BINDIR=${CURRDIR}/bin
3 | GOVER=$(shell go version | perl -nle '/(go\d\S+)/; print $$1;')
4 | SMARTIMPORTS=${BINDIR}/smartimports_${GOVER}
5 | LINTVER=v1.51.1
6 | LINTBIN=${BINDIR}/lint_${GOVER}_${LINTVER}
7 | PACKAGE=github.com/olegdayo/gotcha-page/backend/cmd/app
8 |
9 | .PHONY: all
10 | all: format build test lint
11 |
12 | .PHONY: build
13 | build: bindir
14 | go build -o ${BINDIR}/app ${PACKAGE}
15 |
16 | .PHONY: test
17 | test:
18 | go test ./...
19 |
20 | .PHONY: run
21 | run:
22 | go run ${PACKAGE}
23 |
24 | .PHONY: lint
25 | lint: install-lint
26 | ${LINTBIN} run
27 |
28 | .PHONY: check
29 | check: format build test lint
30 | echo "OK"
31 |
32 | .PHONY: bindir
33 | bindir:
34 | mkdir -p ${BINDIR}
35 |
36 | .PHONY: format
37 | format: install-smartimports
38 | ${SMARTIMPORTS} -exclude internal/mocks
39 |
40 | .PHONY: install-linter
41 | install-linter: bindir
42 | test -f ${LINTBIN} || \
43 | (GOBIN=${BINDIR} go install github.com/golangci/golangci-lint/cmd/golangci-lint@${LINTVER} && \
44 | mv ${BINDIR}/golangci-lint ${LINTBIN})
45 |
46 | .PHONY: install-smartimports
47 | install-smartimports: bindir
48 | test -f ${SMARTIMPORTS} || \
49 | (GOBIN=${BINDIR} go install github.com/pav5000/smartimports/cmd/smartimports@latest && \
50 | mv ${BINDIR}/smartimports ${SMARTIMPORTS})
51 |
--------------------------------------------------------------------------------
/backend/cmd/app/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "runtime"
6 |
7 | "github.com/olegdayo/gotcha-page/backend/internal/server"
8 | "github.com/olegdayo/gotcha-page/backend/internal/sharedData"
9 | )
10 |
11 | func init() {
12 | runtime.GOMAXPROCS(runtime.NumCPU())
13 |
14 | err := sharedData.InitConfig("config.yaml")
15 | if err != nil {
16 | log.Fatalf("Config error: %s\n", err.Error())
17 | }
18 |
19 | log.Printf("Config: %+v\n", sharedData.GetConfig())
20 | }
21 |
22 | // Here we start.
23 | func main() {
24 | s := server.NewServer(sharedData.GetConfig().Server.Port)
25 | err := s.Start()
26 | if err != nil {
27 | log.Fatalf("Server running error: %s\n", err.Error())
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/backend/config.yaml:
--------------------------------------------------------------------------------
1 | server:
2 | url: localhost
3 | port: 8080
4 |
5 | networks:
6 | - id: facebook
7 | name: Facebook
8 | url: facebook.com
9 |
10 | - id: github
11 | name: Github
12 | url: github.com
13 |
14 | - id: gitlab
15 | name: Gitlab
16 | url: gitlab.com
17 |
18 | - id: instagram
19 | name: Instagram
20 | url: instagram.com
21 |
22 | - id: vk
23 | name: VK
24 | url: vk.com
25 |
26 | - id: youtube
27 | name: Youtube
28 | url: youtube.com/c
29 |
--------------------------------------------------------------------------------
/backend/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/olegdayo/gotcha-page/backend
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/PuerkitoBio/goquery v1.8.1
7 | github.com/go-chi/chi/v5 v5.0.8
8 | github.com/go-chi/cors v1.2.1
9 | gopkg.in/yaml.v2 v2.4.0
10 | )
11 |
12 | require (
13 | github.com/andybalholm/cascadia v1.3.1 // indirect
14 | golang.org/x/net v0.7.0 // indirect
15 | )
16 |
--------------------------------------------------------------------------------
/backend/go.sum:
--------------------------------------------------------------------------------
1 | github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
2 | github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
3 | github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
4 | github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
5 | github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
6 | github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
7 | github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
8 | github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
9 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
10 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
11 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
12 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
13 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
14 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
15 | golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
16 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
17 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
18 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
19 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
20 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
21 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
22 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
23 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
24 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
25 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
26 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
27 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
28 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
29 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
30 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
31 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
32 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
33 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
34 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
35 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
36 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
37 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
38 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
39 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
40 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
41 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
42 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
43 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
44 |
--------------------------------------------------------------------------------
/backend/internal/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "gopkg.in/yaml.v2"
5 | "os"
6 | )
7 |
8 | type Network struct {
9 | ID string `json:"id" yaml:"id"`
10 | Name string `json:"name" yaml:"name"`
11 | URL string `json:"url" yaml:"url"`
12 | }
13 |
14 | type Server struct {
15 | URL string `yaml:"url"`
16 | Port uint16 `yaml:"port"`
17 | }
18 |
19 | type Config struct {
20 | Server *Server `yaml:"server" json:"-"`
21 | Networks []Network `yaml:"networks" json:"networks"`
22 | }
23 |
24 | func Import(path string) (Config, error) {
25 | bytes, err := os.ReadFile(path)
26 | if err != nil {
27 | return Config{}, err
28 | }
29 |
30 | config := Config{}
31 | err = yaml.Unmarshal(bytes, &config)
32 | if err != nil {
33 | return Config{}, err
34 | }
35 |
36 | return config, nil
37 | }
38 |
--------------------------------------------------------------------------------
/backend/internal/requesters/reddit_requester.go:
--------------------------------------------------------------------------------
1 | package requesters
2 |
3 | type RedditRequester struct {
4 | // Home page url without "https://".
5 | mainURL string
6 | // User's nickname.
7 | // For example, "olegsama".
8 | nickname string
9 | // Requester availability.
10 | // If selected, it can be used to parse site.
11 | selected bool
12 | }
13 |
14 | // NewRedditRequester is a constructor.
15 | func NewRedditRequester(nickname string) (rr *RedditRequester) {
16 | rr = &RedditRequester{
17 | mainURL: "",
18 | nickname: nickname,
19 | selected: false,
20 | }
21 | return rr
22 | }
23 |
24 | // GetName gets name of a telegram.
25 | func (rr *RedditRequester) GetName() (name string) {
26 | return "Reddit"
27 | }
28 |
29 | // GetNickname gets nickname of a user.
30 | func (rr *RedditRequester) GetNickname() (nickname string) {
31 | return rr.nickname
32 | }
33 |
34 | // IsSelected shows if requester is available.
35 | func (rr *RedditRequester) IsSelected() (selected bool) {
36 | return rr.selected
37 | }
38 |
39 | // SetAvailability sets availability condition.
40 | func (rr *RedditRequester) SetAvailability(cond bool) {
41 | rr.selected = cond
42 | }
43 |
44 | // GetInfo gets url and name of user by their nickname.
45 | func (rr *RedditRequester) GetInfo() (url string, name string, err error) {
46 | return "", "", nil
47 | }
48 |
--------------------------------------------------------------------------------
/backend/internal/requesters/requester.go:
--------------------------------------------------------------------------------
1 | package requesters
2 |
3 | // Requester interface used in ParserContainer to include all parsers which implement it.
4 | type Requester interface {
5 | // GetName gets name of requester.
6 | GetName() (name string)
7 | // GetNickname gets nickname of a user.
8 | GetNickname() (nickname string)
9 | // IsSelected shows if requester is selected.
10 | IsSelected() (selected bool)
11 | // SetAvailability sets availability condition.
12 | SetAvailability(cond bool)
13 | // GetInfo gets url and name of user by their nickname.
14 | GetInfo() (url string, name string, err error)
15 | }
16 |
--------------------------------------------------------------------------------
/backend/internal/requesters/requester_container.go:
--------------------------------------------------------------------------------
1 | package requesters
2 |
3 | import (
4 | "fmt"
5 | "github.com/olegdayo/gotcha-page/backend/internal/sharedData"
6 | "log"
7 | "sort"
8 | )
9 |
10 | // UserInfo is a struct with all user info.
11 | type UserInfo struct {
12 | // User's nickname.
13 | Nickname string `json:"nickname"`
14 | // Social network name.
15 | SocialNetwork string `json:"url"`
16 | // User's profile link.
17 | Link string `json:"link"`
18 | // User's name from
tag.
19 | Name string `json:"name"`
20 | // User availability.
21 | // True if everything is ok.
22 | // False if during parsing an error occurred.
23 | IsAvailable bool `json:"available"`
24 | }
25 |
26 | // RequesterContainer is a container of requesters.
27 | type RequesterContainer struct {
28 | // Nickname of a user we are looking for.
29 | nickname string
30 | // Requesters.
31 | Requesters map[string]Requester
32 | }
33 |
34 | // NewRequesterContainer initializes all requesters we have.
35 | // NewRequesterContainer sets requesters availability to false statement.
36 | func NewRequesterContainer(nickname string) (rc *RequesterContainer) {
37 | rc = new(RequesterContainer)
38 | rc.Requesters = make(map[string]Requester)
39 | for _, network := range sharedData.GetConfig().Networks {
40 | rc.Requesters[network.ID] = NewSocialNetworkRequester(network.Name, network.URL, nickname)
41 | }
42 | return rc
43 | }
44 |
45 | // SetUsedLinks sets ticked checkboxes.
46 | func (rc *RequesterContainer) SetUsedLinks(clients ...string) {
47 | for _, parser := range clients {
48 | if _, ok := rc.Requesters[parser]; ok {
49 | log.Println(parser)
50 | rc.Requesters[parser].SetAvailability(true)
51 | }
52 | }
53 | }
54 |
55 | // Function getNumberOfAvailableRequesters gets number of selected requesters.
56 | func (rc *RequesterContainer) getNumberOfAvailableRequesters() (number int) {
57 | number = 0
58 | for _, requester := range rc.Requesters {
59 | if requester.IsSelected() {
60 | number++
61 | }
62 | }
63 | return number
64 | }
65 |
66 | // GetLink gets all users' with given nickname info from given site.
67 | func GetLink(requester Requester, linksChannel chan<- *UserInfo) {
68 | // Getting info.
69 | link, name, err := requester.GetInfo()
70 | var user *UserInfo
71 |
72 | if err == nil {
73 | // Everything is ok, adding.
74 | log.Println(requester.GetName() + ": " + link)
75 | user = &UserInfo{
76 | Nickname: requester.GetNickname(),
77 | SocialNetwork: requester.GetName(),
78 | Link: link,
79 | Name: name,
80 | IsAvailable: true,
81 | }
82 | } else {
83 | // Error occurred.
84 | log.Println(requester.GetName() + ": " + err.Error())
85 | user = &UserInfo{
86 | Nickname: requester.GetNickname(),
87 | SocialNetwork: requester.GetName(),
88 | Link: link,
89 | Name: fmt.Sprintf("%s: %s not found", requester.GetName(), requester.GetNickname()),
90 | IsAvailable: false,
91 | }
92 | }
93 |
94 | linksChannel <- user
95 | }
96 |
97 | // GetLinks gets all users' with given nickname info from given slice of sites.
98 | func (rc *RequesterContainer) GetLinks() (links []*UserInfo) {
99 | var selectedRequestersNumber int = rc.getNumberOfAvailableRequesters()
100 | linksChannel := make(chan *UserInfo, selectedRequestersNumber)
101 |
102 | for _, requester := range rc.Requesters {
103 | // If requester is not available -> skip.
104 | if !requester.IsSelected() {
105 | continue
106 | }
107 |
108 | log.Println(requester.GetName())
109 | go GetLink(requester, linksChannel)
110 | }
111 |
112 | links = make([]*UserInfo, selectedRequestersNumber)
113 | for i := 0; i < selectedRequestersNumber; i++ {
114 | links[i] = <-linksChannel
115 | log.Println(links[i])
116 | }
117 | close(linksChannel)
118 |
119 | sort.Slice(
120 | links,
121 | func(i int, j int) bool {
122 | return links[i].SocialNetwork < links[j].SocialNetwork
123 | },
124 | )
125 | return links
126 | }
127 |
--------------------------------------------------------------------------------
/backend/internal/requesters/social_network_requester.go:
--------------------------------------------------------------------------------
1 | package requesters
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/PuerkitoBio/goquery"
7 | "io"
8 | "log"
9 | "net/http"
10 | "strings"
11 | )
12 |
13 | type SocialNetworkRequester struct {
14 | // Social network name.
15 | // For github it will be "GitHub".
16 | name string
17 | // Home page url without "https://".
18 | // For github it will be "github.com".
19 | mainURL string
20 | // User's nickname.
21 | // For example, "olegdayo".
22 | nickname string
23 | // Requester availability.
24 | // If selected, it can be used to parse site.
25 | selected bool
26 | }
27 |
28 | // NewSocialNetworkRequester is a constructor.
29 | func NewSocialNetworkRequester(name string, mainURL string, nickname string) (snr *SocialNetworkRequester) {
30 | snr = &SocialNetworkRequester{
31 | name: name,
32 | mainURL: mainURL,
33 | nickname: nickname,
34 | selected: false,
35 | }
36 | return snr
37 | }
38 |
39 | // GetName gets name of a social network.
40 | func (snr *SocialNetworkRequester) GetName() (name string) {
41 | return snr.name
42 | }
43 |
44 | // GetNickname gets nickname of a user.
45 | func (snr *SocialNetworkRequester) GetNickname() (nickname string) {
46 | return snr.nickname
47 | }
48 |
49 | // IsSelected shows if requester is available.
50 | func (snr *SocialNetworkRequester) IsSelected() (selected bool) {
51 | return snr.selected
52 | }
53 |
54 | // SetAvailability sets availability condition.
55 | func (snr *SocialNetworkRequester) SetAvailability(cond bool) {
56 | snr.selected = cond
57 | }
58 |
59 | // GetInfo gets url and name of user by their nickname.
60 | func (snr *SocialNetworkRequester) GetInfo() (url string, name string, err error) {
61 | var link string = fmt.Sprintf("https://%s/", snr.mainURL) + snr.nickname
62 |
63 | // Getting response.
64 | page, err := http.Get(link)
65 | if err != nil {
66 | return "", "", err
67 | }
68 |
69 | // Closing response before leaving the function.
70 | defer func(Body io.ReadCloser) {
71 | err := Body.Close()
72 | if err != nil {
73 | log.Fatalln(err)
74 | }
75 | }(page.Body)
76 |
77 | if page.StatusCode == 404 {
78 | // Page not found.
79 | return "page not found", "", errors.New("page not found")
80 | } else if page.StatusCode != 200 {
81 | // Some other error.
82 | // For example, 403 forbidden.
83 | return "", "", errors.New(fmt.Sprintf("status code is %d", page.StatusCode))
84 | }
85 |
86 | // Getting goquery document.
87 | info, err := goquery.NewDocumentFromReader(page.Body)
88 | if err != nil {
89 | return "", "", err
90 | }
91 |
92 | // The link is ok -> sending it and getting user's name from tag.
93 | return strings.TrimSpace(link), strings.TrimSpace(info.Find("title").Text()), nil
94 | }
95 |
--------------------------------------------------------------------------------
/backend/internal/requesters/telegram_requester.go:
--------------------------------------------------------------------------------
1 | package requesters
2 |
3 | type TelegramRequester struct {
4 | // Home page url without "https://".
5 | mainURL string
6 | // User's nickname.
7 | // For example, "olegsama".
8 | nickname string
9 | // Requester availability.
10 | // If selected, it can be used to parse site.
11 | selected bool
12 | }
13 |
14 | // NewTelegramRequester is a constructor.
15 | func NewTelegramRequester(nickname string) (tr *TelegramRequester) {
16 | tr = &TelegramRequester{
17 | mainURL: "",
18 | nickname: nickname,
19 | selected: false,
20 | }
21 | return tr
22 | }
23 |
24 | // GetName gets name of a telegram.
25 | func (tr *TelegramRequester) GetName() (name string) {
26 | return "Telegram"
27 | }
28 |
29 | // GetNickname gets nickname of a user.
30 | func (tr *TelegramRequester) GetNickname() (nickname string) {
31 | return tr.nickname
32 | }
33 |
34 | // IsSelected shows if requester is available.
35 | func (tr *TelegramRequester) IsSelected() (selected bool) {
36 | return tr.selected
37 | }
38 |
39 | // SetAvailability sets availability condition.
40 | func (tr *TelegramRequester) SetAvailability(cond bool) {
41 | tr.selected = cond
42 | }
43 |
44 | // GetInfo gets url and name of user by their nickname.
45 | func (tr *TelegramRequester) GetInfo() (url string, name string, err error) {
46 | return "", "", nil
47 | }
48 |
--------------------------------------------------------------------------------
/backend/internal/server/server.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/go-chi/chi/v5"
7 | "github.com/go-chi/chi/v5/middleware"
8 | "github.com/go-chi/cors"
9 | "github.com/olegdayo/gotcha-page/backend/internal/requesters"
10 | "github.com/olegdayo/gotcha-page/backend/internal/sharedData"
11 | "log"
12 | "net/http"
13 | "strings"
14 | )
15 |
16 | type Server struct {
17 | http.Server
18 | }
19 |
20 | func NewServer(port uint16) (s *Server) {
21 | s = new(Server)
22 | s.Addr = fmt.Sprintf(":%d", port)
23 | s.Handler = setRouter()
24 | return
25 | }
26 |
27 | func (s *Server) Start() error {
28 | log.Println("Starting the server")
29 | return s.ListenAndServe()
30 | }
31 |
32 | func setRouter() *chi.Mux {
33 | r := chi.NewRouter()
34 | r.Use(middleware.Logger)
35 | c := cors.New(cors.Options{
36 | AllowedOrigins: []string{"*"},
37 | AllowedMethods: []string{"GET", "POST"},
38 | AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
39 | AllowCredentials: true,
40 | MaxAge: 1000,
41 | })
42 | r.Use(c.Handler)
43 |
44 | r.Get("/", getNetworks)
45 | r.Get("/{nickname}", getUsers)
46 |
47 | return r
48 | }
49 |
50 | func getNetworks(w http.ResponseWriter, _ *http.Request) {
51 | w.Header().Set("Access-Control-Allow-Origin", "*")
52 |
53 | links, err := json.Marshal(sharedData.GetConfig())
54 | if err != nil {
55 | log.Fatalf("Marshal error: %s\n", err.Error())
56 | return
57 | }
58 |
59 | _, err = w.Write(links)
60 | if err != nil {
61 | log.Fatalf("Write error: %s\n", err.Error())
62 | }
63 | }
64 |
65 | func getUsers(w http.ResponseWriter, r *http.Request) {
66 | nickname := chi.URLParam(r, "nickname")
67 | clients := strings.Split(
68 | strings.TrimRight(
69 | strings.TrimLeft(
70 | r.URL.Query().Get("networks"),
71 | "[",
72 | ),
73 | "]",
74 | ),
75 | " ",
76 | )
77 | fmt.Println(nickname)
78 | fmt.Println(clients)
79 |
80 | // Container initialization and execution.
81 | container := requesters.NewRequesterContainer(nickname)
82 | container.SetUsedLinks(clients...)
83 |
84 | usersInfo := container.GetLinks()
85 |
86 | users, err := json.Marshal(
87 | struct {
88 | Users []*requesters.UserInfo `json:"users"`
89 | }{
90 | Users: usersInfo,
91 | },
92 | )
93 | if err != nil {
94 | log.Fatalf("Marshal error: %s\n", err.Error())
95 | return
96 | }
97 |
98 | _, err = w.Write(users)
99 | if err != nil {
100 | log.Fatalf("Write error: %s\n", err.Error())
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/backend/internal/sharedData/config.go:
--------------------------------------------------------------------------------
1 | package sharedData
2 |
3 | import "github.com/olegdayo/gotcha-page/backend/internal/config"
4 |
5 | var (
6 | conf config.Config
7 | )
8 |
9 | func InitConfig(path string) error {
10 | var err error
11 | conf, err = config.Import(path)
12 | return err
13 | }
14 |
15 | func GetConfig() config.Config {
16 | return conf
17 | }
18 |
--------------------------------------------------------------------------------
/backend/test/social_network_requester_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "github.com/olegdayo/gotcha-page/backend/internal/requesters"
6 | "testing"
7 | )
8 |
9 | func TestNewSocialNetworkRequester(t *testing.T) {
10 | t.Parallel()
11 | testCases := []struct {
12 | name string
13 | mainURL string
14 | nickname string
15 | selected bool
16 | expected *requesters.SocialNetworkRequester
17 | }{
18 | {
19 | name: "VK",
20 | mainURL: "vk.com",
21 | nickname: "olegdayo",
22 | selected: false,
23 | expected: requesters.NewSocialNetworkRequester(
24 | "VK",
25 | "vk.com",
26 | "olegdayo",
27 | ),
28 | },
29 | {
30 | name: "GitHub",
31 | mainURL: "github.com",
32 | nickname: "olegdayo",
33 | selected: true,
34 | expected: requesters.NewSocialNetworkRequester(
35 | "GitHub",
36 | "github.com",
37 | "olegdayo",
38 | ),
39 | },
40 | {
41 | name: "GitLab",
42 | mainURL: "gitlab.com",
43 | nickname: "olegdayo",
44 | selected: true,
45 | expected: requesters.NewSocialNetworkRequester(
46 | "GitLab",
47 | "gitlab.com",
48 | "olegdayo",
49 | ),
50 | },
51 | }
52 | testCases[1].expected.SetAvailability(true)
53 | testCases[2].expected.SetAvailability(true)
54 |
55 | for index, testCase := range testCases {
56 | t.Run(fmt.Sprintf("Test№%d", index), func(t *testing.T) {
57 | got := requesters.NewSocialNetworkRequester(
58 | testCase.name,
59 | testCase.mainURL,
60 | testCase.nickname,
61 | )
62 | got.SetAvailability(index > 0)
63 |
64 | if *testCase.expected != *got {
65 | t.Errorf("Error while testing %s", testCase.expected.GetName())
66 | }
67 | })
68 | }
69 | }
70 |
71 | func TestGetName(t *testing.T) {
72 | t.Parallel()
73 | testCases := []struct {
74 | requester *requesters.SocialNetworkRequester
75 | expected string
76 | }{
77 | {
78 | requesters.NewSocialNetworkRequester(
79 | "VK",
80 | "vk.com",
81 | "olegdayo",
82 | ),
83 | "VK",
84 | },
85 | {
86 | requesters.NewSocialNetworkRequester(
87 | "GitHub",
88 | "github.com",
89 | "olegdayo",
90 | ),
91 | "GitHub",
92 | },
93 | {
94 | requesters.NewSocialNetworkRequester(
95 | "GitLab",
96 | "gitlab.com",
97 | "olegdayo",
98 | ),
99 | "GitLab",
100 | },
101 | }
102 |
103 | for index, testCase := range testCases {
104 | t.Run(fmt.Sprintf("Test№%d", index), func(t *testing.T) {
105 | got := testCase.requester.GetName()
106 | if testCase.expected != got {
107 | t.Errorf("Expected: %s; got: %s\n", testCase.expected, got)
108 | }
109 | })
110 | }
111 | }
112 |
113 | func TestGetNickname(t *testing.T) {
114 | t.Parallel()
115 | testCases := []struct {
116 | requester *requesters.SocialNetworkRequester
117 | expected string
118 | }{
119 | {
120 | requesters.NewSocialNetworkRequester(
121 | "VK",
122 | "vk.com",
123 | "olegdayo",
124 | ),
125 | "olegsama",
126 | },
127 | {
128 | requesters.NewSocialNetworkRequester(
129 | "GitHub",
130 | "github.com",
131 | "olegdayo",
132 | ),
133 | "olegdayo",
134 | },
135 | {
136 | requesters.NewSocialNetworkRequester(
137 | "GitLab",
138 | "gitlab.com",
139 | "olegdayo",
140 | ),
141 | "olegdayo",
142 | },
143 | }
144 |
145 | for index, testCase := range testCases {
146 | t.Run(fmt.Sprintf("Test№%d", index), func(t *testing.T) {
147 | got := testCase.requester.GetNickname()
148 | if testCase.expected != got {
149 | t.Errorf("Expected: %s; got: %s\n", testCase.expected, got)
150 | }
151 | })
152 | }
153 | }
154 |
155 | func TestIsAvailable(t *testing.T) {
156 | t.Parallel()
157 | testCases := []struct {
158 | requester *requesters.SocialNetworkRequester
159 | expected bool
160 | }{
161 | {
162 | requester: requesters.NewSocialNetworkRequester(
163 | "VK",
164 | "vk.com",
165 | "olegdayo",
166 | ),
167 | expected: false,
168 | },
169 | {
170 | requester: requesters.NewSocialNetworkRequester(
171 | "GitHub",
172 | "github.com",
173 | "olegdayo",
174 | ),
175 | expected: true,
176 | },
177 | {
178 | requester: requesters.NewSocialNetworkRequester(
179 | "GitLab",
180 | "gitlab.com",
181 | "olegdayo",
182 | ),
183 | expected: true,
184 | },
185 | }
186 | testCases[1].requester.SetAvailability(true)
187 | testCases[2].requester.SetAvailability(true)
188 |
189 | for index, testCase := range testCases {
190 | t.Run(fmt.Sprintf("Test№%d", index), func(t *testing.T) {
191 | got := testCase.requester.IsSelected()
192 | if testCase.expected != got {
193 | t.Errorf("Expected: %v; got: %v\n", testCase.expected, got)
194 | }
195 | })
196 | }
197 | }
198 |
199 | func TestSetAvailability(t *testing.T) {
200 | t.Parallel()
201 | testCases := []struct {
202 | requester *requesters.SocialNetworkRequester
203 | expected bool
204 | }{
205 | {
206 | requester: requesters.NewSocialNetworkRequester(
207 | "VK",
208 | "vk.com",
209 | "olegdayo",
210 | ),
211 | expected: false,
212 | },
213 | {
214 | requester: requesters.NewSocialNetworkRequester(
215 | "GitHub",
216 | "github.com",
217 | "olegdayo",
218 | ),
219 | expected: true,
220 | },
221 | {
222 | requester: requesters.NewSocialNetworkRequester(
223 | "GitLab",
224 | "gitlab.com",
225 | "olegdayo",
226 | ),
227 | expected: true,
228 | },
229 | }
230 | testCases[1].requester.SetAvailability(true)
231 | testCases[2].requester.SetAvailability(true)
232 |
233 | var setter bool = true
234 | for index, testCase := range testCases {
235 | t.Run(fmt.Sprintf("Test№%d", index), func(t *testing.T) {
236 | testCase.requester.SetAvailability(setter)
237 | testCase.expected = setter
238 | setter = !setter
239 | got := testCase.requester.IsSelected()
240 | if testCase.expected != got {
241 | t.Errorf("Expected: %v; got: %v\n", testCase.expected, got)
242 | }
243 | })
244 | }
245 | }
246 |
247 | func TestGetInfo(t *testing.T) {
248 | t.Parallel()
249 | testCases := []struct {
250 | requester *requesters.SocialNetworkRequester
251 | expectedLink string
252 | expectedName string
253 | }{
254 | {
255 | requesters.NewSocialNetworkRequester(
256 | "VK",
257 | "vk.com",
258 | "olegsama",
259 | ),
260 | "https://vk.com/olegdayo",
261 | "Oleg Sidorenkov | VK",
262 | },
263 | {
264 | requesters.NewSocialNetworkRequester(
265 | "GitHub",
266 | "github.com",
267 | "olegdayo",
268 | ),
269 | "https://github.com/olegdayo",
270 | "olegdayo (Oleg) · GitHub",
271 | },
272 | {
273 | requesters.NewSocialNetworkRequester(
274 | "GitLab",
275 | "gitlab.com",
276 | "olegdayo",
277 | ),
278 | "https://gitlab.com/olegdayo",
279 | "Oleg · GitLab",
280 | },
281 | {
282 | requesters.NewSocialNetworkRequester(
283 | "VK",
284 | "vk.com",
285 | "dmvdfcjdjk123211hj23123bhwhb1hb3j",
286 | ),
287 | "page not found",
288 | "",
289 | },
290 | {
291 | requesters.NewSocialNetworkRequester(
292 | "GitHub",
293 | "github.com",
294 | "dm5vdfcj31djk2321151e34123214211hj2323e123sd211342bhwhb1hb3j",
295 | ),
296 | "page not found",
297 | "",
298 | },
299 | {
300 | requesters.NewSocialNetworkRequester(
301 | "Youtube",
302 | "youtube.com/c",
303 | "jcsxiuaiunxiu378wbedxs78w33bd2eqw9emimads0wq9oiqwd",
304 | ),
305 | "page not found",
306 | "",
307 | },
308 | }
309 |
310 | for index, testCase := range testCases {
311 | t.Run(fmt.Sprintf("Test№%d", index), func(t *testing.T) {
312 | gotLink, gotName, err := testCase.requester.GetInfo()
313 | if err == nil {
314 | if gotLink != testCase.expectedLink {
315 | t.Errorf("Expected: %s, %s; got: %s, %s\n", testCase.expectedLink, testCase.expectedName, gotLink, gotName)
316 | }
317 | return
318 | }
319 |
320 | if err.Error() == "page not found" {
321 | if gotLink != testCase.expectedLink {
322 | t.Errorf("Expected: %s, %s; got: %s, %s\n", testCase.expectedLink, testCase.expectedName, gotLink, gotName)
323 | }
324 | return
325 | }
326 |
327 | t.Fatalf("Unexpected error: %s\n", err)
328 | })
329 | }
330 | }
331 |
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3.6'
2 |
3 | services:
4 | backend:
5 | build: ./backend
6 | ports:
7 | - "8080:8080"
8 |
9 | frontend:
10 | build: ./frontend
11 | ports:
12 | - "3000:3000"
13 | depends_on:
14 | - backend
15 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # IDE
9 | /.idea/*
10 | /.vscode/*
11 |
12 | # testing
13 | /coverage
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | .env.local
21 | .env.development.local
22 | .env.test.local
23 | .env.production.local
24 |
25 | npm-debug.log*
26 | yarn-debug.log*
27 | yarn-error.log*
28 |
--------------------------------------------------------------------------------
/frontend/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 | WORKDIR /app
3 |
4 | ENV PATH /app/node_modules/.bin:$PATH
5 |
6 | COPY package.json ./
7 | COPY package-lock.json ./
8 | RUN npm install react-scripts@3.4.1 -g
9 |
10 | COPY . .
11 |
12 | CMD ["npm", "start"]
13 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gotcha-front",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.4",
7 | "@testing-library/react": "^13.2.0",
8 | "@testing-library/user-event": "^13.5.0",
9 | "axios": "^0.27.2",
10 | "react": "^18.1.0",
11 | "react-dom": "^18.1.0",
12 | "react-scripts": "5.0.1",
13 | "web-vitals": "^2.1.4"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test",
19 | "eject": "react-scripts eject"
20 | },
21 | "eslintConfig": {
22 | "extends": [
23 | "react-app",
24 | "react-app/jest"
25 | ]
26 | },
27 | "browserslist": {
28 | "production": [
29 | ">0.2%",
30 | "not dead",
31 | "not op_mini all"
32 | ],
33 | "development": [
34 | "last 1 chrome version",
35 | "last 1 firefox version",
36 | "last 1 safari version"
37 | ]
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olegdayo/gotcha-page/0e53d2e991360726418306f316c4fc8a5f3dea78/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/frontend/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olegdayo/gotcha-page/0e53d2e991360726418306f316c4fc8a5f3dea78/frontend/public/logo192.png
--------------------------------------------------------------------------------
/frontend/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olegdayo/gotcha-page/0e53d2e991360726418306f316c4fc8a5f3dea78/frontend/public/logo512.png
--------------------------------------------------------------------------------
/frontend/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/frontend/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/frontend/src/App.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: #96f3cf;
3 | font: 16px "Fira Sans", sans-serif;
4 | }
5 |
6 | .user-info {
7 | text-align: center;
8 | }
9 |
10 | label {
11 | text-align: center;
12 | color: red;
13 | }
14 |
15 | button {
16 | align-self: center;
17 | color: aqua;
18 | }
19 |
20 | label[for="select-all"] {
21 | color: darkblue;
22 | }
23 |
24 | .form {
25 | text-align: center;
26 | }
27 |
28 | ul {
29 | list-style-type: none;
30 | }
31 |
32 | li > label {
33 | color: black;
34 | }
35 |
36 | h3 {
37 | color: blue;
38 | font-size: 20px;
39 | font-style: italic;
40 | }
41 |
42 | .links {
43 | text-align: center;
44 | color: darkblue;
45 | list-style-type: none;
46 | }
47 |
48 | ul {
49 | list-style-type: none;
50 | }
51 |
--------------------------------------------------------------------------------
/frontend/src/App.jsx:
--------------------------------------------------------------------------------
1 | import './App.css';
2 | import React, {useEffect, useState} from "react";
3 | import axios from "axios";
4 |
5 | function App() {
6 | const [checkboxes, setCheckboxes] = useState([]);
7 | const [nickname, setNickname] = useState("");
8 | const [links, setLinks] = useState([]);
9 | useEffect(() => {
10 | axios.get("http://localhost:8080")
11 | .then((resp) => {
12 | setCheckboxes(resp.data.networks);
13 | })
14 | .catch((err) => {
15 | console.log(err)
16 | });
17 | }, []);
18 |
19 | function changeAll(event) {
20 | let selectAllCheckbox = checkboxes.find(x => x.id === event.target.name)
21 | setCheckboxes(checkboxes.map(
22 | (x) => {
23 | return {
24 | ...x, value: !selectAllCheckbox.value
25 | }
26 | }
27 | ))
28 | }
29 |
30 | function change(event) {
31 | let checkbox = checkboxes.find(x => x.id === event.target.name)
32 | console.log(checkbox)
33 | console.log(event.target.name)
34 | checkbox.value = !checkbox.value
35 | setCheckboxes(checkboxes)
36 | }
37 |
38 | function createRequestURL() {
39 | let link = "http://localhost:8080/" + nickname + "?clients=["
40 | checkboxes.map(
41 | x => {
42 | if (x.value) {
43 | link += x.id + " "
44 | }
45 | }
46 | )
47 | link = link.slice(0, -1) + "]"
48 | return link
49 | }
50 |
51 | function usersRequest() {
52 | axios.get(createRequestURL(), {
53 | nickname: nickname,
54 | parsers: checkboxes.filter(x => x.value).map(
55 | x => x.id
56 | )
57 | })
58 | .then((resp) => {
59 | setLinks(resp.data.users)
60 | console.log(resp.data)
61 | console.log(links)
62 | })
63 | .catch(
64 | console.log
65 | )
66 | }
67 |
68 | return (
69 | <>
70 |
71 |
74 | {
75 | setNickname(event.target.value)
76 | }} type="text" name="nickname"/>
77 |
78 |
79 |
80 |
81 |
95 |
96 |
97 |
98 | {
99 | links.map(
100 | (x, k) => {
101 | console.log(x)
102 | if (x.available) {
103 | return -
104 | {x.name}
105 |
106 | }
107 | return -
108 | {x.name}
109 |
110 | }
111 | )
112 | }
113 |
114 |
115 | >
116 | );
117 | }
118 |
119 | export default App;
120 |
--------------------------------------------------------------------------------
/frontend/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/frontend/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | const root = ReactDOM.createRoot(document.getElementById('root'));
8 | root.render(
9 |
10 |
11 |
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/frontend/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/frontend/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/go.work:
--------------------------------------------------------------------------------
1 | go 1.20
2 |
3 | use ./backend
4 |
--------------------------------------------------------------------------------
/go.work.sum:
--------------------------------------------------------------------------------
1 | github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
2 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
3 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
4 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
5 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
6 | golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
7 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
8 | golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
9 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
10 |
--------------------------------------------------------------------------------