pull/50/head
sunface 4 years ago
parent c5205e7e98
commit 63752a0e79

@ -3,26 +3,17 @@ module github.com/imdotdev/im.dev
go 1.14
require (
9fans.net/go v0.0.2 // indirect
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
github.com/gin-gonic/gin v1.6.3
github.com/go-stack/stack v1.8.0
github.com/golang/snappy v0.0.2
github.com/gosimple/slug v1.9.0
github.com/grafana/grafana v5.4.5+incompatible
github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac
github.com/karrick/godirwalk v1.16.1 // indirect
github.com/keegancsmith/rpc v1.3.0 // indirect
github.com/lithammer/shortuuid/v3 v3.0.5
github.com/mattn/go-sqlite3 v1.14.6
github.com/newhook/go-symbols v0.0.0-20151212134530-b75dfefa0d23 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/ramya-rao-a/go-outline v0.0.0-20200117021646-2a048b4510eb // indirect
github.com/rogpeppe/godef v1.1.2 // indirect
github.com/spf13/cobra v1.1.1
github.com/spf13/pflag v1.0.5
github.com/stamblerre/gocode v1.0.0 // indirect
github.com/uudashr/gopkgs v1.3.2 // indirect
golang.org/x/mod v0.4.1 // indirect
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
golang.org/x/tools v0.1.0 // indirect
gopkg.in/yaml.v2 v2.2.8
)

@ -1,7 +1,3 @@
9fans.net/go v0.0.0-20181112161441-237454027057 h1:OcHlKWkAMJEF1ndWLGxp5dnJQkYM/YImUOvsBoz6h5E=
9fans.net/go v0.0.0-20181112161441-237454027057/go.mod h1:diCsxrliIURU9xsYtjCp5AbpQKqdhKmf0ujWDUSkfoY=
9fans.net/go v0.0.2 h1:RYM6lWITV8oADrwLfdzxmt8ucfW6UtP9v1jg4qAbqts=
9fans.net/go v0.0.2/go.mod h1:lfPdxjq9v8pVQXUMBCx5EO5oLXWQFlKRQgs1kEkjoIM=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@ -38,6 +34,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
@ -52,6 +49,7 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
@ -84,12 +82,17 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gosimple/slug v1.9.0 h1:r5vDcYrFz9BmfIAMC829un9hq7hKM4cHUrsv36LbEqs=
github.com/gosimple/slug v1.9.0/go.mod h1:AMZ+sOVe65uByN3kgEyf9WEBKBCSS+dJjMX9x4vDJbg=
github.com/grafana/grafana v1.9.1 h1:4ykSOyQ+qImmZmILEgqsokVnWmvR8/A0zt7jN2RAQxM=
github.com/grafana/grafana v5.4.5+incompatible h1:xNuhSBxLgwDwesuQIAhQu1QCk6tD0TAghKHE36/hxrs=
github.com/grafana/grafana v5.4.5+incompatible/go.mod h1:U8QyUclJHj254BFcuw45p6sg7eeGYX44qn1ShYo5rGE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
@ -115,28 +118,28 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
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=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw=
github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
github.com/keegancsmith/rpc v1.1.0 h1:bXVRk3EzbtrEegTGKxNTc+St1lR7t/Z1PAO8misBnCc=
github.com/keegancsmith/rpc v1.1.0/go.mod h1:Xow74TKX34OPPiPCdz6x1o9c0SCxRqGxDuKGk7ZOo8s=
github.com/keegancsmith/rpc v1.3.0 h1:wGWOpjcNrZaY8GDYZJfvyxmlLljm3YQWF+p918DXtDk=
github.com/keegancsmith/rpc v1.3.0/go.mod h1:6O2xnOGjPyvIPbvp0MdrOe5r6cu1GZ4JoTzpzDhWeo0=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lithammer/shortuuid/v3 v3.0.5 h1:cUCI9JNIWsjVThijRm4K3jInhXZj8+xJxbUGNfm84ms=
github.com/lithammer/shortuuid/v3 v3.0.5/go.mod h1:2QdoCtD4SBzugx2qs3gdR3LXY6McxZYCNEHwDmYvOAE=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@ -157,20 +160,19 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
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/newhook/go-symbols v0.0.0-20151212134530-b75dfefa0d23 h1:bXKgb2O/gIrZd/smUeP3OJcAt1ey3UV+cPYT+mRUNzs=
github.com/newhook/go-symbols v0.0.0-20151212134530-b75dfefa0d23/go.mod h1:9RIESfVnNNAkdTKNuEYS0rEkXXM2HYfchPD4xXNx1CU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
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=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@ -184,12 +186,8 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
github.com/ramya-rao-a/go-outline v0.0.0-20200117021646-2a048b4510eb h1:ilZSL4VaIq4Hsi+lH928xQKnSWymFug6r2gJomUBpW8=
github.com/ramya-rao-a/go-outline v0.0.0-20200117021646-2a048b4510eb/go.mod h1:1WL5IqM+CnRCAbXetRnL1YVoS9KtU2zMhOi/5oAVPo4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/godef v1.1.2 h1:c5mCx0EcCORJOdVMREX7Lgh1raTxAHFmOfXdEB9u8Jw=
github.com/rogpeppe/godef v1.1.2/go.mod h1:WtY9A/ovuQ+UakAJ1/CEqwwulX/WJjb2kgkokCHi/GY=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
@ -208,12 +206,11 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stamblerre/gocode v1.0.0 h1:5aTRgkRTOS8mELHoKatkwhfX44OdEV3iwu3FCXyvLzk=
github.com/stamblerre/gocode v1.0.0/go.mod h1:ONyGamdxpnxaG2+XLyGkNuuoYISmz0QFVHScxvsXsqM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
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/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=
@ -221,10 +218,7 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
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/uudashr/gopkgs v1.3.2 h1:ACme7LZyeSNIRIl9HtAA0RsT0eePUsrkHDVb2+aswhg=
github.com/uudashr/gopkgs v1.3.2/go.mod h1:MtCdKVJkxW7hNKWXPNWfpaeEp8+Ml3Q8myb4yWhn2Hg=
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=
@ -236,8 +230,6 @@ 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=
@ -256,11 +248,6 @@ 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.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=
golang.org/x/mod v0.4.1/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-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -275,7 +262,6 @@ 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-20201021035429-f5854403a974/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=
@ -284,7 +270,6 @@ 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=
@ -300,15 +285,11 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/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=
@ -327,17 +308,9 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191030062658-86caa796c7ab/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-20200226224502-204d844ad48d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
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-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
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=
@ -361,6 +334,7 @@ google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiq
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
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/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=

@ -29,6 +29,7 @@ import { Tag } from "src/types/tag"
import { cloneDeep, remove } from "lodash"
import { requestApi } from "utils/axios/request"
import DarkMode from "components/dark-mode"
import EditModeSelect from "components/edit-mode-select"
@ -56,15 +57,6 @@ function HeaderContent(props: any) {
},[props.ar])
const editOptions = [EditMode.Edit, EditMode.Preview]
const { getRootProps, getRadioProps } = useRadioGroup({
name: "framework",
defaultValue: EditMode.Edit,
onChange: (v) => {
props.changeEditMode(v)
},
})
const group = getRootProps()
const addTag = t => {
setTags(t)
@ -102,16 +94,7 @@ function HeaderContent(props: any) {
<Box>
<Input value={props.ar.title} placeholder="Title..." onChange={props.changeTitle} focusBorderColor={useColorModeValue('teal.400', 'teal.100')} variant="flushed" />
</Box>
<HStack {...group}>
{editOptions.map((value) => {
const radio = getRadioProps({ value })
return (
<RadioCard key={value} {...radio} bg="teal" color="white">
{value}
</RadioCard>
)
})}
</HStack>
<EditModeSelect onChange={props.changeEditMode}/>
<Box
color={useColorModeValue("gray.500", "gray.400")}
>
@ -185,8 +168,6 @@ function EditorNav(props) {
bg={useColorModeValue('white','gray.800')}
left="0"
right="0"
borderTop="4px solid"
borderTopColor="teal.400"
width="full"
>
<chakra.div height="4.5rem" mx="auto" maxW="1200px">

@ -125,8 +125,6 @@ function Header(props) {
zIndex="3"
left="0"
right="0"
borderTop="4px solid"
borderTopColor="teal.400"
width="full"
bg={useColorModeValue('white', 'gray.800')}
{...props}

@ -110,8 +110,6 @@ function PostNav(props) {
zIndex="3"
left="0"
right="0"
borderTop="4px solid"
borderTopColor="teal.400"
width="full"
bg={useColorModeValue('white', 'gray.800')}
{...props}

@ -1,10 +1,13 @@
import { Box, chakra, Divider, Flex, Heading, HStack, IconButton, Image, VStack } from "@chakra-ui/react"
import Comments from "components/comments/comments"
import Container from "components/container"
import LikeButton from "components/like-button"
import LikeButton from "components/posts/unicorn-like"
import { MarkdownRender } from "components/markdown-editor/render"
import PostAuthor from "components/posts/post-author"
import TagTextCard from "components/posts/tag-text-card"
import SEO from "components/seo"
import siteConfig from "configs/site-config"
import useSession from "hooks/use-session"
import Nav from "layouts/nav/nav"
import PostNav from "layouts/nav/post-nav"
import PageContainer from "layouts/page-container"
@ -13,22 +16,34 @@ import { useRouter } from "next/router"
import { title } from "process"
import React, { useEffect, useState } from "react"
import { FaBookmark, FaGithub, FaRegBookmark, FaShare, FaShareAlt } from "react-icons/fa"
import { ReserveUrls } from "src/data/reserve-urls"
import { Comment } from "src/types/comments"
import { Post } from "src/types/posts"
import { Tag } from "src/types/tag"
import { requestApi } from "utils/axios/request"
import UnicornLike from "components/posts/unicorn-like"
const PostPage = () => {
const router = useRouter()
const slug = router.query.post_slug
const id = router.query.post_id
const [post, setPost]: [Post, any] = useState(null)
const [comments,setComments]: [Comment[],any] = useState([])
const session = useSession()
useEffect(() => {
if (slug) {
requestApi.get(`/post/${slug}`).then(res => setPost(res.data))
if (id) {
getData()
}
}, [slug])
}, [id])
const getData = async () => {
const res = await requestApi.get(`/post/${id}`)
setPost(res.data)
getComments(res.data.id)
}
const onLike = async () => {
await requestApi.post(`/post/like/${post.id}`)
await requestApi.post(`/story/like/${post.id}`)
const p = cloneDeep(post)
if (post.liked) {
@ -41,6 +56,12 @@ const PostPage = () => {
setPost(p)
}
const getComments = async (id) => {
const res = await requestApi.get(`/story/comments/${id}`)
console.log(res.data)
setComments(res.data)
}
return (
<>
<SEO
@ -51,10 +72,10 @@ const PostPage = () => {
{post &&
<>
<HStack alignItems="top" spacing={[0, 0, 14, 14]}>
<Box width={["100%", "100%", "75%", "75%"]} height="fit-content">
<Box width={["100%", "100%", "75%", "75%"]} height="fit-content" pl={[0,0,"0%","10%"]}>
<Image src={post.cover} />
<Box px="2">
<Heading size="lg" my="6">{post.title}</Heading>
<Heading size="xl" my="6" lineHeight="1.5">{post.title}</Heading>
<Divider my="4" />
<PostAuthor post={post} />
@ -62,13 +83,16 @@ const PostPage = () => {
<MarkdownRender md={post.md} py="2" mt="6" />
</Box>
<HStack ml="4" spacing="3">{post.rawTags.map(tag => <TagTextCard tag={tag}/>)}</HStack>
<Box mt="6" p="2"><Comments storyID={post.id} comments={comments} onChange={() => getComments(post.id)}/></Box>
</Box>
<Box>
<VStack alignItems="left" pos="fixed" display={{ base: "none", md: 'flex' }} width={["100%", "100%", "25%", "25%"]}>
<VStack alignItems="left" pos="fixed" display={{ base: "none", md: 'flex' }} width={["100%", "100%", "15%", "15%"]}>
<Box pt="16">
{/* <HStack mt="16"> */}
{/* <LikeButton type="like" count={post.likes} onClick={onLike} /> */}
<LikeButton type="unicorn" count={post.likes} onClick={onLike} liked={post.liked}/>
<UnicornLike count={post.likes} onClick={onLike} liked={post.liked}/>
{/* </HStack> */}
</Box>
<Box>
@ -92,6 +116,18 @@ const PostPage = () => {
icon={<svg height="1.7rem" fill="currentColor" viewBox="0 0 448 512"><path d="M352 320c-28.6 0-54.2 12.5-71.8 32.3l-95.5-59.7c9.6-23.4 9.7-49.8 0-73.2l95.5-59.7c17.6 19.8 43.2 32.3 71.8 32.3 53 0 96-43 96-96S405 0 352 0s-96 43-96 96c0 13 2.6 25.3 7.2 36.6l-95.5 59.7C150.2 172.5 124.6 160 96 160c-53 0-96 43-96 96s43 96 96 96c28.6 0 54.2-12.5 71.8-32.3l95.5 59.7c-4.7 11.3-7.2 23.6-7.2 36.6 0 53 43 96 96 96s96-43 96-96c-.1-53-43.1-96-96.1-96zm0-288c35.3 0 64 28.7 64 64s-28.7 64-64 64-64-28.7-64-64 28.7-64 64-64zM96 320c-35.3 0-64-28.7-64-64s28.7-64 64-64 64 28.7 64 64-28.7 64-64 64zm256 160c-35.3 0-64-28.7-64-64s28.7-64 64-64 64 28.7 64 64-28.7 64-64 64z"></path></svg>}
/>
</Box>
{post.creatorId === session?.user.id && <Box mt="4">
<IconButton
aria-label="go to github"
variant="ghost"
layerStyle="textSecondary"
_focus={null}
fontWeight="300"
onClick={() => router.push(`${ReserveUrls.Editor}/post/${post.id}`)}
icon={<svg height="1.5rem" fill="currentColor" viewBox="0 0 512 512"><path d="M493.255 56.236l-37.49-37.49c-24.993-24.993-65.515-24.994-90.51 0L12.838 371.162.151 485.346c-1.698 15.286 11.22 28.203 26.504 26.504l114.184-12.687 352.417-352.417c24.992-24.994 24.992-65.517-.001-90.51zm-95.196 140.45L174 420.745V386h-48v-48H91.255l224.059-224.059 82.745 82.745zM126.147 468.598l-58.995 6.555-30.305-30.305 6.555-58.995L63.255 366H98v48h48v34.745l-19.853 19.853zm344.48-344.48l-49.941 49.941-82.745-82.745 49.941-49.941c12.505-12.505 32.748-12.507 45.255 0l37.49 37.49c12.506 12.506 12.507 32.747 0 45.255z"></path></svg>}
/>
</Box>}
</Box>
@ -103,7 +139,7 @@ const PostPage = () => {
<HStack display={{ base: "flex", md: 'none' }} spacing="4" justifyContent="center">
<Box>
{/* <LikeButton type="like" count={post.likes} onClick={onLike}/> */}
<LikeButton type="unicorn" count={post.likes} onClick={onLike} liked={post.liked}/>
<UnicornLike count={post.likes} onClick={onLike} liked={post.liked}/>
</Box>
<Box>
<IconButton

@ -65,7 +65,7 @@ function PostEditPage() {
duration: 2000,
isClosable: true,
})
router.push(`/${res.data.username}/${res.data.slug}`)
router.push(`/${res.data.username}/${res.data.id}`)
}
return (

@ -0,0 +1,112 @@
package api
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/imdotdev/im.dev/server/internal/session"
"github.com/imdotdev/im.dev/server/internal/story"
"github.com/imdotdev/im.dev/server/pkg/common"
"github.com/imdotdev/im.dev/server/pkg/e"
"github.com/imdotdev/im.dev/server/pkg/models"
"github.com/imdotdev/im.dev/server/pkg/utils"
)
func SubmitComment(c *gin.Context) {
comment := &models.Comment{}
c.Bind(&comment)
comment.Md = strings.TrimSpace(comment.Md)
if comment.Md == "" {
c.JSON(http.StatusBadRequest, "评论内容不能为空")
return
}
// check story exist
exist := story.Exist(comment.TargetID)
if !exist {
c.JSON(http.StatusNotFound, common.RespError(e.NotFound))
return
}
var err *e.Error
if comment.ID == "" { //add comment
user := session.CurrentUser(c)
comment.CreatorID = user.ID
comment.ID = utils.GenStoryID(models.StoryComment)
err = story.AddComment(comment)
} else { // update comment
err = story.EditComment(comment)
}
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
c.JSON(http.StatusOK, common.RespSuccess(nil))
}
func GetStoryComments(c *gin.Context) {
storyID := c.Param("id")
comments, err := story.GetComments(storyID)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
user := session.CurrentUser(c)
for _, comment := range comments {
if user != nil {
comment.Liked = story.GetLiked(comment.ID, user.ID)
}
replies, err := story.GetComments(comment.ID)
if err != nil {
continue
}
comment.Replies = replies
for _, reply := range replies {
if user != nil {
reply.Liked = story.GetLiked(reply.ID, user.ID)
}
}
}
c.JSON(http.StatusOK, common.RespSuccess(comments))
}
func DeleteComment(c *gin.Context) {
id := c.Param("id")
//only admin and owner can delete comment
comment, err := story.GetComment(id)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
user := session.CurrentUser(c)
canDel := false
if user.Role.IsAdmin() {
canDel = true
} else {
if user.ID == comment.CreatorID {
canDel = true
}
}
if !canDel {
c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
return
}
err = story.DeleteComment(id)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
c.JSON(http.StatusOK, common.RespSuccess(nil))
}

@ -2,18 +2,17 @@ package api
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/imdotdev/im.dev/server/internal/posts"
"github.com/imdotdev/im.dev/server/internal/session"
"github.com/imdotdev/im.dev/server/internal/story"
"github.com/imdotdev/im.dev/server/pkg/common"
"github.com/imdotdev/im.dev/server/pkg/e"
)
func GetEditorPosts(c *gin.Context) {
user := session.CurrentUser(c)
ars, err := posts.UserPosts(int64(user.ID))
ars, err := story.UserPosts(int64(user.ID))
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
@ -22,53 +21,15 @@ func GetEditorPosts(c *gin.Context) {
c.JSON(http.StatusOK, common.RespSuccess(ars))
}
func SubmitPost(c *gin.Context) {
res, err := posts.SubmitPost(c)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
c.JSON(http.StatusOK, common.RespSuccess(res))
}
func DeletePost(c *gin.Context) {
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
if id == 0 {
c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
return
}
user := session.CurrentUser(c)
creator, err := posts.GetPostCreator(id)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
if user.ID != creator {
c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
return
}
err = posts.DeletePost(id)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
c.JSON(http.StatusOK, common.RespSuccess(nil))
}
func GetEditorPost(c *gin.Context) {
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
if id == 0 {
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
return
}
user := session.CurrentUser(c)
creator, err := posts.GetPostCreator(id)
creator, err := story.GetPostCreator(id)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
@ -79,7 +40,7 @@ func GetEditorPost(c *gin.Context) {
return
}
ar, err := posts.GetPost(id, "")
ar, err := story.GetPost(id, "")
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return

@ -1,49 +0,0 @@
package api
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/imdotdev/im.dev/server/internal/posts"
"github.com/imdotdev/im.dev/server/internal/session"
"github.com/imdotdev/im.dev/server/pkg/common"
"github.com/imdotdev/im.dev/server/pkg/e"
)
func GetPost(c *gin.Context) {
slug := c.Param("slug")
ar, err := posts.GetPost(0, slug)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
user := session.CurrentUser(c)
if user == nil {
ar.Liked = false
} else {
ar.Liked = posts.GetLiked(ar.ID, user.ID)
}
c.JSON(http.StatusOK, common.RespSuccess(ar))
}
func LikePost(c *gin.Context) {
user := session.CurrentUser(c)
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
if id == 0 {
c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
return
}
err := posts.Like(id, user.ID)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
c.JSON(http.StatusOK, common.RespSuccess(nil))
}

@ -0,0 +1,85 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/imdotdev/im.dev/server/internal/session"
"github.com/imdotdev/im.dev/server/internal/story"
"github.com/imdotdev/im.dev/server/pkg/common"
"github.com/imdotdev/im.dev/server/pkg/e"
)
func SubmitPost(c *gin.Context) {
res, err := story.SubmitPost(c)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
c.JSON(http.StatusOK, common.RespSuccess(res))
}
func DeletePost(c *gin.Context) {
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
return
}
user := session.CurrentUser(c)
creator, err := story.GetPostCreator(id)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
if user.ID != creator {
c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
return
}
err = story.DeletePost(id)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
c.JSON(http.StatusOK, common.RespSuccess(nil))
}
func GetPost(c *gin.Context) {
id := c.Param("id")
ar, err := story.GetPost(id, "")
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
user := session.CurrentUser(c)
if user == nil {
ar.Liked = false
} else {
ar.Liked = story.GetLiked(ar.ID, user.ID)
}
c.JSON(http.StatusOK, common.RespSuccess(ar))
}
func LikeStory(c *gin.Context) {
user := session.CurrentUser(c)
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
return
}
err := story.Like(id, user.ID)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
c.JSON(http.StatusOK, common.RespSuccess(nil))
}

@ -5,8 +5,8 @@ import (
"strconv"
"github.com/gin-gonic/gin"
"github.com/imdotdev/im.dev/server/internal/posts"
"github.com/imdotdev/im.dev/server/internal/session"
"github.com/imdotdev/im.dev/server/internal/tags"
"github.com/imdotdev/im.dev/server/pkg/common"
"github.com/imdotdev/im.dev/server/pkg/e"
"github.com/imdotdev/im.dev/server/pkg/models"
@ -14,7 +14,7 @@ import (
func GetTag(c *gin.Context) {
name := c.Param("name")
res, err := posts.GetTag(name)
res, err := tags.GetTag(0, name)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
@ -24,7 +24,7 @@ func GetTag(c *gin.Context) {
}
func GetTags(c *gin.Context) {
res, err := posts.GetTags()
res, err := tags.GetTags()
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
@ -48,7 +48,7 @@ func SubmitTag(c *gin.Context) {
}
tag.Creator = user.ID
err1 := posts.SubmitTag(tag)
err1 := tags.SubmitTag(tag)
if err1 != nil {
c.JSON(err1.Status, common.RespError(err1.Message))
return
@ -69,7 +69,7 @@ func DeleteTag(c *gin.Context) {
c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
}
err := posts.DeleteTag(id)
err := tags.DeleteTag(id)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return

@ -1,53 +0,0 @@
package posts
import (
"database/sql"
"net/http"
"github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/e"
)
func Like(postId int64, userId int64) *e.Error {
// 判断文章是否存在
exist := postExist(postId)
if !exist {
return e.New(http.StatusNotFound, e.NotFound)
}
// 查询当前like状态
liked := GetLiked(postId, userId)
if liked {
// 已经喜欢过该篇文章,更改为不喜欢
_, err := db.Conn.Exec("DELETE FROM post_like WHERE post_id=? and user_id=?", postId, userId)
if err != nil {
return e.New(http.StatusInternalServerError, e.Internal)
}
db.Conn.Exec("UPDATE posts SET like_count=like_count-1 WHERE id=?", postId)
} else {
_, err := db.Conn.Exec("INSERT INTO post_like (post_id,user_id) VALUES (?,?)", postId, userId)
if err != nil {
return e.New(http.StatusInternalServerError, e.Internal)
}
db.Conn.Exec("UPDATE posts SET like_count=like_count+1 WHERE id=?", postId)
}
return nil
}
func GetLiked(postID, userID int64) bool {
liked := false
var nid int64
err := db.Conn.QueryRow("SELECT post_id FROM post_like WHERE post_id=? and user_id=?", postID, userID).Scan(&nid)
if err != nil && err != sql.ErrNoRows {
logger.Warn("query post like error", "error", err)
return false
}
if nid != 0 {
liked = true
}
return liked
}

@ -1,5 +0,0 @@
package posts
import "github.com/imdotdev/im.dev/server/pkg/log"
var logger = log.RootLogger.New("logger", "posts")

@ -48,32 +48,24 @@ func (s *Server) Start() error {
}
postR := r.Group("/post")
{
postR.GET("/:slug", api.GetPost)
postR.POST("/like/:id", api.LikePost, IsLogin())
}
r.GET("/post/:id", api.GetPost)
r.POST("/story/like/:id", api.LikeStory, IsLogin())
r.GET("/story/comments/:id", api.GetStoryComments)
r.POST("/story/comment", api.SubmitComment, IsLogin())
r.DELETE("/comment/:id", api.DeleteComment, IsLogin())
r.GET("/editor/posts", api.GetEditorPosts, IsLogin())
r.POST("/editor/post", api.SubmitPost, IsLogin())
r.DELETE("/editor/post/:id", api.DeletePost, IsLogin())
r.GET("/editor/post/:id", api.GetEditorPost, IsLogin())
r.POST("/admin/tag", api.SubmitTag, IsLogin())
r.DELETE("/admin/tag/:id", api.DeleteTag, IsLogin())
r.GET("/tags", api.GetTags)
r.GET("/tag/:name", api.GetTag)
// login apis
lr := r.Group("", IsLogin())
{
editorR := lr.Group("/editor")
{
editorR.GET("/posts", api.GetEditorPosts)
editorR.POST("/post", api.SubmitPost)
editorR.DELETE("/post/:id", api.DeletePost)
editorR.GET("/post/:id", api.GetEditorPost)
}
adminR := lr.Group("/admin")
{
adminR.POST("/tag", api.SubmitTag)
adminR.DELETE("/tag/:id", api.DeleteTag)
}
lr.GET("/tags", api.GetTags)
lr.GET("/tag/:name", api.GetTag)
}
err := router.Run(config.Data.Server.Addr)
if err != nil {
logger.Crit("start backend server error", "error", err)

@ -29,7 +29,7 @@ var sqlTables = map[string]string{
`,
"posts": `CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
id VARCHAR(255) PRIMARY KEY,
creator INTEGER NOT NULL,
slug VARCHAR(64) NOT NULL,
title VARCHAR(255) NOT NULL,
@ -37,7 +37,9 @@ var sqlTables = map[string]string{
url VARCHAR(255),
cover VARCHAR(255),
brief TEXT,
like_count INTEGER DEFAULT 0,
likes INTEGER DEFAULT 0,
views INTEGER DEFAULT 0,
status tinyint NOT NULL,
created DATETIME NOT NULL,
updated DATETIME
);
@ -49,14 +51,15 @@ var sqlTables = map[string]string{
ON posts (creator, slug);
`,
"post_like": `CREATE TABLE IF NOT EXISTS post_like (
post_id INTEGER,
user_id INTEGER
"like": `CREATE TABLE IF NOT EXISTS like (
id VARCHAR(255),
user_id INTEGER,
created DATETIME NOT NULL
);
CREATE INDEX IF NOT EXISTS post_like_postid
ON post_like (post_id);
CREATE INDEX IF NOT EXISTS post_like_userid
ON post_like (user_id);
CREATE INDEX IF NOT EXISTS like_id
ON like (id);
CREATE INDEX IF NOT EXISTS like_userid
ON like (user_id);
`,
"tags": `CREATE TABLE IF NOT EXISTS tags (
@ -79,11 +82,26 @@ var sqlTables = map[string]string{
"tag_post": `CREATE TABLE IF NOT EXISTS tag_post (
tag_id INTEGER,
post_id INTEGER
post_id VARCHAR(255)
);
CREATE INDEX IF NOT EXISTS tag_post_tagid
ON tag_post (tag_id);
CREATE INDEX IF NOT EXISTS tag_post_postid
ON tag_post (post_id);
`,
"comments": `CREATE TABLE IF NOT EXISTS comments (
id VARCHAR(255) PRIMARY KEY,
target_id VARCHAR(255),
creator INTEGER,
MD TEXT,
likes INTEGER DEFAULT 0,
created DATETIME NOT NULL,
updated DATETIME
);
CREATE INDEX IF NOT EXISTS comments_targetid
ON comments (target_id);
CREATE INDEX IF NOT EXISTS comments_creator
ON comments (creator);
`,
}

@ -0,0 +1,114 @@
package story
import (
"database/sql"
"net/http"
"sort"
"time"
"github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/e"
"github.com/imdotdev/im.dev/server/pkg/models"
"github.com/imdotdev/im.dev/server/pkg/utils"
)
func AddComment(c *models.Comment) *e.Error {
md := utils.Compress(c.Md)
now := time.Now()
_, err := db.Conn.Exec("INSERT INTO comments (id,target_id,creator,md,created,updated) VALUES(?,?,?,?,?,?)",
c.ID, c.TargetID, c.CreatorID, md, now, now)
if err != nil {
logger.Warn("add comment error", "error", err)
return e.New(http.StatusInternalServerError, e.Internal)
}
return nil
}
func EditComment(c *models.Comment) *e.Error {
md := utils.Compress(c.Md)
now := time.Now()
_, err := db.Conn.Exec("UPDATE comments SET md=?,updated=? WHERE id=?",
md, now, c.ID)
if err != nil {
logger.Warn("update comment error", "error", err)
return e.New(http.StatusInternalServerError, e.Internal)
}
return nil
}
func GetComments(storyID string) (models.Comments, *e.Error) {
comments := make(models.Comments, 0)
rows, err := db.Conn.Query("SELECT id,target_id,creator,md,likes,created,updated FROM comments WHERE target_id=?", storyID)
if err != nil && err != sql.ErrNoRows {
logger.Warn("get comments error", "error", err)
return comments, e.New(http.StatusInternalServerError, e.Internal)
}
for rows.Next() {
c := &models.Comment{}
var rawMd []byte
err := rows.Scan(&c.ID, &c.TargetID, &c.CreatorID, &rawMd, &c.Likes, &c.Created, &c.Updated)
if err != nil {
logger.Warn("scan comment error", "error", err)
continue
}
md, _ := utils.Uncompress(rawMd)
c.Md = string(md)
c.Creator = &models.UserSimple{ID: c.CreatorID}
err = c.Creator.Query()
comments = append(comments, c)
}
sort.Sort(comments)
return comments, nil
}
func GetComment(id string) (*models.Comment, *e.Error) {
c := &models.Comment{}
var rawMd []byte
err := db.Conn.QueryRow("SELECT id,target_id,creator,md,likes,created,updated FROM comments WHERE id=?", id).Scan(
&c.ID, &c.TargetID, &c.CreatorID, &rawMd, &c.Likes, &c.Created, &c.Updated,
)
if err != nil {
logger.Warn("get comment error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
md, _ := utils.Uncompress(rawMd)
c.Md = string(md)
return c, nil
}
func DeleteComment(id string) *e.Error {
_, err := db.Conn.Exec("DELETE FROM comments WHERE id=?", id)
if err != nil {
logger.Warn("delete comment error", "error", err)
return e.New(http.StatusInternalServerError, e.Internal)
}
return nil
}
func commentExist(id string) bool {
var nid string
err := db.Conn.QueryRow("SELECT id FROM comments WHERE id=?", id).Scan(&nid)
if err != nil && err != sql.ErrNoRows {
logger.Warn("query comment error", "error", err)
return false
}
if nid == id {
return true
}
return false
}

@ -0,0 +1,57 @@
package story
import (
"database/sql"
"fmt"
"net/http"
"time"
"github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/e"
)
func Like(storyID string, userId int64) *e.Error {
exist := Exist(storyID)
if !exist {
return e.New(http.StatusNotFound, e.NotFound)
}
tbl := getStorySqlTable(storyID)
// 查询当前like状态
liked := GetLiked(storyID, userId)
if liked {
// 已经喜欢过该篇文章,更改为不喜欢
_, err := db.Conn.Exec("DELETE FROM like WHERE id=? and user_id=?", storyID, userId)
if err != nil {
return e.New(http.StatusInternalServerError, e.Internal)
}
db.Conn.Exec(fmt.Sprintf("UPDATE %s SET likes=likes-1 WHERE id=?", tbl), storyID)
} else {
_, err := db.Conn.Exec("INSERT INTO like (id,user_id,created) VALUES (?,?,?)", storyID, userId, time.Now())
if err != nil {
logger.Warn("add like error", "error", err)
return e.New(http.StatusInternalServerError, e.Internal)
}
db.Conn.Exec(fmt.Sprintf("UPDATE %s SET likes=likes+1 WHERE id=?", tbl), storyID)
}
return nil
}
func GetLiked(storyID string, userID int64) bool {
liked := false
var nid string
err := db.Conn.QueryRow("SELECT id FROM like WHERE id=? and user_id=?", storyID, userID).Scan(&nid)
if err != nil && err != sql.ErrNoRows {
logger.Warn("query story like error", "error", err)
return false
}
if nid == storyID {
liked = true
}
return liked
}

@ -1,4 +1,4 @@
package posts
package story
import (
"database/sql"
@ -12,6 +12,7 @@ import (
"github.com/asaskevich/govalidator"
"github.com/gin-gonic/gin"
"github.com/imdotdev/im.dev/server/internal/session"
"github.com/imdotdev/im.dev/server/internal/tags"
"github.com/imdotdev/im.dev/server/pkg/config"
"github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/e"
@ -21,7 +22,7 @@ import (
func UserPosts(uid int64) (models.Posts, *e.Error) {
ars := make(models.Posts, 0)
rows, err := db.Conn.Query("select id,slug,title,url,cover,brief,created,updated from posts where creator=?", uid)
rows, err := db.Conn.Query("select id,slug,title,url,cover,brief,likes,views,created,updated from posts where creator=?", uid)
if err != nil {
if err == sql.ErrNoRows {
return ars, e.New(http.StatusNotFound, e.NotFound)
@ -34,7 +35,7 @@ func UserPosts(uid int64) (models.Posts, *e.Error) {
creator.Query()
for rows.Next() {
ar := &models.Post{}
err := rows.Scan(&ar.ID, &ar.Slug, &ar.Title, &ar.URL, &ar.Cover, &ar.Brief, &ar.Created, &ar.Updated)
err := rows.Scan(&ar.ID, &ar.Slug, &ar.Title, &ar.URL, &ar.Cover, &ar.Brief, &ar.Likes, &ar.Views, &ar.Created, &ar.Updated)
if err != nil {
logger.Warn("scan post error", "error", err)
continue
@ -98,16 +99,15 @@ func SubmitPost(c *gin.Context) (map[string]string, *e.Error) {
setSlug(user.ID, post)
if post.ID == 0 {
if post.ID == "" {
post.ID = utils.GenStoryID(models.StoryPost)
//create
res, err := db.Conn.Exec("INSERT INTO posts (creator,slug, title, md, url, cover, brief, created, updated) VALUES(?,?,?,?,?,?,?,?,?)",
user.ID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, now, now)
_, err := db.Conn.Exec("INSERT INTO posts (id,creator,slug, title, md, url, cover, brief,status, created, updated) VALUES(?,?,?,?,?,?,?,?,?,?,?)",
post.ID, user.ID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, models.StatusPublished, now, now)
if err != nil {
logger.Warn("submit post error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
post.ID, _ = res.LastInsertId()
} else {
// 只有创建者自己才能更新内容
creator, _ := GetPostCreator(post.ID)
@ -142,11 +142,11 @@ func SubmitPost(c *gin.Context) (map[string]string, *e.Error) {
return map[string]string{
"username": user.Username,
"slug": post.Slug,
"id": post.ID,
}, nil
}
func DeletePost(id int64) *e.Error {
func DeletePost(id string) *e.Error {
_, err := db.Conn.Exec("DELETE FROM posts WHERE id=?", id)
if err != nil {
logger.Warn("delete post error", "error", err)
@ -156,11 +156,11 @@ func DeletePost(id int64) *e.Error {
return nil
}
func GetPost(id int64, slug string) (*models.Post, *e.Error) {
func GetPost(id string, slug string) (*models.Post, *e.Error) {
ar := &models.Post{}
var rawmd []byte
err := db.Conn.QueryRow("select id,slug,title,md,url,cover,brief,creator,like_count,created,updated from posts where id=? or slug=?", id, slug).Scan(
&ar.ID, &ar.Slug, &ar.Title, &rawmd, &ar.URL, &ar.Cover, &ar.Brief, &ar.CreatorID, &ar.Likes, &ar.Created, &ar.Updated,
err := db.Conn.QueryRow("select id,slug,title,md,url,cover,brief,creator,likes,views,created,updated from posts where id=? or slug=?", id, slug).Scan(
&ar.ID, &ar.Slug, &ar.Title, &rawmd, &ar.URL, &ar.Cover, &ar.Brief, &ar.CreatorID, &ar.Likes, &ar.Views, &ar.Created, &ar.Updated,
)
if err != nil {
if err == sql.ErrNoRows {
@ -176,22 +176,35 @@ func GetPost(id int64, slug string) (*models.Post, *e.Error) {
err = ar.Creator.Query()
// get tags
tags := make([]int64, 0)
rows, err := db.Conn.Query("SELECT tag_id FROM tag_post WHERE post_id=?", id)
t := make([]int64, 0)
rows, err := db.Conn.Query("SELECT tag_id FROM tag_post WHERE post_id=?", ar.ID)
if err != nil && err != sql.ErrNoRows {
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
ar.RawTags = make([]*models.Tag, 0)
for rows.Next() {
var tag int64
err = rows.Scan(&tag)
tags = append(tags, tag)
t = append(t, tag)
rawTag, err := tags.GetTag(tag, "")
if err == nil {
ar.RawTags = append(ar.RawTags, rawTag)
}
}
ar.Tags = t
// add views count
_, err = db.Conn.Exec("UPDATE posts SET views=? WHERE id=?", ar.Views+1, ar.ID)
if err != nil {
logger.Warn("update post view count error", "error", err)
}
ar.Tags = tags
return ar, nil
}
func GetPostCreator(id int64) (int64, *e.Error) {
func GetPostCreator(id string) (int64, *e.Error) {
var uid int64
err := db.Conn.QueryRow("SELECT creator FROM posts WHERE id=?", id).Scan(&uid)
if err != nil {
@ -205,15 +218,15 @@ func GetPostCreator(id int64) (int64, *e.Error) {
return uid, nil
}
func postExist(id int64) bool {
var nid int64
func postExist(id string) bool {
var nid string
err := db.Conn.QueryRow("SELECT id from posts WHERE id=?", id).Scan(&nid)
if err != nil {
logger.Warn("query post error", "error", err)
return false
}
if nid == 0 {
if nid != id {
return false
}

@ -0,0 +1,30 @@
package story
import (
"github.com/imdotdev/im.dev/server/pkg/log"
"github.com/imdotdev/im.dev/server/pkg/models"
)
var logger = log.RootLogger.New("logger", "story")
func Exist(id string) bool {
switch id[:1] {
case models.StoryPost:
return postExist(id)
case models.StoryComment:
return commentExist(id)
default:
return false
}
}
func getStorySqlTable(id string) string {
switch id[:1] {
case models.StoryPost:
return "posts"
case models.StoryComment:
return "comments"
default:
return "unknown"
}
}

@ -1,4 +1,4 @@
package posts
package tags
import (
"database/sql"
@ -10,10 +10,13 @@ import (
"github.com/asaskevich/govalidator"
"github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/e"
"github.com/imdotdev/im.dev/server/pkg/log"
"github.com/imdotdev/im.dev/server/pkg/models"
"github.com/imdotdev/im.dev/server/pkg/utils"
)
var logger = log.RootLogger.New("logger", "tags")
func SubmitTag(tag *models.Tag) *e.Error {
if strings.TrimSpace(tag.Title) == "" {
return e.New(http.StatusBadRequest, "title格式不合法")
@ -103,10 +106,10 @@ func DeleteTag(id int64) *e.Error {
return nil
}
func GetTag(name string) (*models.Tag, *e.Error) {
func GetTag(id int64, name string) (*models.Tag, *e.Error) {
tag := &models.Tag{}
var rawmd []byte
err := db.Conn.QueryRow("SELECT id,creator,title,name,icon,cover,created,updated,md from tags where name=?", name).Scan(
err := db.Conn.QueryRow("SELECT id,creator,title,name,icon,cover,created,updated,md from tags where id=? or name=?", id, name).Scan(
&tag.ID, &tag.Creator, &tag.Title, &tag.Name, &tag.Icon, &tag.Cover, &tag.Created, &tag.Updated, &rawmd,
)
if err != nil {

@ -0,0 +1,24 @@
package models
import "time"
type Comment struct {
ID string `json:"id"`
TargetID string `json:"targetID"` // 被评论的文章、书籍等ID
CreatorID int64 `json:"creatorID"`
Creator *UserSimple `json:"creator"`
Md string `json:"md"`
Likes int `json:"likes"`
Liked bool `json:"liked"`
Replies []*Comment `json:"replies"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
type Comments []*Comment
func (ar Comments) Len() int { return len(ar) }
func (ar Comments) Swap(i, j int) { ar[i], ar[j] = ar[j], ar[i] }
func (ar Comments) Less(i, j int) bool {
return ar[i].Created.Unix() > ar[j].Created.Unix()
}

@ -2,22 +2,30 @@ package models
import "time"
const (
StatusDraft = 1
StatusPublished = 2
StatusHidden = 3
)
type Post struct {
ID int64 `json:"id"`
Creator *UserSimple `json:"creator"`
CreatorID int64 `json:"creatorId"`
Title string `json:"title"`
Slug string `json:"slug"`
Md string `json:"md"`
URL string `json:"url"`
Cover string `json:"cover"`
Brief string `json:"brief"`
Tags []int64 `json:"tags"`
Likes int `json:"likes"`
Liked bool `json:"liked"`
Recommands int `json:"recommands"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
ID string `json:"id"`
Creator *UserSimple `json:"creator"`
CreatorID int64 `json:"creatorId"`
Title string `json:"title"`
Slug string `json:"slug"`
Md string `json:"md"`
URL string `json:"url"`
Cover string `json:"cover"`
Brief string `json:"brief"`
Tags []int64 `json:"tags"`
RawTags []*Tag `json:"rawTags"`
Likes int `json:"likes"`
Liked bool `json:"liked"`
Views int `json:"views"`
Status int `json:"status"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
type Posts []*Post

@ -0,0 +1,6 @@
package models
const (
StoryPost = "1"
StoryComment = "2"
)

@ -17,7 +17,7 @@ type User struct {
Created time.Time `json:"created"`
}
const DefaultAvatar = "https://placekitten.com/100/100"
const DefaultAvatar = "https://cdn.hashnode.com/res/hashnode/image/upload/v1600792675173/rY-APy9Fc.png?auto=compress"
func (user *User) Query(id int64, username string, email string) error {
err := db.Conn.QueryRow(`SELECT id,username,role,nickname,email,avatar,last_seen_at,created FROM user WHERE id=? or username=? or email=?`,

@ -0,0 +1,8 @@
package utils
import "github.com/lithammer/shortuuid/v3"
func GenStoryID(storyType string) string {
u := shortuuid.New()
return storyType + u
}

@ -9,7 +9,7 @@ export const Card = (props: BoxProps) => {
borderRadius=".5rem"
borderWidth="1px"
p={["0rem",".5rem","1rem"]}
boxShadow="0 1px 2px 0 rgb(0 0 0 / 5%)"
boxShadow="0 1px 1px 0 rgb(0 0 0 / 5%)"
{...props}
/>
)

@ -0,0 +1,111 @@
import React, { useState } from "react"
import { Avatar, Divider, Flex, Heading, HStack, IconButton, Text, VStack, chakra, Menu, MenuButton, MenuList, MenuItem, Box } from "@chakra-ui/react"
import { Comment } from "src/types/comments"
import Card from "components/card"
import { getUserName } from "utils/user"
import moment from 'moment'
import { MarkdownRender } from "components/markdown-editor/render"
import HeartLike from "components/posts/heart-like"
import { FaRegEdit, FaRegFlag, FaRegTrashAlt, FaReply, FaTrash } from "react-icons/fa"
import { User } from "src/types/session"
import CommentEditor from "./editor"
import { requestApi } from "utils/axios/request"
import Reply from "./reply"
interface Props {
user: User
comment: Comment
onChange: any
}
export const CommentCard = (props: Props) => {
const { comment, user, onChange} = props
const [editorVisible, setEditorVisible] = useState(false)
const [replyVisible,setReplyVisible] = useState(false)
const [reply,setReply] = useState('')
const submitReply = async (md) => {
await requestApi.post('/story/comment',{targetID: comment.id, md: md})
setReplyVisible(false);
}
const changeComment = async (md) => {
await requestApi.post('/story/comment',{...comment, md})
setEditorVisible(false)
onChange()
}
const deleteComment = async id => {
await requestApi.delete(`/comment/${id}`)
onChange()
}
const likeComment = async (id) => {
await requestApi.post(`/story/like/${id}`)
onChange()
}
return (
<>{
editorVisible ? (user && <CommentEditor user={user} md={comment.md} onSubmit={md => {setEditorVisible(false);changeComment(md)}} onCancel={() => setEditorVisible(false)} />) :
<Card>
<VStack alignItems="left">
<Flex justifyContent="space-between" width="100%" alignItems="top">
<HStack spacing="4">
<Avatar src={comment.creator.avatar}></Avatar>
<VStack alignItems="left">
<Heading size="sm" fontSize="1.2rem">{getUserName(comment.creator)}</Heading>
<Text layerStyle="textSecondary" fontSize=".9rem">about this user</Text>
</VStack>
</HStack>
<Text layerStyle="textSecondary" marginTop="2px">{moment(comment.created).fromNow()}</Text>
</Flex>
<MarkdownRender md={comment.md} pl="16" pr="2" mt="3" />
<Flex justifyContent="space-between" pl="16" pr="2">
<HeartLike liked={comment.liked} count={comment.likes} onClick={() => likeComment(comment.id)} />
<HStack>
{user && <IconButton
aria-label="go to github"
variant="ghost"
_focus={null}
color="gray.500"
icon={<FaReply />}
onClick={() => setReplyVisible(!replyVisible)}
fontSize="20px"
/>}
<Menu>
<IconButton
as={MenuButton}
aria-label="go to github"
variant="ghost"
_focus={null}
color="gray.500"
icon={<chakra.svg fill='gray.500' height="24px" viewBox="0 0 512 512"><path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm216 248c0 118.7-96.1 216-216 216-118.7 0-216-96.1-216-216 0-118.7 96.1-216 216-216 118.7 0 216 96.1 216 216zm-207.5 86.6l115-115.1c4.7-4.7 4.7-12.3 0-17l-7.1-7.1c-4.7-4.7-12.3-4.7-17 0L256 303l-99.5-99.5c-4.7-4.7-12.3-4.7-17 0l-7.1 7.1c-4.7 4.7-4.7 12.3 0 17l115 115.1c4.8 4.6 12.4 4.6 17.1-.1z"></path></chakra.svg>}
_hover={null}
_active={null}
/>
<MenuList>
{user && <MenuItem onClick={() => setEditorVisible(true)}><FaRegEdit /><chakra.span ml="2">Edit</chakra.span></MenuItem>}
{user && <MenuItem onClick={() => deleteComment(comment.id)}><FaRegTrashAlt /><chakra.span ml="2">Delete</chakra.span></MenuItem>}
<MenuItem><FaRegFlag /><chakra.span ml="2">Report</chakra.span></MenuItem>
</MenuList>
</Menu>
</HStack>
</Flex>
{replyVisible && <Box pl="16" pr="2"><CommentEditor user={user} md={reply} onSubmit={md => {submitReply(md)}} onCancel={() => setReplyVisible(false)} /></Box>}
{comment.replies.map(reply =>
<Box pl="16" key={reply.id}>
<Divider mb="4"/>
<Reply user={props.user} onChange={props.onChange} comment={reply}/>
</Box>)}
</VStack>
</Card>}
</>
)
}
export default CommentCard

@ -0,0 +1,42 @@
import React, { useState } from "react"
import {Text, Flex, HStack, Button, VStack } from "@chakra-ui/react"
import Card from "components/card"
import useSession from "hooks/use-session"
import { requestApi } from "utils/axios/request"
import CommentCard from "./comment"
import CommentEditor from "./editor"
interface Props {
storyID: string
comments: any[]
onChange: any
}
export const Comments = ({storyID, comments,onChange }: Props) => {
const [editorVisible,setEditorVisible] = useState(false)
const session = useSession()
const submitComment = async (md) => {
await requestApi.post('/story/comment',{targetID: storyID, md: md})
setEditorVisible(false)
onChange()
}
return (
<VStack spacing="4" alignItems="left">
<Card>
<Flex justifyContent="space-between">
<HStack>
<Text fontWeight="600" fontSize="1.1rem">Comments ({comments.length})</Text>
</HStack>
<Button variant="outline" colorScheme="teal" onClick={() => setEditorVisible(true)} _focus={null}>Add comment</Button>
</Flex>
</Card>
{editorVisible && session && <CommentEditor user={session.user} md="" onSubmit={md => submitComment(md)} onCancel={() => setEditorVisible(false)}/>}
{comments.map(comment => <CommentCard user={session?.user} key={comment.id} comment={comment} onChange={onChange}/>)}
</VStack>
)
}
export default Comments

@ -0,0 +1,45 @@
import React, { useState } from "react"
import { Box, Text, Flex, HStack, useColorModeValue, Button, VStack, Avatar, Heading, propNames } from "@chakra-ui/react"
import Card from "components/card"
import { MarkdownEditor } from "components/markdown-editor/editor"
import useSession from "hooks/use-session"
import { getUserName } from "utils/user"
import EditModeSelect from "components/edit-mode-select"
import { EditMode } from "src/types/editor"
import { MarkdownRender } from "components/markdown-editor/render"
import { Comment } from "src/types/comments"
import { User } from "src/types/session"
interface Props {
user: User
md: string
onSubmit: any
onCancel: any
}
export const CommentEditor = (props: Props) => {
const [editMode,setEditMode] = useState(EditMode.Edit)
const [md,setMd] = useState(props.md)
return (
<Card>
<Flex justifyContent="space-between">
<HStack>
<Avatar src={props.user.avatar} size="sm"></Avatar>
<Heading size="md">{getUserName(props.user)}</Heading>
</HStack>
<EditModeSelect onChange={m => setEditMode(m)}/>
</Flex>
<Box mt="4" h="300px">
{editMode===EditMode.Edit ? <MarkdownEditor md={md} onChange={md => setMd(md)}/> : <MarkdownRender md={md} overflowY="scroll"/>}
</Box>
<Flex justifyContent="flex-end">
<Button variant="ghost" onClick={props.onCancel}>Cancel</Button>
<Button variant="outline" onClick={() => props.onSubmit(md)}>Submit</Button>
</Flex>
</Card>
)
}
export default CommentEditor

@ -0,0 +1,105 @@
import React, { useState } from "react"
import { Avatar, Divider, Flex, Heading, HStack, IconButton, Text, VStack, chakra, Menu, MenuButton, MenuList, MenuItem, Box } from "@chakra-ui/react"
import { Comment } from "src/types/comments"
import Card from "components/card"
import { getUserName } from "utils/user"
import moment from 'moment'
import { MarkdownRender } from "components/markdown-editor/render"
import HeartLike from "components/posts/heart-like"
import { FaRegEdit, FaRegFlag, FaRegTrashAlt, FaReply, FaTrash } from "react-icons/fa"
import { User } from "src/types/session"
import CommentEditor from "./editor"
import { requestApi } from "utils/axios/request"
interface Props {
user: User
comment: Comment
onChange: any
}
export const Reply = (props: Props) => {
const { comment, user,onChange} = props
const [editorVisible, setEditorVisible] = useState(false)
const [replyVisible,setReplyVisible] = useState(false)
const [reply,setReply] = useState('')
const submitReply = async (md) => {
await requestApi.post('/story/comment',{targetID: comment.id, md: md})
setReplyVisible(false);
}
const changeReply = async (md) => {
await requestApi.post('/story/comment',{...comment, md})
setEditorVisible(false)
onChange()
}
const deleteReply = async id => {
await requestApi.delete(`/comment/${id}`)
onChange()
}
const likeReply = async (id) => {
await requestApi.post(`/story/like/${id}`)
onChange()
}
return (
<>{
editorVisible ? (user && <CommentEditor user={user} md={comment.md} onSubmit={md => {setEditorVisible(false);changeReply(md)}} onCancel={() => setEditorVisible(false)} />) :
<VStack alignItems="left">
<Flex width="100%" alignItems="center" justifyContent="space-between">
<HStack spacing="4">
<Avatar src={comment.creator.avatar} width="40px" height="40px"></Avatar>
<VStack alignItems="left">
<Heading size="sm" fontSize="1.1rem">{getUserName(comment.creator)}</Heading>
</VStack>
</HStack>
<Text layerStyle="textSecondary" ml="2" fontSize=".9rem">{moment(comment.created).fromNow()}</Text>
</Flex>
<MarkdownRender md={comment.md} pl="16" pr="2" mt="3" />
<Flex justifyContent="space-between" pl="16" pr="2">
<HeartLike liked={comment.liked} count={comment.likes} onClick={() => likeReply(comment.id)} />
<HStack>
{user && <IconButton
aria-label="go to github"
variant="ghost"
_focus={null}
color="gray.500"
icon={<FaReply />}
onClick={() => setReplyVisible(!replyVisible)}
fontSize="18px"
/>}
<Menu>
<IconButton
as={MenuButton}
aria-label="go to github"
variant="ghost"
_focus={null}
color="gray.500"
icon={<chakra.svg fill='gray.500' height="21px" viewBox="0 0 512 512"><path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm216 248c0 118.7-96.1 216-216 216-118.7 0-216-96.1-216-216 0-118.7 96.1-216 216-216 118.7 0 216 96.1 216 216zm-207.5 86.6l115-115.1c4.7-4.7 4.7-12.3 0-17l-7.1-7.1c-4.7-4.7-12.3-4.7-17 0L256 303l-99.5-99.5c-4.7-4.7-12.3-4.7-17 0l-7.1 7.1c-4.7 4.7-4.7 12.3 0 17l115 115.1c4.8 4.6 12.4 4.6 17.1-.1z"></path></chakra.svg>}
_hover={null}
_active={null}
/>
<MenuList>
{user && <MenuItem onClick={() => setEditorVisible(true)}><FaRegEdit /><chakra.span ml="2">Edit</chakra.span></MenuItem>}
{user && <MenuItem onClick={() => deleteReply(comment.id)}><FaRegTrashAlt /><chakra.span ml="2">Delete</chakra.span></MenuItem>}
<MenuItem><FaRegFlag /><chakra.span ml="2">Report</chakra.span></MenuItem>
</MenuList>
</Menu>
</HStack>
</Flex>
{replyVisible &&
<Box pl="16" pr="2">
<CommentEditor user={user} md={reply} onSubmit={md => {submitReply(md)}} onCancel={() => setReplyVisible(false)} />
</Box>}
</VStack>}
</>
)
}
export default Reply

@ -0,0 +1,33 @@
import React from "react"
import { Box, BoxProps, HStack, useRadioGroup } from "@chakra-ui/react"
import { EditMode } from "src/types/editor"
import RadioCard from "./radio-card"
interface Props {
onChange : any
}
export const EditModeSelect = (props: Props) => {
const editOptions = [EditMode.Edit, EditMode.Preview]
const { getRootProps, getRadioProps } = useRadioGroup({
name: "framework",
defaultValue: EditMode.Edit,
onChange: (v) => {
props.onChange(v)
},
})
const group = getRootProps()
return (
<HStack {...group}>
{editOptions.map((value) => {
const radio = getRadioProps({ value })
return (
<RadioCard key={value} {...radio} bg="teal" color="white">
{value}
</RadioCard>
)
})}
</HStack>
)
}
export default EditModeSelect

@ -9,6 +9,7 @@ import WebsiteLink from 'components/website-link';
type Props = PropsOf<typeof chakra.div> & {
md: string
fontSize?: string
scroll?: boolean
}
const ChakraMarkdown = chakra(Markdown)
@ -36,6 +37,7 @@ export function MarkdownRender({ md,fontSize, ...rest }:Props) {
},
},
}}
maxWidth={["800px","800px","800px","1000px"]}
></ChakraMarkdown>
</div>
);

@ -0,0 +1,39 @@
import { chakra, HStack, IconButton, Image, Tooltip, useColorMode, useColorModeValue } from "@chakra-ui/react";
import { FaHeart, FaRegHeart } from "react-icons/fa";
interface Props {
count: number
onClick: any
liked: boolean
}
const UnicornLike = (props: Props) => {
const label = "I like it"
return (
<HStack alignItems="center">
<Tooltip label={label} size="sm">
{props.liked? <IconButton
aria-label="go to github"
variant="ghost"
_focus={null}
color="red.400"
icon={<FaHeart />}
onClick={props.onClick}
fontSize="20px"
/> :
<IconButton
aria-label="go to github"
variant="ghost"
_focus={null}
color="gray.500"
icon={<FaRegHeart />}
onClick={props.onClick}
fontSize="20px"
/>}
</Tooltip>
<chakra.span layerStyle="textSecondary" fontWeight="600">{props.count}</chakra.span>
</HStack>
)
}
export default UnicornLike

@ -0,0 +1,21 @@
import React from "react"
import {Box, Heading, Image, Text, HStack,Button, Flex,PropsOf,Link} from "@chakra-ui/react"
import { Tag } from "src/types/tag"
import { ReserveUrls } from "src/data/reserve-urls"
import NextLink from "next/link"
import { useRouter } from "next/router"
interface Props {
tag: Tag
}
export const TagTextCard = (props:Props) =>{
const {tag} = props
const router = useRouter()
return (
<Button variant="outline" onClick={() => router.push(`/tags/${tag.name}`)}>#{tag.name}</Button>
)
}
export default TagTextCard

@ -17,7 +17,7 @@ export const TextPostCard= (props:Props) =>{
const gap = moment(post.created).fromNow()
return (
<Flex justifyContent="space-between" {...rest}>
<VStack alignItems="left" as="a" href={post.url ? post.url : `/${post.creator.username}/${post.slug}`}>
<VStack alignItems="left" as="a" href={post.url ? post.url : `/${post.creator.username}/${post.id}`}>
<Heading size="sm" display="flex" alignItems="center">
{post.url ? <Tag size="sm" mr="2"></Tag> : <Tag size="sm" mr="2"></Tag>}
{post.title}

@ -1,25 +1,13 @@
import { chakra, HStack, IconButton, Image, Tooltip, useColorMode, useColorModeValue } from "@chakra-ui/react";
interface Props {
type: string
count: number
onClick: any
liked: boolean
}
const LikeButton = (props: Props) => {
let imgSrc: string
let label: string
switch (props.type) {
case "like":
imgSrc = "https://cdn.hashnode.com/res/hashnode/image/upload/v1594643814744/9iXxz71TL.png?auto=compress"
label = "Love it"
break;
case "unicorn":
imgSrc = "https://cdn.hashnode.com/res/hashnode/image/upload/v1594643772437/FYDU5k2kQ.png?auto=compress"
label = "I love it"
default:
break;
}
const UnicornLike = (props: Props) => {
const imgSrc = "https://cdn.hashnode.com/res/hashnode/image/upload/v1594643772437/FYDU5k2kQ.png?auto=compress"
const label = "I love it"
return (
<HStack>
<Tooltip label={label} size="sm">
@ -30,7 +18,7 @@ const LikeButton = (props: Props) => {
_focus={null}
icon={<Image width="38px" src={imgSrc} />}
onClick={props.onClick}
border={props.liked ? `1px solid ${useColorModeValue('gray','pink')}` : null}
border={props.liked ? `1px solid ${useColorModeValue('gray', 'pink')}` : null}
/>
</Tooltip>
<chakra.span layerStyle="textSecondary" fontWeight="600" marginBottom="-3px">{props.count}</chakra.span>
@ -38,4 +26,4 @@ const LikeButton = (props: Props) => {
)
}
export default LikeButton
export default UnicornLike

@ -0,0 +1,14 @@
import { UserSimple } from "./session";
export interface Comment {
id: string
targetID: string
creatorID: number
creator?: UserSimple
md: string
liked?: boolean
likes: number
replies: Comment[]
created?: string
updated?: string
}

@ -1,9 +1,10 @@
import {User} from './session'
import { UserSimple} from './session'
import { Tag } from './tag';
export interface Post {
id?: number
id?: string
slug?: string
creator?: User
creator?: UserSimple
creatorId?: number
title?: string
md?: string
@ -12,6 +13,7 @@ export interface Post {
brief?: string
created?: string
tags?: number[]
rawTags?: Tag[]
likes? : number
liked? : boolean
recommands? : number

@ -13,4 +13,11 @@ export interface User {
email?: string
lastSeenAt?: string
created?: string
}
export interface UserSimple {
id :number
username: string
nickname: string
avatar: string
}

@ -0,0 +1,4 @@
import {User} from 'src/types/session'
export function getUserName(user:User) {
return user.nickname === "" ? user.username : user.nickname
}
Loading…
Cancel
Save