Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,25 @@ require (
github.com/julsemaan/anyfile-notepad/utils v0.0.0-20230202010526-481b7f9b59a2
github.com/julsemaan/rest-layer-file v0.0.0-20230518012330-1c28ed9eb6a7
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/prometheus/client_golang v1.23.2
github.com/rs/rest-layer v0.0.0-20160505213648-cb84bc79b5b8
Comment on lines 6 to 10
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

github.com/julsemaan/anyfile-notepad/utils is still a direct requirement in go.mod, but api/go.sum no longer contains any checksums for that module. This will cause go mod tidy / go test to reintroduce go.sum changes and can break CI if it enforces a clean module graph. Regenerate go.sum (e.g., via go mod tidy) so the required module has matching sum entries.

Copilot uses AI. Check for mistakes.
gopkg.in/alexcesaro/statsd.v2 v2.0.0
)

require (
github.com/rs/cors v1.9.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/evanphx/json-patch v4.1.0+incompatible // indirect
github.com/kr/text v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/stretchr/testify v1.8.2 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/net v0.10.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/sys v0.35.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
)

// Uncomment this to use local directory for utils
Expand Down
60 changes: 43 additions & 17 deletions api/go.sum
Original file line number Diff line number Diff line change
@@ -1,17 +1,42 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7VpzLjCxu+UwBD1FvwOc=
github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/graphql-go/graphql v0.7.6/go.mod h1:k6yrAYQaSP59DC5UVxbgxESlmVyojThKdORUqGDGmrI=
github.com/julsemaan/anyfile-notepad/utils v0.0.0-20230202010526-481b7f9b59a2 h1:nVLPZUH4mD5okHdVlRfQltQZj9De3Gjx8RCBBSi26GQ=
github.com/julsemaan/anyfile-notepad/utils v0.0.0-20230202010526-481b7f9b59a2/go.mod h1:zJsqF8VPdI8dtbIMpRwGC6Q2cSBaKLz71nY4C+Msf5g=
github.com/julsemaan/rest-layer-file v0.0.0-20230518012330-1c28ed9eb6a7 h1:Wb2nBwi5//IkrXZk2wCq6yN1jWPJPGtZILAbqEv86yE=
github.com/julsemaan/rest-layer-file v0.0.0-20230518012330-1c28ed9eb6a7/go.mod h1:cTD+gl7oqD1MpcQuS2LReenHy7aoccUHvzPaLknlcpk=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
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/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE=
github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
Expand All @@ -24,62 +49,63 @@ github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/alexcesaro/statsd.v2 v2.0.0 h1:FXkZSCZIH17vLCO5sO2UucTHsH9pc+17F6pl3JVCwMc=
gopkg.in/alexcesaro/statsd.v2 v2.0.0/go.mod h1:i0ubccKGzBVNBpdGV5MocxyA/XlLUJzA7SLonnE4drU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
10 changes: 8 additions & 2 deletions api/internal/app/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@ import (

const defaultDataDir = "./db"
const defaultListenAddr = ":8080"
const defaultMetricsListenAddr = "127.0.0.1:9090"
const defaultContactRequestsPerDay = 10
const envMaxContactRequestsPerDay = "AFN_MAX_CONTACT_REQUESTS_PER_DAY"

type Config struct {
DataDir string
ListenAddr string
MetricsListenAddr string
Username string
Password string
SupportEmail string
StatsdAddress string
MaxContactRequestsPerDay int
}

Expand All @@ -31,13 +32,18 @@ func LoadConfigFromEnv() Config {
listenAddr = defaultListenAddr
}

metricsListenAddr := os.Getenv("AFN_METRICS_LISTEN_ADDR")
if metricsListenAddr == "" {
metricsListenAddr = defaultMetricsListenAddr
}

return Config{
DataDir: dataDir,
ListenAddr: listenAddr,
MetricsListenAddr: metricsListenAddr,
Username: os.Getenv("AFN_REST_USERNAME"),
Password: os.Getenv("AFN_REST_PASSWORD"),
SupportEmail: os.Getenv("AFN_SUPPORT_EMAIL"),
StatsdAddress: os.Getenv("AFN_STATSD_URI"),
MaxContactRequestsPerDay: loadMaxContactRequestsPerDay(),
}
}
Expand Down
20 changes: 19 additions & 1 deletion api/internal/app/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,25 @@ package app
import "testing"

func TestLoadConfigFromEnv(t *testing.T) {
t.Run("uses default max contact requests when env missing", func(t *testing.T) {
t.Run("uses default metrics listen addr when env is empty", func(t *testing.T) {
t.Setenv("AFN_METRICS_LISTEN_ADDR", "")

cfg := LoadConfigFromEnv()
if cfg.MetricsListenAddr != defaultMetricsListenAddr {
t.Fatalf("expected default metrics listen addr, got %q", cfg.MetricsListenAddr)
}
})

t.Run("uses configured metrics listen addr when provided", func(t *testing.T) {
t.Setenv("AFN_METRICS_LISTEN_ADDR", ":9191")

cfg := LoadConfigFromEnv()
if cfg.MetricsListenAddr != ":9191" {
t.Fatalf("expected configured metrics listen addr, got %q", cfg.MetricsListenAddr)
}
})

t.Run("uses default max contact requests when env is empty", func(t *testing.T) {
t.Setenv(envMaxContactRequestsPerDay, "")

cfg := LoadConfigFromEnv()
Expand Down
68 changes: 52 additions & 16 deletions api/internal/app/run.go
Original file line number Diff line number Diff line change
@@ -1,38 +1,28 @@
package app

import (
"errors"
"log"
"net/http"
"strings"
"time"

"github.com/julsemaan/anyfile-notepad/api/internal/contact"
"github.com/julsemaan/anyfile-notepad/api/internal/httpapi"
"github.com/julsemaan/anyfile-notepad/api/internal/logging"
"github.com/julsemaan/anyfile-notepad/api/internal/resources"
"github.com/julsemaan/anyfile-notepad/api/internal/stats"
cache "github.com/patrickmn/go-cache"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/rs/rest-layer/resource"
"github.com/rs/rest-layer/rest"
"github.com/rs/rest-layer/schema"
"gopkg.in/alexcesaro/statsd.v2"
)

func Run(cfg Config) error {
schema.CreatedField.ReadOnly = false
schema.UpdatedField.ReadOnly = false

statsConn, err := statsd.New(statsd.Address(cfg.StatsdAddress))
if err != nil {
logging.Errorf("statsd initialization failed: %v", err)
}
if statsConn != nil {
defer statsConn.Close()
}

var metrics stats.Metrics
if statsConn != nil {
metrics = statsConn
}
metrics := stats.NewPrometheusMetrics()
statsService := stats.NewService(metrics)
contactCache := cache.New(24*time.Hour, time.Minute)
contactService := contact.NewService(contactCache, cfg.MaxContactRequestsPerDay, cfg.SupportEmail, sendEmailWithOptionalTLS)
Expand All @@ -54,6 +44,52 @@ func Run(cfg Config) error {
cfg.Password,
)

log.Printf("Serving API on http://localhost%s", cfg.ListenAddr)
return http.ListenAndServe(cfg.ListenAddr, router)
metricsMux := http.NewServeMux()
metricsMux.Handle("/metrics", promhttp.Handler())

metricsServer := &http.Server{
Addr: cfg.MetricsListenAddr,
Handler: metricsMux,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
}

apiServer := &http.Server{
Addr: cfg.ListenAddr,
Handler: router,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
}

errCh := make(chan error, 2)
go func() {
log.Printf("Serving Prometheus metrics on %s", logURL(cfg.MetricsListenAddr, "/metrics"))
errCh <- metricsServer.ListenAndServe()
}()

go func() {
log.Printf("Serving API on %s", logURL(cfg.ListenAddr, ""))
errCh <- apiServer.ListenAndServe()
}()
Comment on lines +68 to +77
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Run() starts two servers in goroutines and returns on the first non-ErrServerClosed error, but it doesn't stop the other server. If one listener fails to bind (or crashes), the other goroutine can keep serving unexpectedly until the process exits. Consider using explicit http.Server instances and shutting down the other server (or cancelling a shared context) before returning the error.

Copilot uses AI. Check for mistakes.

for {
err := <-errCh
if errors.Is(err, http.ErrServerClosed) {
continue
}

return err
}
}

func logURL(addr string, path string) string {
if strings.HasPrefix(addr, ":") {
addr = "localhost" + addr
}

return "http://" + addr + path
}
19 changes: 19 additions & 0 deletions api/internal/app/run_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package app

import "testing"

func TestLogURL(t *testing.T) {
t.Run("adds localhost for port only address", func(t *testing.T) {
got := logURL(":8080", "")
if got != "http://localhost:8080" {
t.Fatalf("expected localhost url, got %q", got)
}
})

t.Run("uses host qualified address as is", func(t *testing.T) {
got := logURL("0.0.0.0:9090", "/metrics")
if got != "http://0.0.0.0:9090/metrics" {
t.Fatalf("expected host-qualified url, got %q", got)
}
})
}
1 change: 0 additions & 1 deletion api/internal/httpapi/router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ func TestRouter(t *testing.T) {
statsCalled = true
w.WriteHeader(http.StatusOK)
})

router := NewRouter(apiHandler, statsHandler, "user", "password")

t.Run("stats route bypasses auth", func(t *testing.T) {
Expand Down
33 changes: 33 additions & 0 deletions api/internal/stats/prometheus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package stats

import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)

var statsHitsCounter = promauto.NewCounter(prometheus.CounterOpts{
Name: "afn_stats_hits_total",
Help: "Number of accepted stats payloads.",
})

var statsIncrementCounter = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "afn_stats_increment_total",
Help: "Number of accepted increment stats by key.",
},
[]string{"key"},
)
Comment on lines +13 to +19
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

statsIncrementCounter uses a key label whose value ultimately comes from an unauthenticated request payload. Even with the regex, an attacker can send many distinct keys and create unbounded label cardinality, causing unbounded in-memory series growth (DoS risk). Consider restricting key to a small allowlist (or mapping unknowns to a single value), or avoid using a label here and instead expose separate counters for known keys.

Copilot uses AI. Check for mistakes.

type PrometheusMetrics struct{}

func NewPrometheusMetrics() *PrometheusMetrics {
return &PrometheusMetrics{}
}

func (m *PrometheusMetrics) IncrementStatsHits() {
statsHitsCounter.Inc()
}

func (m *PrometheusMetrics) IncrementKey(key string) {
statsIncrementCounter.WithLabelValues(key).Inc()
}
Loading
Loading