├── back
├── tools
│ ├── .gitignore
│ ├── iso
│ │ ├── file
│ │ │ ├── openbdb
│ │ │ │ └── Dockerfile
│ │ │ └── openbsrv-full
│ │ │ │ └── Dockerfile
│ │ ├── compose
│ │ │ └── poc
│ │ │ │ └── docker-compose.yml
│ │ └── dev
│ ├── install-tools
│ ├── install-go
│ └── install-mariadb
├── internal
│ ├── usersvc
│ │ ├── internal
│ │ │ ├── asset
│ │ │ │ ├── assets
│ │ │ │ │ ├── 0002_setup_role_table.sql
│ │ │ │ │ ├── 0003_setup_join_user_role_join_table.sql
│ │ │ │ │ └── 0001_setup_user_table.sql
│ │ │ │ └── asset.go
│ │ │ └── userdb
│ │ │ │ ├── userdb.go
│ │ │ │ └── qryuser.go
│ │ └── usersvc.go
│ ├── postsvc
│ │ ├── internal
│ │ │ ├── asset
│ │ │ │ ├── assets
│ │ │ │ │ ├── 0003_add_post_fulltext_cols.sql
│ │ │ │ │ ├── 0002_setup_type_table.sql
│ │ │ │ │ └── 0001_setup_post_table.sql
│ │ │ │ └── asset.go
│ │ │ └── postdb
│ │ │ │ ├── postdb.go
│ │ │ │ └── qrypost.go
│ │ └── postsvc.go
│ ├── httpsrv
│ │ ├── internal
│ │ │ └── asset
│ │ │ │ └── asset.go
│ │ ├── gogen.go
│ │ └── httpsrv.go
│ ├── pb
│ │ ├── gogen.go
│ │ ├── auth_grpc.pb.go
│ │ ├── post_grpc.pb.go
│ │ └── user_grpc.pb.go
│ ├── grpcsrv
│ │ ├── io.go
│ │ └── grpcsrv.go
│ ├── altr
│ │ └── altr.go
│ ├── go.mod
│ ├── log
│ │ └── log.go
│ ├── authsvc
│ │ └── authsvc.go
│ └── dbg
│ │ ├── dbg.go
│ │ └── dbg_test.go
├── gengo
├── tests
│ └── openbsrv
│ │ ├── go.mod
│ │ ├── common_test.go
│ │ ├── run-tests
│ │ ├── usersvc_test.go
│ │ └── postsvc_test.go
├── cmd
│ └── openbsrv
│ │ ├── go.mod
│ │ ├── README.md
│ │ ├── httpsrv.go
│ │ ├── srvmgmt.go
│ │ ├── frontsrv.go
│ │ ├── grpcsrv.go
│ │ ├── db.go
│ │ └── main.go
└── README.md
├── front
├── .gitignore
├── public
│ ├── favicon.ico
│ ├── conf
│ │ └── manifest.json
│ ├── boot.js
│ └── index.html
├── src
│ ├── Viewer.elm
│ ├── Session.elm
│ ├── Page.elm
│ ├── Page
│ │ ├── Login.elm
│ │ ├── Home.elm
│ │ └── Posts.elm
│ ├── Route.elm
│ ├── Proto
│ │ └── Auth.elm
│ ├── Ui.elm
│ └── Main.elm
├── genelm
├── tests
│ └── Tests.elm
├── elm.json
├── README.md
└── bin
│ └── pb2elm
├── docs
├── learning
│ ├── README.md
│ ├── go_cmd.md
│ └── git.md
├── AUTHORS
├── CONTRIBUTING.md
└── CODE_OF_CONDUCT.md
├── .github
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
└── workflows
│ ├── projmgmt-issue-set_claimed.yml
│ ├── projmgmt-pullreq-set_initiated.yml
│ ├── projmgmt-pullreq-set_submitted.yml
│ ├── projmgmt-pullreq-set_unsubmitted.yml
│ └── wip.yml
├── msgs
└── proto
│ ├── include
│ └── googleapis
│ │ ├── README.grpc-gateway
│ │ ├── google
│ │ ├── api
│ │ │ └── annotations.proto
│ │ └── rpc
│ │ │ ├── status.proto
│ │ │ ├── code.proto
│ │ │ └── error_details.proto
│ │ └── LICENSE
│ ├── auth.proto
│ ├── post.proto
│ └── user.proto
├── README.md
└── LICENSE
/back/tools/.gitignore:
--------------------------------------------------------------------------------
1 | .bin
2 | .include
3 |
--------------------------------------------------------------------------------
/front/.gitignore:
--------------------------------------------------------------------------------
1 | public/app.js
2 | elm-stuff/
--------------------------------------------------------------------------------
/front/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenEugene/openboard/HEAD/front/public/favicon.ico
--------------------------------------------------------------------------------
/docs/learning/README.md:
--------------------------------------------------------------------------------
1 | # Walk-Throughs
2 |
3 | - [Git](./git.md)
4 |
5 | ## go build
6 |
7 | TODO: this
8 |
9 | ## go test
10 |
11 | TODO: this
12 |
--------------------------------------------------------------------------------
/front/src/Viewer.elm:
--------------------------------------------------------------------------------
1 | module Viewer exposing (Viewer(..), unbox)
2 |
3 |
4 | type Viewer
5 | = Viewer String
6 |
7 |
8 | unbox : Viewer -> String
9 | unbox (Viewer v) =
10 | v
11 |
--------------------------------------------------------------------------------
/docs/AUTHORS:
--------------------------------------------------------------------------------
1 | Jason Valenzuela (@toyotathon)
2 | Daved (@daved)
3 | Nathan Nichols (@nqthqn)
4 | Greg McKelvey (@mckelveygreg)
5 | F. Gold (@codegold79)
6 | Daniel Coats (@coatsd)
7 | Alex Anderson (@appins)
8 | Anna G. (@annag42)
9 |
--------------------------------------------------------------------------------
/back/internal/usersvc/internal/asset/assets/0002_setup_role_table.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | CREATE TABLE role (
4 | role_id VARCHAR(26) NOT NULL PRIMARY KEY,
5 | role_name VARCHAR(255) NOT NULL UNIQUE
6 | );
7 |
8 | -- +migrate Down
9 |
10 | DROP TABLE IF EXISTS role;
11 |
--------------------------------------------------------------------------------
/back/internal/postsvc/internal/asset/assets/0003_add_post_fulltext_cols.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE post ADD FULLTEXT(body);
4 | ALTER TABLE post ADD FULLTEXT(title);
5 |
6 | -- +migrate Down
7 |
8 | ALTER TABLE post DROP INDEX IF EXISTS body;
9 | ALTER TABLE post DROP INDEX IF EXISTS title;
10 |
--------------------------------------------------------------------------------
/back/internal/usersvc/internal/asset/assets/0003_setup_join_user_role_join_table.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | CREATE TABLE user_role (
4 | user_id VARCHAR(26) NOT NULL,
5 | role_id VARCHAR(26) NOT NULL,
6 | PRIMARY KEY (user_id, role_id)
7 | );
8 |
9 | -- +migrate Down
10 |
11 | DROP TABLE IF EXISTS user_role;
12 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature Request
3 | about: Create a feature request
4 | title: 'Feature: '
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | ## Desired Behavior
10 |
11 |
12 | ## Suggested Solution(s)
13 |
14 |
15 | ## Screenshot(s) If Applicable
16 |
17 |
18 | ## Additional Notes
19 |
20 |
--------------------------------------------------------------------------------
/back/gengo:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | function main() {
4 | local rootDir="$(dirname "${0}")"
5 | local embedDir=".embeds"
6 | local dirs=(
7 | "internal/pb"
8 | "internal/httpsrv"
9 | )
10 |
11 | for dir in "${dirs[@]}"; do
12 | pushd "${rootDir}/${dir}" &> /dev/null
13 | go generate
14 | popd &> /dev/null
15 | done
16 | }
17 |
18 | main
19 |
--------------------------------------------------------------------------------
/front/public/conf/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "openboard",
3 | "name": "openboard",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/back/internal/httpsrv/internal/asset/asset.go:
--------------------------------------------------------------------------------
1 | package asset
2 |
3 | import (
4 | "embed"
5 | "fmt"
6 | "io/fs"
7 | )
8 |
9 | var (
10 | //go:embed assets/*
11 | FS embed.FS
12 | )
13 |
14 | func NewFS() (fs.FS, error) {
15 | s, err := fs.Sub(FS, "assets")
16 | if err != nil {
17 | return nil, fmt.Errorf("connect to contents fs: %w", err)
18 | }
19 |
20 | return s, nil
21 | }
22 |
--------------------------------------------------------------------------------
/back/internal/postsvc/internal/asset/asset.go:
--------------------------------------------------------------------------------
1 | package asset
2 |
3 | import (
4 | "embed"
5 | "fmt"
6 | "io/fs"
7 | )
8 |
9 | var (
10 | //go:embed assets/*
11 | FS embed.FS
12 | )
13 |
14 | func NewFS() (fs.FS, error) {
15 | s, err := fs.Sub(FS, "assets")
16 | if err != nil {
17 | return nil, fmt.Errorf("connect to contents fs: %w", err)
18 | }
19 |
20 | return s, nil
21 | }
22 |
--------------------------------------------------------------------------------
/back/internal/usersvc/internal/asset/asset.go:
--------------------------------------------------------------------------------
1 | package asset
2 |
3 | import (
4 | "embed"
5 | "fmt"
6 | "io/fs"
7 | )
8 |
9 | var (
10 | //go:embed assets/*
11 | FS embed.FS
12 | )
13 |
14 | func NewFS() (fs.FS, error) {
15 | s, err := fs.Sub(FS, "assets")
16 | if err != nil {
17 | return nil, fmt.Errorf("connect to contents fs: %w", err)
18 | }
19 |
20 | return s, nil
21 | }
22 |
--------------------------------------------------------------------------------
/front/genelm:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | function main() {
4 | local rootDir="$(dirname "${0}")"
5 | local fpb2elm="$(find "${rootDir}" -name pb2elm -print -quit)"
6 |
7 | [[ ! -f "${fpb2elm}" || ! -x "${fpb2elm}" ]] && echo "cannot find pb2elm" >&2 && exit 1
8 |
9 | "${fpb2elm}" "${rootDir}/src" -I "${rootDir}/../msgs" "${rootDir}/../msgs/proto/*.proto"
10 | }
11 |
12 | main
13 |
--------------------------------------------------------------------------------
/back/tests/openbsrv/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/OpenEugene/openboard/back/tests/openbsrv
2 |
3 | go 1.12
4 |
5 | replace github.com/OpenEugene/openboard/back/internal => ../../internal
6 |
7 | require (
8 | github.com/OpenEugene/openboard/back/internal v0.0.0-20191127022437-d710cfcd0696
9 | github.com/golang/protobuf v1.4.3
10 | google.golang.org/grpc v1.35.0
11 | google.golang.org/protobuf v1.25.0
12 | )
13 |
--------------------------------------------------------------------------------
/back/internal/httpsrv/gogen.go:
--------------------------------------------------------------------------------
1 | package httpsrv
2 |
3 | // alias protobuf/swagger
4 | //go:generate -command pb-sw protoc -I ../../../msgs/proto -I ../../tools/.include -I ../../../msgs/proto/include/googleapis --openapiv2_out=logtostderr=true,allow_merge=true:internal/asset/assets
5 |
6 | // generate swagger.json
7 | //go:generate pb-sw ../../../msgs/proto/auth.proto ../../../msgs/proto/user.proto ../../../msgs/proto/post.proto
8 |
--------------------------------------------------------------------------------
/.github/workflows/projmgmt-issue-set_claimed.yml:
--------------------------------------------------------------------------------
1 | name: Manage issues for project (set claimed)
2 |
3 | on:
4 | issues:
5 | types: [assigned]
6 |
7 | jobs:
8 | automate-project-columns:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: alex-page/github-project-automation-plus@v0.2.4
12 | with:
13 | project: Issue Qualification
14 | column: Claimed
15 | repo-token: ${{ secrets.GH_TOKEN }}
16 |
--------------------------------------------------------------------------------
/back/internal/postsvc/internal/asset/assets/0002_setup_type_table.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | CREATE TABLE `type` (
4 | type_id VARCHAR(26) NOT NULL PRIMARY KEY,
5 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
6 | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
7 | deleted_at DATETIME DEFAULT NULL,
8 |
9 | name VARCHAR(255) NOT NULL UNIQUE
10 | );
11 |
12 | -- +migrate Down
13 |
14 | DROP TABLE IF EXISTS `type`;
--------------------------------------------------------------------------------
/.github/workflows/projmgmt-pullreq-set_initiated.yml:
--------------------------------------------------------------------------------
1 | name: Manage pull requests for project (set initiated)
2 |
3 | on:
4 | pull_request:
5 | types: [opened]
6 |
7 | jobs:
8 | automate-project-columns:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: alex-page/github-project-automation-plus@v0.2.4
12 | with:
13 | project: Change Management
14 | column: Initiated
15 | repo-token: ${{ secrets.GH_TOKEN }}
16 |
--------------------------------------------------------------------------------
/back/internal/pb/gogen.go:
--------------------------------------------------------------------------------
1 | package pb
2 |
3 | // alias protobuf/grpc, protobuf/grpc-gateway
4 | //go:generate -command gen-pb protoc -I ../../tools/.include -I ../../../msgs/proto -I ../../../msgs/proto/include/googleapis --go_out=. --go-grpc_out=require_unimplemented_servers=false:. --grpc-gateway_out=logtostderr=true:.
5 |
6 | // generate grpc, grpc-gateway
7 | //go:generate gen-pb ../../../msgs/proto/auth.proto ../../../msgs/proto/user.proto ../../../msgs/proto/post.proto
8 |
--------------------------------------------------------------------------------
/.github/workflows/projmgmt-pullreq-set_submitted.yml:
--------------------------------------------------------------------------------
1 | name: Manage pull requests for project (set submitted)
2 |
3 | on:
4 | pull_request:
5 | types: [review_requested]
6 |
7 | jobs:
8 | automate-project-columns:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: alex-page/github-project-automation-plus@v0.2.4
12 | with:
13 | project: Change Management
14 | column: Submitted
15 | repo-token: ${{ secrets.GH_TOKEN }}
16 |
--------------------------------------------------------------------------------
/.github/workflows/projmgmt-pullreq-set_unsubmitted.yml:
--------------------------------------------------------------------------------
1 | name: Manage pull requests for project (set unsubmitted)
2 |
3 | on:
4 | pull_request:
5 | types: [review_requested_removed]
6 |
7 | jobs:
8 | automate-project-columns:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: alex-page/github-project-automation-plus@v0.2.4
12 | with:
13 | project: Change Management
14 | column: Initiated
15 | repo-token: ${{ secrets.GH_TOKEN }}
16 |
--------------------------------------------------------------------------------
/back/internal/grpcsrv/io.go:
--------------------------------------------------------------------------------
1 | package grpcsrv
2 |
3 | import (
4 | "fmt"
5 | "net"
6 | )
7 |
8 | func tcpListener(port string) (*net.TCPListener, error) {
9 | we := func(err error) error {
10 | return fmt.Errorf("cannot create tcp listener: %s", err)
11 | }
12 |
13 | a, err := net.ResolveTCPAddr("tcp", port)
14 | if err != nil {
15 | return nil, we(err)
16 | }
17 |
18 | l, err := net.ListenTCP("tcp", a)
19 | if err != nil {
20 | return nil, we(err)
21 | }
22 |
23 | return l, nil
24 | }
25 |
--------------------------------------------------------------------------------
/back/cmd/openbsrv/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/OpenEugene/openboard/back/cmd/openbsrv
2 |
3 | go 1.16
4 |
5 | require (
6 | github.com/OpenEugene/openboard/back/internal v0.0.0
7 | github.com/codemodus/alfred v0.2.1
8 | github.com/codemodus/chain/v2 v2.1.2
9 | github.com/codemodus/hedrs v0.1.1
10 | github.com/codemodus/sigmon/v2 v2.0.1
11 | github.com/codemodus/sqlmig v0.2.3
12 | github.com/go-sql-driver/mysql v1.5.0
13 | )
14 |
15 | replace github.com/OpenEugene/openboard/back/internal => ../../internal
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report
3 | about: Create a bug report
4 | title: 'Bug: '
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | ## Expected Behavior
10 |
11 |
12 | ## Actual Behavior
13 |
14 |
15 | ## Steps To Reproduce The Problem
16 |
17 | 1.
18 | 1.
19 | 1.
20 |
21 | ## Screenshot(s) If Applicable
22 |
23 |
24 | ## Specifications
25 |
26 | - Platform: [e.g. iOS 11.4.1, Windows 20H1 (10.0.18975)]
27 | - Browser: [e.g. Chrome 76.0.3, Safari 12.1.1]
28 |
29 | ## Additional Notes
30 |
31 |
--------------------------------------------------------------------------------
/back/internal/postsvc/internal/asset/assets/0001_setup_post_table.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | CREATE TABLE post (
4 | post_id VARCHAR(26) NOT NULL PRIMARY KEY,
5 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
6 | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
7 | deleted_at DATETIME DEFAULT NULL,
8 |
9 | type_id VARCHAR(26) NOT NULL,
10 | slug VARCHAR(255) NULL,
11 | title VARCHAR(255) NULL,
12 | body VARCHAR(255) NULL
13 | );
14 |
15 | -- +migrate Down
16 |
17 | DROP TABLE IF EXISTS post;
--------------------------------------------------------------------------------
/msgs/proto/include/googleapis/README.grpc-gateway:
--------------------------------------------------------------------------------
1 | Google APIs
2 | ============
3 |
4 | Project: Google APIs
5 | URL: https://github.com/google/googleapis
6 | Revision: 3544ab16c3342d790b00764251e348705991ea4b
7 | License: Apache License 2.0
8 |
9 |
10 | Imported Files
11 | ---------------
12 |
13 | - google/api/annotations.proto
14 | - google/api/http.proto
15 |
16 |
17 | Generated Files
18 | ----------------
19 |
20 | They are generated from the .proto files by protoc-gen-go.
21 | - google/api/annotations.pb.go
22 | - google/api/http.pb.go
23 |
--------------------------------------------------------------------------------
/front/tests/Tests.elm:
--------------------------------------------------------------------------------
1 | module Tests exposing (..)
2 |
3 | import Test exposing (..)
4 | import Expect
5 |
6 |
7 | -- Check out http://package.elm-lang.org/packages/elm-community/elm-test/latest to learn more about testing in Elm!
8 |
9 |
10 | all : Test
11 | all =
12 | describe "A Test Suite"
13 | [ test "Addition" <|
14 | \_ ->
15 | Expect.equal 10 (3 + 7)
16 | , test "String.left" <|
17 | \_ ->
18 | Expect.equal "a" (String.left 1 "abcdefg")
19 | , test "This test should fail" <|
20 | \_ ->
21 | Expect.fail "failed as expected!"
22 | ]
23 |
--------------------------------------------------------------------------------
/back/internal/altr/altr.go:
--------------------------------------------------------------------------------
1 | package altr
2 |
3 | import (
4 | "strings"
5 | "time"
6 |
7 | "github.com/golang/protobuf/ptypes"
8 | "github.com/golang/protobuf/ptypes/timestamp"
9 | )
10 |
11 | // CSVFromStrings ...
12 | func CSVFromStrings(ss []string) string {
13 | return strings.Join(ss, ",")
14 | }
15 |
16 | // LimitUint32 ...
17 | func LimitUint32(n uint32) uint32 {
18 | if n == 0 {
19 | return 1<<32 - 1
20 | }
21 | return n
22 | }
23 |
24 | // Timestamp ...
25 | func Timestamp(t time.Time, valid bool) *timestamp.Timestamp {
26 | var ts *timestamp.Timestamp
27 | if valid {
28 | ts, _ = ptypes.TimestampProto(t)
29 | }
30 | return ts
31 | }
32 |
--------------------------------------------------------------------------------
/back/tools/iso/file/openbdb/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mariadb:10.3
2 |
3 | RUN apt-get update && apt-get dist-upgrade -y
4 |
5 | ENV MYSQL_ROOT_PASSWORD=""
6 | ENV MYSQL_DATABASE=""
7 | ENV MYSQL_USER=""
8 | ENV MYSQL_PASSWORD=""
9 | ENV MYSQL_ALLOW_EMPTY_PASSWORD = no
10 | ENV MYSQL_RANDOM_ROOT_PASSWORD = no
11 |
12 | ARG mycnf="/etc/mysql/my.cnf"
13 | RUN sed -Ei 's/^(.*)(bind.*ress\s*=\s*)[.0-9]{7,15}/\20.0.0.0/g' ${mycnf}
14 | ARG mycnf="/etc/mysql/mariadb.cnf"
15 | RUN sed -Ei 's/^(.*)(char.*rver\s*=\s*)[[:alnum:][:punct:]]*/\2utf8mb4/g' ${mycnf}
16 | RUN sed -Ei 's/^(.*)(coll.*rver\s*=\s*)[[:alnum:][:punct:]]*/\2utf8mb4_unicode_ci/g' ${mycnf}
17 |
18 | EXPOSE 3306
19 |
--------------------------------------------------------------------------------
/back/internal/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/OpenEugene/openboard/back/internal
2 |
3 | go 1.16
4 |
5 | require (
6 | github.com/codemodus/chain/v2 v2.1.2
7 | github.com/codemodus/hedrs v0.1.1
8 | github.com/codemodus/mixmux v0.2.1
9 | github.com/codemodus/sqlo v0.2.1
10 | github.com/codemodus/swagui v0.3.0
11 | github.com/codemodus/uidgen v0.1.0
12 | github.com/go-sql-driver/mysql v1.5.0
13 | github.com/golang/protobuf v1.4.3
14 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.2.0
15 | github.com/jmoiron/sqlx v1.3.1
16 | google.golang.org/genproto v0.0.0-20210207032614-bba0dbe2a9ea
17 | google.golang.org/grpc v1.35.0
18 | google.golang.org/protobuf v1.25.0
19 | )
20 |
--------------------------------------------------------------------------------
/front/src/Session.elm:
--------------------------------------------------------------------------------
1 | module Session exposing (Session(..), navKey, viewer)
2 |
3 | import Browser.Navigation as Nav
4 | import Viewer exposing (Viewer)
5 |
6 |
7 | type Session
8 | = LoggedIn Nav.Key Viewer
9 | | Guest Nav.Key
10 |
11 |
12 |
13 | -- INFO
14 |
15 |
16 | viewer : Session -> Maybe Viewer
17 | viewer session =
18 | case session of
19 | LoggedIn _ val ->
20 | Just val
21 |
22 | Guest _ ->
23 | Nothing
24 |
25 |
26 | navKey : Session -> Nav.Key
27 | navKey session =
28 | case session of
29 | LoggedIn key _ ->
30 | key
31 |
32 | Guest key ->
33 | key
34 |
--------------------------------------------------------------------------------
/back/cmd/openbsrv/README.md:
--------------------------------------------------------------------------------
1 | # openbsrv
2 |
3 | go get -u github.com/OpenEugene/openboard/back/cmd/openbsrv
4 |
5 | openbsrv is an application. This doc will be added to.
6 |
7 | ```
8 | Usage of openbsrv:
9 | -dbaddr string
10 | database addr (default "127.0.0.1")
11 | -dbname string
12 | database name (default "openeug_openb_dev")
13 | -dbpass string
14 | database pass
15 | -dbport string
16 | database port (default ":3306")
17 | -dbuser string
18 | database user (default "openeug_openbdev")
19 | -frontdir string
20 | front public assets directory (default "../../../front/public")
21 | -migrate
22 | migrate up
23 | -rollback
24 | migrate dn
25 | -skipsrv
26 | skip server run
27 |
--------------------------------------------------------------------------------
/back/cmd/openbsrv/httpsrv.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/codemodus/hedrs"
5 |
6 | "github.com/OpenEugene/openboard/back/internal/httpsrv"
7 | )
8 |
9 | type httpSrv struct {
10 | s *httpsrv.HTTPSrv
11 |
12 | rpcPort string
13 | httpPort string
14 | }
15 |
16 | func newHTTPSrv(rpcPort, httpPort string, origins []string) (*httpSrv, error) {
17 | hs, err := httpsrv.New(hedrs.DefaultOrigins)
18 | if err != nil {
19 | return nil, err
20 | }
21 |
22 | s := httpSrv{
23 | s: hs,
24 | rpcPort: rpcPort,
25 | httpPort: httpPort,
26 | }
27 |
28 | return &s, nil
29 | }
30 |
31 | // Serve ...
32 | func (s *httpSrv) Serve() error {
33 | return s.s.Serve(s.rpcPort, s.httpPort)
34 | }
35 |
36 | // Stop ...
37 | func (s *httpSrv) Stop() error {
38 | return s.s.Stop()
39 | }
40 |
--------------------------------------------------------------------------------
/front/public/boot.js:
--------------------------------------------------------------------------------
1 | var storageKey = 'store';
2 | var flags = localStorage.getItem(storageKey);
3 | var app = Elm.Main.init({flags: flags});
4 | // app.ports.storeCache.subscribe(function(val) {
5 | // if (val === null) {
6 | // localStorage.removeItem(storageKey);
7 | // } else {
8 | // localStorage.setItem(storageKey, JSON.stringify(val));
9 | // }
10 | // // Report that the new session was stored successfully.
11 | // setTimeout(function() {
12 | // app.ports.onStoreChange.send(val);
13 | // }, 0);
14 | // });
15 |
16 | // Whenever localStorage changes in another tab, report it if necessary.
17 | window.addEventListener('storage', function(event) {
18 | if (event.storageArea === localStorage && event.key === storageKey) {
19 | app.ports.onStoreChange.send(event.newValue);
20 | }
21 | }, false);
--------------------------------------------------------------------------------
/back/internal/usersvc/internal/asset/assets/0001_setup_user_table.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | CREATE TABLE user (
4 | user_id VARCHAR(26) NOT NULL PRIMARY KEY,
5 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
6 | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
7 | deleted_at DATETIME DEFAULT NULL,
8 |
9 | username VARCHAR(255) NOT NULL UNIQUE,
10 | email VARCHAR(255) NOT NULL UNIQUE,
11 | email_hold BOOL NOT NULL DEFAULT TRUE,
12 | altmail VARCHAR(255) NOT NULL,
13 | altmail_hold BOOL NOT NULL DEFAULT TRUE,
14 | full_name VARCHAR(255) NOT NULL,
15 | avatar VARCHAR(255) NOT NULL DEFAULT "",
16 | password VARCHAR(255) NOT NULL,
17 |
18 | last_login DATETIME DEFAULT NULL,
19 | blocked_at DATETIME DEFAULT NULL
20 | );
21 |
22 | -- +migrate Down
23 |
24 | DROP TABLE IF EXISTS user;
25 |
--------------------------------------------------------------------------------
/back/tests/openbsrv/common_test.go:
--------------------------------------------------------------------------------
1 | package main_test
2 |
3 | import (
4 | "reflect"
5 |
6 | timestamp "github.com/golang/protobuf/ptypes/timestamp"
7 | )
8 |
9 | func unsetUntestedFields(item interface{}) {
10 | val := reflect.Indirect(reflect.ValueOf(item))
11 | if val.Kind() != reflect.Struct {
12 | return
13 | }
14 |
15 | strFldNames := []string{"Id"}
16 | for _, name := range strFldNames {
17 | fv := val.FieldByName(name)
18 | if fv.IsValid() && fv.Kind() == reflect.String && fv.CanSet() {
19 | fv.SetString("")
20 | }
21 | }
22 |
23 | timeFldNames := []string{
24 | "LastLogin",
25 | "Created",
26 | "Updated",
27 | "Deleted",
28 | "Blocked",
29 | }
30 | t := new(timestamp.Timestamp)
31 | tt := reflect.TypeOf(t)
32 |
33 | for _, name := range timeFldNames {
34 | fv := val.FieldByName(name)
35 | if fv.IsValid() && fv.Type() == tt && fv.CanSet() {
36 | fv.Set(reflect.Zero(tt))
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/back/tools/iso/compose/poc/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | volumes:
4 | openbdb_mariadb:
5 |
6 | services:
7 |
8 | openbdb:
9 | environment:
10 | MYSQL_DATABASE: "${OPENBSRV_DBNAME}"
11 | MYSQL_USER: "${OPENBSRV_DBUSER}"
12 | MYSQL_PASSWORD: "${OPENBSRV_DBPASS}"
13 | MYSQL_ROOT_PASSWORD: "${OPENBSRV_DBPASS}"
14 | volumes:
15 | - "openbdb_mariadb:/var/lib/mysql"
16 | build: "../../file/openbdb"
17 | restart: "unless-stopped"
18 |
19 | openbsrv:
20 | depends_on:
21 | - "openbdb"
22 | environment:
23 | DBADDR: "openbdb"
24 | DBNAME: "${OPENBSRV_DBNAME}"
25 | DBUSER: "${OPENBSRV_DBUSER}"
26 | DBPASS: "${OPENBSRV_DBPASS}"
27 | MIGRATETYPE: "${MIGRATETYPE:-migrate}"
28 | ports:
29 | - "${OPENBSRV_APIPORT:-4243}:4243"
30 | - "${OPENBSRV_FRONTPORT:-4244}:4244"
31 | build: "../../file/openbsrv-full"
32 | restart: "unless-stopped"
33 |
--------------------------------------------------------------------------------
/back/tests/openbsrv/run-tests:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | main() {
4 | local deferreds=()
5 | function runDeferreds() {
6 | for deferred in "${deferreds[@]}"; do eval ${deferred}; done
7 | }
8 | trap "runDeferreds" EXIT RETURN
9 |
10 | local rootDir="$(dirname "${0}")"
11 | pushd "${rootDir}" >/dev/null && rootDir="${PWD}" && popd >/dev/null || exit 1
12 | local command="$(basename "${rootDir}")"
13 | local projectDir="${rootDir}/../../cmd/${command}"
14 | local buildDir="${rootDir}/build"
15 | local buildFile="${buildDir}/${command}-test"
16 |
17 | pushd "${projectDir}" >/dev/null
18 | deferreds+=("popd >/dev/null")
19 | go build -o "${buildFile}" || exit 1
20 | deferreds+=("rm \"${buildFile}\"")
21 |
22 | "${buildFile}" ${@} -rollback -skipsrv || exit 1
23 | "${buildFile}" ${@} -migrate &
24 | deferreds+=("kill %1")
25 |
26 | pushd "${rootDir}" >/dev/null
27 | deferreds+=("popd >/dev/null")
28 | go test -v
29 | }
30 |
31 | main ${@}
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Openboard
2 |
3 | An open source version of Switchboard.
4 |
5 | ***Join us first and third Wednesday evenings for sprint wrap & plan, and each Tuesday evening for check-in. [EUG-Go](https://www.meetup.com/eug-go)***
6 |
7 | *We are based in the Pacific Timezone, but all are welcome
8 |
9 | ## Overview
10 |
11 | - The [Front End](https://github.com/OpenEugene/openboard/tree/master/front) is
12 | written in Elm
13 | - Produces assets in the form of JavaScript artifacts
14 | - The [Back End](https://github.com/OpenEugene/openboard/tree/master/back) is
15 | written in Go
16 | - Produces an executable (*nix/Win) managing multiple servers
17 | - gRPC API server component
18 | - JSON/HTTP API server component
19 | - Front end asset server component
20 |
21 | ## Community
22 |
23 | - [Code of Conduct](./docs/CODE_OF_CONDUCT.md)
24 | - [Contributing](./docs/CONTRIBUTING.md)
25 | - [Authors](./docs/AUTHORS)
26 | - [Learning Walk-Throughs](./docs/learning/README.md)
27 |
--------------------------------------------------------------------------------
/front/elm.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "application",
3 | "source-directories": [
4 | "src"
5 | ],
6 | "elm-version": "0.19.0",
7 | "dependencies": {
8 | "direct": {
9 | "elm/browser": "1.0.1",
10 | "elm/core": "1.0.2",
11 | "elm/html": "1.0.0",
12 | "elm/json": "1.1.3",
13 | "elm/time": "1.0.0",
14 | "elm/url": "1.0.0",
15 | "rtfeldman/elm-css": "16.0.0",
16 | "tiziano88/elm-protobuf": "3.0.0"
17 | },
18 | "indirect": {
19 | "Skinney/murmur3": "2.0.8",
20 | "elm/regex": "1.0.0",
21 | "elm/virtual-dom": "1.0.2",
22 | "jweir/elm-iso8601": "5.0.2",
23 | "rtfeldman/elm-hex": "1.0.0"
24 | }
25 | },
26 | "test-dependencies": {
27 | "direct": {
28 | "elm-explorations/test": "1.0.0"
29 | },
30 | "indirect": {
31 | "elm/random": "1.0.0"
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/front/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Welcome
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
33 |
34 |
--------------------------------------------------------------------------------
/front/src/Page.elm:
--------------------------------------------------------------------------------
1 | module Page exposing (Page(..), view)
2 |
3 | import Browser
4 | import Html.Styled exposing (..)
5 | import Viewer exposing (Viewer)
6 | import Route
7 | import Ui
8 |
9 | type Page
10 | = Home
11 | | Other
12 |
13 |
14 |
15 | -- Wraps the Pages
16 |
17 |
18 | view : Maybe Viewer -> Page -> { title : String, content : Html.Styled.Html msg } -> Browser.Document msg
19 | view maybeViewer page { title, content } =
20 | { title = title ++ " - Openboard"
21 | , body = Ui.globalStyle :: navBar page maybeViewer :: content :: [] |> List.map Html.Styled.toUnstyled
22 | }
23 |
24 |
25 | navBar page maybeViewer =
26 | Ui.navBar [] [
27 | Ui.navBarList [] [
28 | li [] [
29 | Ui.linkBtn [ Route.href Route.Home ] [ text "OpenBoard" ]
30 | ]
31 | ]
32 | ]
33 |
34 |
35 |
36 |
37 | viewerAsString : Maybe Viewer -> String
38 | viewerAsString maybeV =
39 | case maybeV of
40 | Just v ->
41 | Viewer.unbox v
42 |
43 | Nothing ->
44 | "no viewer"
45 |
--------------------------------------------------------------------------------
/back/cmd/openbsrv/srvmgmt.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "sync"
7 | "time"
8 | )
9 |
10 | type server interface {
11 | Serve() error
12 | Stop() error
13 | }
14 |
15 | type serverMgmt struct {
16 | ss []server
17 | }
18 |
19 | func newServerMgmt(ss ...server) *serverMgmt {
20 | return &serverMgmt{
21 | ss: ss,
22 | }
23 | }
24 |
25 | func (m *serverMgmt) serve() error {
26 | var wg sync.WaitGroup
27 | wg.Add(len(m.ss))
28 |
29 | for _, s := range m.ss {
30 | go func(s server) {
31 | defer wg.Done()
32 |
33 | // TODO: gather returned errors
34 | if err := s.Serve(); err != nil {
35 | fmt.Fprintln(os.Stderr, "server error:", err)
36 | }
37 | }(s)
38 |
39 | time.Sleep(time.Millisecond * 200)
40 | }
41 |
42 | wg.Wait()
43 |
44 | return nil
45 | }
46 |
47 | func (m *serverMgmt) stop() error {
48 | for _, s := range m.ss {
49 | go func(s server) {
50 | // TODO: gather returned errors
51 | if err := s.Stop(); err != nil {
52 | fmt.Fprintln(os.Stderr, "stop error:", err)
53 | }
54 | }(s)
55 | }
56 |
57 | return nil
58 | }
59 |
--------------------------------------------------------------------------------
/front/src/Page/Login.elm:
--------------------------------------------------------------------------------
1 | module Page.Login exposing (Model, Msg(..), init, toSession, update, view)
2 |
3 | import Html.Styled exposing (text)
4 | import Route
5 | import Session exposing (Session)
6 | import Ui
7 |
8 |
9 | type alias Model =
10 | { session : Session
11 | }
12 |
13 |
14 | type Msg
15 | = Noop
16 |
17 |
18 | init : Session -> ( Model, Cmd Msg )
19 | init s =
20 | ( Model s, Cmd.none )
21 |
22 |
23 | update : Msg -> Model -> ( Model, Cmd Msg )
24 | update msg model =
25 | case msg of
26 | Noop ->
27 | ( model, Cmd.none )
28 |
29 |
30 | view : Model -> { title : String, content : Html.Styled.Html Msg }
31 | view model =
32 | { title = "Login"
33 | , content = loginView model
34 | }
35 |
36 |
37 | loginView : Model -> Html.Styled.Html Msg
38 | loginView model =
39 | Ui.card []
40 | [ Ui.heading "Login"
41 | , Ui.linkBtn [ Route.href Route.Home ] [ text "Home" ]
42 | , Ui.paragraph [] [ text <| Debug.toString model.session ]
43 | ]
44 |
45 |
46 | toSession : Model -> Session
47 | toSession { session } =
48 | session
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 The Openboard Authors
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 |
--------------------------------------------------------------------------------
/back/cmd/openbsrv/frontsrv.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "net/http"
6 |
7 | "github.com/codemodus/alfred"
8 | "github.com/codemodus/chain/v2"
9 | "github.com/codemodus/hedrs"
10 | )
11 |
12 | type frontSrv struct {
13 | s *http.Server
14 | }
15 |
16 | func newFrontSrv(port, dir string, origins []string) (*frontSrv, error) {
17 | origins = append(hedrs.DefaultOrigins, origins...)
18 | corsOrigins := hedrs.CORSOrigins(hedrs.NewAllowed(origins...))
19 | corsMethods := hedrs.CORSMethods(hedrs.NewValues(hedrs.AllMethods...))
20 | corsHeaders := hedrs.CORSHeaders(hedrs.NewValues(hedrs.DefaultHeaders...))
21 |
22 | cmn := chain.New(
23 | corsOrigins,
24 | corsMethods,
25 | corsHeaders,
26 | )
27 |
28 | s := frontSrv{
29 | s: &http.Server{
30 | Addr: port,
31 | Handler: cmn.End(alfred.New(dir)),
32 | },
33 | }
34 |
35 | return &s, nil
36 | }
37 |
38 | func (s *frontSrv) Serve() error {
39 | if err := s.s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
40 | return err
41 | }
42 | return nil
43 | }
44 |
45 | func (s *frontSrv) Stop() error {
46 | // TODO: setup context
47 | return s.s.Shutdown(context.Background())
48 | }
49 |
--------------------------------------------------------------------------------
/back/tools/iso/file/openbsrv-full/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine
2 |
3 | RUN apk add --update git libc6-compat
4 |
5 | RUN wget https://dl.google.com/go/go1.12.4.linux-amd64.tar.gz
6 | RUN tar -C /usr/local -xzf go*.tar.gz
7 | RUN rm go*.tar.gz
8 | ENV PATH=${PATH}:/usr/local/go/bin
9 | ENV GO111MODULE=on
10 |
11 | ARG codedir=/code
12 | ARG codebin=${codedir}/bin
13 | RUN mkdir -p ${codebin}
14 | ENV PATH=${PATH}:${codebin}
15 |
16 | ARG repo=OpenEugene/openboard
17 | ARG codesrc=${codedir}/src
18 | RUN mkdir -p ${codesrc}/${repo}
19 | RUN git clone https://github.com/${repo} ${codesrc}/${repo}
20 | WORKDIR ${codesrc}/${repo}/back/cmd/openbsrv
21 | RUN go build -o ${codebin}/openbsrv
22 |
23 | ENV DBADDR=""
24 | ENV DBPORT=""
25 | ENV DBNAME=""
26 | ENV DBUSER=""
27 | ENV DBPASS=""
28 | ENV MIGRATETYPE=""
29 | ENV FRONTDIR=${codesrc}/${repo}/front/public
30 |
31 | ENTRYPOINT openbsrv \
32 | ${DBADDR/$DBADDR/-dbaddr=$DBADDR} \
33 | ${DBPORT/$DBPORT/-dbport=$DBPORT} \
34 | ${DBNAME/$DBNAME/-dbname=$DBNAME} \
35 | ${DBUSER/$DBUSER/-dbuser=$DBUSER} \
36 | ${DBPASS/$DBPASS/-dbpass=$DBPASS} \
37 | ${MIGRATETYPE/$MIGRATETYPE/-$MIGRATETYPE} \
38 | -frontdir=${FRONTDIR}
39 |
40 | EXPOSE 4243 4244
41 |
--------------------------------------------------------------------------------
/msgs/proto/include/googleapis/google/api/annotations.proto:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | syntax = "proto3";
16 |
17 | package google.api;
18 |
19 | import "google/api/http.proto";
20 | import "google/protobuf/descriptor.proto";
21 |
22 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations";
23 | option java_multiple_files = true;
24 | option java_outer_classname = "AnnotationsProto";
25 | option java_package = "com.google.api";
26 | option objc_class_prefix = "GAPI";
27 |
28 | extend google.protobuf.MethodOptions {
29 | // See `HttpRule`.
30 | HttpRule http = 72295728;
31 | }
32 |
--------------------------------------------------------------------------------
/back/internal/log/log.go:
--------------------------------------------------------------------------------
1 | // Package log allows for outputting information regarding application errors
2 | // and events important to know for proper operation.
3 | package log
4 |
5 | import (
6 | "io"
7 | "log"
8 | )
9 |
10 | // Output is where logger will write to. Prefix is what will precede log text.
11 | type Output struct {
12 | Out io.Writer
13 | Prefix string
14 | }
15 |
16 | // Config allows for configuring the logger outputs for errors and information
17 | // type log messages.
18 | type Config struct {
19 | Err Output
20 | Inf Output
21 | }
22 |
23 | // Log is able to write information to a chosen output.
24 | type Log struct {
25 | err *log.Logger
26 | inf *log.Logger
27 | }
28 |
29 | // New provides a pointer to a new instance of the Log object.
30 | func New(c Config) *Log {
31 | return &Log{
32 | inf: log.New(c.Inf.Out, c.Inf.Prefix, 0),
33 | err: log.New(c.Err.Out, c.Err.Prefix, 0),
34 | }
35 | }
36 |
37 | // Info outputs information from the application.
38 | func (log *Log) Info(format string, as ...interface{}) {
39 | log.inf.Printf(format+"\n", as...)
40 | }
41 |
42 | // Error outputs error information from the application.
43 | func (log *Log) Error(format string, as ...interface{}) {
44 | log.err.Printf(format+"\n", as...)
45 | }
46 |
--------------------------------------------------------------------------------
/back/internal/authsvc/authsvc.go:
--------------------------------------------------------------------------------
1 | package authsvc
2 |
3 | import (
4 | "context"
5 |
6 | "google.golang.org/grpc"
7 |
8 | "github.com/OpenEugene/openboard/back/internal/pb"
9 | )
10 |
11 | var _ pb.AuthServer = &AuthSvc{}
12 |
13 | // AuthSvc ecapsulates dependencies and data required to implement the
14 | // pb.AuthServer interface.
15 | type AuthSvc struct {
16 | // TODO: implement AuthSvc
17 | }
18 |
19 | // New returns a pointer to an AuthSvc instance or an error.
20 | func New() (*AuthSvc, error) {
21 | return &AuthSvc{}, nil
22 | }
23 |
24 | // RegisterWithGRPCServer implements the grpcsrv.Registerable interface.
25 | func (s *AuthSvc) RegisterWithGRPCServer(g *grpc.Server) error {
26 | pb.RegisterAuthServer(g, s)
27 |
28 | return nil
29 | }
30 |
31 | // AddAuth implements part of the pb.AuthServer interface.
32 | func (s *AuthSvc) AddAuth(ctx context.Context, req *pb.AddAuthReq) (*pb.AuthResp, error) {
33 | // TODO: implement AddAuth
34 | return nil, nil
35 | }
36 |
37 | // RmvAuth implements part of the pb.AuthServer interface.
38 | func (s *AuthSvc) RmvAuth(ctx context.Context, req *pb.RmvAuthReq) (*pb.RmvAuthResp, error) {
39 | // TODO: implement RmvAuth
40 |
41 | return nil, nil
42 | }
43 |
44 | // AddVoucher implements part of the pb.AuthServer interface.
45 | func (s *AuthSvc) AddVoucher(ctx context.Context, req *pb.AddVoucherReq) (*pb.AddVoucherResp, error) {
46 | // TODO: implement AddVoucher
47 |
48 | return nil, nil
49 | }
50 |
--------------------------------------------------------------------------------
/back/internal/dbg/dbg.go:
--------------------------------------------------------------------------------
1 | // Package dbg allows for outputting information that can help with debugging
2 | // the application.
3 | package dbg
4 |
5 | import (
6 | "io"
7 | "log"
8 | "sync/atomic"
9 | )
10 |
11 | // dbg is thread-safe.
12 | type dbg struct {
13 | atomVal atomic.Value
14 | }
15 |
16 | func new() *dbg {
17 | var d dbg
18 | var logr *log.Logger
19 | d.atomVal.Store(logr)
20 | return &d
21 | }
22 |
23 | func (d *dbg) logln(as ...interface{}) {
24 | logr := d.atomVal.Load().(*log.Logger)
25 | if logr != nil {
26 | logr.Println(as...)
27 | }
28 | }
29 |
30 | func (d *dbg) logf(format string, as ...interface{}) {
31 | logr := d.atomVal.Load().(*log.Logger)
32 | if logr != nil {
33 | logr.Printf(format+"\n", as...)
34 | }
35 | }
36 |
37 | func (d *dbg) setOut(out io.Writer) {
38 | var logr *log.Logger
39 |
40 | if out != nil {
41 | logr = log.New(out, "", 0)
42 | d.atomVal.Store(logr)
43 | return
44 | }
45 |
46 | d.atomVal.Store(logr)
47 | }
48 |
49 | var debug = new()
50 |
51 | // Log outputs information to help with application debugging.
52 | func Log(as ...interface{}) {
53 | debug.logln(as...)
54 | }
55 |
56 | // Logf outputs debugging information and is able to interpret formatting verbs.
57 | func Logf(format string, as ...interface{}) {
58 | debug.logf(format, as...)
59 | }
60 |
61 | // SetDebugOut allows for choosing where debug information will be written to.
62 | func SetOut(out io.Writer) {
63 | debug.setOut(out)
64 | }
65 |
--------------------------------------------------------------------------------
/back/internal/grpcsrv/grpcsrv.go:
--------------------------------------------------------------------------------
1 | package grpcsrv
2 |
3 | import (
4 | "fmt"
5 |
6 | "google.golang.org/grpc"
7 | )
8 |
9 | // Registerable describes services able to be registered with a grpc.Server.
10 | type Registerable interface {
11 | RegisterWithGRPCServer(*grpc.Server) error
12 | }
13 |
14 | // GRPCSrv wraps a grpc.Server for convenience.
15 | type GRPCSrv struct {
16 | *grpc.Server
17 | }
18 |
19 | // New returns a pointer to a basic GRPCSrv instance or an error.
20 | func New() (*GRPCSrv, error) {
21 | opts := []grpc.ServerOption{}
22 |
23 | s := GRPCSrv{
24 | Server: grpc.NewServer(opts...),
25 | }
26 |
27 | return &s, nil
28 | }
29 |
30 | // RegisterServices registers services with the underlying grpc.Server.
31 | func (s *GRPCSrv) RegisterServices(rs ...Registerable) error {
32 | for _, r := range rs {
33 | if err := r.RegisterWithGRPCServer(s.Server); err != nil {
34 | return fmt.Errorf("cannot register service: %s", err)
35 | }
36 | }
37 |
38 | return nil
39 | }
40 |
41 | // Serve sets up a tcp listener on the provided port and serves the underlying
42 | // grpc.Server instance.
43 | func (s *GRPCSrv) Serve(port string) error {
44 | we := func(err error) error {
45 | return fmt.Errorf("cannot serve: %s", err)
46 | }
47 |
48 | l, err := tcpListener(port)
49 | if err != nil {
50 | return we(err)
51 | }
52 |
53 | if err = s.Server.Serve(l); err != nil {
54 | // TODO: if err "contains closed" err, return nil
55 |
56 | return we(err)
57 | }
58 |
59 | return nil
60 | }
61 |
--------------------------------------------------------------------------------
/msgs/proto/auth.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | // openboard
4 | //
5 | // auth semantics:
6 | //
7 | // Add{T}(s) (Add{T}(s)Req) returns {T}(s)Resp // POST
8 | // Ovr{T}(s) (Ovr{T}(s)Req) returns {T}(s)Resp // PUT
9 | // Mod{T}(s) (Mod{T}(s)Req) returns {T}(s)Resp // PATCH
10 | // Get{T} (Get{T}Req) returns {T}Resp // GET
11 | // Fnd{T}s (Fnd{T}sReq) returns {T}sResp // GET
12 | // Rmv{T}(s) (Rmv{T}(s)Req) returns EmptyResp // DELETE
13 | // Unr{T} (Unr{T}Req) returns {T}Resp // PATCH
14 | package pb;
15 |
16 | option go_package = ".;pb";
17 |
18 | import "google/api/annotations.proto";
19 |
20 | message AuthResp { string token = 1; }
21 |
22 | message AddAuthReq {
23 | string username = 1;
24 | string password = 2;
25 | }
26 |
27 | message RmvAuthResp {}
28 |
29 | message RmvAuthReq { string token = 1; }
30 |
31 | message AddVoucherResp {}
32 |
33 | message AddVoucherReq {
34 | bool notify = 1;
35 | string email = 2;
36 | bool password = 3;
37 | }
38 |
39 | service Auth {
40 | rpc AddAuth(AddAuthReq) returns (AuthResp) {
41 | option (google.api.http) = {
42 | post : "/auth"
43 | body : "*"
44 | };
45 | }
46 |
47 | rpc RmvAuth(RmvAuthReq) returns (RmvAuthResp) {
48 | option (google.api.http) = {
49 | delete : "/auth/{token}"
50 | };
51 | }
52 |
53 | rpc AddVoucher(AddVoucherReq) returns (AddVoucherResp) {
54 | option (google.api.http) = {
55 | post : "/voucher"
56 | body : "*"
57 | };
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/back/internal/dbg/dbg_test.go:
--------------------------------------------------------------------------------
1 | package dbg
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io/ioutil"
7 | "testing"
8 | )
9 |
10 | func BenchmarkDbgUse(b *testing.B) {
11 | SetOut(ioutil.Discard)
12 | b.RunParallel(func(pb *testing.PB) {
13 | for pb.Next() {
14 | Log("")
15 | }
16 | })
17 | }
18 | func BenchmarkDbgUseNil(b *testing.B) {
19 | b.RunParallel(func(pb *testing.PB) {
20 | for pb.Next() {
21 | Log("")
22 | }
23 | })
24 | }
25 | func BenchmarkDbgSetAndUse(b *testing.B) {
26 | b.RunParallel(func(pb *testing.PB) {
27 | for pb.Next() {
28 | SetOut(ioutil.Discard)
29 | Log("")
30 | }
31 | })
32 | }
33 | func BenchmarkDbgSetAndUseNil(b *testing.B) {
34 | b.RunParallel(func(pb *testing.PB) {
35 | for pb.Next() {
36 | SetOut(nil)
37 | Log("")
38 | }
39 | })
40 | }
41 |
42 | func TestSetOut(t *testing.T) {
43 | Log("debug not set")
44 |
45 | buff := bytes.NewBuffer([]byte{})
46 | SetOut(buff)
47 | msg := "debug set to bytes buffer"
48 | Log(msg)
49 | got := buff.String()
50 |
51 | want := msg + "\n"
52 | if got != want {
53 | t.Errorf("want: %s, got: %s", want, got)
54 | }
55 |
56 | SetOut(nil)
57 | buff.Reset()
58 | want = ""
59 | Log("debug set to nil")
60 | got = buff.String()
61 | if got != want {
62 | t.Errorf("want: nothing, got: %s", got)
63 | }
64 |
65 | SetOut(buff)
66 | buff.Reset()
67 | msg = "debug set to bytes buffer, %s time"
68 | Logf(msg, "second")
69 | got = buff.String()
70 | want = fmt.Sprintf(msg+"\n", "second")
71 | if got != want {
72 | t.Errorf("want: %s, got: %s", want, got)
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/front/README.md:
--------------------------------------------------------------------------------
1 | # Setup
2 |
3 | Welcome! Glad you are here.
4 |
5 | ## Installation
6 |
7 | First step would be to [install Elm](https://guide.elm-lang.org/install.html).
8 |
9 | Then you should have the `elm` command available. To compile the application run:
10 |
11 | `elm make src/Main.elm --output=public/app.js --debug`
12 |
13 | *Please be sure to add `**/elm-stuff/` to your global git ignore file.
14 |
15 | ## Get started
16 |
17 | You can serve up the frontend on its own, or use spin the whole stack up together (see top level README)
18 |
19 | ```
20 | npm i -g elm-live
21 | cd front/
22 | elm-live src/Main.elm -u --open --dir=public -- --output=public/app.js --debug
23 | ```
24 |
25 | ## Tests
26 |
27 | Check out `tests/Tests.elm`.
28 |
29 | ```
30 | npm i -g elm-test
31 | elm-test
32 | ```
33 |
34 | ## Protobuf
35 |
36 | Protocol buffers are Google's language-neutral, platform-neutral, extensible mechanism for serializing structured data. Learn More(https://developers.google.com/protocol-buffers/).
37 |
38 | To generate type aliases, encoders and decoders run `./genelm`. This will read all the `.proto` files and generate corresponding `.elm` files. This command should be run frequently and updates to the `Proto/` modules should have there own commit. This gives us a type safe border between the client and the server. If something changes on the server — a field is added or removed, a data model is added or removed — our Elm app should fail to compile. No more `400 Bad Request` at runtime!
39 |
40 | ## Contributing
41 |
42 | - [Start here](../docs/CONTRIBUTING.md)
43 |
--------------------------------------------------------------------------------
/back/tools/install-tools:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | install-protoc() {
4 | local binDest="${1}"
5 | local includeDest="${2}"
6 | local version="${3}"
7 | local ostype="$([[ "${OSTYPE}" == darwin* ]] && echo osx || echo linux)"
8 | local arch="$(uname -m)"
9 | local pfmt="https://github.com/protocolbuffers/protobuf/releases/download/v%s/protoc-%s-%s-%s.zip"
10 | local path="$(printf "${pfmt}" "${version}" "${version}" "${ostype}" "${arch}")"
11 | local tmpDir="$(mktemp -d)"
12 | local zipFile="${tmpDir}/protoc.zip"
13 |
14 | wget -O "${zipFile}" "${path}"
15 |
16 | unzip -j ${zipFile} bin/protoc -d "${binDest}"
17 |
18 | unzip ${zipFile} include/'*' -d "${includeDest}"
19 | mv "${includeDest}/include/"* "${includeDest}"
20 | rm -rf "${includeDest}/include"
21 | }
22 |
23 | main() {
24 | local rootDir="$(cd "$(dirname "${0}")" && echo "${PWD}")"
25 | local binDir="${rootDir}/.bin"
26 | local includeDir="${rootDir}/.include"
27 |
28 | mkdir -p "${binDir}"
29 | pushd "${binDir}" &> /dev/null && rm "${binDir}/"*
30 |
31 | export GOBIN="${PWD}"
32 | go install github.com/codemodus/withdraw@v0.1.0
33 | go install github.com/go-bindata/go-bindata/go-bindata@v3.1.1
34 | go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.25.0
35 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0
36 | go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@v2.2.0
37 | go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@v2.2.0
38 |
39 | rm -rf "${includeDir}/"*
40 | install-protoc "${PWD}" "${includeDir}" 3.17.0
41 |
42 | popd &> /dev/null
43 | }
44 |
45 | main
46 |
--------------------------------------------------------------------------------
/front/src/Page/Home.elm:
--------------------------------------------------------------------------------
1 | module Page.Home exposing (Model, Msg(..), init, toSession, update, view)
2 |
3 | import Html.Styled exposing (..)
4 | import Html.Styled.Events exposing (onClick)
5 | import Route
6 | import Session exposing (Session)
7 | import Ui
8 |
9 |
10 | type alias Model =
11 | { session : Session
12 | , greeting : String
13 | }
14 |
15 |
16 | type Msg
17 | = InternalHomeMsg
18 |
19 |
20 | init : Session -> ( Model, Cmd Msg )
21 | init s =
22 | ( Model s "Change me", Cmd.none )
23 |
24 |
25 | update : Msg -> Model -> ( Model, Cmd Msg )
26 | update msg model =
27 | case msg of
28 | InternalHomeMsg ->
29 | ( { model | greeting = "Hello from Page.Home" }, Cmd.none )
30 |
31 |
32 | view : Model -> { title : String, content : Html.Styled.Html Msg }
33 | view model =
34 | { title = "Home"
35 | , content = homeView model
36 | }
37 |
38 |
39 | homeView : Model -> Html.Styled.Html Msg
40 | homeView model =
41 | Ui.mainContent []
42 | [ Ui.flexBox []
43 | [ Ui.linkBtn [ Route.href Route.NewRequest ] [ text "Request" ]
44 | , Ui.linkBtn [ Route.href Route.NewOffer ] [ text "Offer" ]
45 | ]
46 | , Ui.postingsList []
47 | (List.map Ui.postingBlurb
48 | [ { slug = "1", title = "Golang Needed", body = "A bunch of text blablablaosdihf osidhf sdoifh sdfs df...." }
49 | , { slug = "2", title = "Another Posting Example", body = "lsjkdfno sidhsdf sdf sdfhj osd fsdf sdf sdf sdfmore some posting stuff" }
50 | ]
51 | )
52 | ]
53 |
54 |
55 | toSession : Model -> Session
56 | toSession { session } =
57 | session
58 |
--------------------------------------------------------------------------------
/back/tools/install-go:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 |
4 | gover="$(\
5 | curl -s https://golang.org/dl/ | \
6 | grep -o 'go1\.[0-9]\{1,2\}\.[0-9]' | \
7 | sort -t. -k2 -h | \
8 | tail -1 \
9 | )"
10 | rmvold=""
11 |
12 | if hash go 2>/dev/null; then
13 | gver=${gover#go1.}
14 | goins="$(go version | cut -d " " -f 3)"
15 | gins="${goins#go1.}"
16 | [[ "${goins}" == "" ]] && echo >&2 "cannot find installed go version" && exit 1
17 |
18 | msg="go is already installed and updated"
19 | [[ "$(echo "${gins} >= ${gver}" | bc -l)" == "1" ]] && echo "${msg}" && exit 0
20 |
21 | [[ -d "/usr/local/go" ]] && sudo mv "/usr/local/go" "/usr/local/${goins}"
22 | echo "remove previous go installation (${goins})? [y/N]"
23 | read rmvold
24 | fi
25 |
26 | ostype="$([[ "${OSTYPE}" == "darwin"* ]] && echo "darwin" || echo "linux")"
27 |
28 | if ! hash git 2>/dev/null; then
29 | [[ "${ostype}" == "linux" ]] && sudo apt update && sudo apt install git
30 | [[ "${ostype}" == "darwin" ]] && brew update && brew install git
31 | fi
32 |
33 | gorel="${gover}.${ostype}-amd64.tar.gz"
34 |
35 | pushd /usr/local >/dev/null
36 | sudo curl -O https://dl.google.com/go/${gorel}
37 | sudo tar -C /usr/local -zxf ${gorel}
38 | sudo rm ${gorel}
39 | popd >/dev/null
40 |
41 | srcpro="false"
42 | if ! grep -iP "PATH=.*?go.*?bin" ${HOME}/.profile >/dev/null; then
43 | echo 'PATH=${PATH}:/usr/local/go/bin' >> ${HOME}/.profile
44 | srcpro="true"
45 | fi
46 | if ! grep "GO111MODULE=" ${HOME}/.profile >/dev/null; then
47 | echo 'export GO111MODULE=on' >> ${HOME}/.profile
48 | srcpro="true"
49 | fi
50 |
51 | [[ "${rmvold}" =~ ^y|Y$ ]] && sudo rm -rf "/usr/local/${goins}"
52 | [[ "${srcpro}" == "true" ]] && echo "please run 'source ~/.profile'"
53 |
--------------------------------------------------------------------------------
/back/cmd/openbsrv/grpcsrv.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "database/sql"
5 |
6 | "github.com/OpenEugene/openboard/back/internal/authsvc"
7 | "github.com/OpenEugene/openboard/back/internal/grpcsrv"
8 | "github.com/OpenEugene/openboard/back/internal/postsvc"
9 | "github.com/OpenEugene/openboard/back/internal/usersvc"
10 | )
11 |
12 | type grpcSrv struct {
13 | s *grpcsrv.GRPCSrv
14 |
15 | port string
16 | svcs []interface{}
17 | }
18 |
19 | func newGRPCSrv(port string, db *sql.DB, drvr string) (*grpcSrv, error) {
20 | auth, err := authsvc.New()
21 | if err != nil {
22 | return nil, err
23 | }
24 |
25 | user, err := usersvc.New(db, drvr, 123456)
26 | if err != nil {
27 | return nil, err
28 | }
29 |
30 | post, err := postsvc.New(db, drvr, 123456)
31 | if err != nil {
32 | return nil, err
33 | }
34 |
35 | svcs := []interface{}{
36 | auth, user, post,
37 | }
38 |
39 | gs, err := grpcsrv.New()
40 | if err != nil {
41 | return nil, err
42 | }
43 |
44 | if err := registerServices(gs, svcs...); err != nil {
45 | return nil, err
46 | }
47 |
48 | s := grpcSrv{
49 | s: gs,
50 | port: port,
51 | svcs: svcs,
52 | }
53 |
54 | return &s, nil
55 | }
56 |
57 | func (s *grpcSrv) services() []interface{} {
58 | return s.svcs
59 | }
60 |
61 | func (s *grpcSrv) Serve() error {
62 | return s.s.Serve(s.port)
63 | }
64 |
65 | func (s *grpcSrv) Stop() error {
66 | s.s.GracefulStop()
67 | return nil
68 | }
69 |
70 | func registerServices(srv *grpcsrv.GRPCSrv, svcs ...interface{}) error {
71 | for _, svc := range svcs {
72 | if s, ok := svc.(grpcsrv.Registerable); ok {
73 | if err := srv.RegisterServices(s); err != nil {
74 | return err
75 | }
76 | }
77 | }
78 | return nil
79 | }
80 |
--------------------------------------------------------------------------------
/front/src/Route.elm:
--------------------------------------------------------------------------------
1 | module Route exposing (Route(..), fromUrl, href)
2 |
3 | import Html.Styled exposing (Attribute)
4 | import Html.Styled.Attributes as Attr
5 | import Url exposing (Url)
6 | import Url.Parser as Parser exposing ((>), Parser, oneOf, s, string)
7 |
8 |
9 |
10 | -- ROUTING
11 |
12 |
13 | type Route
14 | = Home
15 | | Login
16 | | NewOffer
17 | | NewRequest
18 | | PostDetail String
19 | | EditPost String
20 |
21 |
22 | parser : Parser (Route -> a) a
23 | parser =
24 | oneOf
25 | [ Parser.map Home Parser.top
26 | , Parser.map Login (s "login")
27 | , Parser.map NewOffer (s "offer" > s "new")
28 | , Parser.map NewRequest (s "request" > s "new")
29 | , Parser.map PostDetail (s "posts" > string)
30 | , Parser.map EditPost (s "posts" > string > s "edit")
31 | ]
32 |
33 |
34 |
35 | -- PUBLIC HELPERS
36 |
37 |
38 | href : Route -> Attribute msg
39 | href targetRoute =
40 | Attr.href (routeToString targetRoute)
41 |
42 |
43 | fromUrl : Url -> Maybe Route
44 | fromUrl url =
45 | -- The RealWorld spec treats the fragment like a path.
46 | -- This makes it *literally* the path, so we can proceed
47 | -- with parsing as if it had been a normal path all along.
48 | { url | path = Maybe.withDefault "" url.fragment, fragment = Nothing }
49 | |> Parser.parse parser
50 |
51 |
52 |
53 | -- INTERNAL
54 |
55 |
56 | routeToString : Route -> String
57 | routeToString page =
58 | let
59 | pieces =
60 | case page of
61 | Home ->
62 | []
63 |
64 | Login ->
65 | [ "login" ]
66 |
67 | NewRequest ->
68 | [ "request", "new" ]
69 |
70 | NewOffer ->
71 | [ "offer", "new" ]
72 |
73 | PostDetail string ->
74 | [ "posts", string ]
75 |
76 | EditPost string ->
77 | [ "posts", string, "edit" ]
78 | in
79 | "#/" ++ String.join "/" pieces
80 |
--------------------------------------------------------------------------------
/back/cmd/openbsrv/db.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 | "fmt"
7 | "time"
8 |
9 | "github.com/codemodus/sqlmig"
10 | _ "github.com/go-sql-driver/mysql"
11 | )
12 |
13 | func newSQLDB(driver, creds string) (*sql.DB, error) {
14 | db, err := sql.Open(driver, creds)
15 | if err != nil {
16 | return nil, err
17 | }
18 |
19 | db.SetMaxIdleConns(128)
20 | db.SetConnMaxLifetime(time.Hour)
21 |
22 | return db, patientPing(db)
23 | }
24 |
25 | func patientPing(db *sql.DB) error {
26 | limit := time.Second * 3
27 | pause := time.Millisecond * 500
28 | iters := 5 // 0 + .5 + 1 + 2 + 4
29 | var err error
30 |
31 | for i := 0; i < iters; i++ {
32 | if i > 0 {
33 | time.Sleep(pause)
34 | pause = pause * 2
35 | }
36 |
37 | func() {
38 | ctx, cancel := context.WithTimeout(context.Background(), limit)
39 | defer cancel()
40 |
41 | err = db.PingContext(ctx)
42 | }()
43 |
44 | if err == nil {
45 | return nil
46 | }
47 | }
48 |
49 | return err
50 | }
51 |
52 | type dbmig struct {
53 | m *sqlmig.SQLMig
54 | }
55 |
56 | func newDBMig(db *sql.DB, driver, tablePrefix string) (*dbmig, error) {
57 | mig, err := sqlmig.New(db, driver, tablePrefix)
58 | if err != nil {
59 | return nil, err
60 | }
61 |
62 | dbm := dbmig{
63 | m: mig,
64 | }
65 |
66 | return &dbm, nil
67 | }
68 |
69 | func (m *dbmig) addMigrators(us ...interface{}) {
70 | for _, u := range us {
71 | if p, ok := u.(sqlmig.DataProvider); ok {
72 | m.m.AddDataProviders(p)
73 | }
74 |
75 | if r, ok := u.(sqlmig.Regularizer); ok {
76 | m.m.AddRegularizers(r)
77 | }
78 | }
79 | }
80 |
81 | func (m *dbmig) run(migrate, rollback bool) (sqlmig.Results, string) {
82 | switch {
83 | case migrate && rollback:
84 | return sqlmig.Results{}, ""
85 | case migrate:
86 | return m.m.Migrate(), "migrated"
87 | case rollback:
88 | return m.m.RollBack(), "rolled back"
89 | default:
90 | return sqlmig.Results{}, ""
91 | }
92 | }
93 |
94 | func dbCreds(name, user, pass, addr, port string) string {
95 | return fmt.Sprintf("%s:%s@tcp(%s%s)/%s?parseTime=true", user, pass, addr, port, name)
96 | }
97 |
--------------------------------------------------------------------------------
/back/tools/install-mariadb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 |
4 | OSLIN="linux"
5 | OSDAR="darwin"
6 | ostype="$([[ "${OSTYPE}" == ${OSDAR}* ]] && echo ${OSDAR} || echo ${OSLIN})"
7 | OSWSL="linux-wsl"
8 | [[ "${ostype}" == ${OSLIN} ]] && grep -qi "Microsoft\|WSL" /proc/version && ostype=${OSWSL}
9 |
10 |
11 | if ! hash mysql 2>/dev/null; then
12 | case ${ostype} in
13 | ${OSLIN}*)
14 | sudo apt update
15 | sudo apt install software-properties-common
16 |
17 | pkey="0xF1656F24C74CD1D8"
18 | case ${ostype} in
19 | ${OSLIN})
20 | keysrv="hkp://keyserver.ubuntu.com:80"
21 | sudo apt-key adv --recv-keys --keyserver "${keysrv}" "${pkey}"
22 | ;;
23 | ${OSWSL})
24 | keysrch="http://keyserver.ubuntu.com/pks/lookup?op=get&search=${pkey}"
25 | curl -sL "${keysrch}" | sudo apt-key add
26 | ;;
27 | esac
28 | repourl=http://sfo1.mirrors.digitalocean.com/mariadb/repo/10.3/ubuntu
29 | sudo add-apt-repository "deb [arch=amd64] ${repourl} $(lsb_release -cs) main"
30 | sudo apt update
31 | sudo apt install mariadb-server
32 | ;;
33 | ${OSDAR})
34 | brew update
35 | brew install mariadb
36 | ;;
37 | esac
38 |
39 | sudo tee "/etc/mysql/mariadb.conf.d/60-encoding.cnf" >/dev/null << EOF
40 | [mysqld]
41 | character-set-server = utf8mb4
42 | collation-server = utf8mb4_unicode_ci
43 | character_set_server = utf8mb4
44 | collation_server = utf8mb4_unicode_ci
45 | EOF
46 |
47 | case ${ostype} in
48 | ${OSLIN}*)
49 | sudo service mysql restart
50 | ;;
51 | ${OSDAR})
52 | mysql.server start
53 | brew services start mariadb
54 | ;;
55 | esac
56 | fi
57 |
58 | adddb=""
59 | rootpass=""
60 | echo "add database? [y/N]"
61 | read adddb
62 | [[ ! ${adddb} =~ y|Y ]] && exit 0
63 | echo "mysql root pass:"
64 | read -s rootpass
65 |
66 | dbname=""
67 | dbuser=""
68 | dbpass=""
69 | echo "database name:"
70 | read dbname
71 | echo "database user:"
72 | read dbuser
73 | echo "database pass:"
74 | read -s dbpass
75 |
76 | mysql -uroot -p"${rootpass}" << EOF
77 | CREATE DATABASE ${dbname} DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
78 | GRANT ALL PRIVILEGES ON ${dbname}.* TO ${dbuser}@'%' IDENTIFIED BY '${dbpass}';
79 | EOF
80 |
--------------------------------------------------------------------------------
/front/src/Page/Posts.elm:
--------------------------------------------------------------------------------
1 | module Page.Posts exposing (Kind(..), Model, Msg(..), init, postsView, toSession, update, view)
2 |
3 | import Html.Styled exposing (button, form, input, label, text, textarea)
4 | import Html.Styled.Attributes exposing (type_)
5 | import Html.Styled.Events exposing (onInput)
6 | import Route
7 | import Session exposing (Session)
8 | import Ui
9 |
10 |
11 | type alias Model =
12 | { session : Session
13 | , title : String
14 | , body : String
15 | , kind : Kind
16 | , slug : String
17 | }
18 |
19 |
20 | type Kind
21 | = Offer
22 | | Request
23 | | Unknown
24 |
25 |
26 | type Msg
27 | = SetTitle String
28 | | SetBody String
29 |
30 |
31 | init : Session -> Kind -> String -> ( Model, Cmd Msg )
32 | init session kind slug =
33 | ( Model session "" "" kind slug, Cmd.none )
34 |
35 |
36 | update : Msg -> Model -> ( Model, Cmd Msg )
37 | update msg model =
38 | case msg of
39 | SetTitle t ->
40 | ( { model | title = t }, Cmd.none )
41 |
42 | SetBody b ->
43 | ( { model | body = b }, Cmd.none )
44 |
45 |
46 | view : Model -> { title : String, content : Html.Styled.Html Msg }
47 | view model =
48 | { title = "Posts"
49 | , content = postsView model
50 | }
51 |
52 |
53 | heading : Kind -> Html.Styled.Html Msg
54 | heading kind =
55 | case kind of
56 | Offer ->
57 | Ui.heading "New Offer"
58 |
59 | Request ->
60 | Ui.heading "New Request"
61 |
62 | Unknown ->
63 | Ui.heading "Impossible!!"
64 |
65 |
66 | postsView : Model -> Html.Styled.Html Msg
67 | postsView model =
68 | Ui.card []
69 | [ heading model.kind
70 | , Ui.linkBtn [ Route.href Route.Home ] [ text "Home" ]
71 | , form []
72 | [ label []
73 | [ text "Title"
74 | , input [ type_ "text", onInput SetTitle ] []
75 | ]
76 | , label []
77 | [ text "Body"
78 | , textarea [ onInput SetBody ] []
79 | ]
80 | , button [] [ text "submit" ]
81 | ]
82 | ]
83 |
84 |
85 | toSession : Model -> Session
86 | toSession { session } =
87 | session
88 |
--------------------------------------------------------------------------------
/back/tools/iso/dev:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | function ensureDocker() {
4 | OSLIN="linux"
5 | OSDAR="darwin"
6 | ostype="$([[ "${OSTYPE}" == ${OSDAR}* ]] && echo ${OSDAR} || echo ${OSLIN})"
7 |
8 | if ! hash docker 2>/dev/null; then
9 | case ${ostype} in
10 | ${OSLIN})
11 | sudo apt install docker.io
12 | ;;
13 | ${OSDAR})
14 | brew install docker || echo
15 | ;;
16 | esac
17 | [[ "$?" != "0" ]] && echo >&2 "cannot install docker" && exit 1
18 | fi
19 |
20 | if ! hash docker-compose 2>/dev/null; then
21 | case ${ostype} in
22 | ${OSLIN})
23 | sudo easy_install pip
24 | pip install --user docker-compose
25 | ;;
26 | ${OSDAR})
27 | brew install docker-compose
28 | ;;
29 | esac
30 | [[ "$?" != "0" ]] && echo >&2 "cannot install docker-compose" && exit 1
31 | fi
32 | }
33 |
34 | function main() {
35 | local dir="${1:-up}"
36 | local proj="dev"
37 | local svc="openbdb"
38 | local file="./compose/poc/docker-compose.yml"
39 |
40 | export OPENBSRV_DBNAME="${OPENBSRV_DBNAME:-openeug_openb_dev}"
41 | export OPENBSRV_DBUSER="${OPENBSRV_DBUSER:-openeug_openbdev}"
42 | export OPENBSRV_DBPASS="${OPENBSRV_DBPASS:-dummy}"
43 |
44 | case "${dir}" in
45 | up)
46 | if [[ "${OPENBSRV_DBPASS}" == "dummy" ]]; then
47 | local dbpass
48 | echo "database pass:"
49 | read -s dbpass
50 | export OPENBSRV_DBPASS="${dbpass}"
51 | fi
52 |
53 | docker-compose \
54 | --file "${file}" \
55 | --project-name "${proj}" \
56 | up \
57 | --detach \
58 | "${svc}"
59 | [[ "$?" != "0" ]] && echo >&2 "cannot setup containers" && exit 1
60 |
61 | main ip
62 | ;;
63 | down|dn|clean)
64 | docker-compose \
65 | --file "${file}" \
66 | --project-name "${proj}" \
67 | down
68 |
69 | [[ "${dir}" != "clean" ]] && exit 0
70 |
71 | docker image rm "${proj}_${svc}"
72 |
73 | docker volume rm \
74 | "$(docker volume ls --filter "name=${proj}_${svc}" -q)"
75 | ;;
76 | ip)
77 |
78 | docker inspect \
79 | --format \
80 | '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
81 | "$(docker ps --filter "name=${proj}_${svc}" -q | head -1)"
82 | ;;
83 | *)
84 | echo >&2 "must be a valid subcmd [up|dn|ip|clean]"
85 | exit 1
86 | ;;
87 | esac
88 | }
89 |
90 | ensureDocker
91 | main "${1}"
92 |
--------------------------------------------------------------------------------
/back/internal/postsvc/internal/postdb/postdb.go:
--------------------------------------------------------------------------------
1 | package postdb
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 |
7 | "github.com/OpenEugene/openboard/back/internal/pb"
8 | "github.com/codemodus/sqlo"
9 | "github.com/codemodus/uidgen"
10 | )
11 |
12 | var _ pb.PostServer = &PostDB{}
13 |
14 | // PostDB encapsulates dependencies and data required to implement the
15 | // pb.PostServer interface.
16 | type PostDB struct {
17 | db *sqlo.SQLO
18 | drv string
19 | ug *uidgen.UIDGen
20 | }
21 |
22 | // New returns a pointer to a PostDB instance or an error.
23 | func New(relDB *sql.DB, driver string, offset uint64) (*PostDB, error) {
24 | db := PostDB{
25 | db: sqlo.New(relDB),
26 | drv: driver,
27 | ug: uidgen.New(offset, uidgen.VARCHAR26),
28 | }
29 |
30 | return &db, nil
31 | }
32 |
33 | // AddType implements part of the pb.PostServer interface.
34 | func (s *PostDB) AddType(ctx context.Context, req *pb.AddTypeReq) (*pb.TypeResp, error) {
35 | r := &pb.TypeResp{}
36 | if err := s.upsertType(ctx, "", req, r); err != nil {
37 | return nil, err
38 | }
39 | return r, nil
40 | }
41 |
42 | // FndTypes implements part of the pb.PostServer interface.
43 | func (s *PostDB) FndTypes(ctx context.Context, req *pb.FndTypesReq) (*pb.TypesResp, error) {
44 | r := &pb.TypesResp{}
45 | if err := s.findTypes(ctx, req, r); err != nil {
46 | return nil, err
47 | }
48 | return r, nil
49 | }
50 |
51 | // AddPost implements part of the pb.PostServer interface.
52 | func (s *PostDB) AddPost(ctx context.Context, req *pb.AddPostReq) (*pb.PostResp, error) {
53 | r := &pb.PostResp{}
54 | if err := s.upsertPost(ctx, "", req, r); err != nil {
55 | return nil, err
56 | }
57 | return r, nil
58 | }
59 |
60 | // FndPosts implements part of the pb.PostServer interface.
61 | func (s *PostDB) FndPosts(ctx context.Context, req *pb.FndPostsReq) (*pb.PostsResp, error) {
62 | r := &pb.PostsResp{}
63 | if err := s.findPosts(ctx, req, r); err != nil {
64 | return nil, err
65 | }
66 | return r, nil
67 | }
68 |
69 | // OvrPost implements part of the pb.PostServer interface.
70 | func (s *PostDB) OvrPost(ctx context.Context, req *pb.OvrPostReq) (*pb.PostResp, error) {
71 | r := &pb.PostResp{}
72 | if err := s.upsertPost(ctx, string(req.Id), req.Req, r); err != nil {
73 | return nil, err
74 | }
75 | return r, nil
76 | }
77 |
78 | // RmvPost implements part of the pb.PostServer interface.
79 | func (s *PostDB) RmvPost(ctx context.Context, req *pb.RmvPostReq) (*pb.RmvPostResp, error) {
80 | if err := s.deletePost(ctx, string(req.Id)); err != nil {
81 | return nil, err
82 | }
83 | return &pb.RmvPostResp{}, nil
84 | }
85 |
--------------------------------------------------------------------------------
/back/internal/usersvc/internal/userdb/userdb.go:
--------------------------------------------------------------------------------
1 | package userdb
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 |
7 | "github.com/OpenEugene/openboard/back/internal/pb"
8 | "github.com/codemodus/uidgen"
9 | "github.com/jmoiron/sqlx"
10 | )
11 |
12 | var _ pb.UserSvcServer = &UserDB{}
13 |
14 | // UserDB encapsulates dependencies and data required to implement the
15 | // pb.UserServer interface.
16 | type UserDB struct {
17 | db *sqlx.DB
18 | drv string
19 | ug *uidgen.UIDGen
20 | }
21 |
22 | // New returns a pointer to a UserDB instance or an error.
23 | func New(relDB *sql.DB, driver string, offset uint64) (*UserDB, error) {
24 | db := UserDB{
25 | db: sqlx.NewDb(relDB, driver),
26 | drv: driver,
27 | ug: uidgen.New(offset, uidgen.VARCHAR26),
28 | }
29 |
30 | return &db, nil
31 | }
32 |
33 | // AddUser implements part of the pb.UserServer interface.
34 | func (s *UserDB) AddUser(ctx context.Context, req *pb.AddUserReq) (*pb.UserResp, error) {
35 | r := &pb.UserResp{}
36 | if err := s.upsertUser(ctx, "", req, r); err != nil {
37 | return nil, err
38 | }
39 | return r, nil
40 | }
41 |
42 | // OvrUser implements part of the pb.UserServer interface.
43 | func (s *UserDB) OvrUser(ctx context.Context, req *pb.OvrUserReq) (*pb.UserResp, error) {
44 | r := &pb.UserResp{}
45 | if err := s.upsertUser(ctx, string(req.Id), req.Req, r); err != nil {
46 | return nil, err
47 | }
48 | return r, nil
49 | }
50 |
51 | // RmvUser implements part of the pb.UserServer interface.
52 | func (s *UserDB) RmvUser(ctx context.Context, req *pb.RmvUserReq) (*pb.RmvUserResp, error) {
53 | if err := s.deleteUser(ctx, string(req.Id)); err != nil {
54 | return nil, err
55 | }
56 | return &pb.RmvUserResp{}, nil
57 | }
58 |
59 | // FndUsers implements part of the pb.UserServer interface.
60 | func (s *UserDB) FndUsers(ctx context.Context, req *pb.FndUsersReq) (*pb.UsersResp, error) {
61 | r := &pb.UsersResp{}
62 | if err := s.findUsers(ctx, req, r); err != nil {
63 | return nil, err
64 | }
65 | return r, nil
66 | }
67 |
68 | // AddRole implements part of the pb.UserServer interface.
69 | func (s *UserDB) AddRole(ctx context.Context, req *pb.AddRoleReq) (*pb.RoleResp, error) {
70 | r := &pb.RoleResp{}
71 | if err := s.upsertRole(ctx, "", req, r); err != nil {
72 | return nil, err
73 | }
74 | return r, nil
75 | }
76 |
77 | // FndRoles implements part of the pb.UserServer interface.
78 | func (s *UserDB) FndRoles(ctx context.Context, req *pb.FndRolesReq) (*pb.RolesResp, error) {
79 | r := &pb.RolesResp{}
80 | if err := s.findRoles(ctx, req, r); err != nil {
81 | return nil, err
82 | }
83 | return r, nil
84 | }
85 |
--------------------------------------------------------------------------------
/docs/learning/go_cmd.md:
--------------------------------------------------------------------------------
1 | # Go and the Go Command Walk-through
2 |
3 | ## Introduction
4 |
5 | - [The Go Project](https://golang.org/project)
6 |
7 | ## Installation
8 |
9 | - [Download and Install](https://golang.org/doc/install)
10 |
11 | ## Operation
12 |
13 | ### The Basics
14 |
15 | - [Go Tour](https://tour.golang.org)
16 | - [Tutorial: Get started with Go](https://golang.org/doc/tutorial/getting-started)
17 | - [Tutorial: Create a Go Module](https://golang.org/doc/tutorial/create-module)
18 |
19 | ### A Little More
20 |
21 | - [Writing Web Applications](https://golang.org/doc/articles/wiki)
22 | - [How to Write Go Code](https://golang.org/doc/code)
23 |
24 | ### Specifics
25 |
26 | - [Command go](https://golang.org/cmd/go)
27 |
28 | #### > `go mod`
29 |
30 | Used to manage the module namespace of your project, as well as the list of
31 | modules that your module is dependent on.
32 |
33 | Start your project:
34 |
35 | ```sh
36 | go mod init github.com/OpenEugene/openboard
37 | ```
38 |
39 | #### > `go get` and `mod` again
40 |
41 | Used to obtain code from a given module to be added to your own module.
42 |
43 | Add a dependency:
44 |
45 | ```sh
46 | go get github.com/codemodus/sigmon/v2@latest
47 | ```
48 |
49 | If you've modified your project imports and wish to update your list of modules,
50 | `go mod` comes back into the picture.
51 |
52 | Ensure dependencies are recorded properly:
53 |
54 | ```sh
55 | go mod tidy
56 | ```
57 |
58 | #### > `go test`
59 |
60 | Used to run tests using the standard library test framework. Files with the
61 | suffix `_test.go` will be picked up for handling.
62 |
63 | Run tests:
64 |
65 | ```sh
66 | go test # can be run with -v to see more info
67 | ```
68 |
69 | #### > `go run`
70 |
71 | Used to build and run a temporary executable of your application.
72 |
73 | Build and run (temporary):
74 |
75 | ```sh
76 | go run .
77 | ```
78 |
79 | #### > `go build`
80 |
81 | Used to build an executable of your application.
82 |
83 | Build and run (persistent):
84 |
85 | ```sh
86 | go build -o ./build/myproject
87 | ./myproject
88 | ```
89 |
90 | #### > `go install`
91 |
92 | Used to install an executable directly from a repository.
93 |
94 | Install from source and run:
95 |
96 | ```sh
97 | go install github.com/OpenEugene/openboard/back/cmd/openbsrv@latest
98 | openbsrv
99 | ```
100 |
101 | You will need to be sure that the install location is in your PATH environment
102 | variable.
103 |
104 | > Executables are installed in the directory named by the GOBIN environment
105 | > variable, which defaults to $GOPATH/bin or $HOME/go/bin if the GOPATH
106 | > environment variable is not set. Executables in $GOROOT are installed in
107 | > $GOROOT/bin or $GOTOOLDIR instead of $GOBIN.
108 |
--------------------------------------------------------------------------------
/msgs/proto/post.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | // openboard
4 | //
5 | // user semantics:
6 | //
7 | // Add{T}(s) (Add{T}(s)Req) returns {T}(s)Resp // POST
8 | // Ovr{T}(s) (Ovr{T}(s)Req) returns {T}(s)Resp // PUT
9 | // Mod{T}(s) (Mod{T}(s)Req) returns {T}(s)Resp // PATCH
10 | // Get{T} (Get{T}Req) returns {T}Resp // GET
11 | // Fnd{T}s (Fnd{T}sReq) returns {T}sResp // GET
12 | // Rmv{T}(s) (Rmv{T}(s)Req) returns EmptyResp // DELETE
13 | // Unr{T} (Unr{T}Req) returns {T}Resp // PATCH
14 | package pb;
15 |
16 | option go_package = ".;pb";
17 |
18 | import "google/api/annotations.proto";
19 | import "google/protobuf/timestamp.proto";
20 |
21 | message CommentResp {}
22 |
23 | message AddCommentResp {}
24 |
25 | message TypeResp {
26 | string id = 1;
27 | string name = 2;
28 | }
29 |
30 | message AddTypeReq { string name = 1; }
31 |
32 | message TypesResp {
33 | repeated TypeResp items = 1;
34 | uint32 total = 2;
35 | }
36 |
37 | message FndTypesReq {
38 | uint32 limit = 1;
39 | uint32 lapse = 2;
40 | }
41 |
42 | message PostResp {
43 | string id = 1;
44 | string slug = 2;
45 | string title = 3;
46 | string body = 4;
47 | string typeId = 5;
48 | google.protobuf.Timestamp created = 6;
49 | google.protobuf.Timestamp updated = 7;
50 | google.protobuf.Timestamp deleted = 8;
51 | google.protobuf.Timestamp blocked = 9;
52 | }
53 |
54 | message AddPostReq {
55 | string title = 1;
56 | string body = 2;
57 | string typeId = 3;
58 | }
59 |
60 | message OvrPostReq {
61 | string id = 1;
62 | AddPostReq req = 2;
63 | }
64 |
65 | message PostsResp {
66 | repeated PostResp posts = 1;
67 | uint32 total = 2;
68 | }
69 |
70 | message FndPostsReq {
71 | repeated string keywords = 1;
72 | google.protobuf.Timestamp created = 2;
73 | google.protobuf.Timestamp updated = 3;
74 | google.protobuf.Timestamp deleted = 4;
75 | }
76 |
77 | message RmvPostReq { string id = 1; }
78 |
79 | message RmvPostResp {}
80 |
81 | service Post {
82 | rpc AddType(AddTypeReq) returns (TypeResp) {
83 | option (google.api.http) = {
84 | post : "/type"
85 | body : "*"
86 | };
87 | }
88 |
89 | rpc FndTypes(FndTypesReq) returns (TypesResp) {
90 | option (google.api.http) = {
91 | get : "/types"
92 | };
93 | }
94 |
95 | rpc AddPost(AddPostReq) returns (PostResp) {
96 | option (google.api.http) = {
97 | post : "/post"
98 | body : "*"
99 | };
100 | };
101 |
102 | rpc FndPosts(FndPostsReq) returns (PostsResp) {
103 | option (google.api.http) = {
104 | get : "/posts"
105 | };
106 | }
107 |
108 | rpc OvrPost(OvrPostReq) returns (PostResp) {
109 | option (google.api.http) = {
110 | put : "/post/{id}"
111 | body : "*"
112 | };
113 | }
114 |
115 | rpc RmvPost(RmvPostReq) returns (RmvPostResp) {
116 | option (google.api.http) = {
117 | delete : "/post/{id}"
118 | };
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/front/src/Proto/Auth.elm:
--------------------------------------------------------------------------------
1 | module Proto.Auth exposing (AddAuthReq, AddVoucherReq, AuthResp, RmvAuthReq, addAuthReqDecoder, addAuthReqEncoder, addVoucherReqDecoder, addVoucherReqEncoder, authRespDecoder, authRespEncoder, rmvAuthReqDecoder, rmvAuthReqEncoder)
2 |
3 | -- DO NOT EDIT
4 | -- AUTOGENERATED BY THE ELM PROTOCOL BUFFER COMPILER
5 | -- https://github.com/tiziano88/elm-protobuf
6 | -- source file: proto/auth.proto
7 | -- ALSO PROCESSED BY elm-format 0.8.1
8 |
9 | import Json.Decode as JD
10 | import Json.Encode as JE
11 | import Protobuf exposing (..)
12 |
13 |
14 | type alias AuthResp =
15 | { token : String -- 1
16 | }
17 |
18 |
19 | authRespDecoder : JD.Decoder AuthResp
20 | authRespDecoder =
21 | JD.lazy <|
22 | \_ ->
23 | decode AuthResp
24 | |> required "token" JD.string ""
25 |
26 |
27 | authRespEncoder : AuthResp -> JE.Value
28 | authRespEncoder v =
29 | JE.object <|
30 | List.filterMap identity <|
31 | [ requiredFieldEncoder "token" JE.string "" v.token
32 | ]
33 |
34 |
35 | type alias AddAuthReq =
36 | { username : String -- 1
37 | , password : String -- 2
38 | }
39 |
40 |
41 | addAuthReqDecoder : JD.Decoder AddAuthReq
42 | addAuthReqDecoder =
43 | JD.lazy <|
44 | \_ ->
45 | decode AddAuthReq
46 | |> required "username" JD.string ""
47 | |> required "password" JD.string ""
48 |
49 |
50 | addAuthReqEncoder : AddAuthReq -> JE.Value
51 | addAuthReqEncoder v =
52 | JE.object <|
53 | List.filterMap identity <|
54 | [ requiredFieldEncoder "username" JE.string "" v.username
55 | , requiredFieldEncoder "password" JE.string "" v.password
56 | ]
57 |
58 |
59 | type alias RmvAuthReq =
60 | { token : String -- 1
61 | }
62 |
63 |
64 | rmvAuthReqDecoder : JD.Decoder RmvAuthReq
65 | rmvAuthReqDecoder =
66 | JD.lazy <|
67 | \_ ->
68 | decode RmvAuthReq
69 | |> required "token" JD.string ""
70 |
71 |
72 | rmvAuthReqEncoder : RmvAuthReq -> JE.Value
73 | rmvAuthReqEncoder v =
74 | JE.object <|
75 | List.filterMap identity <|
76 | [ requiredFieldEncoder "token" JE.string "" v.token
77 | ]
78 |
79 |
80 | type alias AddVoucherReq =
81 | { notify : Bool -- 1
82 | , email : String -- 2
83 | , password : Bool -- 3
84 | }
85 |
86 |
87 | addVoucherReqDecoder : JD.Decoder AddVoucherReq
88 | addVoucherReqDecoder =
89 | JD.lazy <|
90 | \_ ->
91 | decode AddVoucherReq
92 | |> required "notify" JD.bool False
93 | |> required "email" JD.string ""
94 | |> required "password" JD.bool False
95 |
96 |
97 | addVoucherReqEncoder : AddVoucherReq -> JE.Value
98 | addVoucherReqEncoder v =
99 | JE.object <|
100 | List.filterMap identity <|
101 | [ requiredFieldEncoder "notify" JE.bool False v.notify
102 | , requiredFieldEncoder "email" JE.string "" v.email
103 | , requiredFieldEncoder "password" JE.bool False v.password
104 | ]
105 |
--------------------------------------------------------------------------------
/back/cmd/openbsrv/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "os"
7 | "path"
8 |
9 | "github.com/codemodus/sigmon/v2"
10 |
11 | "github.com/OpenEugene/openboard/back/internal/dbg"
12 | "github.com/OpenEugene/openboard/back/internal/log"
13 | )
14 |
15 | func main() {
16 | if err := run(); err != nil {
17 | cmd := path.Base(os.Args[0])
18 | fmt.Fprintf(os.Stderr, "%s: %s\n", cmd, err)
19 | os.Exit(1)
20 | }
21 | }
22 |
23 | func run() error {
24 | var (
25 | dbdrvr = "mysql"
26 | dbname = "openeug_openb_dev"
27 | dbuser = "openeug_openbdev"
28 | dbpass = ""
29 | dbaddr = "127.0.0.1"
30 | dbport = ":3306"
31 | migrate bool
32 | rollback bool
33 | skipsrv bool
34 | debug bool
35 | frontDir = "../../../front/public"
36 | migTblPfx = "mig_"
37 | )
38 |
39 | flag.StringVar(&dbname, "dbname", dbname, "database name")
40 | flag.StringVar(&dbuser, "dbuser", dbuser, "database user")
41 | flag.StringVar(&dbpass, "dbpass", dbpass, "database pass")
42 | flag.StringVar(&dbaddr, "dbaddr", dbaddr, "database addr")
43 | flag.StringVar(&dbport, "dbport", dbport, "database port")
44 | flag.BoolVar(&migrate, "migrate", migrate, "migrate up")
45 | flag.BoolVar(&rollback, "rollback", rollback, "migrate dn")
46 | flag.BoolVar(&skipsrv, "skipsrv", skipsrv, "skip server run")
47 | flag.BoolVar(&debug, "debug", debug, "debug true or false")
48 | flag.StringVar(&frontDir, "frontdir", frontDir, "front public assets directory")
49 | flag.Parse()
50 |
51 | sm := sigmon.New(nil)
52 | sm.Start()
53 | defer sm.Stop()
54 |
55 | logCfg := log.Config{
56 | Err: log.Output{
57 | Out: os.Stdout,
58 | Prefix: "[ERROR] ",
59 | },
60 | Inf: log.Output{
61 | Out: os.Stdout,
62 | Prefix: "[INFO] ",
63 | },
64 | }
65 | log := log.New(logCfg)
66 |
67 | if debug {
68 | dbg.SetOut(os.Stdout)
69 | }
70 |
71 | dbg.Logf("set up SQL database at %s:%s.", dbaddr, dbport)
72 | db, err := newSQLDB(dbdrvr, dbCreds(dbname, dbuser, dbpass, dbaddr, dbport))
73 | if err != nil {
74 | return err
75 | }
76 |
77 | mig, err := newDBMig(db, dbdrvr, migTblPfx)
78 | if err != nil {
79 | return err
80 | }
81 |
82 | gsrv, err := newGRPCSrv(":4242", db, dbdrvr)
83 | if err != nil {
84 | return err
85 | }
86 |
87 | mig.addMigrators(gsrv.services()...)
88 | if mres, migType := mig.run(migrate, rollback); len(migType) > 0 {
89 | if mres.HasError() {
90 | return mres.ErrsErr()
91 | }
92 | log.Info("%s: %s", migType, mres)
93 | }
94 |
95 | if skipsrv {
96 | fmt.Println("servers will not be run; exiting")
97 | return nil
98 | }
99 |
100 | hsrv, err := newHTTPSrv(":4242", ":4243", nil)
101 | if err != nil {
102 | return err
103 | }
104 |
105 | fsrv, err := newFrontSrv(":4244", frontDir, nil)
106 | if err != nil {
107 | return err
108 | }
109 |
110 | m := newServerMgmt(gsrv, hsrv, fsrv)
111 |
112 | sm.Set(func(s *sigmon.State) {
113 | if err := m.stop(); err != nil {
114 | fmt.Fprintln(os.Stderr, err)
115 | }
116 | })
117 |
118 | log.Info("to gracefully stop the application, send signal like TERM (CTRL-C) or HUP")
119 |
120 | return m.serve()
121 | }
122 |
--------------------------------------------------------------------------------
/back/internal/postsvc/postsvc.go:
--------------------------------------------------------------------------------
1 | package postsvc
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 | "io/fs"
7 | "io/ioutil"
8 |
9 | "github.com/OpenEugene/openboard/back/internal/pb"
10 | "github.com/OpenEugene/openboard/back/internal/postsvc/internal/asset"
11 | "github.com/OpenEugene/openboard/back/internal/postsvc/internal/postdb"
12 |
13 | "google.golang.org/grpc"
14 | )
15 |
16 | var _ pb.PostServer = &PostSvc{}
17 |
18 | // var _ grpcsrv.Registerable = &PostSvc{}
19 | // var _ sqlmig.DataProvider = &PostSvc{}
20 | // var _ sqlmig.Regularizer = &PostSvc{}
21 |
22 | type relDB interface {
23 | pb.PostServer
24 | }
25 |
26 | // PostSvc encapsulates dependencies and data required to implement the
27 | // pb.PostServer interface.
28 | type PostSvc struct {
29 | db relDB
30 | }
31 |
32 | // New returns a pointer to a PostSvc instance or an error.
33 | func New(relDb *sql.DB, driver string, offset uint64) (*PostSvc, error) {
34 | db, err := postdb.New(relDb, driver, offset)
35 | if err != nil {
36 | return nil, err
37 | }
38 |
39 | s := PostSvc{
40 | db: db,
41 | }
42 |
43 | return &s, nil
44 | }
45 |
46 | // AddType implements part of the pb.PostServer interface.
47 | func (s *PostSvc) AddType(ctx context.Context, req *pb.AddTypeReq) (*pb.TypeResp, error) {
48 | return s.db.AddType(ctx, req)
49 | }
50 |
51 | // FndTypes implements part of the pb.PostServer interface.
52 | func (s *PostSvc) FndTypes(ctx context.Context, req *pb.FndTypesReq) (*pb.TypesResp, error) {
53 | return s.db.FndTypes(ctx, req)
54 | }
55 |
56 | // AddPost implements part of the pb.PostServer interface.
57 | func (s *PostSvc) AddPost(ctx context.Context, req *pb.AddPostReq) (*pb.PostResp, error) {
58 | return s.db.AddPost(ctx, req)
59 | }
60 |
61 | // FndPosts implements part of the pb.PostServer interface.
62 | func (s *PostSvc) FndPosts(ctx context.Context, req *pb.FndPostsReq) (*pb.PostsResp, error) {
63 | return s.db.FndPosts(ctx, req)
64 | }
65 |
66 | // OvrPost implements part of the pb.PostServer interface.
67 | func (s *PostSvc) OvrPost(ctx context.Context, req *pb.OvrPostReq) (*pb.PostResp, error) {
68 | return s.db.OvrPost(ctx, req)
69 | }
70 |
71 | // RmvPost implements part of the pb.PostServer interface.
72 | func (s *PostSvc) RmvPost(ctx context.Context, req *pb.RmvPostReq) (*pb.RmvPostResp, error) {
73 | return s.db.RmvPost(ctx, req)
74 | }
75 |
76 | // RegisterWithGRPCServer implements the grpcsrv.Registerable interface.
77 | func (s *PostSvc) RegisterWithGRPCServer(g *grpc.Server) error {
78 | pb.RegisterPostServer(g, s)
79 | return nil
80 | }
81 |
82 | // MigrationData ...
83 | func (s *PostSvc) MigrationData() (string, map[string][]byte) {
84 | name := "postsvc"
85 | m := make(map[string][]byte)
86 |
87 | afs, err := asset.NewFS()
88 | if err != nil {
89 | return name, nil
90 | }
91 |
92 | sqls, err := fs.Glob(afs, "*.sql")
93 | if err != nil {
94 | return name, nil
95 | }
96 |
97 | for _, sql := range sqls {
98 | f, err := afs.Open(sql)
99 | if err != nil {
100 | return name, nil
101 | }
102 |
103 | m[sql], err = ioutil.ReadAll(f)
104 | if err != nil {
105 | return name, nil
106 | }
107 | }
108 |
109 | return name, m
110 | }
111 |
112 | // Regularize ...
113 | func (s *PostSvc) Regularize(ctx context.Context) error {
114 | return nil
115 | }
116 |
--------------------------------------------------------------------------------
/back/internal/usersvc/usersvc.go:
--------------------------------------------------------------------------------
1 | package usersvc
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 | "io/fs"
7 | "io/ioutil"
8 |
9 | "github.com/OpenEugene/openboard/back/internal/pb"
10 | "github.com/OpenEugene/openboard/back/internal/usersvc/internal/asset"
11 | "github.com/OpenEugene/openboard/back/internal/usersvc/internal/userdb"
12 | "google.golang.org/grpc"
13 | )
14 |
15 | var _ pb.UserSvcServer = &UserSvc{}
16 |
17 | //var _ grpcsrv.Registerable = &UserSvc{}
18 | //var _ sqlmig.DataProvider = &UserSvc{}
19 | //var _ sqlmig.Regularizer = &UserSvc{}
20 |
21 | type relDb interface {
22 | pb.UserSvcServer
23 | }
24 |
25 | // UserSvc encapsulates dependencies and data required to implement the
26 | // pb.UserServer interface.
27 | type UserSvc struct {
28 | db relDb
29 | }
30 |
31 | // New returns a pointer to a UserSvc instance or an error.
32 | func New(relDb *sql.DB, driver string, offset uint64) (*UserSvc, error) {
33 | db, err := userdb.New(relDb, driver, offset)
34 | if err != nil {
35 | return nil, err
36 | }
37 |
38 | s := UserSvc{
39 | db: db,
40 | }
41 |
42 | return &s, nil
43 | }
44 |
45 | // RegisterWithGRPCServer implements the grpcsrv.Registerable interface.
46 | func (s *UserSvc) RegisterWithGRPCServer(g *grpc.Server) error {
47 | pb.RegisterUserSvcServer(g, s)
48 |
49 | return nil
50 | }
51 |
52 | // AddRole implements part of the pb.UserServer interface.
53 | func (s *UserSvc) AddRole(ctx context.Context, req *pb.AddRoleReq) (*pb.RoleResp, error) {
54 | return s.db.AddRole(ctx, req)
55 | }
56 |
57 | // FndRoles implements part of the pb.UserServer interface.
58 | func (s *UserSvc) FndRoles(ctx context.Context, req *pb.FndRolesReq) (*pb.RolesResp, error) {
59 | return s.db.FndRoles(ctx, req)
60 | }
61 |
62 | // AddUser implements part of the pb.UserServer interface.
63 | func (s *UserSvc) AddUser(ctx context.Context, req *pb.AddUserReq) (*pb.UserResp, error) {
64 | return s.db.AddUser(ctx, req)
65 | }
66 |
67 | // OvrUser implements part of the pb.UserServer interface.
68 | func (s *UserSvc) OvrUser(ctx context.Context, req *pb.OvrUserReq) (*pb.UserResp, error) {
69 | return s.db.OvrUser(ctx, req)
70 | }
71 |
72 | // FndUsers implements part of the pb.UserServer interface.
73 | func (s *UserSvc) FndUsers(ctx context.Context, req *pb.FndUsersReq) (*pb.UsersResp, error) {
74 | return s.db.FndUsers(ctx, req)
75 | }
76 |
77 | // RmvUser implements part of the pb.UserServer interface.
78 | func (s *UserSvc) RmvUser(ctx context.Context, req *pb.RmvUserReq) (*pb.RmvUserResp, error) {
79 | return s.db.RmvUser(ctx, req)
80 | }
81 |
82 | // MigrationData ...
83 | func (s *UserSvc) MigrationData() (string, map[string][]byte) {
84 | name := "usersvc"
85 | m := make(map[string][]byte)
86 |
87 | afs, err := asset.NewFS()
88 | if err != nil {
89 | return name, nil
90 | }
91 |
92 | sqls, err := fs.Glob(afs, "*.sql")
93 | if err != nil {
94 | return name, nil
95 | }
96 |
97 | for _, sql := range sqls {
98 | f, err := afs.Open(sql)
99 | if err != nil {
100 | return name, nil
101 | }
102 |
103 | m[sql], err = ioutil.ReadAll(f)
104 | if err != nil {
105 | return name, nil
106 | }
107 | }
108 |
109 | return name, m
110 | }
111 |
112 | //Regularize ...
113 | func (s *UserSvc) Regularize(ctx context.Context) error {
114 | return nil
115 | }
116 |
--------------------------------------------------------------------------------
/docs/learning/git.md:
--------------------------------------------------------------------------------
1 | # Git Walk-through
2 |
3 | ## Introduction
4 |
5 | - [Programming with Mosh - What is Git?](https://youtu.be/2ReR1YJrNOM)
6 | - [Paul Programming - What is Git?](https://youtu.be/OqmSzXDrJBk)
7 |
8 | The first video is "Git in principle", and the second is "Git in practice".
9 |
10 | The second video seems to imply that Git is centralized, which is in opposition
11 | to the description given in the first video. Centralized version control tools
12 | only permit one user to handle a portion of code at a time. Git is a distributed
13 | version control tool. Many users can operate on a Git project simultaneously,
14 | and the resulting changes are brought together into one place.
15 |
16 | ## Installation
17 |
18 | - [Git - Getting Started](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
19 |
20 | ## Configuration
21 |
22 | - [Git - Customizing Git](https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration)
23 |
24 | The most pertinent information is in the "Git Configuration" section only. Feel
25 | free to read further for more details and advanced configuration. The email and
26 | username you select will show up in publicly available git commit histories.
27 |
28 | ## Operation
29 |
30 | - [Udacity - Git Workflow](https://youtu.be/3a2x1iJFJWc)
31 | - [Kev The Dev - Easy Overview](https://youtu.be/7dYHRI55wxo)
32 |
33 | In the Udacity video, four columns are shown. It may help to think
34 | of the column on the far left as the newest changes to the code. We make changes
35 | to code, then pass those changes through certain steps to ensure the difference
36 | the changes make is correct and understandable.
37 |
38 | Kev's overview has some caveats. First, when cloning a project, if you have not
39 | first setup SSH access to your repository, use HTTPS instead (seen at 6:54).
40 | Second, when you see the usage of `git checkout` as it applies to switching
41 | branches, it is preferrable to use the `git switch` command. So, `git checkout
42 | -b new-branch` should be `git switch -c new-branch`, and `git checkout
43 | existing-branch` should be `git switch existing-branch`. The usage of checkout
44 | to move to a specific commit (seen at 4:50) is correct. The git command will
45 | give notices to use `git switch` in the relevant cases and those messages can be
46 | seen in the video. Lastly, it is currently preferred to use the default branch
47 | name "main" rather than "master".
48 |
49 | ## Interpretation
50 |
51 | A simple list of commands to start work on an issue:
52 | ```sh
53 | git switch main
54 | git pull origin main
55 | git switch -c my_branch-123
56 | # make change(s)
57 | git add .
58 | git commit -m"Make specific change"
59 | git push -u origin my_branch-123
60 | ```
61 |
62 | Additional commits can be added:
63 | ```sh
64 | git add .
65 | git commit -m"Fix some mistake"
66 | git push
67 | ```
68 |
69 | It may be useful to run `git remote prune origin` after `git pull origin main`
70 | to ensure that the references to remote branches are cleaned up.
71 |
72 | ## Visualization
73 |
74 | - [Git GUIs](https://git-scm.com/downloads/guis)
75 |
76 | Git, as a command line interface (CLI) tool, is easy to interact with for those
77 | who are comfortable working from a terminal. However, it can be useful, not
78 | just for learners, to have clear visual represenations of the data being
79 | operated on. Many graphical user interface (GUI) programs are available that
80 | leverage the Git CLI as their "engine".
81 |
--------------------------------------------------------------------------------
/msgs/proto/user.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | // openboard
4 | //
5 | // user semantics:
6 | //
7 | // Add{T}(s) (Add{T}(s)Req) returns {T}(s)Resp // POST
8 | // Ovr{T}(s) (Ovr{T}(s)Req) returns {T}(s)Resp // PUT
9 | // Mod{T}(s) (Mod{T}(s)Req) returns {T}(s)Resp // PATCH
10 | // Get{T} (Get{T}Req) returns {T}Resp // GET
11 | // Fnd{T}s (Fnd{T}sReq) returns {T}sResp // GET
12 | // Rmv{T}(s) (Rmv{T}(s)Req) returns EmptyResp // DELETE
13 | // Unr{T} (Unr{T}Req) returns {T}Resp // PATCH
14 | package pb;
15 |
16 | option go_package = ".;pb";
17 |
18 | import "google/api/annotations.proto";
19 | import "google/protobuf/timestamp.proto";
20 |
21 | message RoleResp {
22 | string id = 1;
23 | string name = 2;
24 | }
25 |
26 | message AddRoleReq { string name = 1; }
27 |
28 | message RolesResp {
29 | repeated RoleResp items = 1;
30 | uint32 total = 2;
31 | }
32 |
33 | message FndRolesReq {
34 | repeated string roleIds = 1;
35 | repeated string roleNames = 2;
36 | uint32 limit = 3;
37 | uint32 lapse = 4;
38 | }
39 |
40 | message User {
41 | string id = 1;
42 | string username = 2;
43 | string email = 3;
44 | bool emailHold = 4;
45 | string altmail = 5;
46 | bool altmailHold = 6;
47 | string fullName = 7;
48 | string avatar = 8;
49 | repeated RoleResp roles = 9;
50 | google.protobuf.Timestamp lastLogin = 10;
51 | google.protobuf.Timestamp created = 11;
52 | google.protobuf.Timestamp updated = 12;
53 | google.protobuf.Timestamp deleted = 13;
54 | google.protobuf.Timestamp blocked = 14;
55 | }
56 |
57 | message UserResp { User item = 1; }
58 |
59 | message AddUserReq {
60 | string username = 1;
61 | string email = 2;
62 | bool emailHold = 3;
63 | string altmail = 4;
64 | bool altmailHold = 5;
65 | string fullName = 6;
66 | string avatar = 7;
67 | string password = 8;
68 | repeated string roleIds = 9;
69 | }
70 |
71 | message OvrUserReq {
72 | string id = 1;
73 | AddUserReq req = 2;
74 | }
75 |
76 | message UsersResp {
77 | repeated User items = 1;
78 | uint32 total = 2;
79 | }
80 |
81 | message FndUsersReq {
82 | repeated string roleIds = 1;
83 | string email = 2;
84 | bool emailHold = 3;
85 | string altmail = 4;
86 | bool altmailHold = 5;
87 | google.protobuf.Timestamp createdFirst = 6;
88 | google.protobuf.Timestamp createdFinal = 7;
89 | bool createdDesc = 8;
90 | uint32 limit = 9;
91 | uint32 lapse = 10;
92 | }
93 |
94 | message RmvUserResp {}
95 |
96 | message RmvUserReq { string id = 1; }
97 |
98 | service UserSvc {
99 | rpc AddRole(AddRoleReq) returns (RoleResp) {}
100 |
101 | rpc FndRoles(FndRolesReq) returns (RolesResp) {
102 | option (google.api.http) = {
103 | get : "/roles"
104 | };
105 | }
106 |
107 | rpc AddUser(AddUserReq) returns (UserResp) {
108 | option (google.api.http) = {
109 | post : "/user"
110 | body : "*"
111 | };
112 | }
113 |
114 | rpc OvrUser(OvrUserReq) returns (UserResp) {
115 | option (google.api.http) = {
116 | put : "/user/{id}"
117 | body : "*"
118 | };
119 | }
120 |
121 | rpc FndUsers(FndUsersReq) returns (UsersResp) {
122 | option (google.api.http) = {
123 | get : "/users"
124 | };
125 | }
126 |
127 | rpc RmvUser(RmvUserReq) returns (RmvUserResp) {
128 | option (google.api.http) = {
129 | delete : "/user/{id}"
130 | };
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/docs/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Welcome
2 |
3 | ### Please & Thank You
4 |
5 | First off, thank you for considering contributing to Openboard. It's people
6 | like you that make projects like this possible!
7 |
8 | Following this guide helps to communicate that you respect the time of the
9 | developers managing and developing this open source project. In return, they
10 | should reciprocate that respect in addressing your issue, assessing changes,
11 | and helping you finalize your pull requests.
12 |
13 | ## Accountability
14 |
15 | Openboard is a project utilizing and teaching [Go](https://golang.org) and
16 | [Elm](https://elm-lang.org). This project is part of the
17 | [OpenEugene](http://openeugene.org) collective, and our meetings are part of
18 | the [EugeneTech](https://eugenetech.org) community. Therefore, we align
19 | ourselves with the codes of conduct put forth by those orgs/communities.
20 |
21 | - [EugeneTech Policies](https://github.com/EugTech/policies)
22 | - [OpenEugene Code of Conduct (via CFA)](https://brigade.codeforamerica.org/about/code-of-conduct)
23 | - [Go Community Code of Conduct](https://golang.org/conduct)
24 | - [Elm Community Spaces Code of Conduct](https://github.com/elm-community/discussions/blob/master/code-of-conduct.md)
25 |
26 | ## Getting Started (Tools and Building)
27 |
28 | - [Openboard Readme](../README.md)
29 |
30 | ## Contributing
31 |
32 | ### General
33 |
34 | We would appreciate that most contributions to this project be made by attending
35 | our sprint wrap & plan and/or check-ins. See the
36 | [README](../README.md) for more
37 | information. Contributing from forks has not yet been tested regarding our
38 | automated tracking mechanisms.
39 |
40 | ### Reporting
41 |
42 | - "Issues" contains requested corrections and improvements.
43 | - "Pull Requests" contains mutations that each directly resolve "Issues"
44 |
45 | ### Tracking
46 |
47 | Issues and Pull Requests are tracked using Kanban boards. Board automation is
48 | provided in both cases.
49 |
50 | #### [Issue Qualification](https://github.com/OpenEugene/openboard/projects/3)
51 |
52 | When an Issue is:
53 | - up for consideration, it is added to the "Proposed" column
54 | - accepted to be resolved, it is moved to the "Accepted" column
55 | - actively being resolved, it is moved to the "Claimed" column
56 | - fully resolved, it is moved to the "Resolved" column
57 |
58 | *Most of these steps are automated. The only step that is often performed
59 | manually is the first step where an issue is added to the "Proposed" column.
60 |
61 | #### [Change Management](https://github.com/OpenEugene/openboard/projects/1)
62 |
63 | When a PR is:
64 | - created, it is added to the "Initiated" column
65 | - ready for review, it is added to the "Submitted" column
66 | - closed, it is added to the "Finished" column
67 |
68 | *All of these steps are automated.
69 |
70 | ### Automation
71 |
72 | #### Pull Request Creation
73 | A Branch that has the suffix "-{issue_number}" (e.g. "my_branch-123") and is
74 | pushed to the project will automatically have a PR opened that is associated
75 | with the relevant Issue. The PR title is derived from the Issue title. The PR is
76 | automatically added to the "Change Management" project.
77 |
78 | #### WIP Convenience
79 | A Branch that contains a commit message that has the prefix "WIP" will have its
80 | PR title prefixed with "WIP: " (e.g. "WIP: Fix some bug"). A Branch that
81 | contains a commit message that has the prefix "NOWIP" and also has an existing
82 | PR tracking it will cause the PR title "WIP: " prefix to be trimmed.
83 |
--------------------------------------------------------------------------------
/back/README.md:
--------------------------------------------------------------------------------
1 | # openboard/back
2 |
3 | ## Getting Started
4 |
5 | ### Easy Mode
6 |
7 | The following scripts should be working for Bash on Linux, WSL, and Darwin. When
8 | installing the database, the suggested defaults for this project are:
9 | dbname = "openeug_openb_dev", and dbuser = "openeug_openbdev".
10 |
11 | ```sh
12 | # from {project_root}
13 | cd back/tools
14 | ./install-go
15 | ./install-tools
16 | ./install-mariadb # container-based alternative below
17 | ```
18 |
19 | ```sh
20 | # database container setup (skip if using the "./install-mariadb" script above)
21 | # installs docker and docker-compose
22 | # from {project_root}
23 | cd back/tools/iso/
24 | ./dev up # subcommands [up|dn|ip|clean] (default: up)
25 | ```
26 |
27 | ### Normal Mode
28 |
29 | - [Install Go](https://golang.org/doc/install)
30 | - [Install Tools](./tools/install-tools)
31 | - [Install MariaDB (>=10.2.22)](https://www.google.com/search?q=install+mariadb+stable+on)
32 |
33 | ### Advanced Options
34 |
35 | #### Protobuf/protoc
36 |
37 | Protocol buffers are Google's language-neutral, platform-neutral, extensible
38 | mechanism for serializing structured data.
39 |
40 | [Learn More](https://developers.google.com/protocol-buffers/) |
41 | [Releases](https://github.com/protocolbuffers/protobuf/releases) |
42 | [Install From
43 | Source](https://github.com/protocolbuffers/protobuf/blob/master/src/README.md)
44 |
45 | ```sh
46 | # may require changes for your OS/ENV
47 | cd {your_source_code_dir}
48 | mkdir -p github.com/protocolbuffers/protobuf
49 | cd github.com/protocolbuffers/protobuf
50 | git clone https://github.com/protocolbuffers/protobuf.git .
51 | git submodule update --init --recursive
52 | ./autogen.sh
53 | ./configure
54 | make
55 | make check
56 | sudo make install
57 | sudo ldconfig # refresh shared library cache.
58 | ```
59 |
60 | ## Project Source
61 |
62 | `openbsrv` will open three ports (4242, 4243, 4244). 4242 is used to serve gRPC
63 | endpoints. gRPC endpoints can be accessed directly, or via an HTTP gateway on
64 | port 4243. In order to view the API endpoints, please visit
65 | http://localhost:4243/v/docs. The frontend assets are served on port 4244.
66 |
67 | ### Clone
68 |
69 | ```sh
70 | cd {your_source_code_dir}
71 | mkdir -p github.com/OpenEugene/openboard
72 | cd github.com/OpenEugene/openboard
73 | git clone https://github.com/OpenEugene/openboard .
74 | ```
75 |
76 | ### Build and Execute
77 |
78 | ```sh
79 | # from {project_root}
80 | cd back/cmd/openbsrv
81 | go build
82 | ./openbsrv --dbpass={your_dbpass} --migrate
83 | # be careful not to git add/commit the executable
84 | ```
85 |
86 | Please refer to the [openbsrv readme](./cmd/openbsrv/README.md) for more details
87 | about usage and flags (e.g. -dbname, -dbuser, etc.).
88 |
89 | ### Database Migration Management
90 |
91 | ```sh
92 | # from {project_root}
93 | cd back/cmd/openbsrv
94 | ./openbsrv --dbpass={your_dbpass} --rollback
95 | ^C # Ctrl-C will send the system signal "SIGINT" and halts the program
96 | ./openbsrv --dbpass={your_dbpass} --migrate
97 | ```
98 |
99 | ### Run Tests
100 |
101 | #### Unit Tests
102 |
103 | None at this time.
104 |
105 | #### Surface Tests
106 |
107 | None at this time.
108 |
109 | #### End-to-end Tests
110 |
111 | A convenience script has been provided to build and run the executable, and also
112 | run the end-to-end tests.
113 |
114 | ```sh
115 | # from {project_root}
116 | cd back/tests/openbsrv
117 | ./run-tests
118 | ```
119 |
120 | ## Contributing
121 |
122 | - [Start here](../docs/CONTRIBUTING.md)
123 |
--------------------------------------------------------------------------------
/docs/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
78 |
--------------------------------------------------------------------------------
/msgs/proto/include/googleapis/google/rpc/status.proto:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | syntax = "proto3";
16 |
17 | package google.rpc;
18 |
19 | import "google/protobuf/any.proto";
20 |
21 | option go_package = "google.golang.org/genproto/googleapis/rpc/status;status";
22 | option java_multiple_files = true;
23 | option java_outer_classname = "StatusProto";
24 | option java_package = "com.google.rpc";
25 | option objc_class_prefix = "RPC";
26 |
27 |
28 | // The `Status` type defines a logical error model that is suitable for different
29 | // programming environments, including REST APIs and RPC APIs. It is used by
30 | // [gRPC](https://github.com/grpc). The error model is designed to be:
31 | //
32 | // - Simple to use and understand for most users
33 | // - Flexible enough to meet unexpected needs
34 | //
35 | // # Overview
36 | //
37 | // The `Status` message contains three pieces of data: error code, error message,
38 | // and error details. The error code should be an enum value of
39 | // [google.rpc.Code][google.rpc.Code], but it may accept additional error codes if needed. The
40 | // error message should be a developer-facing English message that helps
41 | // developers *understand* and *resolve* the error. If a localized user-facing
42 | // error message is needed, put the localized message in the error details or
43 | // localize it in the client. The optional error details may contain arbitrary
44 | // information about the error. There is a predefined set of error detail types
45 | // in the package `google.rpc` that can be used for common error conditions.
46 | //
47 | // # Language mapping
48 | //
49 | // The `Status` message is the logical representation of the error model, but it
50 | // is not necessarily the actual wire format. When the `Status` message is
51 | // exposed in different client libraries and different wire protocols, it can be
52 | // mapped differently. For example, it will likely be mapped to some exceptions
53 | // in Java, but more likely mapped to some error codes in C.
54 | //
55 | // # Other uses
56 | //
57 | // The error model and the `Status` message can be used in a variety of
58 | // environments, either with or without APIs, to provide a
59 | // consistent developer experience across different environments.
60 | //
61 | // Example uses of this error model include:
62 | //
63 | // - Partial errors. If a service needs to return partial errors to the client,
64 | // it may embed the `Status` in the normal response to indicate the partial
65 | // errors.
66 | //
67 | // - Workflow errors. A typical workflow has multiple steps. Each step may
68 | // have a `Status` message for error reporting.
69 | //
70 | // - Batch operations. If a client uses batch request and batch response, the
71 | // `Status` message should be used directly inside batch response, one for
72 | // each error sub-response.
73 | //
74 | // - Asynchronous operations. If an API call embeds asynchronous operation
75 | // results in its response, the status of those operations should be
76 | // represented directly using the `Status` message.
77 | //
78 | // - Logging. If some API errors are stored in logs, the message `Status` could
79 | // be used directly after any stripping needed for security/privacy reasons.
80 | message Status {
81 | // The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code].
82 | int32 code = 1;
83 |
84 | // A developer-facing error message, which should be in English. Any
85 | // user-facing error message should be localized and sent in the
86 | // [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client.
87 | string message = 2;
88 |
89 | // A list of messages that carry the error details. There is a common set of
90 | // message types for APIs to use.
91 | repeated google.protobuf.Any details = 3;
92 | }
93 |
--------------------------------------------------------------------------------
/.github/workflows/wip.yml:
--------------------------------------------------------------------------------
1 | name: Manage WIP workflow
2 | on:
3 | push:
4 | branches:
5 | - '**-[0-9]+'
6 | jobs:
7 | build:
8 | name: WIP it
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Set branch name envvar
12 | run: |
13 | echo "BRANCH_NAME=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV
14 |
15 | - name: Set branch issue envvar
16 | run: |
17 | issue="${BRANCH_NAME##*-}"
18 | ! [[ "${issue}" =~ ^[0-9]+$ ]] && >&2 echo "issue num not parsed" && exit 1
19 | echo "BRANCH_ISSUE=${issue}" >> $GITHUB_ENV
20 |
21 | - uses: actions/checkout@v2
22 | with:
23 | fetch-depth: 128
24 | ref: main
25 |
26 | - uses: actions/checkout@v2
27 | with:
28 | fetch-depth: 0
29 | ref: ${{ github.sha }}
30 |
31 | - name: Setup/update WIP pull requests and related issues
32 | run: |
33 | set -e
34 |
35 | echo "branch issue: ${BRANCH_ISSUE}" # debug
36 |
37 | infoFile="commits_info"
38 | git log \
39 | --left-right \
40 | --graph \
41 | --cherry-pick \
42 | --oneline \
43 | --pretty=format:%s \
44 | origin/main..origin/"${BRANCH_NAME}" >> "${infoFile}"
45 | echo >> "${infoFile}" # add missing newline
46 |
47 | tac "${infoFile}" > "rev_${infoFile}" && mv "rev_${infoFile}" "${infoFile}" # reverse data
48 | cat "${infoFile}" # debug
49 |
50 | wipLineNum="$(cat "${infoFile}" | grep "^> WIP" -n | tail -n 1 | cut -d: -f1)"
51 | nowipLineNum="$(cat "${infoFile}" | grep "^> NOWIP" -n | tail -n 1 | cut -d: -f1)"
52 | prList="$(hub pr list --head "${BRANCH_NAME}" --state=open --format="%i %t\n")"
53 | prCt="$(echo -ne "${prList}" | wc -l)"
54 |
55 | echo "wip:${wipLineNum} nowip:${nowipLineNum}" # debug
56 | echo pr list... # debug
57 | echo -ne "${prList}" # debug
58 |
59 | echo "${prCt} existing pr(s) for the branch issue number ${BRANCH_ISSUE}" # debug
60 |
61 | case "${prCt}" in
62 | 0)
63 | prTitle="$(hub issue show --format="%t" "${BRANCH_ISSUE}")"
64 | if [[ -n "${wipLineNum}" && "${nowipLineNum}" -lt "${wipLineNum}" ]]; then
65 | prTitle="WIP: ${prTitle}"
66 | fi
67 |
68 | echo "pr title: ${prTitle}" # debug
69 |
70 | hub pull-request \
71 | --assign "${GITHUB_ACTOR}" \
72 | --base main \
73 | --head ${BRANCH_NAME} \
74 | --message "${prTitle}" \
75 | --message "Closes #${BRANCH_ISSUE}"
76 |
77 | echo "github actor: ${GITHUB_ACTOR}" # debug
78 |
79 | hub issue update "${BRANCH_ISSUE}" \
80 | -a "${GITHUB_ACTOR}"
81 | ;;
82 |
83 | 1)
84 | prTitle="$(echo -ne "${prList}" | cut -d" " -f2-)"
85 | prIssue="$(echo -ne "${prList}" | grep -o "#[0-9]*" | tr -d "#")"
86 | shouldChange=false
87 |
88 | if [[ -n "${nowipLineNum}" && "${nowipLineNum}" -gt "${wipLineNum}" ]]; then
89 | if [[ "${prTitle}" =~ ^WIP: ]]; then
90 | shouldChange=true
91 | prTitle="${prTitle#WIP:}"
92 | prTitle="${prTitle# }"
93 | fi
94 | elif [[ -n "${wipLineNum}" && "${nowipLineNum}" -lt "${wipLineNum}" ]]; then
95 | prTitle="$(echo -ne "${prList}" | cut -d" " -f2-)"
96 | if [[ ! "${prTitle}" =~ ^WIP: ]]; then
97 | shouldChange=true
98 | prTitle="WIP: ${prTitle}"
99 | fi
100 | fi
101 |
102 | if ${shouldChange}; then
103 | echo "shouldChange: ${shouldChange}" # debug
104 |
105 | prBody="$(hub pr list --head "${BRANCH_NAME}" --state=open --format="%b")"
106 |
107 | echo "pr title: ${prTitle}" # debug
108 | echo "pr body: ${prBody}" # debug
109 |
110 | hub issue update "${prIssue}" \
111 | --message "${prTitle}" \
112 | --message "${prBody}"
113 | fi
114 | ;;
115 |
116 | *)
117 | echo "multiple PRs were found based on the same branch"
118 | echo "taking no action"
119 | ;;
120 | esac
121 | env:
122 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
123 |
--------------------------------------------------------------------------------
/back/internal/postsvc/internal/postdb/qrypost.go:
--------------------------------------------------------------------------------
1 | package postdb
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "strings"
7 | "time"
8 |
9 | "github.com/OpenEugene/openboard/back/internal/altr"
10 | "github.com/OpenEugene/openboard/back/internal/pb"
11 | "github.com/codemodus/uidgen"
12 | "github.com/go-sql-driver/mysql"
13 | )
14 |
15 | type cx = context.Context
16 |
17 | var (
18 | csvStr = altr.CSVFromStrings
19 | lim = altr.LimitUint32
20 | asTS = altr.Timestamp
21 | )
22 |
23 | func parseOrUID(ug *uidgen.UIDGen, sid string) (uidgen.UID, bool) {
24 | if sid == "" {
25 | return ug.UID(), true
26 | }
27 | return ug.Parse(sid)
28 | }
29 |
30 | func (s *PostDB) upsertType(ctx cx, sid string, x *pb.AddTypeReq, y *pb.TypeResp) error {
31 | id, ok := parseOrUID(s.ug, sid)
32 | if !ok {
33 | return fmt.Errorf("invalid uid")
34 | }
35 |
36 | stmt, err := s.db.Prepare("INSERT INTO `type` (type_id, name) VALUES (?, ?) ON DUPLICATE KEY UPDATE type_id = ?, name = ?")
37 | if err != nil {
38 | return err
39 | }
40 |
41 | _, err = stmt.Exec(&id, x.Name, &id, x.Name)
42 | if err != nil {
43 | return err
44 | }
45 |
46 | y.Id = id.String()
47 | y.Name = x.Name
48 |
49 | return nil
50 | }
51 |
52 | func (s *PostDB) findTypes(ctx cx, x *pb.FndTypesReq, y *pb.TypesResp) error {
53 | selStmt, err := s.db.Prepare("SELECT type_id, name FROM type LIMIT ? OFFSET ?")
54 | if err != nil {
55 | return err
56 | }
57 | defer selStmt.Close()
58 |
59 | rows, err := selStmt.Query(x.Limit, x.Lapse)
60 | if err != nil {
61 | return err
62 | }
63 | defer rows.Close()
64 |
65 | for rows.Next() {
66 | r := pb.TypeResp{}
67 |
68 | err := rows.Scan(&r.Id, &r.Name)
69 | if err != nil {
70 | return err
71 | }
72 |
73 | y.Items = append(y.Items, &r)
74 | }
75 |
76 | if err = rows.Err(); err != nil {
77 | return err
78 | }
79 |
80 | err = s.db.QueryRow("SELECT COUNT(*) FROM type").Scan(&y.Total)
81 | if err != nil {
82 | return err
83 | }
84 |
85 | return nil
86 | }
87 |
88 | func (s *PostDB) upsertPost(ctx cx, sid string, x *pb.AddPostReq, y *pb.PostResp) error {
89 | id, ok := parseOrUID(s.ug, sid)
90 | if !ok {
91 | return fmt.Errorf("invalid uid")
92 | }
93 |
94 | stmt, err := s.db.Prepare("INSERT INTO post (post_id, type_id, slug, title, body) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE post_id = ?, type_id = ?, slug = ?, title = ?, body = ?")
95 | if err != nil {
96 | return err
97 | }
98 |
99 | _, err = stmt.Exec(&id, x.TypeId, "", x.Title, x.Body, &id, x.TypeId, "", x.Title, x.Body)
100 | if err != nil {
101 | return err
102 | }
103 |
104 | y.Id = id.String()
105 | y.TypeId = x.TypeId
106 | y.Title = x.Title
107 | y.Body = x.Body
108 |
109 | return nil
110 | }
111 |
112 | func (s *PostDB) findPosts(ctx cx, x *pb.FndPostsReq, y *pb.PostsResp) error {
113 | selStmt, err := s.db.Prepare(`
114 | SELECT post_id, type_id, slug, title, body, created_at, updated_at, deleted_at
115 | FROM post
116 | WHERE (MATCH body AGAINST (? IN NATURAL LANGUAGE MODE)) OR
117 | (MATCH title AGAINST (? IN NATURAL LANGUAGE MODE)) OR
118 | body LIKE ? OR title LIKE ?
119 | ORDER BY
120 | (MATCH body AGAINST (? IN NATURAL LANGUAGE MODE))+(MATCH body AGAINST (? IN NATURAL LANGUAGE MODE))
121 | DESC`)
122 | if err != nil {
123 | return err
124 | }
125 | defer selStmt.Close()
126 |
127 | like := strings.Join(x.Keywords, " ")
128 |
129 | rows, err := selStmt.Query(
130 | like,
131 | like,
132 | "%"+like+"%",
133 | "%"+like+"%",
134 | like,
135 | like,
136 | )
137 | if err != nil {
138 | return err
139 | }
140 | defer rows.Close()
141 |
142 | for rows.Next() {
143 | r := pb.PostResp{}
144 | var tc, tu, td, tb mysql.NullTime
145 |
146 | err := rows.Scan(&r.Id, &r.TypeId, &r.Slug, &r.Title, &r.Body, &tc, &tu, &td)
147 | if err != nil {
148 | return err
149 | }
150 |
151 | r.Created = asTS(tc.Time, tc.Valid)
152 | r.Updated = asTS(tu.Time, tu.Valid)
153 | r.Deleted = asTS(td.Time, td.Valid)
154 | r.Blocked = asTS(tb.Time, tb.Valid)
155 |
156 | y.Posts = append(y.Posts, &r)
157 | }
158 |
159 | if err = rows.Err(); err != nil {
160 | return err
161 | }
162 |
163 | err = s.db.QueryRow(
164 | "SELECT COUNT(*) FROM post WHERE title LIKE ? OR body LIKE ?",
165 | "%"+like+"%",
166 | "%"+like+"%",
167 | ).Scan(&y.Total)
168 | if err != nil {
169 | return err
170 | }
171 |
172 | return nil
173 | }
174 |
175 | func (s *PostDB) deletePost(ctx cx, sid string) error {
176 | _, err := s.db.Exec(
177 | "UPDATE post SET deleted_at = ? WHERE post_id = ?",
178 | time.Now(),
179 | sid,
180 | )
181 | if err != nil {
182 | return err
183 | }
184 |
185 | return nil
186 | }
187 |
--------------------------------------------------------------------------------
/back/internal/httpsrv/httpsrv.go:
--------------------------------------------------------------------------------
1 | package httpsrv
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "encoding/json"
7 | "io/ioutil"
8 | "net/http"
9 | "time"
10 |
11 | "github.com/OpenEugene/openboard/back/internal/httpsrv/internal/asset"
12 | "github.com/OpenEugene/openboard/back/internal/pb"
13 | "github.com/codemodus/chain/v2"
14 | "github.com/codemodus/hedrs"
15 | "github.com/codemodus/mixmux"
16 | "github.com/codemodus/swagui"
17 | "github.com/codemodus/swagui/suidata3"
18 | "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
19 | "google.golang.org/grpc"
20 | )
21 |
22 | // HTTPSrv ...
23 | type HTTPSrv struct {
24 | *http.Server
25 | gmux *runtime.ServeMux
26 | }
27 |
28 | // New ...
29 | func New(origins []string) (*HTTPSrv, error) {
30 | gmux := runtime.NewServeMux() // TODO: set options
31 | mux, err := multiplexer(time.Now(), gmux, origins)
32 | if err != nil {
33 | return nil, err
34 | }
35 |
36 | s := HTTPSrv{
37 | Server: &http.Server{
38 | Handler: mux,
39 | },
40 | gmux: gmux,
41 | }
42 |
43 | return &s, nil
44 | }
45 |
46 | func multiplexer(start time.Time, gmux *runtime.ServeMux, origins []string) (http.Handler, error) {
47 | handleSwagger, err := swaggerHandler(start, "/v", "apidocs.swagger.json")
48 | if err != nil {
49 | return nil, err
50 | }
51 |
52 | m := mixmux.NewTreeMux(nil)
53 |
54 | gm := http.StripPrefix("/v", gmux)
55 | m.Any("/v/", gm)
56 | m.Any("/v/*x", gm)
57 |
58 | m.Any("/v/docs/swagger.json", handleSwagger)
59 |
60 | if ui, err := swagui.New(http.NotFoundHandler(), suidata3.New()); err == nil {
61 | sh := http.StripPrefix("/v/docs", ui.Handler("/v/docs/swagger.json"))
62 | m.Get("/v/docs/", sh)
63 | m.Get("/v/docs/*x", sh)
64 | }
65 |
66 | origins = append(hedrs.DefaultOrigins, origins...)
67 | corsOrigins := hedrs.CORSOrigins(hedrs.NewAllowed(origins...))
68 | corsMethods := hedrs.CORSMethods(hedrs.NewValues(hedrs.AllMethods...))
69 | corsHeaders := hedrs.CORSHeaders(hedrs.NewValues(hedrs.DefaultHeaders...))
70 |
71 | cmn := chain.New(
72 | corsOrigins,
73 | corsMethods,
74 | corsHeaders,
75 | )
76 |
77 | return cmn.End(m), nil
78 | }
79 |
80 | // Serve ...
81 | func (s *HTTPSrv) Serve(rpcPort, httpPort string) error {
82 | opts := []grpc.DialOption{grpc.WithInsecure()}
83 |
84 | conn, err := grpc.Dial(rpcPort, opts...)
85 | if err != nil {
86 | return err
87 | }
88 | defer conn.Close()
89 |
90 | ctx, cancel := context.WithCancel(context.Background())
91 | defer cancel()
92 |
93 | err = pb.RegisterAuthHandler(ctx, s.gmux, conn)
94 | if err != nil {
95 | return err
96 | }
97 |
98 | err = pb.RegisterPostHandler(ctx, s.gmux, conn)
99 | if err != nil {
100 | return err
101 | }
102 |
103 | err = pb.RegisterUserSvcHandler(ctx, s.gmux, conn)
104 | if err != nil {
105 | return err
106 | }
107 |
108 | s.Server.Addr = httpPort
109 |
110 | if err = s.Server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
111 | return err
112 | }
113 |
114 | return nil
115 | }
116 |
117 | // Stop ...
118 | func (s *HTTPSrv) Stop() error {
119 | // TODO: setup shutdown context
120 | return s.Server.Shutdown(context.Background())
121 | }
122 |
123 | func swaggerHandler(start time.Time, basePath, name string) (http.Handler, error) {
124 | fs, err := asset.NewFS()
125 | if err != nil {
126 | return nil, err
127 | }
128 |
129 | f, err := fs.Open(name)
130 | if err != nil {
131 | return nil, err
132 | }
133 | defer f.Close()
134 |
135 | d, err := ioutil.ReadAll(f)
136 | if err != nil {
137 | return nil, err
138 | }
139 |
140 | if basePath != "" {
141 | if d, err = setSwaggerBasePath(basePath, d); err != nil {
142 | return nil, err
143 | }
144 | }
145 |
146 | mt := start
147 | info, err := f.Stat()
148 | if err == nil {
149 | mt = info.ModTime()
150 | }
151 |
152 | h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
153 | b := bytes.NewReader(d)
154 | http.ServeContent(w, r, name, mt, b)
155 | })
156 |
157 | return h, nil
158 | }
159 |
160 | func setSwaggerBasePath(path string, d []byte) ([]byte, error) {
161 | var j swaggerJSON
162 | if err := json.Unmarshal(d, &j); err != nil {
163 | return nil, err
164 | }
165 |
166 | j.BasePath = path
167 |
168 | return json.Marshal(&j)
169 | }
170 |
171 | type swaggerJSON struct {
172 | Swagger string `json:"swagger"`
173 | /*Info struct {
174 | Title string `json:"title,omitempty"`
175 | Description string `json:"description,omitempty"`
176 | Version string `json:"version,omitempty"`
177 | }*/
178 | Info json.RawMessage `json:"info,omitempty"`
179 | BasePath string `json:"basePath,omitempty"`
180 | Schemes json.RawMessage `json:"schemes,omitempty"` // []string
181 | Consumes json.RawMessage `json:"consumes,omitempty"` // []string
182 | Produces json.RawMessage `json:"produces,omitempty"` // []string
183 | Paths json.RawMessage `json:"paths,omitempty"`
184 | Definitions json.RawMessage `json:"definitions,omitempty"`
185 | }
186 |
--------------------------------------------------------------------------------
/front/src/Ui.elm:
--------------------------------------------------------------------------------
1 | module Ui exposing (PostingKind(..), btn, card, flexBox, globalStyle, gridUnit, h1Style, heading, kindButton, kindToColor, linkBtn, mainContent, navBar, navBarList, pStyle, paragraph, postingBlurb, postingsList, theme)
2 |
3 | import Css exposing (..)
4 | import Css.Global exposing (global, selector)
5 | import Html.Styled exposing (..)
6 | import Html.Styled.Attributes exposing (..)
7 | import Route
8 |
9 |
10 | globalStyle =
11 | global
12 | [ selector "body"
13 | [ margin (px 0)
14 | ]
15 | ]
16 |
17 |
18 |
19 | -- Reusabled parts of the Ui used by Pages
20 |
21 |
22 | paragraph =
23 | styled p [ pStyle ]
24 |
25 |
26 | btn =
27 | styled button [ Css.batch [] ]
28 |
29 |
30 | {-| A reusable card module
31 | -}
32 | card : List (Attribute msg) -> List (Html msg) -> Html msg
33 | card =
34 | styled div
35 | [ borderRadius (px 4)
36 | , padding (px (gridUnit * 1))
37 | , boxShadow4 (px 1) (px 1) (px 5) theme.dark
38 | , margin (px (8 * gridUnit))
39 | ]
40 |
41 |
42 | {-| navBar
43 | -}
44 | navBar : List (Attribute msg) -> List (Html msg) -> Html msg
45 | navBar =
46 | styled nav
47 | [ boxShadow4 (px 1) (px 1) (px 5) theme.dark
48 | , displayFlex
49 | ]
50 |
51 |
52 | navBarList : List (Attribute msg) -> List (Html msg) -> Html msg
53 | navBarList =
54 | styled ul
55 | [ listStyleType none
56 | , margin (px 0)
57 | , padding (px 0)
58 | ]
59 |
60 |
61 | mainContent : List (Attribute msg) -> List (Html msg) -> Html msg
62 | mainContent =
63 | styled div
64 | [ maxWidth (px 960)
65 | , marginLeft auto
66 | , marginRight auto
67 | , padding (px 16)
68 | ]
69 |
70 |
71 | type PostingKind
72 | = Request
73 | | Offer
74 |
75 |
76 | kindToColor k =
77 | case k of
78 | Request ->
79 | theme.request
80 |
81 | Offer ->
82 | theme.offer
83 |
84 |
85 | flexBox =
86 | styled div
87 | [ displayFlex
88 | , marginBottom (px 10)
89 | , firstChild [ marginRight (px 10) ]
90 | ]
91 |
92 |
93 | kindButton : PostingKind -> List (Attribute msg) -> List (Html msg) -> Html msg
94 | kindButton k =
95 | styled button
96 | [ padding4 (px 8) (px 16) (px 8) (px 16)
97 | , color (rgb 250 250 250)
98 | , hover
99 | [ backgroundColor theme.dark
100 | , textDecoration underline
101 | ]
102 | , display block
103 | , backgroundColor (kindToColor k)
104 | , cursor pointer
105 | , border (px 0)
106 | , borderRadius (px 3)
107 | , fontSize (Css.em 2)
108 | ]
109 |
110 |
111 | postingsList =
112 | styled ul
113 | [ listStyleType none
114 | , margin (px 0)
115 | , padding (px 0)
116 | ]
117 |
118 |
119 | postingBlurb { title, body, slug } =
120 | styled article
121 | [ borderRadius (px 4)
122 | , padding (px (gridUnit * 1))
123 | , boxShadow4 (px 1) (px 1) (px 5) theme.dark
124 | , marginBottom (px gridUnit)
125 | ]
126 | []
127 | [ a [ Route.PostDetail slug |> Route.href ] [ heading title ], paragraph [] [ text body ] ]
128 |
129 |
130 | {-| A reusable heading
131 | -}
132 | heading : String -> Html msg
133 | heading title =
134 | styled h1 [ h1Style ] [] [ text title ]
135 |
136 |
137 | {-| A reusable link button
138 | -}
139 | linkBtn : List (Attribute msg) -> List (Html msg) -> Html msg
140 | linkBtn =
141 | styled a
142 | [ padding4 (px 8) (px 16) (px 8) (px 16)
143 | , color (rgb 250 250 250)
144 | , hover
145 | [ backgroundColor theme.dark
146 | , textDecoration underline
147 | ]
148 | , display block
149 | , margin (px 10)
150 | , backgroundColor theme.primary
151 | , cursor pointer
152 | , border (px 0)
153 | , borderRadius (px 3)
154 | , fontSize (Css.em 1)
155 | ]
156 |
157 |
158 | {-| Styles for h1
159 | -}
160 | h1Style : Style
161 | h1Style =
162 | Css.batch
163 | [ fontFamilies [ "Palatino Linotype", "Georgia", "serif" ]
164 | , fontSize (3 * gridUnit |> px)
165 | , fontWeight bold
166 | , marginTop (px 0)
167 | ]
168 |
169 |
170 | {-| Styles for h1
171 | -}
172 | pStyle : Style
173 | pStyle =
174 | Css.batch
175 | [ fontFamilies [ "Helvetica", "Arial", "sans-serif", "serif" ]
176 | , fontSize (2 * gridUnit |> px)
177 | , fontWeight normal
178 | , lineHeight (2 * gridUnit + gridUnit |> px)
179 | , color theme.dark
180 | , margin (px 0)
181 | ]
182 |
183 |
184 | {-| Theme colors
185 | -}
186 | theme =
187 | { primary = hex "000"
188 | , secondary = rgb 250 240 230
189 | , hover = hex "3ebc5b"
190 | , dark = hex "999"
191 | , request = hex "2135d1"
192 | , offer = hex "50b320"
193 | }
194 |
195 |
196 | {-| Grid unit to keep everything lined up and balanced
197 | -}
198 | gridUnit : Float
199 | gridUnit =
200 | 8
201 |
--------------------------------------------------------------------------------
/front/bin/pb2elm:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | #? {cmd}:
4 | #? DEST -I INCLUDES... PROTOS...
5 | #? example: {cmd} src -I ../../msgs ../../msgs/proto/*.proto
6 | #?
7 | #? DEST is the root of the output source
8 | #? INCLUDES are the root directories from which to gather proto files
9 | #? PROTOS are generated in paths relative to the provided includes
10 | #?
11 | #? command: {cmd} src -I ../../msgs ../../msgs/proto/example.proto
12 | #? output: "Proto/Example.elm" within the "src" directory
13 | function curcmd() {
14 | echo "$(basename "${0}")"
15 | }
16 |
17 | function echerr() {
18 | echo "$(curcmd):" "${@}" >&2 && exit 1
19 | }
20 |
21 | function checkDep() {
22 | ! hash "${1}" 2>/dev/null && echerr "requires ${1}"
23 | }
24 |
25 | function checkDepOr() {
26 | ! (hash "${1}" 2>/dev/null || hash "${2}" 2>/dev/null) && echerr "requires ${1} or ${2}"
27 | }
28 |
29 | checkDep "protoc"
30 | checkDep "protoc-gen-elm"
31 | checkDep "elm-format"
32 | checkDepOr "sed" "gsed"
33 | checkDepOr "realpath" "grealpath"
34 |
35 | function depOr() {
36 | local ret="${1}"
37 | hash "${2}" 2>/dev/null && ret="${2}"
38 | echo "${ret}"
39 | }
40 |
41 | function xsed() {
42 | local cmd="$(depOr "sed" "gsed")"
43 | ${cmd} "${@}"
44 | }
45 |
46 | function xrealpath() {
47 | local cmd="$(depOr "realpath" "grealpath")"
48 | ${cmd} "${@}"
49 | }
50 |
51 | function yudodis_osx() {
52 | local sedCmd="$(depOr "sed" "gsed")"
53 | [[ "$(uname -s)" == "Darwin" && "${sedCmd}" == "sed" ]] && echerr "darwin requires gsed"
54 | }
55 |
56 | function getDestinationDir() {
57 | ! mkdir -p "${1}" 2>/dev/null && echerr "cannot make directory '${1}'"
58 | echo "${1}"
59 | }
60 |
61 | function getIncludeDirs() {
62 | local args=("${@}")
63 | local flag="-I"
64 | local rets=()
65 |
66 | for (( i=0; i<${#args}; i++ )); do
67 | local arg="${args[i]}"
68 | local argv="${args[i+1]}"
69 |
70 | [[ "${arg}" == "${flag}" ]] && rets+=("${arg}" "${argv}") && ((i++)); continue
71 | [[ "${arg}" == "${flag}"* ]] && rets+=("${arg}") && continue
72 | done
73 |
74 | echo "${rets[@]}"
75 | }
76 |
77 | function getSourceFiles() {
78 | local args=("${@}")
79 | local rets=()
80 |
81 | for arg in ${args[@]}; do
82 | [[ "${arg}" == "-"* ]] && continue
83 |
84 | [[ ! -f "${arg}" ]] && echerr "non-file '${arg}'"
85 |
86 | rets+=("${arg}")
87 | done
88 |
89 | [[ ${#rets[@]} == 0 ]] && echerr "must provide one or more files"
90 | echo "${rets[@]}"
91 | }
92 |
93 | function filterProto {
94 | local in="${1}"
95 | local out="${2}"
96 |
97 | cat "${in}" | \
98 | # delete consecutive empty lines (simplifies multi-line empty message deletion)
99 | xsed '/^$/N;/^\n$/D' | \
100 | # delete service definitions
101 | xsed '/^service/,/^}$/d' | \
102 | # delete annotations.proto (contains proto2 dependency)
103 | xsed '/annotations.proto/d' | \
104 | # delete empty messages (single line)
105 | xsed '/^message.*{\s*}/d' | \
106 | # delete empty messages (multiple lines)
107 | xsed '/^message.*/ {N;N; /^message.*{[\s|\n]*\}/d}' | \
108 | # delete consecutive empty lines (again, some have likely been created)
109 | xsed '/^$/N;/^\n$/D' | \
110 | # delete last line if empty
111 | xsed '${/^$/d}' \
112 | >> "${out}"
113 |
114 | return $?
115 | }
116 |
117 | function elmfmtMessage() {
118 | local elmfmt="$(elm-format -h 2>&1 | grep "^elm-format.*")"
119 | echo "-- ALSO PROCESSED BY ${elmfmt}"
120 | }
121 |
122 | function main {
123 | local cleanups=()
124 | function runCleanups() {
125 | for cleanup in "${cleanups[@]}"; do eval ${cleanup}; done
126 | }
127 | trap "runCleanups" EXIT RETURN
128 |
129 | local skip=1
130 | local dstDir incs files
131 | dstDir="$(getDestinationDir "${1}")" || exit 1
132 | ((skip+=1))
133 | incs=($(getIncludeDirs ${@:${skip}})) || exit 1
134 | ((skip+=${#incs[@]}))
135 | files=($(getSourceFiles ${@:$skip})) || exit 1
136 |
137 | local token="xxfiltxx"
138 | local filtfiles=()
139 |
140 | for file in ${files[@]}; do
141 | local filtfile="${file/\.proto/${token}.proto}"
142 | ! filterProto "${file}" "${filtfile}" && continue
143 | filtfiles+=("${filtfile}")
144 | cleanups+=("rm ${filtfile}")
145 | done
146 |
147 | protoc ${incs[@]} --elm_out="${dstDir}" ${filtfiles[@]} > /dev/null 2>&1
148 |
149 | find "${dstDir}" -name "*${token}.elm" | while read file; do
150 | elm-format --yes "${file}" > /dev/null
151 | xsed -i 's/'${token}'//g' ${file}
152 | xsed -i '/^-- source.*/a '"$(elmfmtMessage)"'' ${file}
153 | local newfile=${file/${token}/}
154 | [[ "${file}" != "${newfile}" ]] && mv "${file}" "${newfile}"
155 | done
156 | }
157 |
158 | function doc() {
159 | function display() {
160 | grep '^#?' $(xrealpath "${0}") | \
161 | xsed -e 's/#?\s*//g' -e 's/{cmd}/'"$(curcmd)"'/g'
162 | }
163 |
164 | local args=("${@}")
165 | for arg in ${args[@]}; do
166 | [[ "${arg}" =~ ^(help|-h|-help|--help)$ ]] && display && exit 0
167 | done
168 | }
169 |
170 | doc "${@}"
171 | yudodis_osx
172 | main "${@}"
173 |
--------------------------------------------------------------------------------
/front/src/Main.elm:
--------------------------------------------------------------------------------
1 | port module Main exposing (Model, Msg(..), init, main, update, view)
2 |
3 | import Browser exposing (Document, UrlRequest(..))
4 | import Browser.Navigation as Nav
5 | import Html
6 | import Html.Styled
7 | import Json.Decode exposing (Value)
8 | import Page
9 | import Page.Home
10 | import Page.Login
11 | import Page.Posts
12 | import Route exposing (Route(..))
13 | import Session
14 | import Url exposing (Url)
15 |
16 |
17 |
18 | ---- MODEL ----
19 |
20 |
21 | type Model
22 | = Login Page.Login.Model
23 | | Home Page.Home.Model
24 | | Posts Page.Posts.Model
25 |
26 |
27 |
28 | --TODO: decode incoming JWT from localStorage and initialize Session accordingly
29 |
30 |
31 | init : () -> Url -> Nav.Key -> ( Model, Cmd Msg )
32 | init _ url navKey =
33 | changeRouteTo (Route.fromUrl url) (Home (Page.Home.Model (Session.Guest navKey) ""))
34 |
35 |
36 |
37 | ---- UPDATE ----
38 |
39 |
40 | type Msg
41 | = ChangedUrl Url
42 | | ClickedLink UrlRequest
43 | | GotHomeMsg Page.Home.Msg
44 | | GotLoginMsg Page.Login.Msg
45 | | GotPostsMsg Page.Posts.Msg
46 | | GotJwt Value
47 |
48 |
49 | update : Msg -> Model -> ( Model, Cmd Msg )
50 | update msg model =
51 | case ( msg, model ) of
52 | ( ChangedUrl url, _ ) ->
53 | changeRouteTo (Route.fromUrl url) model
54 |
55 | ( ClickedLink urlRequest, _ ) ->
56 | case urlRequest of
57 | Internal url ->
58 | ( model, Nav.pushUrl (Session.navKey (toSession model)) (Url.toString url) )
59 |
60 | External url ->
61 | ( model, Nav.load url )
62 |
63 | ( GotHomeMsg subMsg, Home home ) ->
64 | Page.Home.update subMsg home
65 | |> updateWith Home GotHomeMsg model
66 |
67 | ( GotLoginMsg subMsg, Login home ) ->
68 | Page.Login.update subMsg home
69 | |> updateWith Login GotLoginMsg model
70 |
71 | ( GotPostsMsg subMsg, Posts home ) ->
72 | Page.Posts.update subMsg home
73 | |> updateWith Posts GotPostsMsg model
74 |
75 | ( _, _ ) ->
76 | -- Disregard messages that arrived for the wrong page.
77 | ( model, Cmd.none )
78 |
79 |
80 | updateWith : (subModel -> Model) -> (subMsg -> Msg) -> Model -> ( subModel, Cmd subMsg ) -> ( Model, Cmd Msg )
81 | updateWith toModel toMsg _ ( subModel, subCmd ) =
82 | ( toModel subModel
83 | , Cmd.map toMsg subCmd
84 | )
85 |
86 |
87 | changeRouteTo : Maybe Route -> Model -> ( Model, Cmd Msg )
88 | changeRouteTo maybeRoute model =
89 | case maybeRoute of
90 | Just Route.Home ->
91 | Page.Home.init (toSession model)
92 | |> updateWith Home GotHomeMsg model
93 |
94 | Just Route.Login ->
95 | Page.Login.init (toSession model)
96 | |> updateWith Login GotLoginMsg model
97 |
98 | Just Route.NewRequest ->
99 | Page.Posts.init (toSession model) Page.Posts.Request ""
100 | |> updateWith Posts GotPostsMsg model
101 |
102 | Just Route.NewOffer ->
103 | Page.Posts.init (toSession model) Page.Posts.Offer ""
104 | |> updateWith Posts GotPostsMsg model
105 |
106 | Just (Route.PostDetail slug) ->
107 | Page.Posts.init (toSession model) Page.Posts.Unknown slug
108 | |> updateWith Posts GotPostsMsg model
109 |
110 | Just (Route.EditPost slug) ->
111 | Page.Posts.init (toSession model) Page.Posts.Unknown slug
112 | |> updateWith Posts GotPostsMsg model
113 |
114 | Nothing ->
115 | ( model, Cmd.none )
116 |
117 |
118 | toSession : Model -> Session.Session
119 | toSession model =
120 | case model of
121 | Login loginModel ->
122 | Page.Login.toSession loginModel
123 |
124 | Home homeModel ->
125 | Page.Home.toSession homeModel
126 |
127 | Posts postsModel ->
128 | Page.Posts.toSession postsModel
129 |
130 |
131 |
132 | ---- VIEW ----
133 |
134 |
135 | view : Model -> Document Msg
136 | view model =
137 | let
138 | viewPage : Page.Page -> (msg -> Msg) -> { title : String, content : Html.Styled.Html msg } -> Document Msg
139 | viewPage page toMsg config =
140 | let
141 | { title, body } =
142 | Page.view (Session.viewer (toSession model)) page config
143 | in
144 | { title = title
145 | , body = List.map (Html.map toMsg) body
146 | }
147 | in
148 | case model of
149 | Home home ->
150 | viewPage Page.Home GotHomeMsg (Page.Home.view home)
151 |
152 | Login login ->
153 | viewPage Page.Other GotLoginMsg (Page.Login.view login)
154 |
155 | Posts posts ->
156 | viewPage Page.Other GotPostsMsg (Page.Posts.view posts)
157 |
158 |
159 |
160 | -- Ports
161 | -- port storeCache : Maybe Value -> Cmd msg
162 |
163 |
164 | port onStoreChange : (Value -> msg) -> Sub msg
165 |
166 |
167 | subscriptions : Model -> Sub Msg
168 | subscriptions model =
169 | onStoreChange GotJwt
170 |
171 |
172 |
173 | ---- PROGRAM ----
174 |
175 |
176 | main : Program () Model Msg
177 | main =
178 | Browser.application
179 | { init = init
180 | , onUrlChange = ChangedUrl
181 | , onUrlRequest = ClickedLink
182 | , subscriptions = subscriptions
183 | , update = update
184 | , view = view
185 | }
186 |
--------------------------------------------------------------------------------
/back/internal/pb/auth_grpc.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
2 |
3 | package pb
4 |
5 | import (
6 | context "context"
7 | grpc "google.golang.org/grpc"
8 | codes "google.golang.org/grpc/codes"
9 | status "google.golang.org/grpc/status"
10 | )
11 |
12 | // This is a compile-time assertion to ensure that this generated file
13 | // is compatible with the grpc package it is being compiled against.
14 | // Requires gRPC-Go v1.32.0 or later.
15 | const _ = grpc.SupportPackageIsVersion7
16 |
17 | // AuthClient is the client API for Auth service.
18 | //
19 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
20 | type AuthClient interface {
21 | AddAuth(ctx context.Context, in *AddAuthReq, opts ...grpc.CallOption) (*AuthResp, error)
22 | RmvAuth(ctx context.Context, in *RmvAuthReq, opts ...grpc.CallOption) (*RmvAuthResp, error)
23 | AddVoucher(ctx context.Context, in *AddVoucherReq, opts ...grpc.CallOption) (*AddVoucherResp, error)
24 | }
25 |
26 | type authClient struct {
27 | cc grpc.ClientConnInterface
28 | }
29 |
30 | func NewAuthClient(cc grpc.ClientConnInterface) AuthClient {
31 | return &authClient{cc}
32 | }
33 |
34 | func (c *authClient) AddAuth(ctx context.Context, in *AddAuthReq, opts ...grpc.CallOption) (*AuthResp, error) {
35 | out := new(AuthResp)
36 | err := c.cc.Invoke(ctx, "/pb.Auth/AddAuth", in, out, opts...)
37 | if err != nil {
38 | return nil, err
39 | }
40 | return out, nil
41 | }
42 |
43 | func (c *authClient) RmvAuth(ctx context.Context, in *RmvAuthReq, opts ...grpc.CallOption) (*RmvAuthResp, error) {
44 | out := new(RmvAuthResp)
45 | err := c.cc.Invoke(ctx, "/pb.Auth/RmvAuth", in, out, opts...)
46 | if err != nil {
47 | return nil, err
48 | }
49 | return out, nil
50 | }
51 |
52 | func (c *authClient) AddVoucher(ctx context.Context, in *AddVoucherReq, opts ...grpc.CallOption) (*AddVoucherResp, error) {
53 | out := new(AddVoucherResp)
54 | err := c.cc.Invoke(ctx, "/pb.Auth/AddVoucher", in, out, opts...)
55 | if err != nil {
56 | return nil, err
57 | }
58 | return out, nil
59 | }
60 |
61 | // AuthServer is the server API for Auth service.
62 | // All implementations should embed UnimplementedAuthServer
63 | // for forward compatibility
64 | type AuthServer interface {
65 | AddAuth(context.Context, *AddAuthReq) (*AuthResp, error)
66 | RmvAuth(context.Context, *RmvAuthReq) (*RmvAuthResp, error)
67 | AddVoucher(context.Context, *AddVoucherReq) (*AddVoucherResp, error)
68 | }
69 |
70 | // UnimplementedAuthServer should be embedded to have forward compatible implementations.
71 | type UnimplementedAuthServer struct {
72 | }
73 |
74 | func (UnimplementedAuthServer) AddAuth(context.Context, *AddAuthReq) (*AuthResp, error) {
75 | return nil, status.Errorf(codes.Unimplemented, "method AddAuth not implemented")
76 | }
77 | func (UnimplementedAuthServer) RmvAuth(context.Context, *RmvAuthReq) (*RmvAuthResp, error) {
78 | return nil, status.Errorf(codes.Unimplemented, "method RmvAuth not implemented")
79 | }
80 | func (UnimplementedAuthServer) AddVoucher(context.Context, *AddVoucherReq) (*AddVoucherResp, error) {
81 | return nil, status.Errorf(codes.Unimplemented, "method AddVoucher not implemented")
82 | }
83 |
84 | // UnsafeAuthServer may be embedded to opt out of forward compatibility for this service.
85 | // Use of this interface is not recommended, as added methods to AuthServer will
86 | // result in compilation errors.
87 | type UnsafeAuthServer interface {
88 | mustEmbedUnimplementedAuthServer()
89 | }
90 |
91 | func RegisterAuthServer(s grpc.ServiceRegistrar, srv AuthServer) {
92 | s.RegisterService(&Auth_ServiceDesc, srv)
93 | }
94 |
95 | func _Auth_AddAuth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
96 | in := new(AddAuthReq)
97 | if err := dec(in); err != nil {
98 | return nil, err
99 | }
100 | if interceptor == nil {
101 | return srv.(AuthServer).AddAuth(ctx, in)
102 | }
103 | info := &grpc.UnaryServerInfo{
104 | Server: srv,
105 | FullMethod: "/pb.Auth/AddAuth",
106 | }
107 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
108 | return srv.(AuthServer).AddAuth(ctx, req.(*AddAuthReq))
109 | }
110 | return interceptor(ctx, in, info, handler)
111 | }
112 |
113 | func _Auth_RmvAuth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
114 | in := new(RmvAuthReq)
115 | if err := dec(in); err != nil {
116 | return nil, err
117 | }
118 | if interceptor == nil {
119 | return srv.(AuthServer).RmvAuth(ctx, in)
120 | }
121 | info := &grpc.UnaryServerInfo{
122 | Server: srv,
123 | FullMethod: "/pb.Auth/RmvAuth",
124 | }
125 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
126 | return srv.(AuthServer).RmvAuth(ctx, req.(*RmvAuthReq))
127 | }
128 | return interceptor(ctx, in, info, handler)
129 | }
130 |
131 | func _Auth_AddVoucher_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
132 | in := new(AddVoucherReq)
133 | if err := dec(in); err != nil {
134 | return nil, err
135 | }
136 | if interceptor == nil {
137 | return srv.(AuthServer).AddVoucher(ctx, in)
138 | }
139 | info := &grpc.UnaryServerInfo{
140 | Server: srv,
141 | FullMethod: "/pb.Auth/AddVoucher",
142 | }
143 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
144 | return srv.(AuthServer).AddVoucher(ctx, req.(*AddVoucherReq))
145 | }
146 | return interceptor(ctx, in, info, handler)
147 | }
148 |
149 | // Auth_ServiceDesc is the grpc.ServiceDesc for Auth service.
150 | // It's only intended for direct use with grpc.RegisterService,
151 | // and not to be introspected or modified (even as a copy)
152 | var Auth_ServiceDesc = grpc.ServiceDesc{
153 | ServiceName: "pb.Auth",
154 | HandlerType: (*AuthServer)(nil),
155 | Methods: []grpc.MethodDesc{
156 | {
157 | MethodName: "AddAuth",
158 | Handler: _Auth_AddAuth_Handler,
159 | },
160 | {
161 | MethodName: "RmvAuth",
162 | Handler: _Auth_RmvAuth_Handler,
163 | },
164 | {
165 | MethodName: "AddVoucher",
166 | Handler: _Auth_AddVoucher_Handler,
167 | },
168 | },
169 | Streams: []grpc.StreamDesc{},
170 | Metadata: "auth.proto",
171 | }
172 |
--------------------------------------------------------------------------------
/msgs/proto/include/googleapis/google/rpc/code.proto:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | syntax = "proto3";
16 |
17 | package google.rpc;
18 |
19 | option go_package = "google.golang.org/genproto/googleapis/rpc/code;code";
20 | option java_multiple_files = true;
21 | option java_outer_classname = "CodeProto";
22 | option java_package = "com.google.rpc";
23 | option objc_class_prefix = "RPC";
24 |
25 |
26 | // The canonical error codes for Google APIs.
27 | //
28 | //
29 | // Sometimes multiple error codes may apply. Services should return
30 | // the most specific error code that applies. For example, prefer
31 | // `OUT_OF_RANGE` over `FAILED_PRECONDITION` if both codes apply.
32 | // Similarly prefer `NOT_FOUND` or `ALREADY_EXISTS` over `FAILED_PRECONDITION`.
33 | enum Code {
34 | // Not an error; returned on success
35 | //
36 | // HTTP Mapping: 200 OK
37 | OK = 0;
38 |
39 | // The operation was cancelled, typically by the caller.
40 | //
41 | // HTTP Mapping: 499 Client Closed Request
42 | CANCELLED = 1;
43 |
44 | // Unknown error. For example, this error may be returned when
45 | // a `Status` value received from another address space belongs to
46 | // an error space that is not known in this address space. Also
47 | // errors raised by APIs that do not return enough error information
48 | // may be converted to this error.
49 | //
50 | // HTTP Mapping: 500 Internal Server Error
51 | UNKNOWN = 2;
52 |
53 | // The client specified an invalid argument. Note that this differs
54 | // from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` indicates arguments
55 | // that are problematic regardless of the state of the system
56 | // (e.g., a malformed file name).
57 | //
58 | // HTTP Mapping: 400 Bad Request
59 | INVALID_ARGUMENT = 3;
60 |
61 | // The deadline expired before the operation could complete. For operations
62 | // that change the state of the system, this error may be returned
63 | // even if the operation has completed successfully. For example, a
64 | // successful response from a server could have been delayed long
65 | // enough for the deadline to expire.
66 | //
67 | // HTTP Mapping: 504 Gateway Timeout
68 | DEADLINE_EXCEEDED = 4;
69 |
70 | // Some requested entity (e.g., file or directory) was not found.
71 | //
72 | // Note to server developers: if a request is denied for an entire class
73 | // of users, such as gradual feature rollout or undocumented whitelist,
74 | // `NOT_FOUND` may be used. If a request is denied for some users within
75 | // a class of users, such as user-based access control, `PERMISSION_DENIED`
76 | // must be used.
77 | //
78 | // HTTP Mapping: 404 Not Found
79 | NOT_FOUND = 5;
80 |
81 | // The entity that a client attempted to create (e.g., file or directory)
82 | // already exists.
83 | //
84 | // HTTP Mapping: 409 Conflict
85 | ALREADY_EXISTS = 6;
86 |
87 | // The caller does not have permission to execute the specified
88 | // operation. `PERMISSION_DENIED` must not be used for rejections
89 | // caused by exhausting some resource (use `RESOURCE_EXHAUSTED`
90 | // instead for those errors). `PERMISSION_DENIED` must not be
91 | // used if the caller can not be identified (use `UNAUTHENTICATED`
92 | // instead for those errors). This error code does not imply the
93 | // request is valid or the requested entity exists or satisfies
94 | // other pre-conditions.
95 | //
96 | // HTTP Mapping: 403 Forbidden
97 | PERMISSION_DENIED = 7;
98 |
99 | // The request does not have valid authentication credentials for the
100 | // operation.
101 | //
102 | // HTTP Mapping: 401 Unauthorized
103 | UNAUTHENTICATED = 16;
104 |
105 | // Some resource has been exhausted, perhaps a per-user quota, or
106 | // perhaps the entire file system is out of space.
107 | //
108 | // HTTP Mapping: 429 Too Many Requests
109 | RESOURCE_EXHAUSTED = 8;
110 |
111 | // The operation was rejected because the system is not in a state
112 | // required for the operation's execution. For example, the directory
113 | // to be deleted is non-empty, an rmdir operation is applied to
114 | // a non-directory, etc.
115 | //
116 | // Service implementors can use the following guidelines to decide
117 | // between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`:
118 | // (a) Use `UNAVAILABLE` if the client can retry just the failing call.
119 | // (b) Use `ABORTED` if the client should retry at a higher level
120 | // (e.g., when a client-specified test-and-set fails, indicating the
121 | // client should restart a read-modify-write sequence).
122 | // (c) Use `FAILED_PRECONDITION` if the client should not retry until
123 | // the system state has been explicitly fixed. E.g., if an "rmdir"
124 | // fails because the directory is non-empty, `FAILED_PRECONDITION`
125 | // should be returned since the client should not retry unless
126 | // the files are deleted from the directory.
127 | //
128 | // HTTP Mapping: 400 Bad Request
129 | FAILED_PRECONDITION = 9;
130 |
131 | // The operation was aborted, typically due to a concurrency issue such as
132 | // a sequencer check failure or transaction abort.
133 | //
134 | // See the guidelines above for deciding between `FAILED_PRECONDITION`,
135 | // `ABORTED`, and `UNAVAILABLE`.
136 | //
137 | // HTTP Mapping: 409 Conflict
138 | ABORTED = 10;
139 |
140 | // The operation was attempted past the valid range. E.g., seeking or
141 | // reading past end-of-file.
142 | //
143 | // Unlike `INVALID_ARGUMENT`, this error indicates a problem that may
144 | // be fixed if the system state changes. For example, a 32-bit file
145 | // system will generate `INVALID_ARGUMENT` if asked to read at an
146 | // offset that is not in the range [0,2^32-1], but it will generate
147 | // `OUT_OF_RANGE` if asked to read from an offset past the current
148 | // file size.
149 | //
150 | // There is a fair bit of overlap between `FAILED_PRECONDITION` and
151 | // `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific
152 | // error) when it applies so that callers who are iterating through
153 | // a space can easily look for an `OUT_OF_RANGE` error to detect when
154 | // they are done.
155 | //
156 | // HTTP Mapping: 400 Bad Request
157 | OUT_OF_RANGE = 11;
158 |
159 | // The operation is not implemented or is not supported/enabled in this
160 | // service.
161 | //
162 | // HTTP Mapping: 501 Not Implemented
163 | UNIMPLEMENTED = 12;
164 |
165 | // Internal errors. This means that some invariants expected by the
166 | // underlying system have been broken. This error code is reserved
167 | // for serious errors.
168 | //
169 | // HTTP Mapping: 500 Internal Server Error
170 | INTERNAL = 13;
171 |
172 | // The service is currently unavailable. This is most likely a
173 | // transient condition, which can be corrected by retrying with
174 | // a backoff.
175 | //
176 | // See the guidelines above for deciding between `FAILED_PRECONDITION`,
177 | // `ABORTED`, and `UNAVAILABLE`.
178 | //
179 | // HTTP Mapping: 503 Service Unavailable
180 | UNAVAILABLE = 14;
181 |
182 | // Unrecoverable data loss or corruption.
183 | //
184 | // HTTP Mapping: 500 Internal Server Error
185 | DATA_LOSS = 15;
186 | }
187 |
--------------------------------------------------------------------------------
/back/tests/openbsrv/usersvc_test.go:
--------------------------------------------------------------------------------
1 | package main_test
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "testing"
7 |
8 | "github.com/OpenEugene/openboard/back/internal/pb"
9 | "google.golang.org/grpc"
10 | "google.golang.org/protobuf/proto"
11 | )
12 |
13 | func TestClientServices(t *testing.T) {
14 | ctx := context.Background()
15 | conn, err := grpc.Dial(":4242", grpc.WithInsecure())
16 | if err != nil {
17 | t.Fatal(err)
18 | }
19 | defer conn.Close()
20 |
21 | // User Service Tests
22 | userClnt := pb.NewUserSvcClient(conn)
23 | t.Run("Add and find user role", userSvcAddAndFndRoleFn(ctx, conn, userClnt))
24 | t.Run("Add and find users", userSvcAddAndFndUsersFn(ctx, conn, userClnt))
25 | t.Run("Find and delete user", userSvcDelUserFn(ctx, conn, userClnt))
26 | }
27 |
28 | func userSvcAddAndFndRoleFn(ctx context.Context, conn *grpc.ClientConn, clnt pb.UserSvcClient) func(*testing.T) {
29 | return func(t *testing.T) {
30 | tests := []struct {
31 | desc string
32 | want string
33 | addReq *pb.AddRoleReq
34 | fndReq *pb.FndRolesReq
35 | }{
36 | {
37 | "add and find user role",
38 | "user",
39 | &pb.AddRoleReq{Name: "user"},
40 | &pb.FndRolesReq{
41 | RoleIds: []string{},
42 | RoleNames: []string{"user"},
43 | Limit: 100,
44 | Lapse: 0,
45 | },
46 | },
47 | {
48 | "add and find admin role",
49 | "admin",
50 | &pb.AddRoleReq{Name: "admin"},
51 | &pb.FndRolesReq{
52 | RoleIds: []string{},
53 | RoleNames: []string{"admin"},
54 | Limit: 100,
55 | Lapse: 0,
56 | },
57 | },
58 | }
59 |
60 | for _, tt := range tests {
61 | _, err := clnt.AddRole(ctx, tt.addReq)
62 | if err != nil {
63 | t.Fatalf("%s: adding role %v: %v", tt.desc, tt.want, err)
64 | }
65 |
66 | r, err := clnt.FndRoles(ctx, tt.fndReq)
67 | if err != nil {
68 | t.Fatalf("%s: finding role %v: %v", tt.desc, tt.want, err)
69 | }
70 |
71 | if len(r.Items) == 0 {
72 | t.Fatalf("%s: got: no items, want: %s", tt.desc, tt.want)
73 | }
74 |
75 | if len(r.Items) > 1 {
76 | t.Fatalf("%s: got: %+v, want: %s", tt.desc, r, tt.want)
77 | }
78 |
79 | if got := r.Items[0].Name; got != tt.want {
80 | t.Fatalf("%s: got: %v, want: %s", tt.desc, got, tt.want)
81 | }
82 | }
83 | }
84 | }
85 |
86 | func userSvcAddAndFndUsersFn(ctx context.Context, conn *grpc.ClientConn, clnt pb.UserSvcClient) func(*testing.T) {
87 | return func(t *testing.T) {
88 | fndAdminRoleIdReq := &pb.FndRolesReq{
89 | RoleIds: []string{},
90 | RoleNames: []string{"admin"},
91 | Limit: 100,
92 | Lapse: 0,
93 | }
94 |
95 | fndUserRoleIdReq := &pb.FndRolesReq{
96 | RoleIds: []string{},
97 | RoleNames: []string{"user"},
98 | Limit: 100,
99 | Lapse: 0,
100 | }
101 |
102 | adminRoleID, err := roleID(ctx, conn, clnt, fndAdminRoleIdReq)
103 | if err != nil {
104 | t.Fatalf("retrieving role ID from role name: %v", err)
105 | }
106 |
107 | userRoleID, err := roleID(ctx, conn, clnt, fndUserRoleIdReq)
108 | if err != nil {
109 | t.Fatalf("retrieving role ID from role name: %v", err)
110 | }
111 |
112 | tests := []struct {
113 | desc string
114 | addUserReq *pb.AddUserReq
115 | want *pb.User
116 | fndUserReq *pb.FndUsersReq
117 | }{
118 | {
119 | "Add and find user A",
120 | &pb.AddUserReq{
121 | Username: "test username A",
122 | Email: "user_a@email.com",
123 | EmailHold: false,
124 | Altmail: "",
125 | AltmailHold: false,
126 | FullName: "test user full name A",
127 | Avatar: "test user avatar A",
128 | Password: "test user password A",
129 | RoleIds: []string{userRoleID},
130 | },
131 | &pb.User{
132 | Username: "test username A",
133 | Email: "user_a@email.com",
134 | EmailHold: false,
135 | Altmail: "",
136 | AltmailHold: false,
137 | FullName: "test user full name A",
138 | Avatar: "test user avatar A",
139 | Roles: []*pb.RoleResp{{Name: "user"}},
140 | },
141 | &pb.FndUsersReq{
142 | RoleIds: []string{},
143 | Email: "user_a@email.com",
144 | EmailHold: false,
145 | Altmail: "",
146 | AltmailHold: false,
147 | Limit: 100,
148 | Lapse: 0,
149 | },
150 | },
151 | {
152 | "Add and find user B",
153 | &pb.AddUserReq{
154 | Username: "test username B",
155 | Email: "userB@email.com",
156 | EmailHold: false,
157 | Altmail: "",
158 | AltmailHold: false,
159 | FullName: "test user full name B",
160 | Avatar: "test user avatar B",
161 | Password: "test user password B",
162 | RoleIds: []string{userRoleID, adminRoleID},
163 | },
164 | &pb.User{
165 | Username: "test username B",
166 | Email: "userB@email.com",
167 | EmailHold: false,
168 | Altmail: "",
169 | AltmailHold: false,
170 | FullName: "test user full name B",
171 | Avatar: "test user avatar B",
172 | Roles: []*pb.RoleResp{
173 | {Name: "user"},
174 | {Name: "admin"},
175 | },
176 | },
177 | &pb.FndUsersReq{
178 | RoleIds: []string{},
179 | Email: "userB@email.com",
180 | EmailHold: false,
181 | Altmail: "",
182 | AltmailHold: false,
183 | Limit: 100,
184 | Lapse: 0,
185 | },
186 | },
187 | }
188 |
189 | for _, tc := range tests {
190 | r, err := clnt.AddUser(ctx, tc.addUserReq)
191 | if err != nil {
192 | t.Fatal(err)
193 | }
194 |
195 | got := r.Item
196 | wantUserID := got.Id
197 |
198 | unsetUntestedFields(got)
199 |
200 | for i := 0; i < len(got.Roles); i++ {
201 | unsetUntestedFields(got.Roles[i])
202 | }
203 |
204 | if !proto.Equal(got, tc.want) {
205 | t.Fatalf("%s: got: %#v, want: %#v", tc.desc, got, tc.want)
206 | }
207 |
208 | gotUserID, err := userSvcFndUser(ctx, conn, clnt, tc.fndUserReq)
209 | if err != nil {
210 | t.Errorf("unable to find user: %v", err)
211 | }
212 | if gotUserID != wantUserID {
213 | t.Fatalf("got: %s, want: %s", gotUserID, wantUserID)
214 | }
215 | }
216 | }
217 | }
218 |
219 | func userSvcDelUserFn(ctx context.Context, conn *grpc.ClientConn, clnt pb.UserSvcClient) func(*testing.T) {
220 | return func(t *testing.T) {
221 | req := &pb.FndUsersReq{
222 | RoleIds: []string{},
223 | Email: "user_a@email.com",
224 | EmailHold: false,
225 | Altmail: "",
226 | AltmailHold: false,
227 | Limit: 100,
228 | Lapse: 0,
229 | }
230 |
231 | userID, err := userSvcFndUser(ctx, conn, clnt, req)
232 | if err != nil {
233 | t.Error(err)
234 | }
235 |
236 | if userID == "" {
237 | t.Fatalf("unable to find user %s", userID)
238 | }
239 |
240 | _, err = clnt.RmvUser(ctx, &pb.RmvUserReq{Id: userID})
241 | if err != nil {
242 | t.Error(err)
243 | }
244 |
245 | resp, err := clnt.FndUsers(ctx, req)
246 | if err != nil {
247 | t.Error(err)
248 | }
249 |
250 | if resp.Items[0].Deleted == nil {
251 | t.Fatalf("expected user %s deleted_at to not be nil", resp.Items[0].Id)
252 | t.Fail()
253 | }
254 | }
255 | }
256 |
257 | func roleID(ctx context.Context, conn *grpc.ClientConn, clnt pb.UserSvcClient, req *pb.FndRolesReq) (string, error) {
258 | r, err := clnt.FndRoles(ctx, req)
259 | if err != nil {
260 | return "", err
261 | }
262 |
263 | if len(r.Items) == 0 {
264 | return "", nil
265 | }
266 |
267 | return r.Items[0].Id, nil
268 | }
269 |
270 | func userSvcFndUser(ctx context.Context, conn *grpc.ClientConn, clnt pb.UserSvcClient, req *pb.FndUsersReq) (string, error) {
271 | r, err := clnt.FndUsers(ctx, req)
272 | if err != nil {
273 | return "", fmt.Errorf("unable to find user: %w", err)
274 | }
275 | if len(r.Items) > 0 {
276 | return r.Items[0].Id, nil
277 | }
278 | return "", nil
279 | }
280 |
--------------------------------------------------------------------------------
/msgs/proto/include/googleapis/google/rpc/error_details.proto:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | syntax = "proto3";
16 |
17 | package google.rpc;
18 |
19 | import "google/protobuf/duration.proto";
20 |
21 | option go_package = "google.golang.org/genproto/googleapis/rpc/errdetails;errdetails";
22 | option java_multiple_files = true;
23 | option java_outer_classname = "ErrorDetailsProto";
24 | option java_package = "com.google.rpc";
25 | option objc_class_prefix = "RPC";
26 |
27 |
28 | // Describes when the clients can retry a failed request. Clients could ignore
29 | // the recommendation here or retry when this information is missing from error
30 | // responses.
31 | //
32 | // It's always recommended that clients should use exponential backoff when
33 | // retrying.
34 | //
35 | // Clients should wait until `retry_delay` amount of time has passed since
36 | // receiving the error response before retrying. If retrying requests also
37 | // fail, clients should use an exponential backoff scheme to gradually increase
38 | // the delay between retries based on `retry_delay`, until either a maximum
39 | // number of retires have been reached or a maximum retry delay cap has been
40 | // reached.
41 | message RetryInfo {
42 | // Clients should wait at least this long between retrying the same request.
43 | google.protobuf.Duration retry_delay = 1;
44 | }
45 |
46 | // Describes additional debugging info.
47 | message DebugInfo {
48 | // The stack trace entries indicating where the error occurred.
49 | repeated string stack_entries = 1;
50 |
51 | // Additional debugging information provided by the server.
52 | string detail = 2;
53 | }
54 |
55 | // Describes how a quota check failed.
56 | //
57 | // For example if a daily limit was exceeded for the calling project,
58 | // a service could respond with a QuotaFailure detail containing the project
59 | // id and the description of the quota limit that was exceeded. If the
60 | // calling project hasn't enabled the service in the developer console, then
61 | // a service could respond with the project id and set `service_disabled`
62 | // to true.
63 | //
64 | // Also see RetryDetail and Help types for other details about handling a
65 | // quota failure.
66 | message QuotaFailure {
67 | // A message type used to describe a single quota violation. For example, a
68 | // daily quota or a custom quota that was exceeded.
69 | message Violation {
70 | // The subject on which the quota check failed.
71 | // For example, "clientip:" or "project:".
73 | string subject = 1;
74 |
75 | // A description of how the quota check failed. Clients can use this
76 | // description to find more about the quota configuration in the service's
77 | // public documentation, or find the relevant quota limit to adjust through
78 | // developer console.
79 | //
80 | // For example: "Service disabled" or "Daily Limit for read operations
81 | // exceeded".
82 | string description = 2;
83 | }
84 |
85 | // Describes all quota violations.
86 | repeated Violation violations = 1;
87 | }
88 |
89 | // Describes what preconditions have failed.
90 | //
91 | // For example, if an RPC failed because it required the Terms of Service to be
92 | // acknowledged, it could list the terms of service violation in the
93 | // PreconditionFailure message.
94 | message PreconditionFailure {
95 | // A message type used to describe a single precondition failure.
96 | message Violation {
97 | // The type of PreconditionFailure. We recommend using a service-specific
98 | // enum type to define the supported precondition violation types. For
99 | // example, "TOS" for "Terms of Service violation".
100 | string type = 1;
101 |
102 | // The subject, relative to the type, that failed.
103 | // For example, "google.com/cloud" relative to the "TOS" type would
104 | // indicate which terms of service is being referenced.
105 | string subject = 2;
106 |
107 | // A description of how the precondition failed. Developers can use this
108 | // description to understand how to fix the failure.
109 | //
110 | // For example: "Terms of service not accepted".
111 | string description = 3;
112 | }
113 |
114 | // Describes all precondition violations.
115 | repeated Violation violations = 1;
116 | }
117 |
118 | // Describes violations in a client request. This error type focuses on the
119 | // syntactic aspects of the request.
120 | message BadRequest {
121 | // A message type used to describe a single bad request field.
122 | message FieldViolation {
123 | // A path leading to a field in the request body. The value will be a
124 | // sequence of dot-separated identifiers that identify a protocol buffer
125 | // field. E.g., "field_violations.field" would identify this field.
126 | string field = 1;
127 |
128 | // A description of why the request element is bad.
129 | string description = 2;
130 | }
131 |
132 | // Describes all violations in a client request.
133 | repeated FieldViolation field_violations = 1;
134 | }
135 |
136 | // Contains metadata about the request that clients can attach when filing a bug
137 | // or providing other forms of feedback.
138 | message RequestInfo {
139 | // An opaque string that should only be interpreted by the service generating
140 | // it. For example, it can be used to identify requests in the service's logs.
141 | string request_id = 1;
142 |
143 | // Any data that was used to serve this request. For example, an encrypted
144 | // stack trace that can be sent back to the service provider for debugging.
145 | string serving_data = 2;
146 | }
147 |
148 | // Describes the resource that is being accessed.
149 | message ResourceInfo {
150 | // A name for the type of resource being accessed, e.g. "sql table",
151 | // "cloud storage bucket", "file", "Google calendar"; or the type URL
152 | // of the resource: e.g. "type.googleapis.com/google.pubsub.v1.Topic".
153 | string resource_type = 1;
154 |
155 | // The name of the resource being accessed. For example, a shared calendar
156 | // name: "example.com_4fghdhgsrgh@group.calendar.google.com", if the current
157 | // error is [google.rpc.Code.PERMISSION_DENIED][google.rpc.Code.PERMISSION_DENIED].
158 | string resource_name = 2;
159 |
160 | // The owner of the resource (optional).
161 | // For example, "user:" or "project:".
163 | string owner = 3;
164 |
165 | // Describes what error is encountered when accessing this resource.
166 | // For example, updating a cloud project may require the `writer` permission
167 | // on the developer console project.
168 | string description = 4;
169 | }
170 |
171 | // Provides links to documentation or for performing an out of band action.
172 | //
173 | // For example, if a quota check failed with an error indicating the calling
174 | // project hasn't enabled the accessed service, this can contain a URL pointing
175 | // directly to the right place in the developer console to flip the bit.
176 | message Help {
177 | // Describes a URL link.
178 | message Link {
179 | // Describes what the link offers.
180 | string description = 1;
181 |
182 | // The URL of the link.
183 | string url = 2;
184 | }
185 |
186 | // URL(s) pointing to additional information on handling the current error.
187 | repeated Link links = 1;
188 | }
189 |
190 | // Provides a localized error message that is safe to return to the user
191 | // which can be attached to an RPC error.
192 | message LocalizedMessage {
193 | // The locale used following the specification defined at
194 | // http://www.rfc-editor.org/rfc/bcp/bcp47.txt.
195 | // Examples are: "en-US", "fr-CH", "es-MX"
196 | string locale = 1;
197 |
198 | // The localized error message in the above locale.
199 | string message = 2;
200 | }
201 |
--------------------------------------------------------------------------------
/back/internal/usersvc/internal/userdb/qryuser.go:
--------------------------------------------------------------------------------
1 | package userdb
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "time"
8 |
9 | "github.com/OpenEugene/openboard/back/internal/altr"
10 | "github.com/OpenEugene/openboard/back/internal/pb"
11 | "github.com/codemodus/uidgen"
12 | "github.com/go-sql-driver/mysql"
13 | "github.com/jmoiron/sqlx"
14 | )
15 |
16 | type cx = context.Context
17 |
18 | var (
19 | csvStr = altr.CSVFromStrings
20 | lim = altr.LimitUint32
21 | asTS = altr.Timestamp
22 | )
23 |
24 | func parseOrUID(ug *uidgen.UIDGen, sid string) (uidgen.UID, bool) {
25 | if sid == "" {
26 | return ug.UID(), true
27 | }
28 | return ug.Parse(sid)
29 | }
30 |
31 | func (s *UserDB) upsertUser(ctx cx, sid string, x *pb.AddUserReq, y *pb.UserResp) error {
32 | id, ok := parseOrUID(s.ug, sid)
33 | if !ok {
34 | return fmt.Errorf("invalid uid")
35 | }
36 |
37 | tx, err := s.db.BeginTx(ctx, nil)
38 | if err != nil {
39 | return err
40 | }
41 |
42 | qry := `
43 | INSERT INTO user (
44 | user_id, username, email, email_hold, altmail, altmail_hold, full_name, avatar, password
45 | ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
46 | ON DUPLICATE KEY UPDATE
47 | user_id = ?,
48 | username = ?,
49 | email = ?,
50 | email_hold = ?,
51 | altmail = ?,
52 | altmail_hold = ?,
53 | full_name = ?,
54 | avatar = ?,
55 | password = ?
56 | `
57 | _, err = tx.ExecContext(
58 | ctx,
59 | qry,
60 | &id,
61 | x.Username,
62 | x.Email,
63 | x.EmailHold,
64 | x.Altmail,
65 | x.AltmailHold,
66 | x.FullName,
67 | x.Avatar,
68 | x.Password,
69 | &id,
70 | x.Username,
71 | x.Email,
72 | x.EmailHold,
73 | x.Altmail,
74 | x.AltmailHold,
75 | x.FullName,
76 | x.Avatar,
77 | x.Password,
78 | )
79 | if err != nil {
80 | tx.Rollback()
81 | return err
82 | }
83 |
84 | qry = "INSERT into user_role (user_id, role_id) VALUES "
85 | vals, args := buildValsAndArgs(id.String(), x.RoleIds)
86 | _, err = tx.ExecContext(ctx, qry+vals, args...)
87 | if err != nil {
88 | tx.Rollback()
89 | return err
90 | }
91 |
92 | if err := tx.Commit(); err != nil {
93 | return err
94 | }
95 |
96 | // Execute another query that will return the user fields.
97 | req := pb.FndUsersReq{
98 | RoleIds: []string{},
99 | Email: x.Email,
100 | EmailHold: false,
101 | Altmail: "",
102 | AltmailHold: false,
103 | Limit: 1,
104 | Lapse: 0,
105 | }
106 | resp := pb.UsersResp{}
107 |
108 | if err = s.findUsers(ctx, &req, &resp); err != nil {
109 | return err
110 | }
111 |
112 | if len(resp.Items) == 0 {
113 | return errors.New("expected user to be found, but found none")
114 | }
115 |
116 | // There is only one user (Item) expected to be found.
117 | y.Item = resp.Items[0]
118 |
119 | return nil
120 | }
121 |
122 | // buildValsAndArgs enables adding all the role IDs for the user ID being added.
123 | // The values returned have bindvar pairs for each userId/roleID pair in args.
124 | func buildValsAndArgs(uid string, rids []string) (string, []interface{}) {
125 | vals := "(?, ?)"
126 |
127 | args := make([]interface{}, len(rids)*2)
128 | args[0] = uid
129 | args[1] = rids[0]
130 |
131 | for i, rid := range rids[1:] {
132 | vals += ", (?, ?)"
133 |
134 | args[i*2+2] = uid
135 | args[i*2+3] = rid
136 | }
137 |
138 | return vals, args
139 | }
140 |
141 | func (s *UserDB) deleteUser(ctx cx, sid string) error {
142 | _, err := s.db.Exec(
143 | "UPDATE user SET deleted_at = ? WHERE user_id = ?",
144 | time.Now(),
145 | sid,
146 | )
147 | if err != nil {
148 | return err
149 | }
150 |
151 | return nil
152 | }
153 |
154 | type userTemp struct {
155 | uid, username, email, altmail, fullName, avatar, rid, rolename string
156 | emailHold, altmailHold bool
157 | tl, tc, tu, td, tb mysql.NullTime
158 | }
159 |
160 | func (s *UserDB) findUsers(ctx cx, x *pb.FndUsersReq, y *pb.UsersResp) error {
161 | qry := `
162 | SELECT u.user_id, u.username, u.email, u.email_hold, u.altmail,
163 | u.altmail_hold, u.full_name, u.avatar, r.role_id, r.role_name,
164 | u.last_login, u.created_at, u.updated_at, u.deleted_at, u.blocked_at
165 | FROM (
166 | SELECT user_id, username, email, email_hold, altmail, altmail_hold,
167 | full_name, avatar, last_login, created_at, updated_at, deleted_at,
168 | blocked_at
169 | FROM user WHERE email = ? AND email_hold = ?
170 | LIMIT ? OFFSET ?
171 | ) u
172 | LEFT JOIN user_role ur
173 | ON u.user_id = ur.user_id
174 | LEFT JOIN role r
175 | ON r.role_id = ur.role_id
176 | `
177 |
178 | rows, err := s.db.Query(qry, x.Email, x.EmailHold, x.Limit, x.Lapse)
179 | defer rows.Close()
180 | if err != nil {
181 | return err
182 | }
183 |
184 | temps := []userTemp{}
185 |
186 | for rows.Next() {
187 | u := userTemp{}
188 |
189 | err := rows.Scan(
190 | &u.uid,
191 | &u.username,
192 | &u.email,
193 | &u.emailHold,
194 | &u.altmail,
195 | &u.altmailHold,
196 | &u.fullName,
197 | &u.avatar,
198 | &u.rid,
199 | &u.rolename,
200 | &u.tl,
201 | &u.tc,
202 | &u.tu,
203 | &u.td,
204 | &u.tb,
205 | )
206 | if err != nil {
207 | return err
208 | }
209 |
210 | temps = append(temps, u)
211 | }
212 | if err = rows.Err(); err != nil {
213 | return err
214 | }
215 |
216 | users := squashUsers(temps)
217 |
218 | for _, u := range users {
219 | y.Items = append(y.Items, &u)
220 | }
221 |
222 | err = s.db.QueryRow(
223 | "SELECT COUNT(*) FROM user WHERE email = ? AND email_hold = ?",
224 | x.Email,
225 | x.EmailHold,
226 | ).Scan(&y.Total)
227 | if err != nil {
228 | return err
229 | }
230 |
231 | return nil
232 | }
233 |
234 | // squashUsers combines user information so there are no duplicate user IDs in slice.
235 | func squashUsers(uts []userTemp) []pb.User {
236 | var users []pb.User
237 |
238 | for _, ut := range uts {
239 | i := fndUserIndex(ut, users)
240 |
241 | if i == -1 {
242 | usr := convertUserTemp(ut)
243 | users = append(users, usr)
244 | } else {
245 | r := pb.RoleResp{
246 | Id: ut.rid,
247 | Name: ut.rolename,
248 | }
249 |
250 | users[i].Roles = append(users[i].Roles, &r)
251 | }
252 | }
253 |
254 | return users
255 | }
256 |
257 | // userIndex gets the index of a user in []pb.User, or -1 if not found.
258 | func fndUserIndex(ut userTemp, users []pb.User) int {
259 | for i, u := range users {
260 | if u.Id == ut.uid {
261 | return i
262 | }
263 | }
264 |
265 | return -1
266 | }
267 |
268 | // convertUserTemp transfers information from userTemp to pb.User.
269 | func convertUserTemp(ut userTemp) pb.User {
270 | var u pb.User
271 |
272 | r := pb.RoleResp{
273 | Id: ut.rid,
274 | Name: ut.rolename,
275 | }
276 |
277 | u.Id = ut.uid
278 | u.Username = ut.username
279 | u.Email = ut.email
280 | u.EmailHold = ut.emailHold
281 | u.Altmail = ut.altmail
282 | u.AltmailHold = ut.altmailHold
283 | u.FullName = ut.fullName
284 | u.Avatar = ut.avatar
285 | u.Roles = append(u.Roles, &r)
286 | u.LastLogin = asTS(ut.tl.Time, ut.tl.Valid)
287 | u.Created = asTS(ut.tc.Time, ut.tc.Valid)
288 | u.Updated = asTS(ut.tu.Time, ut.tu.Valid)
289 | u.Deleted = asTS(ut.td.Time, ut.td.Valid)
290 |
291 | return u
292 | }
293 |
294 | func (s *UserDB) upsertRole(ctx cx, sid string, x *pb.AddRoleReq, y *pb.RoleResp) error {
295 | id, ok := parseOrUID(s.ug, sid)
296 | if !ok {
297 | return fmt.Errorf("invalid uid")
298 | }
299 |
300 | qry := `
301 | INSERT INTO role (role_id, role_name)
302 | VALUES (?, ?)
303 | ON DUPLICATE KEY UPDATE role_id = ?, role_name = ?
304 | `
305 |
306 | _, err := s.db.Exec(qry, &id, x.Name, &id, x.Name)
307 | if err != nil {
308 | return err
309 | }
310 |
311 | // Execute another query that will return the role fields.
312 | req := pb.FndRolesReq{
313 | RoleIds: []string{},
314 | RoleNames: []string{x.Name},
315 | Limit: 1,
316 | Lapse: 0,
317 | }
318 | resp := pb.RolesResp{}
319 | if err = s.findRoles(ctx, &req, &resp); err != nil {
320 | return err
321 | }
322 |
323 | if len(resp.Items) == 0 {
324 | return errors.New("upserted role not found")
325 | }
326 |
327 | // There is only one role (Item) expected to be found.
328 | y.Id = resp.Items[0].Id
329 | y.Name = resp.Items[0].Name
330 |
331 | return nil
332 | }
333 |
334 | func (s *UserDB) findRoles(ctx cx, x *pb.FndRolesReq, y *pb.RolesResp) error {
335 | req := *x
336 | roleIds := req.RoleIds
337 | roleNames := req.RoleNames
338 |
339 | if len(roleIds) == 0 {
340 | roleIds = []string{""}
341 | }
342 | if len(roleNames) == 0 {
343 | roleNames = []string{""}
344 | }
345 |
346 | qry := `
347 | SELECT role_id, role_name
348 | FROM role
349 | WHERE role_id IN (?) OR role_name IN (?) LIMIT ? OFFSET ?;
350 | `
351 |
352 | query, args, err := sqlx.In(qry, roleIds, roleNames, x.Limit, x.Lapse)
353 | if err != nil {
354 | return err
355 | }
356 |
357 | rows, err := s.db.Query(query, args...)
358 | if err != nil {
359 | return err
360 | }
361 | defer rows.Close()
362 |
363 | for rows.Next() {
364 | r := pb.RoleResp{}
365 |
366 | if err := rows.Scan(&r.Id, &r.Name); err != nil {
367 | return err
368 | }
369 |
370 | y.Items = append(y.Items, &r)
371 | }
372 | if err = rows.Err(); err != nil {
373 | return err
374 | }
375 |
376 | qry = "SELECT COUNT(*) FROM role WHERE role_id IN (?) OR role_name IN (?);"
377 |
378 | query, args, err = sqlx.In(qry, roleIds, roleNames)
379 | if err != nil {
380 | return err
381 | }
382 |
383 | err = s.db.QueryRow(query, args...).Scan(&y.Total)
384 | if err != nil {
385 | return err
386 | }
387 |
388 | return nil
389 | }
390 |
--------------------------------------------------------------------------------
/back/tests/openbsrv/postsvc_test.go:
--------------------------------------------------------------------------------
1 | package main_test
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "testing"
7 |
8 | "github.com/OpenEugene/openboard/back/internal/pb"
9 | "google.golang.org/grpc"
10 | "google.golang.org/protobuf/proto"
11 | )
12 |
13 | func TestPostClientServices(t *testing.T) {
14 | ctx := context.Background()
15 | conn, err := grpc.Dial(":4242", grpc.WithInsecure())
16 | if err != nil {
17 | t.Fatal(err)
18 | }
19 | defer conn.Close()
20 |
21 | // Post Service Tests
22 | postClnt := pb.NewPostClient(conn)
23 | t.Run("Add type", postSvcAddAndFndTypesFn(ctx, conn, postClnt))
24 | t.Run("Add and find posts", postSvcAddAndFndPostsFn(ctx, conn, postClnt))
25 | t.Run("Find all posts", postSvcFindAllPosts(ctx, conn, postClnt))
26 | t.Run("Add, find and edit post", postSvcEdtPostFn(ctx, conn, postClnt))
27 | t.Run("Find and delete post", postSvcDelPostFn(ctx, conn, postClnt))
28 | t.Run("Find posts by keywords", postSvcKeywordSearch(ctx, conn, postClnt))
29 | }
30 |
31 | func postSvcAddAndFndTypesFn(ctx context.Context, conn *grpc.ClientConn, clnt pb.PostClient) func(*testing.T) {
32 | return func(t *testing.T) {
33 | ctx := context.Background()
34 |
35 | tests := []struct {
36 | wantType string
37 | addTypeReq *pb.AddTypeReq
38 | fndTypeReq *pb.FndTypesReq
39 | wantCount int
40 | }{
41 | {
42 | "testTypeA",
43 | &pb.AddTypeReq{Name: "testTypeA"},
44 | &pb.FndTypesReq{Limit: 100, Lapse: 0},
45 | 1,
46 | },
47 | {
48 | "testTypeB",
49 | &pb.AddTypeReq{Name: "testTypeB"},
50 | &pb.FndTypesReq{Limit: 100, Lapse: 0},
51 | 2,
52 | },
53 | }
54 |
55 | for _, tc := range tests {
56 | r1, err := clnt.AddType(ctx, tc.addTypeReq)
57 | if err != nil {
58 | t.Error(err)
59 | }
60 |
61 | gotType := r1.Name
62 | if gotType != tc.wantType {
63 | t.Fatalf("got: %s, want: %s", gotType, tc.wantType)
64 | }
65 |
66 | r2, err := clnt.FndTypes(ctx, tc.fndTypeReq)
67 | if err != nil {
68 | t.Error(err)
69 | }
70 |
71 | gotCount := len(r2.Items)
72 | if gotCount != tc.wantCount {
73 | t.Errorf("got %d items, want %d", gotCount, tc.wantCount)
74 | }
75 | gotType = r2.Items[tc.wantCount-1].Name
76 | if gotType != tc.wantType {
77 | t.Fatalf("got: %s, want: %s", gotType, tc.wantType)
78 | }
79 | }
80 | }
81 | }
82 |
83 | func postSvcAddAndFndPostsFn(ctx context.Context, conn *grpc.ClientConn, clnt pb.PostClient) func(*testing.T) {
84 | return func(t *testing.T) {
85 | tests := []struct {
86 | addReq *pb.AddPostReq
87 | want *pb.PostResp
88 | fndReq *pb.FndPostsReq
89 | }{
90 | {
91 | &pb.AddPostReq{
92 | Title: "test title postA first",
93 | Body: "test body of first post",
94 | TypeId: "2",
95 | },
96 | &pb.PostResp{
97 | Title: "test title postA first",
98 | Body: "test body of first post",
99 | TypeId: "2",
100 | },
101 | &pb.FndPostsReq{Keywords: []string{
102 | "postA",
103 | "first",
104 | "multiple",
105 | "keywords",
106 | "not",
107 | "available",
108 | "yet",
109 | },
110 | },
111 | },
112 | {
113 | &pb.AddPostReq{
114 | Title: "test title postB second",
115 | Body: "test body of second postB",
116 | TypeId: "3",
117 | },
118 | &pb.PostResp{
119 | Title: "test title postB second",
120 | Body: "test body of second postB",
121 | TypeId: "3",
122 | },
123 | &pb.FndPostsReq{Keywords: []string{"postB"}},
124 | },
125 | }
126 |
127 | for _, tc := range tests {
128 | r, err := clnt.AddPost(ctx, tc.addReq)
129 | if err != nil {
130 | t.Fatal(err)
131 | }
132 |
133 | got := r
134 | wantPostID := got.Id
135 |
136 | // Unset fields that aren't being tested.
137 | unsetUntestedFields(got)
138 |
139 | if !proto.Equal(got, tc.want) {
140 | t.Errorf("got: %#v, want: %#v", got, tc.want)
141 | }
142 |
143 | gotPostID, err := postSvcFndPost(ctx, conn, clnt, tc.fndReq)
144 | if err != nil {
145 | t.Error(err)
146 | }
147 |
148 | if gotPostID != wantPostID {
149 | t.Fatalf("got: %s, want: %s", gotPostID, wantPostID)
150 | }
151 | }
152 | }
153 | }
154 |
155 | func postSvcEdtPostFn(ctx context.Context, conn *grpc.ClientConn, clnt pb.PostClient) func(*testing.T) {
156 | return func(t *testing.T) {
157 | addReq := &pb.AddPostReq{
158 | Title: "Post C",
159 | Body: "This is post C.",
160 | TypeId: "2",
161 | }
162 | _, err := clnt.AddPost(ctx, addReq)
163 | if err != nil {
164 | t.Fatal(err)
165 | }
166 |
167 | fndReq := &pb.FndPostsReq{Keywords: []string{"post C"}}
168 | postID, err := postSvcFndPost(ctx, conn, clnt, fndReq)
169 | if err != nil {
170 | t.Error(err)
171 | }
172 |
173 | editReq := &pb.AddPostReq{
174 | Title: "Post C (edited)",
175 | Body: "This is post C after edits.",
176 | TypeId: "2",
177 | }
178 |
179 | ovrReq := &pb.OvrPostReq{Id: postID, Req: editReq}
180 | r, err := clnt.OvrPost(ctx, ovrReq)
181 | if err != nil {
182 | t.Error(err)
183 | }
184 |
185 | want := &pb.PostResp{
186 | Title: editReq.Title,
187 | Body: editReq.Body,
188 | TypeId: editReq.TypeId,
189 | }
190 |
191 | got := r
192 |
193 | unsetUntestedFields(got)
194 |
195 | if !proto.Equal(got, want) {
196 | t.Fatalf("got: %#v, want: %#v", got, want)
197 | }
198 | }
199 | }
200 |
201 | func postSvcDelPostFn(ctx context.Context, conn *grpc.ClientConn, clnt pb.PostClient) func(*testing.T) {
202 | return func(t *testing.T) {
203 | addReq := &pb.AddPostReq{
204 | Title: "test title postD first",
205 | Body: "test body of fourth post",
206 | TypeId: "4",
207 | }
208 |
209 | _, err := clnt.AddPost(ctx, addReq)
210 | if err != nil {
211 | t.Fatal(err)
212 | }
213 |
214 | fndReq := &pb.FndPostsReq{Keywords: []string{"postD"}}
215 |
216 | fndResp, err := clnt.FndPosts(ctx, fndReq)
217 | if err != nil {
218 | t.Error(err)
219 | }
220 |
221 | if fndResp.Posts[0].Deleted != nil {
222 | t.Fatalf(
223 | "expected post %s deleted_at to be nil, got %v",
224 | fndResp.Posts[0].Id,
225 | fndResp.Posts[0].Deleted,
226 | )
227 | t.Fail()
228 | }
229 |
230 | _, err = clnt.RmvPost(ctx, &pb.RmvPostReq{Id: fndResp.Posts[0].Id})
231 | if err != nil {
232 | t.Error(err)
233 | }
234 |
235 | fndResp, err = clnt.FndPosts(ctx, fndReq)
236 | if err != nil {
237 | t.Error(err)
238 | }
239 |
240 | if fndResp.Posts[0].Deleted == nil {
241 | t.Fatalf("expected post %s deleted_at to not be nil", fndResp.Posts[0].Id)
242 | t.Fail()
243 | }
244 | }
245 | }
246 |
247 | func postSvcFndPost(ctx context.Context, conn *grpc.ClientConn, clnt pb.PostClient, req *pb.FndPostsReq) (string, error) {
248 | postID, err := clnt.FndPosts(ctx, req)
249 | if err != nil {
250 | return "", fmt.Errorf("Unable to find post: %w", err)
251 | }
252 |
253 | if len(postID.Posts) > 0 {
254 | return postID.Posts[0].Id, nil
255 | }
256 | return "", nil
257 | }
258 |
259 | func postsContain(resp *pb.PostsResp, post *pb.PostResp) bool {
260 | for _, j := range resp.Posts {
261 | if j.Title == post.Title && j.Body == post.Body && j.TypeId == post.TypeId {
262 | return true
263 | }
264 | }
265 | return false
266 | }
267 |
268 | func postSvcFindAllPosts(ctx context.Context, conn *grpc.ClientConn, clnt pb.PostClient) func(*testing.T) {
269 | return func(t *testing.T) {
270 | tests := []struct {
271 | fndReq *pb.FndPostsReq
272 | want *pb.PostsResp
273 | }{
274 | {
275 | &pb.FndPostsReq{Keywords: []string{}},
276 | &pb.PostsResp{
277 | Posts: []*pb.PostResp{
278 | {
279 | Title: "test title postA first",
280 | Body: "test body of first post",
281 | TypeId: "2",
282 | },
283 | {
284 | Title: "test title postB second",
285 | Body: "test body of second postB",
286 | TypeId: "3",
287 | },
288 | },
289 | },
290 | },
291 | }
292 |
293 | for _, tc := range tests {
294 | resp, err := clnt.FndPosts(ctx, tc.fndReq)
295 | if err != nil {
296 | t.Errorf("unexpected error: %v", err)
297 | }
298 |
299 | if len(tc.want.Posts) != len(resp.Posts) {
300 | t.Errorf("mismatch between response length and post length, "+
301 | "want: %d got: %d", len(tc.want.Posts), len(resp.Posts))
302 | }
303 |
304 | for _, post := range tc.want.Posts {
305 | if !postsContain(resp, post) {
306 | t.Errorf("couldn't find post with title: %s", post.Title)
307 | }
308 | }
309 | }
310 | }
311 | }
312 |
313 | func postSvcKeywordSearch(ctx context.Context, conn *grpc.ClientConn, clnt pb.PostClient) func(*testing.T) {
314 | return func(t *testing.T) {
315 | tests := []struct {
316 | name string
317 | fndReq *pb.FndPostsReq
318 | want *pb.PostsResp
319 | }{
320 | {
321 | "not find non-matching posts",
322 | &pb.FndPostsReq{Keywords: []string{"randomgiberish"}},
323 | &pb.PostsResp{
324 | Posts: []*pb.PostResp{},
325 | },
326 | },
327 | {
328 | "find posts by several keywords",
329 | &pb.FndPostsReq{Keywords: []string{"postB", "postD"}},
330 | &pb.PostsResp{
331 | Posts: []*pb.PostResp{
332 | {
333 | Title: "test title postB second",
334 | Body: "test body of second postB",
335 | TypeId: "3",
336 | },
337 | {
338 | Title: "test title postD first",
339 | Body: "test body of fourth post",
340 | TypeId: "4",
341 | },
342 | },
343 | },
344 | },
345 | {
346 | "find keyword within parenthesis",
347 | &pb.FndPostsReq{Keywords: []string{"edited"}},
348 | &pb.PostsResp{
349 | Posts: []*pb.PostResp{
350 | {
351 | Title: "Post C (edited)",
352 | Body: "This is post C after edits.",
353 | TypeId: "2",
354 | },
355 | },
356 | },
357 | },
358 | }
359 |
360 | for _, tc := range tests {
361 | resp, err := clnt.FndPosts(ctx, tc.fndReq)
362 | if err != nil {
363 | t.Errorf("%s: unexpected error: %v", tc.name, err)
364 | }
365 |
366 | if len(tc.want.Posts) != len(resp.Posts) {
367 | t.Errorf("%s: mismatch between response length and post length, "+
368 | "want: %d got: %d", tc.name, len(tc.want.Posts), len(resp.Posts))
369 | }
370 |
371 | for _, post := range tc.want.Posts {
372 | if !postsContain(resp, post) {
373 | t.Errorf("%s: couldn't find post with title: %s", tc.name, post.Title)
374 | }
375 | }
376 | }
377 | }
378 | }
379 |
--------------------------------------------------------------------------------
/back/internal/pb/post_grpc.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
2 |
3 | package pb
4 |
5 | import (
6 | context "context"
7 | grpc "google.golang.org/grpc"
8 | codes "google.golang.org/grpc/codes"
9 | status "google.golang.org/grpc/status"
10 | )
11 |
12 | // This is a compile-time assertion to ensure that this generated file
13 | // is compatible with the grpc package it is being compiled against.
14 | // Requires gRPC-Go v1.32.0 or later.
15 | const _ = grpc.SupportPackageIsVersion7
16 |
17 | // PostClient is the client API for Post service.
18 | //
19 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
20 | type PostClient interface {
21 | AddType(ctx context.Context, in *AddTypeReq, opts ...grpc.CallOption) (*TypeResp, error)
22 | FndTypes(ctx context.Context, in *FndTypesReq, opts ...grpc.CallOption) (*TypesResp, error)
23 | AddPost(ctx context.Context, in *AddPostReq, opts ...grpc.CallOption) (*PostResp, error)
24 | FndPosts(ctx context.Context, in *FndPostsReq, opts ...grpc.CallOption) (*PostsResp, error)
25 | OvrPost(ctx context.Context, in *OvrPostReq, opts ...grpc.CallOption) (*PostResp, error)
26 | RmvPost(ctx context.Context, in *RmvPostReq, opts ...grpc.CallOption) (*RmvPostResp, error)
27 | }
28 |
29 | type postClient struct {
30 | cc grpc.ClientConnInterface
31 | }
32 |
33 | func NewPostClient(cc grpc.ClientConnInterface) PostClient {
34 | return &postClient{cc}
35 | }
36 |
37 | func (c *postClient) AddType(ctx context.Context, in *AddTypeReq, opts ...grpc.CallOption) (*TypeResp, error) {
38 | out := new(TypeResp)
39 | err := c.cc.Invoke(ctx, "/pb.Post/AddType", in, out, opts...)
40 | if err != nil {
41 | return nil, err
42 | }
43 | return out, nil
44 | }
45 |
46 | func (c *postClient) FndTypes(ctx context.Context, in *FndTypesReq, opts ...grpc.CallOption) (*TypesResp, error) {
47 | out := new(TypesResp)
48 | err := c.cc.Invoke(ctx, "/pb.Post/FndTypes", in, out, opts...)
49 | if err != nil {
50 | return nil, err
51 | }
52 | return out, nil
53 | }
54 |
55 | func (c *postClient) AddPost(ctx context.Context, in *AddPostReq, opts ...grpc.CallOption) (*PostResp, error) {
56 | out := new(PostResp)
57 | err := c.cc.Invoke(ctx, "/pb.Post/AddPost", in, out, opts...)
58 | if err != nil {
59 | return nil, err
60 | }
61 | return out, nil
62 | }
63 |
64 | func (c *postClient) FndPosts(ctx context.Context, in *FndPostsReq, opts ...grpc.CallOption) (*PostsResp, error) {
65 | out := new(PostsResp)
66 | err := c.cc.Invoke(ctx, "/pb.Post/FndPosts", in, out, opts...)
67 | if err != nil {
68 | return nil, err
69 | }
70 | return out, nil
71 | }
72 |
73 | func (c *postClient) OvrPost(ctx context.Context, in *OvrPostReq, opts ...grpc.CallOption) (*PostResp, error) {
74 | out := new(PostResp)
75 | err := c.cc.Invoke(ctx, "/pb.Post/OvrPost", in, out, opts...)
76 | if err != nil {
77 | return nil, err
78 | }
79 | return out, nil
80 | }
81 |
82 | func (c *postClient) RmvPost(ctx context.Context, in *RmvPostReq, opts ...grpc.CallOption) (*RmvPostResp, error) {
83 | out := new(RmvPostResp)
84 | err := c.cc.Invoke(ctx, "/pb.Post/RmvPost", in, out, opts...)
85 | if err != nil {
86 | return nil, err
87 | }
88 | return out, nil
89 | }
90 |
91 | // PostServer is the server API for Post service.
92 | // All implementations should embed UnimplementedPostServer
93 | // for forward compatibility
94 | type PostServer interface {
95 | AddType(context.Context, *AddTypeReq) (*TypeResp, error)
96 | FndTypes(context.Context, *FndTypesReq) (*TypesResp, error)
97 | AddPost(context.Context, *AddPostReq) (*PostResp, error)
98 | FndPosts(context.Context, *FndPostsReq) (*PostsResp, error)
99 | OvrPost(context.Context, *OvrPostReq) (*PostResp, error)
100 | RmvPost(context.Context, *RmvPostReq) (*RmvPostResp, error)
101 | }
102 |
103 | // UnimplementedPostServer should be embedded to have forward compatible implementations.
104 | type UnimplementedPostServer struct {
105 | }
106 |
107 | func (UnimplementedPostServer) AddType(context.Context, *AddTypeReq) (*TypeResp, error) {
108 | return nil, status.Errorf(codes.Unimplemented, "method AddType not implemented")
109 | }
110 | func (UnimplementedPostServer) FndTypes(context.Context, *FndTypesReq) (*TypesResp, error) {
111 | return nil, status.Errorf(codes.Unimplemented, "method FndTypes not implemented")
112 | }
113 | func (UnimplementedPostServer) AddPost(context.Context, *AddPostReq) (*PostResp, error) {
114 | return nil, status.Errorf(codes.Unimplemented, "method AddPost not implemented")
115 | }
116 | func (UnimplementedPostServer) FndPosts(context.Context, *FndPostsReq) (*PostsResp, error) {
117 | return nil, status.Errorf(codes.Unimplemented, "method FndPosts not implemented")
118 | }
119 | func (UnimplementedPostServer) OvrPost(context.Context, *OvrPostReq) (*PostResp, error) {
120 | return nil, status.Errorf(codes.Unimplemented, "method OvrPost not implemented")
121 | }
122 | func (UnimplementedPostServer) RmvPost(context.Context, *RmvPostReq) (*RmvPostResp, error) {
123 | return nil, status.Errorf(codes.Unimplemented, "method RmvPost not implemented")
124 | }
125 |
126 | // UnsafePostServer may be embedded to opt out of forward compatibility for this service.
127 | // Use of this interface is not recommended, as added methods to PostServer will
128 | // result in compilation errors.
129 | type UnsafePostServer interface {
130 | mustEmbedUnimplementedPostServer()
131 | }
132 |
133 | func RegisterPostServer(s grpc.ServiceRegistrar, srv PostServer) {
134 | s.RegisterService(&Post_ServiceDesc, srv)
135 | }
136 |
137 | func _Post_AddType_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
138 | in := new(AddTypeReq)
139 | if err := dec(in); err != nil {
140 | return nil, err
141 | }
142 | if interceptor == nil {
143 | return srv.(PostServer).AddType(ctx, in)
144 | }
145 | info := &grpc.UnaryServerInfo{
146 | Server: srv,
147 | FullMethod: "/pb.Post/AddType",
148 | }
149 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
150 | return srv.(PostServer).AddType(ctx, req.(*AddTypeReq))
151 | }
152 | return interceptor(ctx, in, info, handler)
153 | }
154 |
155 | func _Post_FndTypes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
156 | in := new(FndTypesReq)
157 | if err := dec(in); err != nil {
158 | return nil, err
159 | }
160 | if interceptor == nil {
161 | return srv.(PostServer).FndTypes(ctx, in)
162 | }
163 | info := &grpc.UnaryServerInfo{
164 | Server: srv,
165 | FullMethod: "/pb.Post/FndTypes",
166 | }
167 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
168 | return srv.(PostServer).FndTypes(ctx, req.(*FndTypesReq))
169 | }
170 | return interceptor(ctx, in, info, handler)
171 | }
172 |
173 | func _Post_AddPost_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
174 | in := new(AddPostReq)
175 | if err := dec(in); err != nil {
176 | return nil, err
177 | }
178 | if interceptor == nil {
179 | return srv.(PostServer).AddPost(ctx, in)
180 | }
181 | info := &grpc.UnaryServerInfo{
182 | Server: srv,
183 | FullMethod: "/pb.Post/AddPost",
184 | }
185 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
186 | return srv.(PostServer).AddPost(ctx, req.(*AddPostReq))
187 | }
188 | return interceptor(ctx, in, info, handler)
189 | }
190 |
191 | func _Post_FndPosts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
192 | in := new(FndPostsReq)
193 | if err := dec(in); err != nil {
194 | return nil, err
195 | }
196 | if interceptor == nil {
197 | return srv.(PostServer).FndPosts(ctx, in)
198 | }
199 | info := &grpc.UnaryServerInfo{
200 | Server: srv,
201 | FullMethod: "/pb.Post/FndPosts",
202 | }
203 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
204 | return srv.(PostServer).FndPosts(ctx, req.(*FndPostsReq))
205 | }
206 | return interceptor(ctx, in, info, handler)
207 | }
208 |
209 | func _Post_OvrPost_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
210 | in := new(OvrPostReq)
211 | if err := dec(in); err != nil {
212 | return nil, err
213 | }
214 | if interceptor == nil {
215 | return srv.(PostServer).OvrPost(ctx, in)
216 | }
217 | info := &grpc.UnaryServerInfo{
218 | Server: srv,
219 | FullMethod: "/pb.Post/OvrPost",
220 | }
221 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
222 | return srv.(PostServer).OvrPost(ctx, req.(*OvrPostReq))
223 | }
224 | return interceptor(ctx, in, info, handler)
225 | }
226 |
227 | func _Post_RmvPost_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
228 | in := new(RmvPostReq)
229 | if err := dec(in); err != nil {
230 | return nil, err
231 | }
232 | if interceptor == nil {
233 | return srv.(PostServer).RmvPost(ctx, in)
234 | }
235 | info := &grpc.UnaryServerInfo{
236 | Server: srv,
237 | FullMethod: "/pb.Post/RmvPost",
238 | }
239 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
240 | return srv.(PostServer).RmvPost(ctx, req.(*RmvPostReq))
241 | }
242 | return interceptor(ctx, in, info, handler)
243 | }
244 |
245 | // Post_ServiceDesc is the grpc.ServiceDesc for Post service.
246 | // It's only intended for direct use with grpc.RegisterService,
247 | // and not to be introspected or modified (even as a copy)
248 | var Post_ServiceDesc = grpc.ServiceDesc{
249 | ServiceName: "pb.Post",
250 | HandlerType: (*PostServer)(nil),
251 | Methods: []grpc.MethodDesc{
252 | {
253 | MethodName: "AddType",
254 | Handler: _Post_AddType_Handler,
255 | },
256 | {
257 | MethodName: "FndTypes",
258 | Handler: _Post_FndTypes_Handler,
259 | },
260 | {
261 | MethodName: "AddPost",
262 | Handler: _Post_AddPost_Handler,
263 | },
264 | {
265 | MethodName: "FndPosts",
266 | Handler: _Post_FndPosts_Handler,
267 | },
268 | {
269 | MethodName: "OvrPost",
270 | Handler: _Post_OvrPost_Handler,
271 | },
272 | {
273 | MethodName: "RmvPost",
274 | Handler: _Post_RmvPost_Handler,
275 | },
276 | },
277 | Streams: []grpc.StreamDesc{},
278 | Metadata: "post.proto",
279 | }
280 |
--------------------------------------------------------------------------------
/back/internal/pb/user_grpc.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
2 |
3 | package pb
4 |
5 | import (
6 | context "context"
7 | grpc "google.golang.org/grpc"
8 | codes "google.golang.org/grpc/codes"
9 | status "google.golang.org/grpc/status"
10 | )
11 |
12 | // This is a compile-time assertion to ensure that this generated file
13 | // is compatible with the grpc package it is being compiled against.
14 | // Requires gRPC-Go v1.32.0 or later.
15 | const _ = grpc.SupportPackageIsVersion7
16 |
17 | // UserSvcClient is the client API for UserSvc service.
18 | //
19 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
20 | type UserSvcClient interface {
21 | AddRole(ctx context.Context, in *AddRoleReq, opts ...grpc.CallOption) (*RoleResp, error)
22 | FndRoles(ctx context.Context, in *FndRolesReq, opts ...grpc.CallOption) (*RolesResp, error)
23 | AddUser(ctx context.Context, in *AddUserReq, opts ...grpc.CallOption) (*UserResp, error)
24 | OvrUser(ctx context.Context, in *OvrUserReq, opts ...grpc.CallOption) (*UserResp, error)
25 | FndUsers(ctx context.Context, in *FndUsersReq, opts ...grpc.CallOption) (*UsersResp, error)
26 | RmvUser(ctx context.Context, in *RmvUserReq, opts ...grpc.CallOption) (*RmvUserResp, error)
27 | }
28 |
29 | type userSvcClient struct {
30 | cc grpc.ClientConnInterface
31 | }
32 |
33 | func NewUserSvcClient(cc grpc.ClientConnInterface) UserSvcClient {
34 | return &userSvcClient{cc}
35 | }
36 |
37 | func (c *userSvcClient) AddRole(ctx context.Context, in *AddRoleReq, opts ...grpc.CallOption) (*RoleResp, error) {
38 | out := new(RoleResp)
39 | err := c.cc.Invoke(ctx, "/pb.UserSvc/AddRole", in, out, opts...)
40 | if err != nil {
41 | return nil, err
42 | }
43 | return out, nil
44 | }
45 |
46 | func (c *userSvcClient) FndRoles(ctx context.Context, in *FndRolesReq, opts ...grpc.CallOption) (*RolesResp, error) {
47 | out := new(RolesResp)
48 | err := c.cc.Invoke(ctx, "/pb.UserSvc/FndRoles", in, out, opts...)
49 | if err != nil {
50 | return nil, err
51 | }
52 | return out, nil
53 | }
54 |
55 | func (c *userSvcClient) AddUser(ctx context.Context, in *AddUserReq, opts ...grpc.CallOption) (*UserResp, error) {
56 | out := new(UserResp)
57 | err := c.cc.Invoke(ctx, "/pb.UserSvc/AddUser", in, out, opts...)
58 | if err != nil {
59 | return nil, err
60 | }
61 | return out, nil
62 | }
63 |
64 | func (c *userSvcClient) OvrUser(ctx context.Context, in *OvrUserReq, opts ...grpc.CallOption) (*UserResp, error) {
65 | out := new(UserResp)
66 | err := c.cc.Invoke(ctx, "/pb.UserSvc/OvrUser", in, out, opts...)
67 | if err != nil {
68 | return nil, err
69 | }
70 | return out, nil
71 | }
72 |
73 | func (c *userSvcClient) FndUsers(ctx context.Context, in *FndUsersReq, opts ...grpc.CallOption) (*UsersResp, error) {
74 | out := new(UsersResp)
75 | err := c.cc.Invoke(ctx, "/pb.UserSvc/FndUsers", in, out, opts...)
76 | if err != nil {
77 | return nil, err
78 | }
79 | return out, nil
80 | }
81 |
82 | func (c *userSvcClient) RmvUser(ctx context.Context, in *RmvUserReq, opts ...grpc.CallOption) (*RmvUserResp, error) {
83 | out := new(RmvUserResp)
84 | err := c.cc.Invoke(ctx, "/pb.UserSvc/RmvUser", in, out, opts...)
85 | if err != nil {
86 | return nil, err
87 | }
88 | return out, nil
89 | }
90 |
91 | // UserSvcServer is the server API for UserSvc service.
92 | // All implementations should embed UnimplementedUserSvcServer
93 | // for forward compatibility
94 | type UserSvcServer interface {
95 | AddRole(context.Context, *AddRoleReq) (*RoleResp, error)
96 | FndRoles(context.Context, *FndRolesReq) (*RolesResp, error)
97 | AddUser(context.Context, *AddUserReq) (*UserResp, error)
98 | OvrUser(context.Context, *OvrUserReq) (*UserResp, error)
99 | FndUsers(context.Context, *FndUsersReq) (*UsersResp, error)
100 | RmvUser(context.Context, *RmvUserReq) (*RmvUserResp, error)
101 | }
102 |
103 | // UnimplementedUserSvcServer should be embedded to have forward compatible implementations.
104 | type UnimplementedUserSvcServer struct {
105 | }
106 |
107 | func (UnimplementedUserSvcServer) AddRole(context.Context, *AddRoleReq) (*RoleResp, error) {
108 | return nil, status.Errorf(codes.Unimplemented, "method AddRole not implemented")
109 | }
110 | func (UnimplementedUserSvcServer) FndRoles(context.Context, *FndRolesReq) (*RolesResp, error) {
111 | return nil, status.Errorf(codes.Unimplemented, "method FndRoles not implemented")
112 | }
113 | func (UnimplementedUserSvcServer) AddUser(context.Context, *AddUserReq) (*UserResp, error) {
114 | return nil, status.Errorf(codes.Unimplemented, "method AddUser not implemented")
115 | }
116 | func (UnimplementedUserSvcServer) OvrUser(context.Context, *OvrUserReq) (*UserResp, error) {
117 | return nil, status.Errorf(codes.Unimplemented, "method OvrUser not implemented")
118 | }
119 | func (UnimplementedUserSvcServer) FndUsers(context.Context, *FndUsersReq) (*UsersResp, error) {
120 | return nil, status.Errorf(codes.Unimplemented, "method FndUsers not implemented")
121 | }
122 | func (UnimplementedUserSvcServer) RmvUser(context.Context, *RmvUserReq) (*RmvUserResp, error) {
123 | return nil, status.Errorf(codes.Unimplemented, "method RmvUser not implemented")
124 | }
125 |
126 | // UnsafeUserSvcServer may be embedded to opt out of forward compatibility for this service.
127 | // Use of this interface is not recommended, as added methods to UserSvcServer will
128 | // result in compilation errors.
129 | type UnsafeUserSvcServer interface {
130 | mustEmbedUnimplementedUserSvcServer()
131 | }
132 |
133 | func RegisterUserSvcServer(s grpc.ServiceRegistrar, srv UserSvcServer) {
134 | s.RegisterService(&UserSvc_ServiceDesc, srv)
135 | }
136 |
137 | func _UserSvc_AddRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
138 | in := new(AddRoleReq)
139 | if err := dec(in); err != nil {
140 | return nil, err
141 | }
142 | if interceptor == nil {
143 | return srv.(UserSvcServer).AddRole(ctx, in)
144 | }
145 | info := &grpc.UnaryServerInfo{
146 | Server: srv,
147 | FullMethod: "/pb.UserSvc/AddRole",
148 | }
149 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
150 | return srv.(UserSvcServer).AddRole(ctx, req.(*AddRoleReq))
151 | }
152 | return interceptor(ctx, in, info, handler)
153 | }
154 |
155 | func _UserSvc_FndRoles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
156 | in := new(FndRolesReq)
157 | if err := dec(in); err != nil {
158 | return nil, err
159 | }
160 | if interceptor == nil {
161 | return srv.(UserSvcServer).FndRoles(ctx, in)
162 | }
163 | info := &grpc.UnaryServerInfo{
164 | Server: srv,
165 | FullMethod: "/pb.UserSvc/FndRoles",
166 | }
167 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
168 | return srv.(UserSvcServer).FndRoles(ctx, req.(*FndRolesReq))
169 | }
170 | return interceptor(ctx, in, info, handler)
171 | }
172 |
173 | func _UserSvc_AddUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
174 | in := new(AddUserReq)
175 | if err := dec(in); err != nil {
176 | return nil, err
177 | }
178 | if interceptor == nil {
179 | return srv.(UserSvcServer).AddUser(ctx, in)
180 | }
181 | info := &grpc.UnaryServerInfo{
182 | Server: srv,
183 | FullMethod: "/pb.UserSvc/AddUser",
184 | }
185 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
186 | return srv.(UserSvcServer).AddUser(ctx, req.(*AddUserReq))
187 | }
188 | return interceptor(ctx, in, info, handler)
189 | }
190 |
191 | func _UserSvc_OvrUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
192 | in := new(OvrUserReq)
193 | if err := dec(in); err != nil {
194 | return nil, err
195 | }
196 | if interceptor == nil {
197 | return srv.(UserSvcServer).OvrUser(ctx, in)
198 | }
199 | info := &grpc.UnaryServerInfo{
200 | Server: srv,
201 | FullMethod: "/pb.UserSvc/OvrUser",
202 | }
203 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
204 | return srv.(UserSvcServer).OvrUser(ctx, req.(*OvrUserReq))
205 | }
206 | return interceptor(ctx, in, info, handler)
207 | }
208 |
209 | func _UserSvc_FndUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
210 | in := new(FndUsersReq)
211 | if err := dec(in); err != nil {
212 | return nil, err
213 | }
214 | if interceptor == nil {
215 | return srv.(UserSvcServer).FndUsers(ctx, in)
216 | }
217 | info := &grpc.UnaryServerInfo{
218 | Server: srv,
219 | FullMethod: "/pb.UserSvc/FndUsers",
220 | }
221 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
222 | return srv.(UserSvcServer).FndUsers(ctx, req.(*FndUsersReq))
223 | }
224 | return interceptor(ctx, in, info, handler)
225 | }
226 |
227 | func _UserSvc_RmvUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
228 | in := new(RmvUserReq)
229 | if err := dec(in); err != nil {
230 | return nil, err
231 | }
232 | if interceptor == nil {
233 | return srv.(UserSvcServer).RmvUser(ctx, in)
234 | }
235 | info := &grpc.UnaryServerInfo{
236 | Server: srv,
237 | FullMethod: "/pb.UserSvc/RmvUser",
238 | }
239 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
240 | return srv.(UserSvcServer).RmvUser(ctx, req.(*RmvUserReq))
241 | }
242 | return interceptor(ctx, in, info, handler)
243 | }
244 |
245 | // UserSvc_ServiceDesc is the grpc.ServiceDesc for UserSvc service.
246 | // It's only intended for direct use with grpc.RegisterService,
247 | // and not to be introspected or modified (even as a copy)
248 | var UserSvc_ServiceDesc = grpc.ServiceDesc{
249 | ServiceName: "pb.UserSvc",
250 | HandlerType: (*UserSvcServer)(nil),
251 | Methods: []grpc.MethodDesc{
252 | {
253 | MethodName: "AddRole",
254 | Handler: _UserSvc_AddRole_Handler,
255 | },
256 | {
257 | MethodName: "FndRoles",
258 | Handler: _UserSvc_FndRoles_Handler,
259 | },
260 | {
261 | MethodName: "AddUser",
262 | Handler: _UserSvc_AddUser_Handler,
263 | },
264 | {
265 | MethodName: "OvrUser",
266 | Handler: _UserSvc_OvrUser_Handler,
267 | },
268 | {
269 | MethodName: "FndUsers",
270 | Handler: _UserSvc_FndUsers_Handler,
271 | },
272 | {
273 | MethodName: "RmvUser",
274 | Handler: _UserSvc_RmvUser_Handler,
275 | },
276 | },
277 | Streams: []grpc.StreamDesc{},
278 | Metadata: "user.proto",
279 | }
280 |
--------------------------------------------------------------------------------
/msgs/proto/include/googleapis/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------