pull/52/head
sunface 4 years ago
parent 0d8816f305
commit 91769f4c9f

@ -5,6 +5,7 @@ go 1.14
require (
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
github.com/gin-gonic/gin v1.6.3
github.com/go-redis/redis/v8 v8.8.0
github.com/go-stack/stack v1.8.0
github.com/golang/snappy v0.0.2
github.com/gosimple/slug v1.9.0
@ -15,5 +16,7 @@ require (
github.com/spf13/cobra v1.1.1
github.com/spf13/pflag v1.0.5
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
gopkg.in/yaml.v2 v2.2.8
gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e // indirect
gopkg.in/redis.v2 v2.3.2
gopkg.in/yaml.v2 v2.3.0
)

@ -25,7 +25,10 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@ -37,9 +40,12 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
@ -57,6 +63,9 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis/v8 v8.8.0 h1:fDZP58UN/1RD3DjtTXP/fFZ04TFohSYhjZDkcDe2dnw=
github.com/go-redis/redis/v8 v8.8.0/go.mod h1:F7resOH5Kdug49Otu24RjHWwgK7u9AmtqWMnCV1iP5Y=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -71,12 +80,22 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@ -116,6 +135,7 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac h1:n1DqxAo4oWPMvH1+v+DLYlMCecgumhhgnxAPdqDIFHI=
github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
@ -166,7 +186,14 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -212,6 +239,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
@ -219,9 +247,17 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opentelemetry.io/otel v0.19.0 h1:Lenfy7QHRXPZVsw/12CWpxX6d/JkrX8wrx2vO8G80Ng=
go.opentelemetry.io/otel v0.19.0/go.mod h1:j9bF567N9EfomkSidSfmMwIwIBuP37AMAIzVW85OxSg=
go.opentelemetry.io/otel/metric v0.19.0 h1:dtZ1Ju44gkJkYvo+3qGqVXmf88tc+a42edOywypengg=
go.opentelemetry.io/otel/metric v0.19.0/go.mod h1:8f9fglJPRnXuskQmKpnad31lcLJ2VmNNqIsx/uIwBSc=
go.opentelemetry.io/otel/oteltest v0.19.0/go.mod h1:tI4yxwh8U21v7JD6R3BcA/2+RBoTKFexE/PJ/nSO7IA=
go.opentelemetry.io/otel/trace v0.19.0 h1:1ucYlenXIDA1OlHVLDZKX0ObXV5RLaq06DtUKz5e5zc=
go.opentelemetry.io/otel/trace v0.19.0/go.mod h1:4IXiNextNOpPnRlI4ryK69mn5iC84bjBWZQA5DXz/qg=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
@ -230,6 +266,8 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -248,8 +286,10 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -262,6 +302,9 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -270,9 +313,11 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -283,13 +328,20 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -310,7 +362,12 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@ -332,19 +389,35 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e h1:wGA78yza6bu/mWcc4QfBuIEHEtc06xdiU0X8sY36yUU=
gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e/go.mod h1:xsQCaysVCudhrYTfzYWe577fCe7Ceci+6qjO2Rdc0Z4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/redis.v2 v2.3.2 h1:GPVIIB/JnL1wvfULefy3qXmPu1nfNu2d0yA09FHgwfs=
gopkg.in/redis.v2 v2.3.2/go.mod h1:4wl9PJ/CqzeHk3LVq1hNLHH8krm3+AXEgut4jVc++LU=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

@ -23,6 +23,7 @@ import UserCard from "components/users/user-card"
import userCustomTheme from "theme/user-custom"
import SearchFilters from "components/search-filters"
import Follow from "components/interaction/follow"
import { SearchFilter } from "src/types/search"
const UserPage = () => {
const { isOpen, onOpen, onClose } = useDisclosure()
@ -31,10 +32,8 @@ const UserPage = () => {
const nav = router.query.nav
const session = useSession()
const [user, setUser]: [User, any] = useState(null)
const [rawPosts, setRawPosts]: [Story[], any] = useState([])
const [posts, setPosts]: [Story[], any] = useState([])
const [tags, setTags]: [Tag[], any] = useState([])
const [tagFilter, setTagFilter]: [Tag, any] = useState(null)
const [tagFilter, setTagFilter]: [string, any] = useState("")
const [followers, setFollowers]: [User[], any] = useState([])
const [navbars,setNavbars]:[Navbar[],any] = useState([])
const borderColor = useColorModeValue('white', 'transparent')
@ -45,15 +44,17 @@ const UserPage = () => {
}
}, [username])
const loadStories = (p) => {
return user.type === IDType.User ? requestApi.get(`/user/posts/${user.id}?filter=${tagFilter}&page=${p}&per_page=5`) : requestApi.get(`/story/posts/org/${user.id}?type=0&filter=${tagFilter}&page=${p}&per_page=5`)
}
const initData = async (username) => {
const res = await requestApi.get(`/user/info/${username}`)
setUser(res.data)
getTags(res.data.id)
getNavbars(res.data.id)
const res1 = res.data.type === IDType.User ? await requestApi.get(`/user/posts/${res.data.id}`) : await requestApi.get(`/story/posts/org/${res.data.id}?type=0`)
setPosts(res1.data)
setRawPosts(res1.data)
}
const getNavbars = async userID => {
@ -66,26 +67,7 @@ const UserPage = () => {
setTags(res.data)
}
const filterPostsByTag = tag => {
if (tag.id === tagFilter?.id) {
setTagFilter(null)
setPosts(rawPosts)
return
}
setTagFilter(tag)
const p = []
rawPosts.forEach(post => {
for (let i = 0; i < post.rawTags.length; i++) {
if (post.rawTags[i].id === tag.id) {
p.push(post)
break
}
}
})
setPosts(p)
}
const viewFollowers = async tp => {
let res
@ -115,6 +97,15 @@ const UserPage = () => {
const isSubNavActive = (id) => {
return id === nav
}
const onChangeTagFilter = id => {
if (id === tagFilter) {
setTagFilter("")
} else {
setTagFilter(id)
}
}
return (
<>
<SEO
@ -233,8 +224,8 @@ const UserPage = () => {
<Wrap mt="4" p="1">
{
tags.map(tag =>
<Button key={tag.id} size="sm" variant="text" p="0" onClick={() => filterPostsByTag(tag)} _focus={null}>
<Box className={tagFilter?.id === tag.id ? "tag-bg" : null} py="2" px="1">{tag.name} &nbsp; {tag.posts}</Box>
<Button key={tag.id} size="sm" variant="text" p="0" onClick={() => onChangeTagFilter(tag.id)} _focus={null}>
<Box className={tagFilter === tag.id ? "tag-bg" : null} py="2" px="1">{tag.name} &nbsp; {tag.posts}</Box>
</Button>
)
}
@ -245,15 +236,9 @@ const UserPage = () => {
<Box width={["100%", "100%", "70%", "70%"]}>
{posts.length === 0 ?
<Card width="100%" height="fit-content">
<Empty />
</Card>
:
<Card width="100%" height="fit-content" p="0">
<Stories stories={posts} showFooter={tagFilter === null} showPinned={true} showOrg={user.type === IDType.User}/>
{user.id && <Stories onLoad={p => loadStories(p)} showPinned={true} showOrg={user.type === IDType.User} filter={tagFilter}/>}
</Card>
}
</Box>

@ -1,4 +1,4 @@
import { Box, Button, Text, useToast } from '@chakra-ui/react';
import { Box, useToast } from '@chakra-ui/react';
import React, { useEffect, useState } from 'react';
import { MarkdownEditor } from 'components/markdown-editor/editor';
import PageContainer from 'layouts/page-container';
@ -11,7 +11,6 @@ import { useRouter } from 'next/router';
import { config } from 'configs/config';
import { cloneDeep } from 'lodash';
import Card from 'components/card';
import { updateUrl } from 'utils/url';
import { IDType } from 'src/types/id';
@ -35,6 +34,10 @@ function PostEditPage() {
if (id && id !== 'new') {
requestApi.get(`/story/post/${id}`).then(res => setAr(res.data))
}
if (id === 'new') {
}
}, [id])
const onMdChange = newMd => {
@ -72,6 +75,7 @@ function PostEditPage() {
ar.id = res.data.id
let url = window.location.origin + `/editor/post/${ar.id}`
window.history.pushState({},null,url);
setAr(cloneDeep(ar))
}
}
@ -84,7 +88,7 @@ function PostEditPage() {
setAr(newAr)
}
const onChangeTitle = title => {
const onChangeTitle = async title => {
if (title.length > config.posts.titleMaxLen) {
toast({
description: `Title长度不能超过${config.posts.titleMaxLen}`,

@ -109,7 +109,7 @@ export const SeriesEditor = ({orgID=""}) => {
const submitSeries = async (values, _) => {
// 这里必须按照顺序同步提交
if (orgID) {
await requestApi.post(`/story`, {...values,ownerID: orgID})
await requestApi.post(`/story`, {...values,ownerId: orgID})
} else {
await requestApi.post(`/story`, values)
}

@ -16,24 +16,17 @@ import siteConfig from "configs/site-config"
import PageContainer1 from "layouts/page-container1"
import React, { useEffect, useState } from "react"
import { requestApi } from "utils/axios/request"
import { SearchFilter } from "src/types/search"
import SearchFilters from "components/search-filters"
import StoryFilters from "components/story/story-filter"
import { concat } from "lodash"
import useInfiniteScroll from 'src/hooks/use-infinite-scroll'
const HomePage = () => {
let filter:string
const [posts, setPosts] = useState([])
const initData = async () => {
const res = await requestApi.get(`/story/posts/home/${filter}`)
setPosts(res.data)
const [filter,setFilter] = useState('Best')
const initData = (p) => {
return requestApi.get(`/story/posts/home?filter=${filter}&page=${p}&per_page=5`)
}
useEffect(() => {
initData()
}, [])
const onFilterChange = filter => {
const onFilterChange = f => {
setFilter(f)
}
return (
@ -47,7 +40,7 @@ const HomePage = () => {
<VStack alignItems="left" width={["100%", "100%", "70%", "70%"]} spacing="3">
<Card p="2">
<Flex justifyContent="space-between" alignItems="center">
<SearchFilters onChange={onFilterChange}/>
<StoryFilters onChange={onFilterChange}/>
<Menu>
<MenuButton
as={IconButton}
@ -69,7 +62,7 @@ const HomePage = () => {
</Flex>
</Card>
<Card width="100%" height="fit-content" p="0">
<Stories stories={posts} />
<Stories onLoad={initData} filter={filter}/>
</Card>
</VStack>
<HomeSidebar />
@ -84,14 +77,14 @@ export default HomePage
export const HomeSidebar = () => {
const [posts, setPosts] = useState([])
const initData = async () => {
const res = await requestApi.get(`/story/posts/home/aa`)
setPosts(res.data)
}
// const initData = async () => {
// const res = await requestApi.get(`/story/posts/home/aa`)
// setPosts(res.data)
// }
useEffect(() => {
initData()
}, [])
// useEffect(() => {
// initData()
// }, [])
return (
<VStack alignItems="left" width="30%" display={{ base: "none", md: "flex" }}>
@ -106,9 +99,9 @@ export const HomeSidebar = () => {
</CardHeader>
<Divider />
<CardBody>
<VStack alignItems="left">
{/* <VStack alignItems="left">
<Stories stories={posts} card={SimplePostCard} size="sm" showFooter={false}/>
</VStack>
</VStack> */}
</CardBody>
</Card>
</VStack>

@ -1,9 +1,8 @@
import { Box, Divider, Flex, HStack, Input } from "@chakra-ui/react"
import { Box, Divider, Flex, Input } from "@chakra-ui/react"
import Card from "components/card"
import Empty from "components/empty"
import SEO from "components/seo"
import Stories from "components/story/stories"
import SearchFilters from "components/search-filters"
import SearchFilters from "components/search-filters"
import siteConfig from "configs/site-config"
import PageContainer1 from "layouts/page-container1"
import Sidebar from "layouts/sidebar/sidebar"
@ -16,51 +15,41 @@ import { requestApi } from "utils/axios/request"
import { addParamToUrl, removeParamFromUrl } from "utils/url"
const PostsSearchPage = () => {
let filter = SearchFilter.Favorites
const router = useRouter()
const q = router.query.q
const [results,setResults] = useState([])
const [query,setQuery] = useState("")
const [tempQuery,setTempQuery] = useState("")
const [query, setQuery] = useState("")
const [tempQuery, setTempQuery] = useState("")
useEffect(() => {
if (q) {
setQuery(q as string)
setTempQuery(q as string)
initData()
}
},[q])
}, [q])
useEffect(() => {
initData()
},[query])
const initData = async () => {
if (query) {
const res = await requestApi.get(`/search/posts/${filter}?query=${query}`)
setResults(res.data)
}
const [filter, setFilter] = useState(SearchFilter.Favorites)
const initPosts = (p) => {
return requestApi.get(`/search/posts?query=${query}&filter=${filter}&page=${p}&per_page=5`)
}
const onFilterChange = f => {
filter = f
initData()
setFilter(f)
}
const startSearch = e => {
if (e.keyCode == 13) {
if (tempQuery === '') {
removeParamFromUrl(["q"])
setResults([])
} else {
addParamToUrl({q: tempQuery})
addParamToUrl({ q: tempQuery })
}
setQuery(tempQuery)
setQuery("")
setTimeout(() => setQuery(tempQuery), 100)
}
}
function getFilters():[] {
function getFilters(): [] {
for (const link of searchLinks) {
if (link.path.indexOf("posts") > -1) {
return link.filters
@ -78,17 +67,15 @@ const PostsSearchPage = () => {
/>
<PageContainer1>
<Flex width="100%">
<Sidebar query={query ?{q:query} : null} routes={searchLinks} title="全站搜索" />
<Sidebar query={query ? { q: query } : null} routes={searchLinks} title="全站搜索" />
<Box ml="3" width={['100%', '100%', '100%', '70%']}>
<Card p="5">
<Input value={tempQuery} onChange={(e) => setTempQuery(e.currentTarget.value)} onKeyUp={(e) => startSearch(e)} size="lg" placeholder="type and enter to search..." variant="unstyled" />
</Card>
<Card mt="2" p="0" pt="4" px="4">
<SearchFilters filters={getFilters()} onChange={onFilterChange} initFilter={filter}/>
<Divider mt="3"/>
{results.length === 0 && <Empty /> }
{results.length > 0 &&
<Stories stories={results} showFooter={false} type="compact" highlight={query}/>}
<SearchFilters filters={getFilters()} onChange={onFilterChange} initFilter={filter} />
<Divider mt="3" />
{query && <Stories onLoad={initPosts} filter={filter} />}
</Card>
</Box>
</Flex>

@ -41,7 +41,7 @@ const PostsSearchPage = () => {
const initData = async () => {
if (query) {
const res = await requestApi.get(`/search/users/${filter}?query=${query}`)
const res = await requestApi.get(`/search/users?query=${query}&filter=${filter}`)
setResults(res.data)
}
}

@ -14,6 +14,7 @@ import { requestApi } from "utils/axios/request"
import StorySidebar from "components/story/story-sidebar"
import Stroies from "components/story/stories"
import Card from "components/card"
import StoryCard from "components/story/story-card"
const PostPage = () => {
const router = useRouter()
@ -73,7 +74,13 @@ const PostPage = () => {
<Text position="relative" top="-41px" layerStyle="textSecondary">Articles in this series</Text>
</VStack>
<Card><Stroies stories={posts} showFooter={false} /></Card>
{posts.length > 0 && <Card>
<VStack alignItems="left">
{
posts.map(p => <StoryCard story={p}/>)
}
</VStack>
</Card>}
<Box mt="6"><Comments storyID={series.id} /></Box>
</Box>

@ -1,6 +1,4 @@
import { Text, Box, VStack, Divider, useToast, Heading, Alert, Tag, Button, HStack, Modal, ModalOverlay, ModalContent, ModalBody, Select, useDisclosure, Flex } from "@chakra-ui/react"
import Card from "components/card"
import Nav from "layouts/nav/nav"
import { Box} from "@chakra-ui/react"
import PageContainer from "layouts/page-container"
import Sidebar from "layouts/sidebar/sidebar"
import React, { useEffect, useState } from "react"
@ -8,15 +6,6 @@ import { orgSettingLinks } from "src/data/links"
import { requestApi } from "utils/axios/request"
import { useRouter } from "next/router"
import { User } from "src/types/user"
import UserCard from "components/users/user-card"
import { config } from "configs/config"
import OrgMember from "components/users/org-member"
import { Role } from "src/types/role"
import { cloneDeep } from "lodash"
import { Story } from "src/types/story"
import Empty from "components/empty"
import ManageStories from "components/story/manage-stories"
import { IDType } from "src/types/id"
import { SeriesEditor } from "pages/editor/series"

@ -16,11 +16,11 @@ import { requestApi } from "utils/axios/request"
import { isAdmin } from "utils/role"
import Follow from "components/interaction/follow"
import Count from "components/count"
import StoryFilters from "components/story/story-filter"
const UserPage = () => {
const router = useRouter()
const [posts, setPosts]: [Story[], any] = useState([])
const [tag, setTag]: [Tag, any] = useState(null)
const [followed, setFollowed] = useState(null)
@ -30,13 +30,18 @@ const UserPage = () => {
}
}, [tag])
const [filter, setFilter] = useState('Recent')
const initPosts = (p) => {
return requestApi.get(`/tag/posts/${tag.id}?filter=${filter}&page=${p}&per_page=5`)
}
const onFilterChange = f => {
setFilter(f)
}
const initData = async () => {
const res = await requestApi.get(`/tag/info/${router.query.name}`)
setTag(res.data)
const res1 = await requestApi.get(`/tag/posts/${res.data.id}`)
setPosts(res1.data)
}
useEffect(() => {
@ -55,38 +60,36 @@ const UserPage = () => {
<PageContainer1>
{tag && tag.name &&
<HStack alignItems="top" spacing="4" p="2">
<VStack width={["100%","100%","70%","70%"]} alignItems="left" spacing="2">
<VStack width={["100%", "100%", "70%", "70%"]} alignItems="left" spacing="2">
<Card p="0">
<Image src={tag.cover} maxHeight="250px"/>
<Image src={tag.icon} width="80px" position="relative" top="-40px" left="40px" className="shadowed"/>
<Image src={tag.cover} maxHeight="250px" />
<Image src={tag.icon} width="80px" position="relative" top="-40px" left="40px" className="shadowed" />
<Flex justifyContent="space-between" alignItems="center" px="8" pb="6" mt="-1rem">
<Box>
<Heading size="lg">{tag.title}</Heading>
<Text layerStyle="textSecondary" fontWeight="500" fontSize="1.2rem" mt="1" ml="1">#{tag.name}</Text>
</Box>
<Box>
{followed !== null && <Follow followed={followed} targetID={tag.id}/>}
{followed !== null && <Follow followed={followed} targetID={tag.id} />}
{isAdmin(session?.user.role) && <Button ml="2" onClick={() => router.push(`${ReserveUrls.Admin}/tag/${tag.name}`)}>Edit</Button>}
</Box>
</Flex>
</Card>
{
posts.length === 0 ?
<Card width="100%" height="fit-content">
<Empty />
</Card>
:
<Card width="100%" height="fit-content" p="0" px="3">
<Stories stories={posts} />
</Card>
}
<Card p="2">
<StoryFilters showBest={false} onChange={onFilterChange} />
</Card>
<Card width="100%" height="fit-content" p="0" px="3">
{tag.id &&
<Stories onLoad={initPosts} filter={filter} />
}
</Card>
</VStack>
<VStack width="30%" alignItems="left" spacing="2" display={{base: "none",md:"flex"}}>
<VStack width="30%" alignItems="left" spacing="2" display={{ base: "none", md: "flex" }}>
<Card>
<Flex justifyContent="space-between" alignItems="center" px={[0, 2, 4, 8]}>
<Box>
<Heading size="lg"><Count count={tag.follows}/></Heading>
<Heading size="lg"><Count count={tag.follows} /></Heading>
<Text layerStyle="textSecondary" fontWeight="500" fontSize="1.2rem" mt="1" ml="1">Followers</Text>
</Box>

@ -1,10 +1,13 @@
package api
import (
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/imdotdev/im.dev/server/internal/story"
"github.com/imdotdev/im.dev/server/internal/top"
"github.com/imdotdev/im.dev/server/internal/user"
"github.com/imdotdev/im.dev/server/pkg/common"
"github.com/imdotdev/im.dev/server/pkg/e"
@ -18,7 +21,7 @@ func GetEditorPosts(c *gin.Context) {
c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
return
}
ars, err := story.UserPosts(tp, user, user.ID)
ars, err := story.UserPosts(tp, user, user.ID, 0, 0)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
@ -57,11 +60,23 @@ func GetEditorDrafts(c *gin.Context) {
}
func GetUserPosts(c *gin.Context) {
filter := c.Query("filter")
page, _ := strconv.ParseInt(c.Query("page"), 10, 64)
perPage, _ := strconv.ParseInt(c.Query("per_page"), 10, 64)
userID := c.Param("userID")
user := user.CurrentUser(c)
posts, err := story.UserPosts(models.IDTypeUndefined, user, userID)
var posts []*models.Story
var err *e.Error
if filter == "" {
posts, err = story.UserPosts(models.IDTypeUndefined, user, userID, page, perPage)
} else {
posts, err = story.UserTagPosts(user, filter, userID, page, perPage)
}
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
@ -71,9 +86,23 @@ func GetUserPosts(c *gin.Context) {
}
func GetTagPosts(c *gin.Context) {
var posts []*models.Story
var err *e.Error
tagID := c.Param("id")
user := user.CurrentUser(c)
posts, err := story.TagPosts(user, tagID)
filter := c.Query("filter")
page, _ := strconv.ParseInt(c.Query("page"), 10, 64)
perPage, _ := strconv.ParseInt(c.Query("per_page"), 10, 64)
if filter == models.FilterLatest {
posts, err = story.TagPosts(user, tagID, page, perPage)
} else {
var ids []string
ids = top.GetTopList(fmt.Sprintf(top.TagFormat, tagID, filter), (page-1)*perPage, page*perPage)
posts, err = story.GetPostsByIDs(user, ids)
}
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
@ -83,9 +112,23 @@ func GetTagPosts(c *gin.Context) {
}
func GetHomePosts(c *gin.Context) {
filter := c.Param("filter")
var posts []*models.Story
var err *e.Error
user := user.CurrentUser(c)
posts, err := story.HomePosts(user, filter)
filter := c.Query("filter")
page, _ := strconv.ParseInt(c.Query("page"), 10, 64)
perPage, _ := strconv.ParseInt(c.Query("per_page"), 10, 64)
if filter == models.FilterBest {
posts, err = story.HomePosts(user, filter, page, perPage)
} else if filter == models.FilterLatest {
posts, err = story.HomePosts(user, filter, page, perPage)
} else {
var ids []string
ids = top.GetTopList(top.GlobalPrefix+filter, (page-1)*perPage, page*perPage)
posts, err = story.GetPostsByIDs(user, ids)
}
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return

@ -2,6 +2,7 @@ package api
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/imdotdev/im.dev/server/internal/search"
@ -12,21 +13,24 @@ import (
)
func SearchPosts(c *gin.Context) {
filter := c.Param("filter")
filter := c.Query("filter")
query := c.Query("query")
if !models.ValidSearchFilter(filter) || query == "" {
c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
return
}
page, _ := strconv.ParseInt(c.Query("page"), 10, 64)
perPage, _ := strconv.ParseInt(c.Query("per_page"), 10, 64)
user := user.CurrentUser(c)
posts := search.Posts(user, filter, query)
posts := search.Posts(user, filter, query, page, perPage)
c.JSON(http.StatusOK, common.RespSuccess(posts))
}
func SearchUsers(c *gin.Context) {
filter := c.Param("filter")
filter := c.Query("filter")
query := c.Query("query")
if !models.ValidSearchFilter(filter) || query == "" {
c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))

@ -5,6 +5,7 @@ import (
"net/http"
"time"
"github.com/imdotdev/im.dev/server/internal/top"
"github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/e"
"github.com/imdotdev/im.dev/server/pkg/models"
@ -62,7 +63,12 @@ func Like(storyID string, userId string) *e.Error {
return e.New(http.StatusInternalServerError, e.Internal)
}
tx.Exec("UPDATE story SET likes=? WHERE id=?", count, storyID)
tx.Commit()
top.Update(storyID, count)
return nil
}

@ -1,6 +1,7 @@
package search
import (
"database/sql"
"sort"
"strings"
@ -13,14 +14,21 @@ import (
var logger = log.RootLogger.New("logger", "search")
func Posts(user *models.User, filter, query string) []*models.Story {
func Posts(user *models.User, filter, query string, page, perPage int64) []*models.Story {
posts := make([]*models.Story, 0)
// postsMap := make(map[string]*models.Post)
// search by title
sqlq := "%" + query + "%"
rows, err := db.Conn.Query("select id,type,slug,title,url,cover,brief,creator,created,updated from story where status=? and (title LIKE ? or brief LIKE ?)", models.StatusPublished, sqlq, sqlq)
var rows *sql.Rows
var err error
if filter == models.FilterFavorites {
rows, err = db.Conn.Query(story.PostQueryPrefix+"where status=? and (title LIKE ? or brief LIKE ?) ORDER BY likes DESC LIMIT ?,?", models.StatusPublished, sqlq, sqlq, (page-1)*perPage, perPage)
} else {
rows, err = db.Conn.Query(story.PostQueryPrefix+"where status=? and (title LIKE ? or brief LIKE ?) ORDER BY created DESC LIMIT ?,?", models.StatusPublished, sqlq, sqlq, (page-1)*perPage, perPage)
}
if err != nil {
logger.Warn("get user posts error", "error", err)
return posts
@ -28,12 +36,6 @@ func Posts(user *models.User, filter, query string) []*models.Story {
posts = story.GetPosts(user, rows)
if filter == models.FilterFavorites {
sort.Sort(models.FavorStories(posts))
} else {
sort.Sort(models.Stories(posts))
}
return posts
}

@ -1,15 +1,19 @@
package internal
import (
"context"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"github.com/imdotdev/im.dev/server/internal/api"
"github.com/imdotdev/im.dev/server/internal/cache"
"github.com/imdotdev/im.dev/server/internal/storage"
"github.com/imdotdev/im.dev/server/internal/user"
"github.com/imdotdev/im.dev/server/pkg/common"
"github.com/imdotdev/im.dev/server/pkg/config"
"github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/e"
"github.com/imdotdev/im.dev/server/pkg/log"
)
@ -31,6 +35,10 @@ func (s *Server) Start() error {
return err
}
err = initRedis()
if err != nil {
return err
}
// if config.Data.Common.IsProd {
gin.SetMode((gin.ReleaseMode))
// } else {
@ -50,10 +58,11 @@ func (s *Server) Start() error {
r.GET("/story/comments/:id", api.GetStoryComments)
r.POST("/story/comment", IsLogin(), api.SubmitComment)
r.DELETE("/story/comment/:id", IsLogin(), api.DeleteStoryComment)
r.GET("/story/posts/editor", IsLogin(), api.GetEditorPosts)
r.GET("/story/posts/org/:id", IsLogin(), api.GetOrgPosts)
r.GET("/story/posts/drafts", IsLogin(), api.GetEditorDrafts)
r.GET("/story/posts/home/:filter", api.GetHomePosts)
r.GET("/story/posts/home", api.GetHomePosts)
r.POST("/story", IsLogin(), api.SubmitStory)
r.POST("/story/pin/:storyID", IsLogin(), api.PinStory)
r.POST("/story/series", api.GetSeries)
@ -100,8 +109,8 @@ func (s *Server) Start() error {
r.GET("/interaction/followers/:userID", api.GetFollowers)
// search apis
r.GET("/search/posts/:filter", api.SearchPosts)
r.GET("/search/users/:filter", api.SearchUsers)
r.GET("/search/posts", api.SearchPosts)
r.GET("/search/users", api.SearchUsers)
// org apis
r.POST("/org/create", IsLogin(), api.CreateOrg)
@ -184,3 +193,19 @@ func InvasionCheck() gin.HandlerFunc {
c.Next()
}
}
func initRedis() error {
// init redis client
db.Redis = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})
ctx := context.Background()
err := db.Redis.Set(ctx, "test_redis", "alive", 10*time.Second).Err()
if err != nil {
return err
}
return nil
}

@ -82,6 +82,7 @@ var sqlTables = map[string]string{
url VARCHAR(255),
cover VARCHAR(255),
brief TEXT,
likes INTEGER DEFAULT 0,
status tinyint NOT NULL,
created DATETIME NOT NULL,
updated DATETIME
@ -154,7 +155,8 @@ var sqlTables = map[string]string{
tag_id VARCHAR(255),
target_type VARCHAR(1),
target_id VARCHAR(255),
target_creator VARCHAR(255)
target_creator VARCHAR(255),
target_created DATETIME
);
CREATE INDEX IF NOT EXISTS tags_using_tagid
ON tags_using (tag_id);

@ -2,7 +2,6 @@ package story
import (
"database/sql"
"fmt"
"net/http"
"strings"
"time"
@ -13,6 +12,7 @@ import (
"github.com/imdotdev/im.dev/server/internal/interaction"
"github.com/imdotdev/im.dev/server/internal/org"
"github.com/imdotdev/im.dev/server/internal/tags"
"github.com/imdotdev/im.dev/server/internal/top"
"github.com/imdotdev/im.dev/server/internal/user"
"github.com/imdotdev/im.dev/server/pkg/config"
"github.com/imdotdev/im.dev/server/pkg/db"
@ -99,9 +99,10 @@ func SubmitStory(c *gin.Context) (map[string]string, *e.Error) {
post.ID = utils.GenID(post.Type)
}
post.Created = now
//create
_, err := db.Conn.Exec("INSERT INTO story (id,type,creator,owner,slug, title, md, url, cover, brief,status, created, updated) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)",
post.ID, post.Type, post.CreatorID, post.OwnerID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, models.StatusPublished, now, now)
post.ID, post.Type, post.CreatorID, post.OwnerID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, models.StatusPublished, post.Created, post.Created)
if err != nil {
logger.Warn("submit post error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
@ -113,8 +114,15 @@ func SubmitStory(c *gin.Context) (map[string]string, *e.Error) {
return nil, e.New(http.StatusForbidden, e.NoEditorPermission)
}
_, err = db.Conn.Exec("UPDATE story SET owner=?, slug=?, title=?, md=?, url=?, cover=?, brief=?, updated=? WHERE id=?",
post.OwnerID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, now, post.ID)
if post.Status == models.StatusDraft {
// 首次发布,需要更新创建时间
_, err = db.Conn.Exec("UPDATE story SET owner=?, slug=?, title=?, md=?, url=?, cover=?, brief=?,created=?,updated=?,status=? WHERE id=?",
post.OwnerID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, now, now, models.StatusPublished, post.ID)
} else {
_, err = db.Conn.Exec("UPDATE story SET owner=?, slug=?, title=?, md=?, url=?, cover=?, brief=?, updated=? WHERE id=?",
post.OwnerID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, now, post.ID)
}
if err != nil {
logger.Warn("upate post error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
@ -122,7 +130,7 @@ func SubmitStory(c *gin.Context) (map[string]string, *e.Error) {
}
//update tags
err = tags.UpdateTargetTags(user.ID, post.ID, post.Tags)
err = tags.UpdateTargetTags(user.ID, post.ID, post.Tags, post.Created)
if err != nil {
logger.Warn("upate tags error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
@ -162,7 +170,6 @@ func SubmitPostDraft(c *gin.Context) (map[string]string, *e.Error) {
//create
_, err := db.Conn.Exec("INSERT INTO story (id,type,creator,slug, title, md, url, cover, brief,status, created, updated) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)",
post.ID, models.IDTypePost, user.ID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, models.StatusDraft, now, now)
fmt.Println(post.Brief)
if err != nil {
logger.Warn("submit post draft error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
@ -182,13 +189,6 @@ func SubmitPostDraft(c *gin.Context) (map[string]string, *e.Error) {
}
}
//update tags
err = tags.UpdateTargetTags(user.ID, post.ID, post.Tags)
if err != nil {
logger.Warn("upate tags error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
return map[string]string{
"id": post.ID,
}, nil
@ -216,6 +216,7 @@ func DeletePost(id string) *e.Error {
tx.Commit()
top.Update(id, 0)
return nil
}
@ -244,7 +245,7 @@ func GetStory(id string, slug string) (*models.Story, *e.Error) {
}
// get tags
t, rawTags, err := tags.GetTargetTags(ar.ID)
t, rawTags, err := models.GetTargetTags(ar.ID)
if err != nil {
return nil, e.New(http.StatusInternalServerError, e.Internal)
}

@ -8,7 +8,6 @@ import (
"strings"
"github.com/imdotdev/im.dev/server/internal/interaction"
"github.com/imdotdev/im.dev/server/internal/tags"
"github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/e"
"github.com/imdotdev/im.dev/server/pkg/models"
@ -16,9 +15,9 @@ import (
const PostQueryPrefix = "select id,type,slug,title,url,cover,brief,creator,owner,created,updated from story "
func HomePosts(user *models.User, filter string) (models.Stories, *e.Error) {
func HomePosts(user *models.User, filter string, page int64, perPage int64) (models.Stories, *e.Error) {
rows, err := db.Conn.Query(PostQueryPrefix+"where status=?", models.StatusPublished)
rows, err := db.Conn.Query(PostQueryPrefix+"where status=? ORDER BY created DESC LIMIT ?,?", models.StatusPublished, (page-1)*perPage, perPage)
if err != nil && err != sql.ErrNoRows {
logger.Warn("get user posts error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
@ -30,13 +29,21 @@ func HomePosts(user *models.User, filter string) (models.Stories, *e.Error) {
return posts, nil
}
func UserPosts(tp string, user *models.User, uid string) (models.Stories, *e.Error) {
func UserPosts(tp string, user *models.User, uid string, page int64, perPage int64) (models.Stories, *e.Error) {
var rows *sql.Rows
var err error
if tp == models.IDTypeUndefined {
rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and status=?", uid, models.StatusPublished)
if perPage == 0 {
rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and status=?", uid, models.StatusPublished)
} else {
rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and status=? ORDER BY created DESC LIMIT ?,?", uid, models.StatusPublished, (page-1)*perPage, perPage)
}
} else {
rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and type=? and status=?", uid, tp, models.StatusPublished)
if perPage == 0 {
rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and type=? and status=?", uid, tp, models.StatusPublished)
} else {
rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and type=? and status=? ORDER BY created DESC LIMIT ?,? ", uid, tp, models.StatusPublished, (page-1)*perPage, perPage)
}
}
if err != nil && err != sql.ErrNoRows {
@ -64,6 +71,28 @@ func UserPosts(tp string, user *models.User, uid string) (models.Stories, *e.Err
return newPosts, nil
}
func UserTagPosts(user *models.User, tid string, uid string, page int64, perPage int64) (models.Stories, *e.Error) {
ids := make([]string, 0)
rows, err := db.Conn.Query("SELECT target_id FROM tags_using WHERE tag_id=? and target_creator=? ORDER BY target_created DESC LIMIT ?,?", tid, uid, (page-1)*perPage, perPage)
if err != nil {
logger.Warn("get user posts error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
for rows.Next() {
var id string
rows.Scan(&id)
ids = append(ids, id)
}
posts, err1 := GetPostsByIDs(user, ids)
if err1 != nil {
return nil, err1
}
return posts, nil
}
func OrgPosts(tp string, user *models.User, orgID string) (models.Stories, *e.Error) {
var rows *sql.Rows
var err error
@ -111,26 +140,25 @@ func UserDrafts(user *models.User, uid string) (models.Stories, *e.Error) {
return posts, nil
}
func TagPosts(user *models.User, tagID string) (models.Stories, *e.Error) {
// get post ids
postIDs, err := tags.GetTargetIDs(tagID)
func TagPosts(user *models.User, tagID string, page, perPage int64) (models.Stories, *e.Error) {
ids := make([]string, 0)
rows, err := db.Conn.Query("SELECT target_id FROM tags_using WHERE tag_id=? and target_creator != ? ORDER BY target_created DESC LIMIT ?,?", tagID, "", (page-1)*perPage, perPage)
if err != nil {
logger.Warn("get user posts error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
ids := strings.Join(postIDs, "','")
q := fmt.Sprintf(PostQueryPrefix+"where id in ('%s') and status='%d'", ids, models.StatusPublished)
rows, err := db.Conn.Query(q)
if err != nil && err != sql.ErrNoRows {
logger.Warn("get user posts error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
for rows.Next() {
var id string
rows.Scan(&id)
ids = append(ids, id)
}
posts := GetPosts(user, rows)
posts, err1 := GetPostsByIDs(user, ids)
if err1 != nil {
return nil, err1
}
sort.Sort(posts)
return posts, nil
}
@ -161,7 +189,7 @@ func BookmarkPosts(user *models.User, filter string) (models.Stories, *e.Error)
posts := GetPosts(user, rows)
for _, post := range posts {
_, rawTags, err := tags.GetTargetTags(post.ID)
_, rawTags, err := models.GetTargetTags(post.ID)
if err != nil {
logger.Warn("get story tags error", "error", err)
continue
@ -174,6 +202,26 @@ func BookmarkPosts(user *models.User, filter string) (models.Stories, *e.Error)
return posts, nil
}
func GetPostsByIDs(user *models.User, ids []string) ([]*models.Story, *e.Error) {
posts := make([]*models.Story, 0)
for _, id := range ids {
post, err := GetStory(id, "")
if err != nil {
continue
}
// 获取当前登录用户的like
if user != nil {
post.Liked = interaction.GetLiked(post.ID, user.ID)
// 获取当前登录用户的bookmark
post.Bookmarked, _ = Bookmarked(user.ID, post.ID)
}
posts = append(posts, post)
}
return posts, nil
}
func GetPosts(user *models.User, rows *sql.Rows) models.Stories {
posts := make(models.Stories, 0)
for rows.Next() {
@ -206,7 +254,7 @@ func GetPosts(user *models.User, rows *sql.Rows) models.Stories {
}
ar.Likes = interaction.GetLikes(ar.ID)
tags, rawTags, err := tags.GetTargetTags(ar.ID)
tags, rawTags, err := models.GetTargetTags(ar.ID)
if err != nil {
logger.Warn("get tags error", "error", err)
}

@ -106,7 +106,7 @@ func GetTags() (models.Tags, *e.Error) {
func GetTagsByIDs(ids []string) ([]*models.Tag, *e.Error) {
tags := make([]*models.Tag, 0, len(ids))
for _, id := range ids {
tag, err := GetSimpleTag(id, "")
tag, err := models.GetSimpleTag(id, "")
if err != nil {
logger.Warn("get tag error", "error", err)
continue
@ -152,52 +152,14 @@ func GetTag(id string, name string) (*models.Tag, *e.Error) {
return tag, nil
}
func GetSimpleTag(id string, name string) (*models.Tag, *e.Error) {
tag := &models.Tag{}
err := db.Conn.QueryRow("SELECT id,name,title,icon from tags where id=? or name=?", id, name).Scan(
&tag.ID, &tag.Name, &tag.Title, &tag.Icon,
)
if err != nil {
if err == sql.ErrNoRows {
return nil, e.New(http.StatusNotFound, e.NotFound)
}
logger.Warn("get tag error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
return tag, nil
}
func GetTargetTags(targetID string) ([]string, []*models.Tag, error) {
ids := make([]string, 0)
rows, err := db.Conn.Query("SELECT tag_id FROM tags_using WHERE target_id=?", targetID)
if err != nil {
return nil, nil, err
}
rawTags := make([]*models.Tag, 0)
for rows.Next() {
var id string
err = rows.Scan(&id)
ids = append(ids, id)
rawTag, err := GetSimpleTag(id, "")
if err == nil {
rawTags = append(rawTags, rawTag)
}
}
return ids, rawTags, nil
}
func UpdateTargetTags(targetCreator string, targetID string, tags []string) error {
func UpdateTargetTags(targetCreator string, targetID string, tags []string, targetCreated time.Time) error {
_, err := db.Conn.Exec("DELETE FROM tags_using WHERE target_id=?", targetID)
if err != nil {
return err
}
for _, tag := range tags {
_, err = db.Conn.Exec("INSERT INTO tags_using (tag_id,target_type,target_id,target_creator) VALUES (?,?,?,?)", tag, models.GetIDType(targetID), targetID, targetCreator)
_, err = db.Conn.Exec("INSERT INTO tags_using (tag_id,target_type,target_id,target_creator,target_created) VALUES (?,?,?,?,?)", tag, models.GetIDType(targetID), targetID, targetCreator, targetCreated)
if err != nil {
logger.Warn("add post tag error", "error", err)
}
@ -259,7 +221,7 @@ func GetUserTags(userID string) ([]*models.Tag, *e.Error) {
tags := make(models.Tags, 0)
for _, t := range tagsMap {
tag, err := GetSimpleTag(t.ID, "")
tag, err := models.GetSimpleTag(t.ID, "")
if err != nil {
logger.Warn("get simple tag error", "error", err)
continue

@ -0,0 +1,113 @@
package top
import (
"context"
"fmt"
"time"
"github.com/go-redis/redis/v8"
"github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/log"
"github.com/imdotdev/im.dev/server/pkg/models"
)
var logger = log.RootLogger.New("logger", "hot")
type HotData struct {
Key string
Data *redis.Z
}
const GlobalPrefix = "im.dev-global-"
const TagFormat = "im.dev-tag-%s-%s"
const (
TopRecent = models.FilterRecent
TopWeek = models.FilterWeek
TopMonth = models.FilterMonth
TopYear = models.FilterYear
)
// 更新文章的HOT列表
// 时间维度yestoday, week, month, year,infinity
// 范围维度: 全局、Tag
func Update(storyID string, count int) {
var created time.Time
err := db.Conn.QueryRow("SELECT created FROM story WHERE id=?", storyID).Scan(&created)
if err != nil {
logger.Warn("select story created error", "error", err)
return
}
tillNow := time.Now().Sub(created).Hours()
hots := make([]*HotData, 0)
if tillNow >= 365*24 {
return
}
ts, _, err := models.GetTargetTags(storyID)
if err != nil {
logger.Warn("get tags error", "error", err)
return
}
if tillNow < 365*24 {
hots = append(hots, &HotData{GlobalPrefix + TopYear, &redis.Z{float64(count), storyID}})
for _, tag := range ts {
hots = append(hots, &HotData{fmt.Sprintf(TagFormat, tag, TopYear), &redis.Z{float64(count), storyID}})
}
}
if tillNow < 30*24 {
hots = append(hots, &HotData{GlobalPrefix + TopMonth, &redis.Z{float64(count), storyID}})
for _, tag := range ts {
hots = append(hots, &HotData{fmt.Sprintf(TagFormat, tag, TopMonth), &redis.Z{float64(count), storyID}})
}
}
if tillNow < 7*24 {
hots = append(hots, &HotData{GlobalPrefix + TopWeek, &redis.Z{float64(count), storyID}})
for _, tag := range ts {
hots = append(hots, &HotData{fmt.Sprintf(TagFormat, tag, TopWeek), &redis.Z{float64(count), storyID}})
}
}
if tillNow < 2*24 {
hots = append(hots, &HotData{GlobalPrefix + TopRecent, &redis.Z{float64(count), storyID}})
for _, tag := range ts {
hots = append(hots, &HotData{fmt.Sprintf(TagFormat, tag, TopRecent), &redis.Z{float64(count), storyID}})
}
}
ctx := context.Background()
for _, hot := range hots {
if count > 0 {
err = db.Redis.ZAdd(ctx, hot.Key, hot.Data).Err()
} else {
err = db.Redis.ZRem(ctx, hot.Key, hot.Data.Member).Err()
}
if err != nil {
logger.Warn("update hot error", "error", err, "key", hot.Key, "score", hot.Data.Score)
continue
}
}
}
func GetTopList(key string, start, end int64) []string {
ids := make([]string, 0)
ctx := context.Background()
fmt.Println(start, end)
keys, err := db.Redis.ZRevRange(ctx, key, start, end-1).Result()
if err != nil {
logger.Warn("scan top list error", "error", err, "key", key)
return ids
}
fmt.Println(keys)
for _, key := range keys {
ids = append(ids, key)
}
return ids
}

@ -74,7 +74,7 @@ func GetUserDetail(id string, username string) (*models.User, *e.Error) {
}
// get user skills
skills, rawSkills, err := tags.GetTargetTags(user.ID)
skills, rawSkills, err := models.GetTargetTags(user.ID)
if err != nil {
logger.Warn("get user skills error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
@ -124,7 +124,7 @@ func UpdateUser(u *models.User) *e.Error {
}
//update user skills
err = tags.UpdateTargetTags("", u.ID, u.Skills)
err = tags.UpdateTargetTags("", u.ID, u.Skills, u.Created)
if err != nil {
logger.Warn("upate tags error", "error", err)
return e.New(http.StatusInternalServerError, e.Internal)

@ -2,6 +2,9 @@ package db
import (
"database/sql"
"github.com/go-redis/redis/v8"
)
var Conn *sql.DB
var Redis *redis.Client

@ -1,10 +1,14 @@
package models
const (
FilterBest = "best"
FilterFeature = "feature"
FilterRecent = "recent"
FilterFavorites = "favorites"
FilterBest = "Best"
FilterFeature = "Feature"
FilterRecent = "Recent"
FilterFavorites = "Favorites"
FilterWeek = "Week"
FilterMonth = "Month"
FilterYear = "Year"
FilterLatest = "Latest"
)
func ValidSearchFilter(f string) bool {

@ -1,5 +1,13 @@
package models
import (
"database/sql"
"net/http"
"github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/e"
)
type Tag struct {
ID string `json:"id"`
Creator string `json:"creator,omitempty"`
@ -27,3 +35,41 @@ func (t Tags) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
func (t Tags) Less(i, j int) bool {
return t[i].Posts > t[j].Posts
}
func GetTargetTags(targetID string) ([]string, []*Tag, error) {
ids := make([]string, 0)
rows, err := db.Conn.Query("SELECT tag_id FROM tags_using WHERE target_id=?", targetID)
if err != nil {
return nil, nil, err
}
rawTags := make([]*Tag, 0)
for rows.Next() {
var id string
err = rows.Scan(&id)
ids = append(ids, id)
rawTag, err := GetSimpleTag(id, "")
if err == nil {
rawTags = append(rawTags, rawTag)
}
}
return ids, rawTags, nil
}
func GetSimpleTag(id string, name string) (*Tag, *e.Error) {
tag := &Tag{}
err := db.Conn.QueryRow("SELECT id,name,title,icon from tags where id=? or name=?", id, name).Scan(
&tag.ID, &tag.Name, &tag.Title, &tag.Icon,
)
if err != nil {
if err == sql.ErrNoRows {
return nil, e.New(http.StatusNotFound, e.NotFound)
}
logger.Warn("get tag error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
return tag, nil
}

@ -1,45 +1,93 @@
import React from "react"
import React, { useEffect, useState } from "react"
import { Box, Center, Text, useColorModeValue, VStack } from "@chakra-ui/react"
import { Story } from "src/types/story"
import StoryCard from "./story-card"
import userCustomTheme from "theme/user-custom"
import useInfiniteScroll from 'src/hooks/use-infinite-scroll'
import { concat } from "lodash"
import Empty from "components/empty"
interface Props {
stories: Story[]
card?: any
size?: 'sm' | 'md'
showFooter?: boolean
showPinned?: boolean
type?: string
highlight?: string
showOrg?:boolean
showOrg?: boolean
onLoad?: any
filter?: string
}
export const Stroies = (props: Props) => {
const { stories,card=StoryCard,showFooter=true,type="classic",showPinned = false,showOrg=true} = props
const { card = StoryCard, showFooter = true, type = "classic", showPinned = false, showOrg = true, onLoad, filter } = props
const [posts, setPosts] = useState([])
const [noMore, setNoMore] = useState(false)
const [isFetching, setIsFetching] = useInfiniteScroll(fetchMoreListItems);
const borderColor = useColorModeValue(userCustomTheme.borderColor.light, userCustomTheme.borderColor.dark)
const [page, setPage] = useState(1)
const Card = card
useEffect(() => {
setPosts([])
setNoMore(false)
setPage(1)
onLoad(1).then(res => {
if (res.data.length < 5) {
setNoMore(true)
}
setPosts(res.data)
})
}, [filter])
function fetchMoreListItems() {
if (noMore) {
//@ts-ignore
setIsFetching(false)
return
}
setPage(page + 1)
onLoad(page + 1).then(res => {
if (res.data.length < 5) {
setNoMore(true)
}
setPosts(concat(posts, ...res.data))
}
)
//@ts-ignore
setIsFetching(false)
}
const showBorder = i => {
if (i < stories.length -1) {
if (i < posts.length - 1) {
return true
}
if (showFooter) {
return true
return true
} else {
return false
}
}
return (
<>
<VStack alignItems="left">
{stories.map((story,i) =>
<Box py="2" borderBottom={showBorder(i)? `1px solid ${borderColor}`:null} key={story.id} px="1">
<Card story={story} size={props.size} type={type} highlight={props.highlight} showPinned={showPinned} showOrg={showOrg}/>
</Box>)}
</VStack>
{showFooter && <Center><Text layerStyle="textSecondary" fontSize="sm" py="4"></Text></Center>}
{
posts.length === 0 ? <Empty /> :
<>
<VStack alignItems="left">
{posts.map((story, i) =>
<Box py="2" borderBottom={showBorder(i) ? `1px solid ${borderColor}` : null} key={story.id} px="1">
<Card story={story} size={props.size} type={type} highlight={props.highlight} showPinned={showPinned} showOrg={showOrg} />
</Box>)}
</VStack>
{isFetching && 'Fetching more list items...'}
{noMore && <Center><Text layerStyle="textSecondary" fontSize="sm" py="4"></Text></Center>}
</>
}
</>
)
}

@ -0,0 +1,39 @@
import React, { useState } from "react"
import { Button, HStack } from "@chakra-ui/react"
interface Props {
showBest?: boolean
onChange: any
}
export const StoryFilters = (props:Props) => {
const {showBest=true,onChange} = props
let initFilter = 'Best'
let filters = ['Recent','Week','Month','Year','Latest']
if (!showBest) {
initFilter = 'Recent'
} else {
filters.unshift('Best')
}
const [filter, setFilter] = useState(initFilter)
const changeFilter = f => {
onChange(f)
setFilter(f)
}
return (
<HStack spacing="0">
{
filters.map(f =>
<Button key={f} _focus={null} onClick={() => changeFilter(f)} size="sm" colorScheme={filter === f ? 'teal' : null} variant="ghost" >
{f}
</Button>)
}
</HStack>
)
}
export default StoryFilters

@ -1,4 +1,7 @@
import { toLower } from "lodash"
export function getSvgIcon(name, height = "1.4rem") {
name = toLower(name)
let svg
switch (name) {
case "comments1":

@ -0,0 +1,24 @@
import { useState, useEffect } from 'react';
const useInfiniteScroll = (callback) => {
const [isFetching, setIsFetching] = useState(false);
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
useEffect(() => {
if (!isFetching) return;
callback();
}, [isFetching]);
function handleScroll() {
if (window.innerHeight + document.documentElement.scrollTop !== document.documentElement.offsetHeight || isFetching) return;
setIsFetching(true);
}
return [isFetching, setIsFetching];
};
export default useInfiniteScroll;

@ -1,6 +1,7 @@
export enum SearchFilter {
Best = "best",
Featured = "feature",
Recent = "recent",
Favorites = "favorites"
Best = "Best",
Featured = "Feature",
Recent = "Recent",
Favorites = "Favorites",
Latest = "Latest"
}
Loading…
Cancel
Save