diff --git a/.env.development b/.env.development index 9aab430..a34671c 100644 --- a/.env.development +++ b/.env.development @@ -1,4 +1,5 @@ WEB_PORT = 3006 +WEB_URL = http://localhost:3006 GRAPH_PORT = 3009 -GRAPH_URL = http://localhost:3009/query -VITE_GRAPH_URL = http://localhost:3009/query +GRAPH_URL = http://localhost:3009 +VITE_GRAPH_URL = http://localhost:3009 diff --git a/.gitignore b/.gitignore index 0837648..5d95468 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ node_modules +# Jetbrains IDE +.idea + # Output .output .vercel diff --git a/README.md b/README.md index 5c06c35..4ef973e 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,16 @@ ```bash go install github.com/go-task/task/v3/cmd/task@latest ``` + +## Go packages + +- [gqlgen](https://gqlgen.com/) +- [mux](https://gitserver.in/patialtech/mux) + +## Web modules + +- [SvelteKit](https://svelte.dev/docs/kit/introduction) +- [Tailwind](https://tailwindcss.com/docs/installation) +- [daisyui](https://daisyui.com/docs/install/) +- [urql](https://commerce.nearform.com/open-source/urql/docs/basics/svelte/) +- [GraphQL Codegen](https://the-guild.dev/graphql/codegen) diff --git a/codegen.ts b/codegen.ts index 10a9b41..2f1b9c8 100644 --- a/codegen.ts +++ b/codegen.ts @@ -1,10 +1,10 @@ import { CodegenConfig } from '@graphql-codegen/cli'; const config: CodegenConfig = { - schema: process.env.GRAPH_URL, - documents: ['./graph/*.ts'], + schema: `${process.env.GRAPH_URL}/query`, + documents: ['./web/lib/gql/*.ts'], generates: { - './graph/d.ts': { + './web/lib/gql/graph.d.ts': { plugins: ['typescript', 'typescript-operations'] } } diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..28bc5a6 --- /dev/null +++ b/config/config.go @@ -0,0 +1,78 @@ +package config + +import ( + "fmt" + "reflect" + "strconv" + + "gitserver.in/patialtech/rano/config/dotenv" + "gitserver.in/patialtech/rano/pkg/logger" +) + +const Env = "development" + +var conf *Config + +func init() { + envVar, err := dotenv.Read(fmt.Sprintf(".env.%s", Env)) + if err != nil { + panic(err) + } + + conf = &Config{} + conf.loadEnv(envVar) +} + +// Read config for Env +func Read() *Config { + if conf == nil { + panic("config not initialized") + } + + return conf +} + +type Config struct { + WebPort int `env:"WEB_PORT"` + WebURL string `env:"WEB_URL"` + GraphPort int `env:"GRAPH_PORT"` + GrapURL string `env:"GRAPH_URL"` +} + +func (c *Config) loadEnv(vars map[string]string) { + if c == nil { + return + } + + val := reflect.Indirect(reflect.ValueOf(c)) + for i := 0; i < val.NumField(); i++ { + f := val.Type().Field(i) + tag := f.Tag.Get("env") + if tag == "" { + continue + } + + v, found := vars[tag] + if !found { + logger.Warn("var %q not found in file .env.%s", tag, Env) + continue + } + + field := val.FieldByName(f.Name) + if !field.IsValid() { + continue + } + + switch f.Type.Kind() { + case reflect.Int: + if intV, err := strconv.ParseInt(v, 10, 64); err != nil { + panic(err) + } else { + field.SetInt(intV) + } + default: + field.SetString(v) + } + } + +} diff --git a/config/dotenv/parser.go b/config/dotenv/parser.go new file mode 100644 index 0000000..5b3651a --- /dev/null +++ b/config/dotenv/parser.go @@ -0,0 +1,282 @@ +package dotenv + +import ( + "bytes" + "errors" + "fmt" + "os" + "regexp" + "strings" + "unicode" +) + +// +// copied from https://github.com/joho/godotenv/blob/main/parser.go +// + +const ( + charComment = '#' + prefixSingleQuote = '\'' + prefixDoubleQuote = '"' + + exportPrefix = "export" +) + +func parseBytes(src []byte, out map[string]string) error { + src = bytes.Replace(src, []byte("\r\n"), []byte("\n"), -1) + cutset := src + for { + cutset = getStatementStart(cutset) + if cutset == nil { + // reached end of file + break + } + + key, left, err := locateKeyName(cutset) + if err != nil { + return err + } + + value, left, err := extractVarValue(left, out) + if err != nil { + return err + } + + out[key] = value + cutset = left + } + + return nil +} + +// getStatementPosition returns position of statement begin. +// +// It skips any comment line or non-whitespace character. +func getStatementStart(src []byte) []byte { + pos := indexOfNonSpaceChar(src) + if pos == -1 { + return nil + } + + src = src[pos:] + if src[0] != charComment { + return src + } + + // skip comment section + pos = bytes.IndexFunc(src, isCharFunc('\n')) + if pos == -1 { + return nil + } + + return getStatementStart(src[pos:]) +} + +// locateKeyName locates and parses key name and returns rest of slice +func locateKeyName(src []byte) (key string, cutset []byte, err error) { + // trim "export" and space at beginning + src = bytes.TrimLeftFunc(src, isSpace) + if bytes.HasPrefix(src, []byte(exportPrefix)) { + trimmed := bytes.TrimPrefix(src, []byte(exportPrefix)) + if bytes.IndexFunc(trimmed, isSpace) == 0 { + src = bytes.TrimLeftFunc(trimmed, isSpace) + } + } + + // locate key name end and validate it in single loop + offset := 0 +loop: + for i, char := range src { + rchar := rune(char) + if isSpace(rchar) { + continue + } + + switch char { + case '=', ':': + // library also supports yaml-style value declaration + key = string(src[0:i]) + offset = i + 1 + break loop + case '_': + default: + // variable name should match [A-Za-z0-9_.] + if unicode.IsLetter(rchar) || unicode.IsNumber(rchar) || rchar == '.' { + continue + } + + return "", nil, fmt.Errorf( + `unexpected character %q in variable name near %q`, + string(char), string(src)) + } + } + + if len(src) == 0 { + return "", nil, errors.New("zero length string") + } + + // trim whitespace + key = strings.TrimRightFunc(key, unicode.IsSpace) + cutset = bytes.TrimLeftFunc(src[offset:], isSpace) + return key, cutset, nil +} + +// extractVarValue extracts variable value and returns rest of slice +func extractVarValue(src []byte, vars map[string]string) (value string, rest []byte, err error) { + quote, hasPrefix := hasQuotePrefix(src) + if !hasPrefix { + // unquoted value - read until end of line + endOfLine := bytes.IndexFunc(src, isLineEnd) + + // Hit EOF without a trailing newline + if endOfLine == -1 { + endOfLine = len(src) + + if endOfLine == 0 { + return "", nil, nil + } + } + + // Convert line to rune away to do accurate countback of runes + line := []rune(string(src[0:endOfLine])) + + // Assume end of line is end of var + endOfVar := len(line) + if endOfVar == 0 { + return "", src[endOfLine:], nil + } + + // Work backwards to check if the line ends in whitespace then + // a comment (ie asdasd # some comment) + for i := endOfVar - 1; i >= 0; i-- { + if line[i] == charComment && i > 0 { + if isSpace(line[i-1]) { + endOfVar = i + break + } + } + } + + trimmed := strings.TrimFunc(string(line[0:endOfVar]), isSpace) + + return expandVariables(trimmed, vars), src[endOfLine:], nil + } + + // lookup quoted string terminator + for i := 1; i < len(src); i++ { + if char := src[i]; char != quote { + continue + } + + // skip escaped quote symbol (\" or \', depends on quote) + if prevChar := src[i-1]; prevChar == '\\' { + continue + } + + // trim quotes + trimFunc := isCharFunc(rune(quote)) + value = string(bytes.TrimLeftFunc(bytes.TrimRightFunc(src[0:i], trimFunc), trimFunc)) + if quote == prefixDoubleQuote { + // unescape newlines for double quote (this is compat feature) + // and expand environment variables + value = expandVariables(expandEscapes(value), vars) + } + + return value, src[i+1:], nil + } + + // return formatted error if quoted string is not terminated + valEndIndex := bytes.IndexFunc(src, isCharFunc('\n')) + if valEndIndex == -1 { + valEndIndex = len(src) + } + + return "", nil, fmt.Errorf("unterminated quoted value %s", src[:valEndIndex]) +} + +func expandEscapes(str string) string { + out := escapeRegex.ReplaceAllStringFunc(str, func(match string) string { + c := strings.TrimPrefix(match, `\`) + switch c { + case "n": + return "\n" + case "r": + return "\r" + default: + return match + } + }) + return unescapeCharsRegex.ReplaceAllString(out, "$1") +} + +func indexOfNonSpaceChar(src []byte) int { + return bytes.IndexFunc(src, func(r rune) bool { + return !unicode.IsSpace(r) + }) +} + +// hasQuotePrefix reports whether charset starts with single or double quote and returns quote character +func hasQuotePrefix(src []byte) (prefix byte, isQuored bool) { + if len(src) == 0 { + return 0, false + } + + switch prefix := src[0]; prefix { + case prefixDoubleQuote, prefixSingleQuote: + return prefix, true + default: + return 0, false + } +} + +func isCharFunc(char rune) func(rune) bool { + return func(v rune) bool { + return v == char + } +} + +// isSpace reports whether the rune is a space character but not line break character +// +// this differs from unicode.IsSpace, which also applies line break as space +func isSpace(r rune) bool { + switch r { + case '\t', '\v', '\f', '\r', ' ', 0x85, 0xA0: + return true + } + return false +} + +func isLineEnd(r rune) bool { + if r == '\n' || r == '\r' { + return true + } + return false +} + +var ( + escapeRegex = regexp.MustCompile(`\\.`) + expandVarRegex = regexp.MustCompile(`(\\)?(\$)(\()?\{?([A-Z0-9_]+)?\}?`) + unescapeCharsRegex = regexp.MustCompile(`\\([^$])`) +) + +func expandVariables(v string, m map[string]string) string { + return expandVarRegex.ReplaceAllStringFunc(v, func(s string) string { + submatch := expandVarRegex.FindStringSubmatch(s) + + if submatch == nil { + return s + } + if submatch[1] == "\\" || submatch[2] == "(" { + return submatch[0][1:] + } else if submatch[4] != "" { + if val, ok := m[submatch[4]]; ok { + return val + } + if val, ok := os.LookupEnv(submatch[4]); ok { + return val + } + return m[submatch[4]] + } + return s + }) +} diff --git a/config/dotenv/read.go b/config/dotenv/read.go new file mode 100644 index 0000000..20fc10b --- /dev/null +++ b/config/dotenv/read.go @@ -0,0 +1,69 @@ +package dotenv + +import ( + "bytes" + "io" + "os" +) + +// +// copied from https://github.com/joho/godotenv/blob/main/godotenv.go +// + +// Read all env (with same file loading semantics as Load) but return values as +// a map rather than automatically writing values into env +func Read(filenames ...string) (envMap map[string]string, err error) { + filenames = filenamesOrDefault(filenames) + envMap = make(map[string]string) + + for _, filename := range filenames { + individualEnvMap, individualErr := readFile(filename) + + if individualErr != nil { + err = individualErr + return // return early on a spazout + } + + for key, value := range individualEnvMap { + envMap[key] = value + } + } + + return +} + +func filenamesOrDefault(filenames []string) []string { + if len(filenames) == 0 { + return []string{".env"} + } + return filenames +} + +func readFile(filename string) (envMap map[string]string, err error) { + file, err := os.Open(filename) + if err != nil { + return + } + defer file.Close() + + return parse(file) +} + +// parse reads an env file from io.Reader, returning a map of keys and values. +func parse(r io.Reader) (map[string]string, error) { + var buf bytes.Buffer + _, err := io.Copy(&buf, r) + if err != nil { + return nil, err + } + + return unmarshal(buf.Bytes()) +} + +// unmarshal parses env file from byte slice of chars, returning a map of keys and values. +func unmarshal(src []byte) (map[string]string, error) { + out := make(map[string]string) + err := parseBytes(src, out) + + return out, err +} diff --git a/config/dotenv/write.go b/config/dotenv/write.go new file mode 100644 index 0000000..02e4928 --- /dev/null +++ b/config/dotenv/write.go @@ -0,0 +1,62 @@ +package dotenv + +import ( + "fmt" + "os" + "sort" + "strconv" + "strings" +) + +// +// copied from https://github.com/joho/godotenv/blob/main/godotenv.go +// + +const doubleQuoteSpecialChars = "\\\n\r\"!$`" + +// Write serializes the given environment and writes it to a file. +func Write(envMap map[string]string, filename string) error { + content, err := marshal(envMap) + if err != nil { + return err + } + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + _, err = file.WriteString(content + "\n") + if err != nil { + return err + } + return file.Sync() +} + +// marshal outputs the given environment as a dotenv-formatted environment file. +// Each line is in the format: KEY="VALUE" where VALUE is backslash-escaped. +func marshal(envMap map[string]string) (string, error) { + lines := make([]string, 0, len(envMap)) + for k, v := range envMap { + if d, err := strconv.Atoi(v); err == nil { + lines = append(lines, fmt.Sprintf(`%s=%d`, k, d)) + } else { + lines = append(lines, fmt.Sprintf(`%s="%s"`, k, doubleQuoteEscape(v))) + } + } + sort.Strings(lines) + return strings.Join(lines, "\n"), nil +} + +func doubleQuoteEscape(line string) string { + for _, c := range doubleQuoteSpecialChars { + toReplace := "\\" + string(c) + if c == '\n' { + toReplace = `\n` + } + if c == '\r' { + toReplace = `\r` + } + line = strings.Replace(line, string(c), toReplace, -1) + } + return line +} diff --git a/go.mod b/go.mod index d130a48..003413f 100644 --- a/go.mod +++ b/go.mod @@ -4,25 +4,24 @@ go 1.23.2 require ( github.com/99designs/gqlgen v0.17.55 - github.com/vektah/gqlparser v1.3.1 github.com/vektah/gqlparser/v2 v2.5.18 - gitserver.in/patialtech/mux v0.0.3 + gitserver.in/patialtech/mux v0.3.1 ) require ( - github.com/agnivade/levenshtein v1.1.1 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/agnivade/levenshtein v1.2.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sosodev/duration v1.3.1 // indirect - github.com/urfave/cli/v2 v2.27.4 // indirect + github.com/urfave/cli/v2 v2.27.5 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - golang.org/x/mod v0.20.0 // indirect + golang.org/x/mod v0.21.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/text v0.18.0 // indirect - golang.org/x/tools v0.24.0 // indirect + golang.org/x/text v0.19.0 // indirect + golang.org/x/tools v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1cbb888..8b7ca86 100644 --- a/go.sum +++ b/go.sum @@ -2,26 +2,24 @@ github.com/99designs/gqlgen v0.17.55 h1:3vzrNWYyzSZjGDFo68e5j9sSauLxfKvLp+6ioRok github.com/99designs/gqlgen v0.17.55/go.mod h1:3Bq768f8hgVPGZxL8aY9MaYmbxa6llPM/qu1IGH1EJo= github.com/PuerkitoBio/goquery v1.9.3 h1:mpJr/ikUA9/GNJB/DBZcGeFDXUtosHRyRrwh7KGdTG0= github.com/PuerkitoBio/goquery v1.9.3/go.mod h1:1ndLHPdTz+DyQPICCWYlYQMPl0oXZj0G6D4LCYA6u4U= -github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= -github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= -github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/agnivade/levenshtein v1.2.0 h1:U9L4IOT0Y3i0TIlUIDJ7rVUziKi/zPbrJGaFrtYH3SY= +github.com/agnivade/levenshtein v1.2.0/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= -github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= -github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= +github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -30,39 +28,32 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8= -github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ= -github.com/vektah/gqlparser v1.3.1 h1:8b0IcD3qZKWJQHSzynbDlrtP3IxVydZ2DZepCGofqfU= -github.com/vektah/gqlparser v1.3.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/vektah/gqlparser/v2 v2.5.18 h1:zSND3GtutylAQ1JpWnTHcqtaRZjl+y3NROeW8vuNo6Y= github.com/vektah/gqlparser/v2 v2.5.18/go.mod h1:6HLzf7JKv9Fi3APymudztFQNmLXR5qJeEo6BOFcXVfc= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -gitserver.in/patialtech/mux v0.0.3 h1:Td0nAqArLGjB2pAVatEX2/pKaNqHvj4jqLD+PJHobxc= -gitserver.in/patialtech/mux v0.0.3/go.mod h1:/pYaLBNkRiMuxMKn9e2X0BIWt1bvHM19yQE/cJsm0q0= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +gitserver.in/patialtech/mux v0.3.1 h1:lbhQVr2vBvTcUp64Qjd2+4/s2lQXiDtsl8c+PpZvnDE= +gitserver.in/patialtech/mux v0.3.1/go.mod h1:/pYaLBNkRiMuxMKn9e2X0BIWt1bvHM19yQE/cJsm0q0= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/graph/resolver.go b/graph/resolver.go index af083b4..0ed1972 100644 --- a/graph/resolver.go +++ b/graph/resolver.go @@ -10,7 +10,7 @@ import ( "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/handler/extension" "github.com/99designs/gqlgen/graphql/playground" - "github.com/vektah/gqlparser/gqlerror" + "github.com/vektah/gqlparser/v2/gqlerror" "gitserver.in/patialtech/rano/graph/generated" "gitserver.in/patialtech/rano/pkg/logger" ) @@ -149,4 +149,5 @@ func GraphiQL(queryEndpoint string) func(w http.ResponseWriter, r *http.Request) func Query(w http.ResponseWriter, r *http.Request) { // srv.ServeHTTP(w, r.WithContext(userCtx(r))) srv.ServeHTTP(w, r) + } diff --git a/graph/server/main.go b/graph/server/main.go index 92d0c53..349f9a0 100644 --- a/graph/server/main.go +++ b/graph/server/main.go @@ -1,33 +1,42 @@ package main import ( + "fmt" "net/http" - "os" "gitserver.in/patialtech/mux" + "gitserver.in/patialtech/mux/middleware" + "gitserver.in/patialtech/rano/config" "gitserver.in/patialtech/rano/graph" "gitserver.in/patialtech/rano/pkg/logger" ) func main() { r := mux.NewRouter() + // CORS + r.Use(middleware.CORS(middleware.CORSOption{ + AllowedHeaders: []string{"Content-Type"}, + MaxAge: 60, + })) + // Secure Headers + r.Use(middleware.Helmet(middleware.HelmetOption{ + ContentSecurityPolicy: middleware.CSP{ + ScriptSrc: []string{"self", "https://cdn.jsdelivr.net", "unsafe-inline"}, + }, + })) + // graphiql - r.Get("/graphiql", graph.GraphiQL("/query")) + r.GET("/graphiql", graph.GraphiQL("/query")) // graph query - r.Post("/query", graph.Query) + r.POST("/query", graph.Query) // catch all - r.Get("/", func(w http.ResponseWriter, r *http.Request) { + r.GET("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("hello there")) }) - port := os.Getenv("GRAPH_PORT") - if port == "" { - port = "4001" - } - r.Serve(func(srv *http.Server) error { - srv.Addr = ":" + port + srv.Addr = fmt.Sprintf(":%d", config.Read().GraphPort) logger.Info("graph server listening on %s", srv.Addr) return srv.ListenAndServe() }) diff --git a/graphql.config.yml b/graphql.config.yml index 8f4beb3..249937e 100644 --- a/graphql.config.yml +++ b/graphql.config.yml @@ -1,2 +1,2 @@ -schema: 'graph/*.graphql' # or 'http://localhost:9876/graphql' -documents: 'graph/**/*' +schema: 'graph/*.graphql' +documents: 'web/lib/gql/*' diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index cccbae2..a8b4579 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -10,6 +10,11 @@ func Info(msg string, args ...any) { slog.Info(fmt.Sprintf(msg, a...), b...) } +func Warn(msg string, args ...any) { + a, b := getArgs(args) + slog.Warn(fmt.Sprintf(msg, a...), b...) +} + func Error(err error, args ...any) { a, b := getArgs(args) slog.Error(fmt.Sprintf(err.Error(), a...), b...) diff --git a/svelte.config.js b/svelte.config.js index fcbca6c..80b60b3 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -17,7 +17,6 @@ const config = { assets: path.resolve(webDir, 'public') }, alias: { - $graph: path.resolve(webDir, 'graph'), $image: path.resolve(webDir, 'assets', 'image'), $svg: path.resolve(webDir, 'assets', 'svg') }, diff --git a/taskfile.yml b/taskfile.yml index e1ed1d0..a54cbea 100644 --- a/taskfile.yml +++ b/taskfile.yml @@ -3,8 +3,6 @@ version: '3' env: ENV: development -dotenv: ['.env.{{.ENV}}'] - tasks: gen: desc: use go generate, for graph files @@ -30,10 +28,12 @@ tasks: codegen: desc: generate graph types + dotenv: ['.env.{{.ENV}}'] cmds: - cmd: deno task codegen web: desc: run web in dev mode + dotenv: ['.env.{{.ENV}}'] cmds: - cmd: deno task dev diff --git a/vite.config.js b/vite.config.js index feeb067..5a5e9bf 100644 --- a/vite.config.js +++ b/vite.config.js @@ -6,7 +6,7 @@ export default defineConfig({ server: { port: parseInt(process.env.WEB_PORT ?? '3001'), fs: { - allow: ['./web/app.css', './web/assets'] + allow: ['./web/assets', './web/app.css', './graph'] } } }); diff --git a/web/lib/.keep b/web/lib/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/graph/auth.gql.ts b/web/lib/gql/auth.gql.ts similarity index 76% rename from graph/auth.gql.ts rename to web/lib/gql/auth.gql.ts index a562d65..eeed9e0 100644 --- a/graph/auth.gql.ts +++ b/web/lib/gql/auth.gql.ts @@ -1,5 +1,5 @@ import { gql } from '@urql/svelte'; -export const QryMe = gql` +export const Me = gql` query Me { me { id diff --git a/web/lib/client.ts b/web/lib/gql/client.ts similarity index 76% rename from web/lib/client.ts rename to web/lib/gql/client.ts index 7c24a10..0fe1191 100644 --- a/web/lib/client.ts +++ b/web/lib/gql/client.ts @@ -3,6 +3,6 @@ import { Client, cacheExchange, fetchExchange } from '@urql/svelte'; export function newClient(url: string) { return new Client({ url, - exchanges: [cacheExchange, fetchExchange] + exchanges: [ fetchExchange] }); } diff --git a/graph/d.ts b/web/lib/gql/graph.d.ts similarity index 100% rename from graph/d.ts rename to web/lib/gql/graph.d.ts diff --git a/web/routes/+layout.svelte b/web/routes/+layout.svelte index 0a3a239..6329cb5 100644 --- a/web/routes/+layout.svelte +++ b/web/routes/+layout.svelte @@ -1,10 +1,24 @@ {@render children()}