Compare commits

...

41 Commits

Author SHA1 Message Date
Suzanne Soy
145beb0e3b Missing dependency in README 2021-04-04 21:27:37 +01:00
Suzanne Soy
d925c3ff09 quote special character hash in readme (doesn't work in ZSH otherwise) 2021-04-04 21:24:52 +01:00
Tony Garnock-Jones
016686d254 Add a hidden field, not shown to humans, to see if we can catch dumb scripts out this way 2021-02-22 09:47:56 +01:00
Tony Garnock-Jones
3dd7c80de9 Repair config key usage 2021-02-18 20:26:38 +01:00
Tony Garnock-Jones
6d42a5ea87 User registration moved from API backend to frontend, for spam prevention reasons 2021-02-18 20:16:07 +01:00
Tony Garnock-Jones
aa95ca4fb8 Anti-spam. (What kind of spammer would abuse our login facility? Weird.) 2021-02-18 10:25:30 +01:00
Tony Garnock-Jones
020d9c47e8 Avoid nested <form>s in search page 2020-06-04 14:13:23 +02:00
Tony Garnock-Jones
41d3a39efe Bulk operations on package lists 2020-06-04 14:02:24 +02:00
Tony Garnock-Jones
4fabb334dc Remove obsolete scripts and config 2020-06-04 12:26:39 +02:00
Tony Garnock-Jones
e6c88647f5 Update my dev configuration 2020-06-04 12:25:45 +02:00
Tony Garnock-Jones
5ef4f02a2b Add new necessary PLTSTDERR filters to get a usable log output 2020-06-04 12:17:48 +02:00
Tony Garnock-Jones
12082a3fe4 Tell people to use --skip-installed 2020-06-04 12:17:21 +02:00
Jay McCarthy
71e6d5b516 fix typo 2020-05-29 16:44:52 -04:00
Jesse Alama
eec1479c18 If available, add description meta element to head 2020-05-28 09:09:07 -04:00
Jesse Alama
79a6ba2ada Pass pkg description to bootstrap-response 2020-05-28 09:09:07 -04:00
Sorawee Porncharoenwase
5cd2460b85 Correct contract and clarify comment 2020-02-29 14:04:46 -05:00
Sorawee Porncharoenwase
ee467b6095 Yet another PR feedback: avoid hash-ref when possible 2020-02-29 14:04:46 -05:00
Sorawee Porncharoenwase
f32e2c752b PR feedback: try to minimize the diff 2020-02-29 14:04:46 -05:00
Sorawee Porncharoenwase
6fbcfdfa09 Address PR feedback, use a better ranking function 2020-02-29 14:04:46 -05:00
Sorawee Porncharoenwase
2ea6722383 Feat: prioritize search result 2020-02-29 14:04:46 -05:00
Ben Greenman
f9c3578ec4 avoid 'failssucceeds' status
If a package has a failure log and a success log, then ignore the
success and only show "fails" in the build summary.

fixes https://github.com/racket/pkg-build/issues/9
2020-02-25 15:27:43 -05:00
Jay McCarthy
575a34bdca date added 2019-01-19 13:38:13 +00:00
Jesse Alama
0d63ebcae7 Remove colon to ensure label consistency
For the two labels to be consistent, either (a) the colon after
"Password" should be removed, as done here, or (b) a colon should be
added after the other label text ("Email address"). In this commit, I
opt for option (a), though (b) would also be reasonable.
2019-01-18 13:42:33 +00:00
Jay McCarthy
fc9e4f4646 Use https 2018-10-03 13:13:27 -04:00
Tony Garnock-Jones
37294bd8f7 Be more aggressive about killing an unresponsive service 2018-06-02 19:18:56 +01:00
Tony Garnock-Jones
aec4090018 Make a nicer little monitoring script 2018-06-01 19:01:52 +01:00
Tony Garnock-Jones
7925cfdbc2 Recommend a small sleep 2018-06-01 18:44:48 +01:00
Tony Garnock-Jones
c6fd690712 ping-service.sh 2018-06-01 18:40:43 +01:00
Tony Garnock-Jones
bb5f253b95 Add /ping endpoint 2018-06-01 18:40:30 +01:00
Tony Garnock-Jones
1291904a53 Wrap main in a layer of custodian, so we can get debug info on running threads.
Exploit this in a new signal handler, listening for `signals/.dumpinfo`.
2018-06-01 18:01:44 +01:00
Tony Garnock-Jones
8bb5a8646b Give names to the threads we create 2018-06-01 18:01:27 +01:00
Jay McCarthy
2174e0ccd9 Add conflicts 2018-03-26 11:02:22 -04:00
Jay McCarthy
73d0d166c0 Fix 63 2018-03-09 12:36:33 -05:00
Jay McCarthy
46039083ea Remove custom version and update racket 2018-01-04 16:15:19 -05:00
Jay McCarthy
108b4ec9d6 typo 2017-11-02 14:17:33 -04:00
Phil Hagelberg
180e0c9a87 Remove support for unencrypted git transports. (git:// and http://)
Downloading executable code from a git repository that doesn't have
encryption is inadvisable since it can be intercepted and replaced by
a man-in-the-middle attacker. GitHub recommends doing clones over
HTTPS, and according to mflatt even if you request the git://
transport it will be ignored and https:// is used scenes anyway, so we
shouldn't claim to support it.

It may make sense to allow SSH connections as well as HTTPS, but I
don't think the "transport" drop-down is a good idea for this since
you need to specify a username when making an SSH connection.
2017-11-01 12:10:08 -04:00
Conor Finegan
da358025bd Possible fix for get-implied-docs. (#57)
* Added todo level for no description.

* Added todo category for no description, added warnings in table for todos.

* Fixed label xexps

* now displays implied docs.

* fixed formatting

* removed comment

* fixed issue with implied docs not being gathered correctly.

* replaced 'for/fold' with 'append-map' in 'get-implied-docs'.

* Possible fix for get-implied-docs.
2017-09-06 09:36:48 +01:00
Conor Finegan
c3f60fdcf1 Fixes get-implied-docs procedure. (#56)
* Added todo level for no description.

* Added todo category for no description, added warnings in table for todos.

* Fixed label xexps

* now displays implied docs.

* fixed formatting

* removed comment

* fixed issue with implied docs not being gathered correctly.

* replaced 'for/fold' with 'append-map' in 'get-implied-docs'.
2017-08-23 20:00:09 -04:00
Jay McCarthy
164faa4ee7 version note 2017-08-17 10:34:03 -04:00
Jay McCarthy
e8e3466f48 append vs list-star 2017-08-13 22:09:00 -06:00
Georges Dupéron
f63a6544e6 Tentative fix for #51 2017-08-13 21:15:36 -06:00
25 changed files with 767 additions and 340 deletions

View File

@ -4,7 +4,10 @@
You will need to install the following Racket packages: You will need to install the following Racket packages:
raco pkg install reloadable raco pkg install --skip-installed \
'https://github.com/racket/infrastructure-userdb.git#main' \
reloadable \
aws
## Configuration ## Configuration
@ -50,6 +53,11 @@ Keys useful for deployment:
statically. The source file `static.rkt` in this codebase knows statically. The source file `static.rkt` in this codebase knows
precisely which files and directories within precisely which files and directories within
`pkg-index-generated-directory` to upload to the final site. `pkg-index-generated-directory` to upload to the final site.
- *user-directory*: directory containing the user database; should be
the same as `pkg-index` uses.
- *email-sender-address*: string; defaults to `pkgs@racket-lang.org`.
Used as the "from" address when sending authentication emails on
behalf of the server.
Keys useful for development: Keys useful for development:

View File

@ -9,6 +9,8 @@
(format "file://~a/public_html/pkg-index-static/pkgs-all.json.gz" var) (format "file://~a/public_html/pkg-index-static/pkgs-all.json.gz" var)
'backend-baseurl "https://localhost:9004" 'backend-baseurl "https://localhost:9004"
'pkg-index-generated-directory (build-path var "public_html/pkg-index-static") 'pkg-index-generated-directory (build-path var "public_html/pkg-index-static")
'user-directory (build-path var "pkg-index/users.new")
'email-sender-address "The Racket Package Server <pkgs@racket-lang.org>"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; To configure a split, S3-based setup, comment out the following lines: ;; To configure a split, S3-based setup, comment out the following lines:
;; ;;

View File

@ -1,13 +0,0 @@
#lang racket/base
;; Configuration for pkgd
(require "../src/main.rkt")
(main (hash 'port 8444
'backend-baseurl "https://localhost:9004"
'package-index-url "file:///home/ubuntu/local/new-plt/pkgs/plt-services/meta/pkg-index/official/static-gen/pkgs-all.json.gz"
'static-output-type 'aws-s3
'aws-s3-bucket+path "pkgn.racket-lang.org/"
'dynamic-urlprefix "https://pkgd.racket-lang.org/pkgn"
'static-urlprefix "https://pkgn.racket-lang.org"
'dynamic-static-urlprefix "https://pkgn.racket-lang.org"
'pkg-index-generated-directory "/home/ubuntu/local/new-plt/pkgs/plt-services/meta/pkg-index/official/static-gen/"
))

View File

@ -1,9 +1,17 @@
#lang racket/base #lang racket/base
;; Configuration for tonyg's development setup. ;; Configuration for tonyg's development setup.
(require "../src/main.rkt") (require "../src/main.rkt")
(define pkg-index-generated-directory
(build-path (find-system-path 'home-dir) "src/pkg-index/official/static-gen"))
(main (hash 'port 8444 (main (hash 'port 8444
'ssl? #f
'reloadable? #t 'reloadable? #t
'package-index-url "file:///home/tonyg/public_html/pkg-index-static/pkgs-all.json.gz" 'package-index-url (format "file://~a/pkgs-all.json.gz" pkg-index-generated-directory)
'user-directory (build-path (find-system-path 'home-dir)
"src/pkg-index/official/root/users.new")
'email-sender-address "tonyg@racket-lang.org"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Either: ;; Either:
;; ;;
@ -22,8 +30,7 @@
;; 'dynamic-static-urlprefix "https://localhost:8446" ;; 'dynamic-static-urlprefix "https://localhost:8446"
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
'dynamic-urlprefix "https://localhost:8444" 'dynamic-urlprefix "http://localhost:8444"
'backend-baseurl "https://localhost:8445" 'backend-baseurl "http://localhost:8445"
'pkg-index-generated-directory (build-path (find-system-path 'home-dir) 'pkg-index-generated-directory pkg-index-generated-directory
"public_html/pkg-index-static")
)) ))

View File

@ -1,30 +0,0 @@
# As a regular user, run
#
# nginx -p . -c nginx.locals3proxy.conf
daemon off;
pid ./nginx.pid;
error_log locals3proxy-error.log;
events {
worker_connections 768;
}
http {
server {
listen 8446 default_server ssl;
access_log locals3proxy-access.log;
error_log locals3proxy-error.log;
ssl_certificate /home/tonyg/src/racket-pkg-website/server-cert.pem;
ssl_certificate_key /home/tonyg/src/racket-pkg-website/private-key.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://pkgs.leastfixedpoint.com.s3-website-us-east-1.amazonaws.com/;
proxy_http_version 1.1;
}
}
}

View File

@ -1,2 +0,0 @@
#!/bin/sh
exec nginx -p . -c nginx.locals3proxy.conf

39
on-ping-service-failure.sh Executable file
View File

@ -0,0 +1,39 @@
#!/bin/bash
#
# I am potentially invoked when ping-service.sh (which see) fails to
# contact the racket-pkg-website service.
#
# Request a dump of running threads:
dumprequestfile=$HOME/racket-pkg-website/signals/.dumpinfo
touch $dumprequestfile
# Wait a few seconds for the dump to complete:
sleep 10
# Tar up the most recent few hours' worth of logs:
logarchive=$HOME/ping-failure-logs-$(date +%Y%m%d%H%M%S).tar.gz
(
cd $HOME/service/racket-pkg-website/log/main/; \
ls -tr | tail -n 10 | xargs tar -zcf $logarchive \
)
# Restart the service using daemontools.
if [ -f $dumprequestfile ]
then
# If the `.dumpinfo` signal is still there after our sleep, then
# the process is so far off the rails we shouldn't bother waiting
# for it, so kill it hard.
echo "Killing service hard and restarting it."
svc -dku $HOME/service/racket-pkg-website
else
# Otherwise, it's at least partially awake, so try asking it
# nicely.
echo "Politely requesting service termination before restart."
svc -du $HOME/service/racket-pkg-website
fi
# Finally, complain out loud. We expect to be running in some kind of
# cron-ish context, so the output we produce here will likely find its
# way into an email to a responsible party.
echo "racket-pkg-website on-ping-service-failure.sh invoked. logarchive=$logarchive"

32
ping-service.sh Executable file
View File

@ -0,0 +1,32 @@
#!/bin/bash
#
# I am a script, intended to run from `cron`, which pings a
# configurable URL, and if no suitable response is forthcoming,
# performs a configurable command.
#
# For example, to monitor racket-pkg-website, try
#
# ./ping-service.sh https://localhost:8444/ping $HOME/racket-pkg-website/on-ping-service-failure.sh
if [ "$#" != "2" ]
then
echo 'Usage: ping-service.sh <url> <command>'
echo 'Note that <command> has to be a single string.'
exit 1
fi
url="$1"
failurecommand="$2"
# curl flags:
# -f == fail, interrogate the HTTP response status code
# -s == silent, don't print a progress meter or any other cruft
# -k == Ignore certificates, where url is an HTTPS URL
#
if curl -f -s -k --max-time 10 "$url" > /dev/null
then
# Do nothing -- the retrieval was successful
true
else
exec sh -c "$failurecommand"
fi

17
run
View File

@ -12,7 +12,22 @@ if [ ! -f configs/${CONFIG}.rkt ]; then
exit 1 exit 1
fi fi
PLTSTDERR="info warning@cm warning@compiler/cm warning@module-prefetch warning@setup/parallel-build warning@cm-accomplice warning@online-check-syntax error@racket/contract" PLTSTDERR="
info
warning@GC
warning@cm
warning@compiler/cm
warning@module-prefetch
warning@setup/parallel-build
warning@cm-accomplice
warning@online-check-syntax
error@racket/contract
error@collapsible-cache-fail
error@collapsible-contract-arrow-wrapper-arity
error@collapsible-contract-bailout
error@collapsible-merging
error@collapsible-value-bailout
"
export PLTSTDERR export PLTSTDERR
echo '=============================================' echo '============================================='
cd src cd src

View File

@ -53,6 +53,7 @@
#:code [code 200] #:code [code 200]
#:message [message #"Okay"] #:message [message #"Okay"]
#:body-class [body-class #f] #:body-class [body-class #f]
#:description [description #f]
. .
body-contents) body-contents)
(response/xexpr (response/xexpr
@ -65,6 +66,10 @@
(meta ((http-equiv "X-UA-Compatible") (content "IE=edge"))) (meta ((http-equiv "X-UA-Compatible") (content "IE=edge")))
(meta ((name "viewport") (content "width=device-width, initial-scale=1"))) (meta ((name "viewport") (content "width=device-width, initial-scale=1")))
(title ,title) (title ,title)
,@(if (non-empty-string? description)
`((meta ((name "description")
(content ,description))))
'())
(link ((rel "stylesheet") (href ,(static "/bootstrap/css/bootstrap.min.css")) (type "text/css"))) (link ((rel "stylesheet") (href ,(static "/bootstrap/css/bootstrap.min.css")) (type "text/css")))
(link ((rel "stylesheet") (href ,(static "/jquery-ui.min.css")) (type "text/css"))) (link ((rel "stylesheet") (href ,(static "/jquery-ui.min.css")) (type "text/css")))
(link ((rel "stylesheet") (href ,(static "/style.css")) (type "text/css"))) (link ((rel "stylesheet") (href ,(static "/style.css")) (type "text/css")))

48
src/challenge.rkt Normal file
View File

@ -0,0 +1,48 @@
#lang racket/base
(provide (struct-out challenge)
generate-challenge
challenge-passed?)
(require racket/pretty)
(struct challenge (expr question answer) #:transparent)
(define (random-element lst)
(list-ref lst (random (length lst))))
(define (generate-expr)
(if (>= (random) 0.5)
`(,(random-element '(car cadr caddr))
(map (lambda (v) (+ ,(random 4) (* v ,(random 4))))
(list ,(random 4) ,(random 4) ,(random 4))))
(let ()
(define (random-op) (random-element '(+ * -)))
(define (e fuel)
(if (zero? fuel)
(random 10)
(cons (random-op)
(for/list [(i (in-range (+ 1 (random 2))))]
(e (- fuel 1))))))
(e 2))))
(define (generate-challenge)
(define expr (generate-expr))
(challenge expr
`(div
(p (b "What is the result of evaluating:"))
(pre (code ,(pretty-format expr 40 #:mode 'write))))
(eval expr (make-base-namespace))))
(define (safe-string->value str)
(parameterize (;; Hmm, this is a big list. Did I miss any important ones?
(read-accept-box #f)
(read-accept-compiled #f)
(read-accept-graph #f)
(read-accept-reader #f)
(read-accept-lang #f))
(read (open-input-string str))))
(define (challenge-passed? challenge response-str)
(define response (safe-string->value response-str))
(equal? response (challenge-answer challenge)))

View File

@ -6,6 +6,7 @@
(require (only-in racket/exn exn->string)) (require (only-in racket/exn exn->string))
(define (daemonize-thunk name boot-thunk) (define (daemonize-thunk name boot-thunk)
(procedure-rename
(lambda () (lambda ()
(let reboot () (let reboot ()
;; We would catch exn:fail? here, but exn:pretty in the web ;; We would catch exn:fail? here, but exn:pretty in the web
@ -18,7 +19,8 @@
(sleep 5) (sleep 5)
(reboot)))) (reboot))))
(define result (boot-thunk)) (define result (boot-thunk))
(log-warning "Daemon thread ~a exited normally (returning ~v)" name result))))) (log-warning "Daemon thread ~a exited normally (returning ~v)" name result))))
(string->symbol (format "~v" name))))
(define (daemon-thread name boot-thunk) (define (daemon-thread name boot-thunk)
(thread (daemonize-thunk name boot-thunk))) (thread (daemonize-thunk name boot-thunk)))

36
src/debug.rkt Normal file
View File

@ -0,0 +1,36 @@
#lang racket/base
;; Code for debugging a live server.
(provide debug-information-dump!)
(require "main.rkt")
(require racket/exn)
(require racket/tcp)
(require (only-in racket/string string-join))
(define (format-path path)
(string-join (map number->string (reverse path)) "."))
(define (enumerate-custodian-managed-items cust super path)
(for [(index (in-naturals))
(item (custodian-managed-list cust super))]
(eprintf "\nItem ~a.\n~v\n" (format-path (cons index path)) item)
(cond
[(thread? item)
(eprintf "~a" (exn->string (exn "Stack snapshot:" (continuation-marks item))))]
[(tcp-port? item)
(eprintf "TCP port: (addresses ~v)\n"
(call-with-values (lambda () (tcp-addresses item #t)) list))]
[(custodian? item)
(enumerate-custodian-managed-items item cust (cons index path))]
[else (void)])))
(define (debug-information-dump!)
(eprintf "===========================================================================\n")
(eprintf "======================================================================\n")
(eprintf "=================================================================")
(collect-garbage)
(enumerate-custodian-managed-items (current-custodian) (outermost-custodian) '())
(eprintf "=================================================================\n")
(eprintf "======================================================================\n")
(eprintf "===========================================================================\n"))

View File

@ -1,10 +1,13 @@
#lang racket/base #lang racket/base
;; A utilities module :-/ ;; A utilities module :-/
(require web-server/servlet)
(provide maybe-splice (provide maybe-splice
define-form-bindings) define-form-bindings/xform
define-form-bindings
define-form-bindings/trim)
(require web-server/servlet)
(require (only-in racket/string string-trim))
;; Boolean XExpr ... -> (Listof XExpr) ;; Boolean XExpr ... -> (Listof XExpr)
;; Useful for optionally splicing in some contents to a list. ;; Useful for optionally splicing in some contents to a list.
@ -12,22 +15,28 @@
(define-syntax-rule (maybe-splice guard contents ...) (define-syntax-rule (maybe-splice guard contents ...)
(if guard (list contents ...) '())) (if guard (list contents ...) '()))
;; Extracts named single-valued bindings from the given request.
;; If a given binding is missing, the extracted value will be #f.
(define-syntax-rule (define-form-bindings req (specs ...))
(begin (define bs (request-bindings req))
(define-form-bindings* bs (specs ...))))
(define-syntax define-form-bindings* (define-syntax define-form-bindings*
(syntax-rules () (syntax-rules ()
[(_ bs ()) [(_ bs xform ())
(begin)] (begin)]
[(_ bs ([name fieldname defaultval] rest ...)) [(_ bs xform ([name fieldname defaultval] rest ...))
(begin (define name (if (exists-binding? 'fieldname bs) (begin (define name (if (exists-binding? 'fieldname bs)
(extract-binding/single 'fieldname bs) (xform (extract-binding/single 'fieldname bs))
defaultval)) defaultval))
(define-form-bindings* bs (rest ...)))] (define-form-bindings* bs xform (rest ...)))]
[(_ bs ([name defaultval] rest ...)) [(_ bs xform ([name defaultval] rest ...))
(define-form-bindings* bs ([name name defaultval] rest ...))] (define-form-bindings* bs xform ([name name defaultval] rest ...))]
[(_ bs (name rest ...)) [(_ bs xform (name rest ...))
(define-form-bindings* bs ([name #f] rest ...))])) (define-form-bindings* bs xform ([name #f] rest ...))]))
;; Extracts named single-valued bindings from the given request.
;; If a given binding is missing, the extracted value will be #f.
(define-syntax-rule (define-form-bindings/xform req xform (specs ...))
(begin (define bs (request-bindings req))
(define-form-bindings* bs xform (specs ...))))
(define-syntax-rule (define-form-bindings req (specs ...))
(define-form-bindings/xform req values (specs ...)))
(define-syntax-rule (define-form-bindings/trim req (specs ...))
(define-form-bindings/xform req string-trim (specs ...)))

View File

@ -6,7 +6,7 @@
http-interpret-response http-interpret-response
http-simple-interpret-response http-simple-interpret-response
http-follow-redirects http-follow-redirects
http-sendrecv/url custom-http-sendrecv/url
http/interpret-response http/interpret-response
http/simple-interpret-response http/simple-interpret-response
@ -17,7 +17,7 @@
(require racket/match) (require racket/match)
(require net/http-client) (require net/http-client)
(require net/head) (require net/head)
(require (except-in net/url http-sendrecv/url)) (require net/url)
;; (Parameterof Number) ;; (Parameterof Number)
;; Number of redirections to automatically follow when retrieving via GET or HEAD. ;; Number of redirections to automatically follow when retrieving via GET or HEAD.
@ -79,17 +79,13 @@
headers headers
body)) body))
(define ((http-follow-redirects method (define ((check-response method remaining-redirect-count)
#:version [version #"1.1"])
status-line
response-headers
response-body-port)
(define ((check-response remaining-redirect-count)
status-line status-line
response-headers response-headers
response-body-port) response-body-port)
(log-debug "http-follow-redirects: Checking request result: ~a\n" status-line) (log-debug "http-follow-redirects: Checking request result: ~a\n" status-line)
(define-values (http-version status-code reason-phrase) (parse-status-line status-line)) (define-values (http-version status-code reason-phrase)
(parse-status-line status-line))
(if (and (positive? remaining-redirect-count) (if (and (positive? remaining-redirect-count)
(eq? (http-classify-status-code status-code) 'redirection)) (eq? (http-classify-status-code status-code) 'redirection))
(match (assq 'location (parse-headers response-headers)) (match (assq 'location (parse-headers response-headers))
@ -98,45 +94,24 @@
(define location (string->url (bytes->string/latin-1 location-urlbytes))) (define location (string->url (bytes->string/latin-1 location-urlbytes)))
(void (port->bytes response-body-port)) ;; consume and discard input (void (port->bytes response-body-port)) ;; consume and discard input
(close-input-port response-body-port) (close-input-port response-body-port)
(log-debug "http-follow-redirects: Following redirection to ~a\n" location-urlbytes) (log-debug "http-follow-redirects: Following redirection to ~a\n"
(call-with-values (lambda () (http-sendrecv/url location location-urlbytes)
#:version version (call-with-values (lambda () (custom-http-sendrecv/url location
#:method method)) #:method method))
(check-response (- remaining-redirect-count 1)))]) (check-response method (- remaining-redirect-count 1)))])
(values status-line response-headers response-body-port))) (values status-line response-headers response-body-port)))
((check-response (http-redirection-limit))
(define ((http-follow-redirects method)
status-line
response-headers
response-body-port)
((check-response method (http-redirection-limit))
status-line status-line
response-headers response-headers
response-body-port)) response-body-port))
;; Already present in net/url, but that variant doesn't take #:version (define (custom-http-sendrecv/url u #:method method)
;; or allow overriding of #:ssl? and #:port. (http-sendrecv/url u #:method method))
;;
;; Furthermore, currently 2016-08-14 there is a fd leak when using
;; method HEAD with `http-sendrecv` [1], so we implement our own crude
;; connection management here.
;;
;; [1] https://github.com/racket/racket/issues/1414
;;
(define (http-sendrecv/url u
#:ssl? [ssl? (equal? (url-scheme u) "https")]
#:port [port (or (url-port u) (if ssl? 443 80))]
#:version [version #"1.1"]
#:method [method #"GET"]
#:headers [headers '()]
#:data [data #f]
#:content-decode [decodes '(gzip)])
(define hc (http-conn-open (url-host u) #:ssl? ssl? #:port port))
(http-conn-send! hc (url->string u)
#:version version
#:method method
#:headers headers
#:data data
#:content-decode decodes
#:close? #t)
(begin0 (http-conn-recv! hc #:method method #:content-decode decodes #:close? #t)
(when (member method (list #"HEAD" "HEAD" 'HEAD))
(http-conn-close! hc))))
(define-syntax-rule (http/interpret-response customization ... req-expr) (define-syntax-rule (http/interpret-response customization ... req-expr)
(call-with-values (lambda () req-expr) (call-with-values (lambda () req-expr)
@ -151,10 +126,15 @@
(http-follow-redirects customization ...))) (http-follow-redirects customization ...)))
(module+ test (module+ test
(require rackunit) (define parent-cust (current-custodian))
(define this-cust (make-custodian))
(parameterize ([current-custodian this-cust])
(for ([i (in-range 100)])
(http/simple-interpret-response (http/simple-interpret-response
(http/follow-redirects (http/follow-redirects
#"HEAD" #"HEAD"
(http-sendrecv/url (string->url "http://google.com/") #:method #"HEAD"))) (custom-http-sendrecv/url (string->url "http://google.com/")
) #:method #"HEAD")))))
(require racket/pretty)
(pretty-print
(custodian-managed-list this-cust parent-cust)))

21
src/main-inner.rkt Normal file
View File

@ -0,0 +1,21 @@
#lang racket/base
;; Inner startup module - required after establishment of server-wide custodian.
(provide main)
(require reloadable)
(require "entrypoint.rkt")
(define (main [config (hash)])
(make-persistent-state '*config* (lambda () config))
(void (make-reloadable-entry-point 'refresh-packages! "packages.rkt"))
(void (make-reloadable-entry-point 'rerender! "site.rkt"))
(void (make-reloadable-entry-point 'debug-information-dump! "debug.rkt"))
(start-service #:port (hash-ref config 'port (lambda ()
(let ((port-str (getenv "SITE_PORT")))
(if port-str (string->number port-str) 7443))))
#:ssl? (hash-ref config 'ssl? (lambda () #t))
#:reloadable? (hash-ref config 'reloadable? (lambda () (getenv "SITE_RELOADABLE")))
(make-reloadable-entry-point 'request-handler "site.rkt")
(make-reloadable-entry-point 'on-continuation-expiry "site.rkt")
(make-reloadable-entry-point 'extra-files-paths "static.rkt")))

View File

@ -1,19 +1,12 @@
#lang racket/base #lang racket/base
;; Outer startup module - delegates to main-inner.rkt after installing a custodian
(provide main) (provide main
outermost-custodian)
(require reloadable) (define *outermost-custodian* (current-custodian))
(require "entrypoint.rkt") (define (outermost-custodian) *outermost-custodian*)
(define (main [config (hash)]) (define (main [config (hash)])
(make-persistent-state '*config* (lambda () config)) (parameterize ((current-custodian (make-custodian (outermost-custodian))))
(void (make-reloadable-entry-point 'refresh-packages! "packages.rkt")) ((dynamic-require "main-inner.rkt" 'main) config)))
(void (make-reloadable-entry-point 'rerender! "site.rkt"))
(start-service #:port (hash-ref config 'port (lambda ()
(let ((port-str (getenv "SITE_PORT")))
(if port-str (string->number port-str) 7443))))
#:ssl? (hash-ref config 'ssl? (lambda () #t))
#:reloadable? (hash-ref config 'reloadable? (lambda () (getenv "SITE_RELOADABLE")))
(make-reloadable-entry-point 'request-handler "site.rkt")
(make-reloadable-entry-point 'on-continuation-expiry "site.rkt")
(make-reloadable-entry-point 'extra-files-paths "static.rkt")))

View File

@ -34,7 +34,7 @@
(define package-index-url (define package-index-url
(or (@ (config) package-index-url) (or (@ (config) package-index-url)
"http://pkgs.racket-lang.org/pkgs-all.json.gz")) "https://pkgs.racket-lang.org/pkgs-all.json.gz"))
(define package-fetch-interval (define package-fetch-interval
(* (or (@ (config) package-fetch-interval) (* (or (@ (config) package-fetch-interval)
@ -73,9 +73,12 @@
(eq? pkg 'tombstone)) (eq? pkg 'tombstone))
(define (asynchronously-fetch-remote-packages state) (define (asynchronously-fetch-remote-packages state)
(thread (lambda () (thread
(procedure-rename
(lambda ()
(define raw-remote-packages (fetch-remote-packages)) (define raw-remote-packages (fetch-remote-packages))
(manager-rpc 'refresh-packages! raw-remote-packages))) (manager-rpc 'refresh-packages! raw-remote-packages))
(string->symbol (format "~v" (list 'asynchronously-fetch-remote-packages (current-inexact-milliseconds))))))
(struct-copy package-manager-state state (struct-copy package-manager-state state
[next-fetch-deadline (+ (current-inexact-milliseconds) package-fetch-interval)])) [next-fetch-deadline (+ (current-inexact-milliseconds) package-fetch-interval)]))
@ -287,19 +290,55 @@
;; to do this at package save time, but this will do for now. ;; to do this at package save time, but this will do for now.
(pkg->searchable-text pkg))))) (pkg->searchable-text pkg)))))
;; sort-package-names/priority :: (listof string?) (listof (cons/c symbol? package?)) -> (listof symbol?)
;; Rank packages by favoring those whose name prefixes or contains search strings
;; and whose description contains search strings
(define (sort-package-names/priority text-list packages)
;; A key is a pair of a priority and a package name
;; where higher priority means it's more relevant to the search text
;; Note that the tombstone packages are filtered already,
;; so it's safe to use (@ pkg ...)
(define (package-pair->key package-pair)
(define pkg (cdr package-pair))
(define pkg-name (@ pkg name))
(define pkg-desc (@ pkg description))
(define priority
(for/sum ([text (in-list text-list)])
(cond
;; NOTE: no need to check for string=? (the exact match)
;; because it will also be a prefix, and will be
;; weighted more due to its lexicographic order.
[(string-prefix? pkg-name text) 100]
[(string-contains? pkg-name text) 10]
[(and pkg-desc (string-contains? pkg-desc text)) 1]
[else 0])))
(cons priority pkg-name))
(define (key< a b)
(cond
[(= (car a) (car b)) (string-ci<? (cdr a) (cdr b))]
[else (> (car a) (car b))]))
(define sorted (sort packages key< #:key package-pair->key #:cache-keys? #t))
(map car sorted))
(define (package-search text tags) (define (package-search text tags)
(define res (map (lambda (r) (regexp (regexp-quote r #f))) (string-split text))) (define text-list (remove-duplicates (string-split text)))
(define res (map (lambda (r) (regexp (regexp-quote r #f))) text-list))
(define packages (manager-rpc 'packages)) (define packages (manager-rpc 'packages))
(sort-package-names
(filter (lambda (package-name) (sort-package-names/priority
(define pkg (hash-ref packages package-name)) text-list
(filter (lambda (package-pair)
(define pkg (cdr package-pair))
(andmap (package-text-matches? pkg) res)) (andmap (package-text-matches? pkg) res))
(hash-keys (hash->list
(for/fold ((ps packages)) ((tag-spec tags)) (for/fold ((ps packages)) ((tag-spec tags))
(match-define (list tag-name include?) tag-spec) (match-define (list tag-name include?) tag-spec)
(for/hash (((package-name pkg) (in-hash ps)) (for/hash (((package-name pkg) (in-hash ps))
#:when (and (not (tombstone? pkg)) #:when (and (not (tombstone? pkg))
((if include? values not) (@ref (@ pkg search-terms) tag-name)))) ((if include? values not)
(@ref (@ pkg search-terms) tag-name))))
(values package-name pkg))))))) (values package-name pkg)))))))
(define (packages-jsexpr) (define (packages-jsexpr)

View File

@ -51,5 +51,10 @@
(if (eof-object? items-to-rerender) (if (eof-object? items-to-rerender)
#f #f
items-to-rerender)))) items-to-rerender))))
(poll-signal "../signals/.dumpinfo"
"Debug information dump request received"
(lambda ()
((reloadable-entry-point->procedure
(lookup-reloadable-entry-point 'debug-information-dump! "debug.rkt")))))
(sleep 0.5) (sleep 0.5)
(loop))))) (loop)))))

View File

@ -10,13 +10,13 @@
(require racket/match) (require racket/match)
(require racket/format) (require racket/format)
(require racket/date) (require racket/date)
(require racket/string) (require (only-in racket/string string-join string-split))
(require racket/port) (require racket/port)
(require (only-in racket/list filter-map drop-right)) (require (only-in racket/list filter-map drop-right))
(require (only-in racket/exn exn->string)) (require (only-in racket/exn exn->string))
(require (except-in net/url http-sendrecv/url)) (require net/url)
(require net/uri-codec) (require net/uri-codec)
(require (except-in web-server/servlet http-sendrecv/url)) (require web-server/servlet)
(require json) (require json)
(require "gravatar.rkt") (require "gravatar.rkt")
(require "bootstrap.rkt") (require "bootstrap.rkt")
@ -31,6 +31,8 @@
(require "static.rkt") (require "static.rkt")
(require "package-source.rkt") (require "package-source.rkt")
(require "http-utils.rkt") (require "http-utils.rkt")
(require "challenge.rkt")
(require "users.rkt")
(define static-urlprefix (define static-urlprefix
(or (@ (config) static-urlprefix) (or (@ (config) static-urlprefix)
@ -52,7 +54,7 @@
(define nav-search "Search") (define nav-search "Search")
(define (navbar-header) (define (navbar-header)
`(a ((href "http://www.racket-lang.org/")) `(a ((href "https://www.racket-lang.org/"))
(img ((src ,(static-resource-url "/logo-and-text.png")) (img ((src ,(static-resource-url "/logo-and-text.png"))
(height "60") (height "60")
(alt "Racket Package Index"))))) (alt "Racket Package Index")))))
@ -62,7 +64,7 @@
"https://pkgd.racket-lang.org")) "https://pkgd.racket-lang.org"))
(define default-empty-parsed-package-source (define default-empty-parsed-package-source
(git-source "git://github.com/" #f 'git 'git "github.com" #f "" "" "")) (git-source "https://github.com/" #f 'git 'git "github.com" #f "" "" ""))
(define COOKIE "pltsession") (define COOKIE "pltsession")
@ -72,7 +74,7 @@
(define pkg-build-baseurl (define pkg-build-baseurl
(or (@ (config) pkg-build-baseurl) (or (@ (config) pkg-build-baseurl)
"http://pkg-build.racket-lang.org/")) "https://pkg-build.racket-lang.org/"))
(struct draft-package (old-name name description authors tags versions) #:prefab) (struct draft-package (old-name name description authors tags versions) #:prefab)
@ -95,6 +97,8 @@
[("json" "tag-search-completions") json-tag-search-completions] [("json" "tag-search-completions") json-tag-search-completions]
[("json" "formal-tags") json-formal-tags] [("json" "formal-tags") json-formal-tags]
[("pkgs-all.json") pkgs-all-json] [("pkgs-all.json") pkgs-all-json]
[("ping") ping-page]
[("bulk-operation") #:method "post" bulk-operation-page]
)) ))
(define (on-continuation-expiry request) (define (on-continuation-expiry request)
@ -142,11 +146,11 @@
(,nav-search ,(named-url search-page)) (,nav-search ,(named-url search-page))
("About" ("About"
(("The Racket Package System" (("The Racket Package System"
"http://docs.racket-lang.org/pkg/getting-started.html") "https://docs.racket-lang.org/pkg/getting-started.html")
("Package Builds" "https://pkg-build.racket-lang.org/about.html"))) ("Package Builds" "https://pkg-build.racket-lang.org/about.html")))
((div ,(glyphicon 'download-alt) ((div ,(glyphicon 'download-alt)
" Download Racket") " Download Racket")
"http://download.racket-lang.org/") "https://download.racket-lang.org/")
)) ))
(bootstrap-static-urlprefix (bootstrap-static-urlprefix
(if (rendering-static-page?) (if (rendering-static-page?)
@ -243,17 +247,23 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define ((generic-input type) name [initial-value ""] #:placeholder [placeholder #f]) (define ((generic-input type #:extra-classes [extra-classes1 '()])
`(input ((class "form-control") name
[initial-value ""]
#:id [id name]
#:extra-classes [extra-classes2 '()]
#:placeholder [placeholder #f])
`(input ((class ,(string-join (cons "form-control" (append extra-classes1 extra-classes2)) " "))
(type ,type) (type ,type)
(name ,name) (name ,name)
(id ,name) ,@(maybe-splice id `(id ,id))
,@(maybe-splice placeholder `(placeholder ,placeholder)) ,@(maybe-splice placeholder `(placeholder ,placeholder))
(value ,initial-value)))) (value ,initial-value))))
(define email-input (generic-input "email")) (define email-input (generic-input "email"))
(define password-input (generic-input "password")) (define password-input (generic-input "password"))
(define text-input (generic-input "text")) (define text-input (generic-input "text"))
(define checkbox-input (generic-input "checkbox"))
(define (label for . content) (define (label for . content)
`(label ((class "control-label") ,@(maybe-splice for `(for ,for))) `(label ((class "control-label") ,@(maybe-splice for `(for ,for)))
@ -334,7 +344,7 @@
(role "form")) (role "form"))
,(form-group 2 2 (label "email" "Email address") ,(form-group 2 2 (label "email" "Email address")
0 5 (email-input "email")) 0 5 (email-input "email"))
,(form-group 2 2 (label "password" "Password:") ,(form-group 2 2 (label "password" "Password")
0 5 (password-input "password")) 0 5 (password-input "password"))
,(form-group 4 5 ,(form-group 4 5
`(a ((href ,(embed-url (lambda (req) (register-form))))) `(a ((href ,(embed-url (lambda (req) (register-form)))))
@ -349,45 +359,39 @@
(p ,error-message)))) (p ,error-message))))
,(form-group 4 5 (primary-button "Log in")))))))) ,(form-group 4 5 (primary-button "Log in"))))))))
(define (authenticate-with-server! email password code) (define (create-session-after-authentication-success! email password)
(define user-facts
(simple-json-rpc! #:sensitive? #t (simple-json-rpc! #:sensitive? #t
#:include-credentials? #f #:include-credentials? #f
backend-baseurl backend-baseurl
"/api/authenticate" "/api/authenticate"
(hash 'email email (hash 'email email
'passwd password 'passwd password)))
'code code))) (when (not (hash? user-facts)) ;; Uh-oh. Something went wrong
(error 'create-session-after-authentication-success! "Cannot retrieve user-facts for ~v" email))
(define (create-session-from-authentication-success! email password success)
;; An "authentication success" is either #t, signalling a new user,
;; or a hash-table with interesting facts in it.
(define user-facts (cond [(eq? success #t) (hasheq)]
[(hash? success) success]
[else (log-warning "Bad auth success for user ~v: ~v" email success)
(hasheq)]))
(create-session! email password (create-session! email password
#:curator? (if (hash-ref user-facts 'curation #f) #t #f) #:curator? (if (hash-ref user-facts 'curation #f) #t #f)
#:superuser? (if (hash-ref user-facts 'superuser #f) #t #f))) #:superuser? (if (hash-ref user-facts 'superuser #f) #t #f)))
(define (process-login-credentials request) (define (process-login-credentials request)
(define-form-bindings request (email password)) (define-form-bindings/trim request (email password))
(if (or (equal? (string-trim email) "") (cond [(or (equal? email "") (equal? password ""))
(equal? (string-trim password) "")) (login-form "Please enter your email address and password.")]
(login-form "Please enter your email address and password.") [(not (login-password-correct? email password))
(match (authenticate-with-server! email password "") (login-form "Incorrect password, or nonexistent user.")]
[(or "wrong-code" (? eof-object?)) [else
(login-form "Something went awry; please try again.")] (create-session-after-authentication-success! email password)]))
[(or "emailed" #f)
(summarise-code-emailing "Incorrect password, or nonexistent user." email)]
[success
(create-session-from-authentication-success! email password success)])))
(define (register-form #:email [email ""] (define (register-form #:email [email ""]
#:email_for_code [email_for_code ""]
#:code [code ""] #:code [code ""]
#:error-message [error-message #f]) #:step1a-error-message [step1a-error-message #f]
#:step1b-error-message [step1b-error-message #f]
#:step2-error-message [step2-error-message #f])
(with-site-config (with-site-config
(send/suspend/dispatch/dynamic (send/suspend/dispatch/dynamic
(lambda (embed-url) (lambda (embed-url)
(define challenge (generate-challenge))
(bootstrap-response "Register/Reset Account" (bootstrap-response "Register/Reset Account"
#:title-element "" #:title-element ""
`(div ((class "registration-step-container")) `(div ((class "registration-step-container"))
@ -404,10 +408,32 @@
(p "Enter your email address below, and we'll send you one.") (p "Enter your email address below, and we'll send you one.")
(form ((class "form-horizontal") (form ((class "form-horizontal")
(method "post") (method "post")
(action ,(embed-url notify-of-emailing)) (action ,(embed-url (check-challenge challenge)))
(role "form")) (role "form"))
,(form-group 1 3 (label "email" "Email address") ,(form-group 1 3 (label "email" "Email address")
0 5 (email-input "email_for_code")) 0 5 (email-input "email_for_code" email_for_code))
,@(maybe-splice
step1a-error-message
(form-group 4 5
`(div ((class "alert alert-danger"))
(p ,step1a-error-message))))
,(form-group 1 3 (label "antispam" "Anti-spam")
0 5 `(div ((class "form-control-static"))
(p "Please help us defend our "
"infrastructure from spammers "
"by answering the following question.")
,@(maybe-splice
step1b-error-message
`(div ((class "alert alert-danger"))
(p ,step1b-error-message)))
,(challenge-question challenge)
,(form-group
0 2 `(p ((class "form-control-static"))
(b "Answer:"))
0 10 (text-input "question_answer"))
,(text-input "body"
#:extra-classes
'("not-shown-to-humans"))))
,(form-group 4 5 (primary-button "Email me a code")))) ,(form-group 4 5 (primary-button "Email me a code"))))
`(div `(div
@ -426,57 +452,76 @@
,(form-group 1 3 (label "password" "Confirm password") ,(form-group 1 3 (label "password" "Confirm password")
0 5 (password-input "confirm_password")) 0 5 (password-input "confirm_password"))
,@(maybe-splice ,@(maybe-splice
error-message step2-error-message
(form-group 4 5 (form-group 4 5
`(div ((class "alert alert-danger")) `(div ((class "alert alert-danger"))
(p ,error-message)))) (p ,step2-error-message))))
,(form-group 4 5 (primary-button "Continue"))))))))) ,(form-group 4 5 (primary-button "Continue")))))))))
(define (apply-account-code request) (define (apply-account-code request)
(define-form-bindings request (email code password confirm_password)) (define-form-bindings/trim request (email code password confirm_password))
(define (retry msg) (define (retry msg)
(register-form #:email email (register-form #:email email
#:code code #:code code
#:error-message msg)) #:step2-error-message msg))
(cond (cond
[(equal? (string-trim email) "") [(equal? email "")
(retry "Please enter your email address.")] (retry "Please enter your email address.")]
[(equal? (string-trim code) "") [(equal? code "")
(retry "Please enter the code you received in your email.")] (retry "Please enter the code you received in your email.")]
[(not (equal? password confirm_password)) [(not (equal? password confirm_password))
(retry "Please make sure the two password fields match.")] (retry "Please make sure the two password fields match.")]
[(equal? (string-trim password) "") [(equal? password "")
(retry "Please enter a password.")] (retry "Please enter a password.")]
[else [(not (registration-code-correct? email code))
(match (authenticate-with-server! email password code)
[(? eof-object?)
(retry "Something went awry. Please try again.")]
["wrong-code"
(retry "The code you entered was incorrect. Please try again.")] (retry "The code you entered was incorrect. Please try again.")]
[(or "emailed" #f) [else
(retry "Something went awry; you have been emailed another code. Please check your email.")] (register-or-update-user! email password)
[success (create-session-after-authentication-success! email password)]))
;; The email and password combo we have been given is good to go.
;; Set a cookie and consider ourselves logged in.
(create-session-from-authentication-success! email password success)])]))
(define (notify-of-emailing request) (define ((check-challenge challenge) request)
(define-form-bindings request (email_for_code)) (define-form-bindings/trim request (email_for_code question_answer))
(authenticate-with-server! email_for_code "" "") ;; TODO check result? (define (retry msg-a msg-b)
(summarise-code-emailing "Account registration/reset code emailed" email_for_code)) (register-form #:email_for_code email_for_code
#:step1a-error-message msg-a
(define (summarise-code-emailing reason email) #:step1b-error-message msg-b))
(cond
[(equal? email_for_code "")
(log-info "REGISTRATION/RESET EMAIL: address missing")
(retry "Please enter your email address."
"Don't forget to answer the new question!")]
[(equal? question_answer "")
(log-info "REGISTRATION/RESET EMAIL: no challenge answer provided")
(retry #f
"Please answer the anti-spam question. (It changes each time!)")]
[(not (challenge-passed? challenge question_answer))
(log-info "REGISTRATION/RESET EMAIL: challenge answer incorrect")
(log-info " ✗ email: ~v" email_for_code)
(log-info " ✗ challenge expr: ~a" (challenge-expr challenge))
(log-info " ✗ expected answer: ~v" (~a (challenge-answer challenge)))
(log-info " ✗ provided answer: ~v" question_answer)
(log-info " ✗ HTTP request details: ~v" request)
(retry #f
"Unfortunately, that was not the correct answer. Please try this new question.")]
[else
(log-info "REGISTRATION/RESET EMAIL: sent")
(log-info " ✓ email: ~v" email_for_code)
(log-info " ✓ challenge expr: ~a" (challenge-expr challenge))
(log-info " ✓ expected answer: ~v" (~a (challenge-answer challenge)))
(log-info " ✓ provided answer: ~v" question_answer)
(log-info " ✓ HTTP request details: ~v" request)
(send-registration-or-reset-email! email_for_code)
(with-site-config (with-site-config
(send/suspend/dispatch/dynamic (send/suspend/dispatch/dynamic
(lambda (embed-url) (lambda (embed-url)
(bootstrap-response reason (bootstrap-response "Account registration/reset code emailed"
`(p `(p
"We've emailed an account registration/reset code to " "We've emailed an account registration/reset code to "
(code ,email) ". Please check your email and then click " (code ,email_for_code) ". Please check your email and then click "
"the button to continue:") "the button to continue:")
`(a ((class "btn btn-primary") `(a ((class "btn btn-primary")
(href ,(embed-url (lambda (req) (register-form))))) (href ,(embed-url (lambda (req) (register-form)))))
"Enter your code")))))) "Enter your code")))))]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -552,6 +597,10 @@
(string-append (date->string (seconds->date utc #f) #t) " (UTC)") (string-append (date->string (seconds->date utc #f) #t) " (UTC)")
"N/A")) "N/A"))
(define (get-implied-docs pkg)
(define implied-names (map string->symbol (package-implies pkg)))
(append-map package-docs (package-batch-detail implied-names)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Package hashtable getters. ;; Package hashtable getters.
;; TODO factor this stuff out into a proper data structure ;; TODO factor this stuff out into a proper data structure
@ -582,6 +631,7 @@
(define (package-last-updated pkg) (or (@ pkg last-updated) 0)) (define (package-last-updated pkg) (or (@ pkg last-updated) 0))
(define (package-last-checked pkg) (or (@ pkg last-checked) 0)) (define (package-last-checked pkg) (or (@ pkg last-checked) 0))
(define (package-last-edit pkg) (or (@ pkg last-edit) 0)) (define (package-last-edit pkg) (or (@ pkg last-edit) 0))
(define (package-date-added pkg) (or (@ pkg date-added) 0))
(define (package-authors pkg) (or (@ pkg authors) '())) (define (package-authors pkg) (or (@ pkg authors) '()))
(define (package-description pkg) (or (@ pkg description) "")) (define (package-description pkg) (or (@ pkg description) ""))
(define (package-tags pkg) (or (@ pkg tags) '())) (define (package-tags pkg) (or (@ pkg tags) '()))
@ -595,30 +645,50 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (package-summary-table package-names) (define (package-summary-table package-names)
(define bulk-operations-enabled? (current-user-curator?))
(define column-count (+ 4 (if bulk-operations-enabled? 1 0)))
(define-values (pkg-rows num-todos) (define-values (pkg-rows num-todos)
(build-pkg-rows/num-todos package-names)) (build-pkg-rows/num-todos bulk-operations-enabled? package-names))
`(table `(form ((role "form")
(action ,(named-url bulk-operation-page))
(method "post"))
(table
((class "packages sortable") (data-todokey ,(number->string num-todos))) ((class "packages sortable") (data-todokey ,(number->string num-todos)))
(thead (thead
,@(maybe-splice
bulk-operations-enabled?
`(tr
(td ((colspan ,(~a column-count)))
(div ((class "input-group"))
(select ((class "form-control") (id "bulk-action") (name "bulk-action"))
(option ((value "")) "--- Select a bulk action to perform ---")
(option ((value "make-ring-0")) "Set selected packages to ring 0")
(option ((value "make-ring-1")) "Set selected packages to ring 1")
(option ((value "make-ring-2")) "Set selected packages to ring 2")
)
(span ((class "input-group-btn"))
(button ((class "btn btn-default") (type "submit"))
"Go!")))
(div ((class "input-group"))
(button ((class "btn")
(type "button")
(onclick "toggleBulkOperationSelections()"))
"Select all/none")))))
(tr (tr
(th 'nbsp) (th 'nbsp)
,@(maybe-splice bulk-operations-enabled? `(th 'nbsp))
(th "Package") (th "Package")
(th "Description") (th "Description")
(th "Build") (th "Build")
(th ((style "display: none")) 'nbsp))) ;; todokey (th ((style "display: none")) 'nbsp))) ;; todokey
(tbody (tbody
,@(maybe-splice (null? package-names) ,@(maybe-splice (null? package-names)
`(tr (td ((colspan "4")) `(tr (td ((colspan ,(~a column-count)))
(div ((class "alert alert-info")) (div ((class "alert alert-info"))
"No packages found.")))) "No packages found."))))
,@pkg-rows))) ,@pkg-rows))))
(define (get-implied-docs pkg) (define (build-pkg-rows/num-todos bulk-operations-enabled? package-names)
(let* ([implied-names (package-implies pkg)]
[implied-pkgs (package-batch-detail implied-names)])
(map package-docs implied-pkgs)))
(define (build-pkg-rows/num-todos package-names)
;; Builds the list of rows in the package table as an x-exp. ;; Builds the list of rows in the package table as an x-exp.
;; Also returns the total number of non-zero todo keys, ;; Also returns the total number of non-zero todo keys,
;; representing packages with outstanding build errors or ;; representing packages with outstanding build errors or
@ -627,12 +697,7 @@
(define-values (pkg-rows num-todos) (define-values (pkg-rows num-todos)
(for/fold ([pkg-rows null] [num-todos 0]) (for/fold ([pkg-rows null] [num-todos 0])
([pkg (package-batch-detail package-names)]) ([pkg (package-batch-detail package-names)])
(define pkg-docs (define pkg-docs (append (package-docs pkg) (get-implied-docs pkg)))
(let ([implied-docs (get-implied-docs)]
[pkg-docs (package-docs pkg)])
(if (null? pkg-docs)
implied-docs
(list* pkg-docs implied-docs))))
(define has-docs? (pair? pkg-docs)) (define has-docs? (pair? pkg-docs))
(define has-readme? (pair? (package-readme-url pkg))) (define has-readme? (pair? (package-readme-url pkg)))
(define has-tags? (pair? (package-tags pkg))) (define has-tags? (pair? (package-tags pkg)))
@ -657,6 +722,13 @@
(label-p (if (< todokey 5) (label-p (if (< todokey 5)
"label-warning" "label-warning"
"label-danger") "Todo"))) "label-danger") "Todo")))
,@(maybe-splice
bulk-operations-enabled?
`(td (p "Ring " ,(~a (package-ring pkg)))
,(checkbox-input "selected-packages"
(package-name pkg)
#:id #f
#:extra-classes `("selected-packages"))))
(td (h2 ,(package-link (package-name pkg))) (td (h2 ,(package-link (package-name pkg)))
,(authors-list (package-authors pkg))) ,(authors-list (package-authors pkg)))
(td (p ,(if (string=? "" (package-description pkg)) (td (p ,(if (string=? "" (package-description pkg))
@ -698,15 +770,18 @@
(define dep-failure-log-url (package-build-dep-failure-log pkg)) (define dep-failure-log-url (package-build-dep-failure-log pkg))
(define test-failure-log-url (package-build-test-failure-log pkg)) (define test-failure-log-url (package-build-test-failure-log pkg))
(define test-success-log-url (package-build-test-success-log pkg)) (define test-success-log-url (package-build-test-success-log pkg))
(define conflicts-log-url (package-build-conflicts-log pkg))
(define td-class (cond [failure-log-url "build_red"] (define td-class (cond [(or failure-log-url conflicts-log-url) "build_red"]
[(not success-log-url) ""] [(not success-log-url) ""]
[(or dep-failure-log-url test-failure-log-url) "build_yellow"] [(or dep-failure-log-url test-failure-log-url) "build_yellow"]
[else "build_green"])) [else "build_green"]))
`(td ((class ,td-class)) `(td ((class ,td-class))
,@(for/list [(e (list (list failure-log-url "" "fails") ,@(for/list [(e (list (if failure-log-url
(list success-log-url "" "succeeds") (list failure-log-url "" "fails")
(list success-log-url "" "succeeds"))
(list conflicts-log-url "; has " "conflicts")
(list dep-failure-log-url "; has " "dependency problems") (list dep-failure-log-url "; has " "dependency problems")
(list test-failure-log-url "; has " "failing tests")))] (list test-failure-log-url "; has " "failing tests")))]
(match-define (list u p l) e) (match-define (list u p l) e)
@ -719,7 +794,7 @@
(parameterize ((bootstrap-active-navigation nav-index) (parameterize ((bootstrap-active-navigation nav-index)
(bootstrap-page-scripts (list (static-resource-url "/searchbox.js") (bootstrap-page-scripts (list (static-resource-url "/searchbox.js")
(static-resource-url "/index.js") (static-resource-url "/index.js")
(static-resource-url "/todos.js")))) (static-resource-url "/package-list.js"))))
(define package-name-list (package-search "" '((main-distribution #f) (define package-name-list (package-search "" '((main-distribution #f)
(main-tests #f) (main-tests #f)
(deprecated #f)))) (deprecated #f))))
@ -733,9 +808,9 @@
`(div ((class "jumbotron")) `(div ((class "jumbotron"))
(h1 "Racket Packages") (h1 "Racket Packages")
(p "These are the packages in the official " (p "These are the packages in the official "
(a ((href "http://docs.racket-lang.org/pkg/getting-started.html")) (a ((href "https://docs.racket-lang.org/pkg/getting-started.html"))
"package catalog") ".") "package catalog") ".")
(p (a ((href "http://docs.racket-lang.org/pkg/cmdline.html")) (p (a ((href "https://docs.racket-lang.org/pkg/cmdline.html"))
(kbd "raco pkg install " (var "package-name"))) (kbd "raco pkg install " (var "package-name")))
" installs a package.") " installs a package.")
(p "You can " (p "You can "
@ -805,10 +880,17 @@
`(ul (li (a ((href ,(main-page-url))) `(ul (li (a ((href ,(main-page-url)))
"Return to the package index")))))) "Return to the package index"))))))
(define (current-user-superuser?)
(and (current-session)
(session-superuser? (current-session))))
(define (current-user-curator?)
(and (current-session)
(session-curator? (current-session))))
(define (current-user-may-edit? pkg) (define (current-user-may-edit? pkg)
(or (member (current-email) (package-authors pkg)) (or (member (current-email) (package-authors pkg))
(and (current-session) (current-user-superuser?)))
(session-superuser? (current-session)))))
(define (package-page request package-name-str) (define (package-page request package-name-str)
(define package-name (string->symbol package-name-str)) (define package-name (string->symbol package-name-str))
@ -831,6 +913,7 @@
(let ((default-version (package-default-version pkg))) (let ((default-version (package-default-version pkg)))
(bootstrap-response (~a package-name) (bootstrap-response (~a package-name)
#:title-element "" #:title-element ""
#:description (package-description pkg)
`(div ((class "jumbotron")) `(div ((class "jumbotron"))
(h1 ,(~a package-name)) (h1 ,(~a package-name))
(p ,(package-description pkg)) (p ,(package-description pkg))
@ -935,9 +1018,7 @@
(tr (th "Ring") (tr (th "Ring")
(td ,(~a (or (package-ring pkg) "N/A")) (td ,(~a (or (package-ring pkg) "N/A"))
,@(maybe-splice ,@(maybe-splice
(and (package-ring pkg) (and (package-ring pkg) (current-user-curator?))
(current-session)
(session-curator? (current-session)))
" " " "
(ring-change-link pkg (- (package-ring pkg) 1) 'blacktriangledown) (ring-change-link pkg (- (package-ring pkg) 1) 'blacktriangledown)
(ring-change-link pkg (+ (package-ring pkg) 1) 'blacktriangle)))) (ring-change-link pkg (+ (package-ring pkg) 1) 'blacktriangle))))
@ -988,7 +1069,7 @@
(values k v)))) (values k v))))
(maybe-splice (maybe-splice
(not (hash-empty? vs)) (not (hash-empty? vs))
`(tr (th "Versions") `(tr (th (a ([href "https://docs.racket-lang.org/pkg/getting-started.html#%28part._.Version_.Exceptions%29"]) "Version Exceptions"))
(td (table ((class "package-versions")) (td (table ((class "package-versions"))
(tr (th "Version") (tr (th "Version")
(th "Source") (th "Source")
@ -1005,6 +1086,8 @@
(td ,(utc->string (package-last-checked pkg)))) (td ,(utc->string (package-last-checked pkg))))
(tr (th "Last edited") (tr (th "Last edited")
(td ,(utc->string (package-last-edit pkg)))) (td ,(utc->string (package-last-edit pkg))))
(tr (th "Date added")
(td ,(utc->string (package-date-added pkg))))
(tr (th "Modules") (tr (th "Modules")
(td (ul ((class "module-list")) (td (ul ((class "module-list"))
,@(for/list ((mod (package-modules pkg))) ,@(for/list ((mod (package-modules pkg)))
@ -1128,18 +1211,7 @@
,(textfield "g_host_port" "Host" g-host+port) ,(textfield "g_host_port" "Host" g-host+port)
,(textfield "g_repo" "Repository" g-repo "user/repo") ,(textfield "g_repo" "Repository" g-repo "user/repo")
,(textfield "g_commit" "Branch or commit" g-commit "master") ,(textfield "g_commit" "Branch or commit" g-commit "master")
,(textfield "g_path" "Path within repository" g-path) ,(textfield "g_path" "Path within repository" g-path))))))
,(row #:id (group-name "g_transport")
0 3
(label (control-name "g_transport") "Transport")
0 9
`(select ((id ,(control-name "g_transport"))
(name ,(control-name "g_transport")))
,@(for/list [(t (list "git" "https" "http"))]
`(option ((value ,t)
,@(maybe-splice (equal? t g-transport)
'(selected "selected")))
,t)))))))))
(tr (td ((colspan "2")) (tr (td ((colspan "2"))
(div ((class "form-inline")) (div ((class "form-inline"))
@ -1236,7 +1308,7 @@
(define ((update-draft draft0) request) (define ((update-draft draft0) request)
(define draft (read-draft-form draft0 (request-bindings request))) (define draft (read-draft-form draft0 (request-bindings request)))
(define-form-bindings request (action new_version)) (define-form-bindings/trim request (action new_version))
(match action (match action
["save_changes" ["save_changes"
(if (save-draft! draft) (if (save-draft! draft)
@ -1248,7 +1320,7 @@
draft))] draft))]
["add_version" ["add_version"
(cond (cond
[(equal? (string-trim new_version) "") [(equal? new_version "")
(package-form "Please enter a version number to add." draft)] (package-form "Please enter a version number to add." draft)]
[(assoc new_version (draft-package-versions draft)) [(assoc new_version (draft-package-versions draft))
(package-form (format "Could not add version ~a, as it already exists." new_version) (package-form (format "Could not add version ~a, as it already exists." new_version)
@ -1272,12 +1344,10 @@
(g (string->symbol (format "version__~a__~a" version name)) d)) (g (string->symbol (format "version__~a__~a" version name)) d))
(define type (vg 'type "simple")) (define type (vg 'type "simple"))
(define simple_url (vg 'simple_url "")) (define simple_url (vg 'simple_url ""))
(define g_transport (vg 'g_transport ""))
(define g_host_port (vg 'g_host_port "")) (define g_host_port (vg 'g_host_port ""))
(define g_repo0 (vg 'g_repo "")) (define g_repo0 (vg 'g_repo ""))
(define g_repo (cond (define g_repo (cond
[(regexp-match #rx"[.]git$" g_repo0) g_repo0] [(regexp-match #rx"[.]git$" g_repo0) g_repo0]
[(equal? g_transport "git") g_repo0]
[else (string-append g_repo0 ".git")])) [else (string-append g_repo0 ".git")]))
(define g_commit0 (vg 'g_commit "")) (define g_commit0 (vg 'g_commit ""))
(define g_path (vg 'g_path "")) (define g_path (vg 'g_path ""))
@ -1291,7 +1361,7 @@
(match type (match type
["simple" simple_url] ["simple" simple_url]
["git" (unparse-package-source (git-source "" #f #f ["git" (unparse-package-source (git-source "" #f #f
(string->symbol g_transport) 'https
g_host g_host
g_port g_port
g_repo g_repo
@ -1411,27 +1481,35 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (update-package-ring-page request package-name-str proposed-new-ring) (define (update-package-ring-page request package-name-str proposed-new-ring)
(define new-ring (clamp-ring proposed-new-ring))
(authentication-wrap/require-login (authentication-wrap/require-login
#:request request #:request request
(when (session-curator? (current-session)) (update-package-rings! (list package-name-str) proposed-new-ring)
(when (simple-json-rpc! backend-baseurl
"/api/package/curate"
(hash 'pkg package-name-str
'ring new-ring))
(define old-pkg (package-detail (string->symbol package-name-str)))
(let* ((new-pkg (hash-set old-pkg 'ring new-ring))
(completion-ch (make-channel)))
(replace-package! completion-ch old-pkg new-pkg)
(channel-get completion-ch))))
(bootstrap-redirect (view-package-url package-name-str)))) (bootstrap-redirect (view-package-url package-name-str))))
(define (update-package-rings! package-name-strings proposed-new-ring)
(if (not (current-user-curator?))
#f
(let ((new-ring (clamp-ring proposed-new-ring)))
(if (not (simple-json-rpc! backend-baseurl
"/api/package/curate"
(hash 'package-names package-name-strings
'ring new-ring)))
#f
(begin
(for [(package-name-str (in-list package-name-strings))]
(define old-pkg (package-detail (string->symbol package-name-str)))
(define new-pkg (hash-set old-pkg 'ring new-ring))
(let ((completion-ch (make-channel)))
(replace-package! completion-ch old-pkg new-pkg)
(channel-get completion-ch)))
#t)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (search-page request) (define (search-page request)
(parameterize ((bootstrap-active-navigation nav-search) (parameterize ((bootstrap-active-navigation nav-search)
(bootstrap-page-scripts (list (static-resource-url "/searchbox.js") (bootstrap-page-scripts (list (static-resource-url "/searchbox.js")
(static-resource-url "/todos.js")))) (static-resource-url "/package-list.js"))))
(authentication-wrap (authentication-wrap
#:request request #:request request
(define-form-bindings request ([search-text q ""] (define-form-bindings request ([search-text q ""]
@ -1452,8 +1530,8 @@
0 10(text-input "tags" tags-input 0 10(text-input "tags" tags-input
#:placeholder #:placeholder
"tag1 tag2 tag3 ...")) "tag1 tag2 tag3 ..."))
,(form-group 2 10 (primary-button (glyphicon 'search) " Search")) ,(form-group 2 10 (primary-button (glyphicon 'search) " Search")))
(div ((class "search-results")) `(div ((class "search-results"))
,@(maybe-splice ,@(maybe-splice
(or (pair? tags) (not (equal? search-text ""))) (or (pair? tags) (not (equal? search-text "")))
(let ((package-name-list (package-search search-text tags))) (let ((package-name-list (package-search search-text tags)))
@ -1461,7 +1539,7 @@
(p ((class "package-count")) (p ((class "package-count"))
,(format "~a packages found" (length package-name-list))) ,(format "~a packages found" (length package-name-list)))
(p ((class "package-count") (id "todo-msg")) "") (p ((class "package-count") (id "todo-msg")) "")
,(package-summary-table package-name-list)))))))))) ,(package-summary-table package-name-list)))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -1486,6 +1564,46 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (ping-page request)
(response/full 200 #"Alive" (current-seconds) #"text/plain" '() '()))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (bulk-operation-page request)
(authentication-wrap/require-login
#:request request
(cond
[(not (or (current-user-curator?) (current-user-superuser?)))
(bootstrap-redirect (main-page-url))]
[else
(define bindings (request-bindings request))
(define action (extract-binding/single 'bulk-action bindings))
(define package-names (extract-bindings 'selected-packages bindings))
(cond
[(equal? action "")
(bootstrap-response "No action selected.")]
[else
(send/suspend/dynamic
(lambda (k-url)
(bootstrap-response "Confirm bulk operation"
`(div ((class "confirm-bulk-operation"))
(h2 "You are about to " (code ,action) " the following packages:")
(ul ,@(map (lambda (p) `(li ,p))
package-names))
(p "This cannot be undone.")
(form ((action ,k-url) (method "post"))
(button ((class "btn btn-default") (type "submit"))
"Confirm bulk operation"))))))
(match action
["make-ring-0" (update-package-rings! package-names 0)]
["make-ring-1" (update-package-rings! package-names 1)]
["make-ring-2" (update-package-rings! package-names 2)]
[_ (error 'bulk-operation-page "No such action: ~a" action)])
(bootstrap-response "Bulk operation complete."
`(a ((href ,(main-page-url))) "Return to main index page."))])])))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TODO: fold the collection of this information into the package ;; TODO: fold the collection of this information into the package
;; database itself. ;; database itself.
(define (update-external-package-information! package-name) (define (update-external-package-information! package-name)
@ -1532,7 +1650,7 @@
(match/values (http/simple-interpret-response (match/values (http/simple-interpret-response
(http/follow-redirects (http/follow-redirects
#"HEAD" #"HEAD"
(http-sendrecv/url readme-u #:method #"HEAD"))) (custom-http-sendrecv/url readme-u #:method #"HEAD")))
[('success _headers _body) (url->string readme-u)] [('success _headers _body) (url->string readme-u)]
[(_ _ _) #f]))) [(_ _ _) #f])))
@ -1546,7 +1664,7 @@
(match/values (http/simple-interpret-response (match/values (http/simple-interpret-response
(http/follow-redirects (http/follow-redirects
#"GET" #"GET"
(http-sendrecv/url u #:method #"GET"))) (custom-http-sendrecv/url u #:method #"GET")))
[('success _headers body) [('success _headers body)
(and (regexp-match? #px"(?i:id=.readme.)" body) (and (regexp-match? #px"(?i:id=.readme.)" body)
(string-append (url->string u) "#readme"))] (string-append (url->string u) "#readme"))]

View File

@ -173,12 +173,14 @@
(define (put/bytes^ p cb mt h) (define (put/bytes^ p cb mt h)
(semaphore-wait put-bytes-sema) (semaphore-wait put-bytes-sema)
(thread (thread
(procedure-rename
(lambda () (lambda ()
(with-handlers ((values (lambda (e) (with-handlers ((values (lambda (e)
(semaphore-post put-bytes-sema) (semaphore-post put-bytes-sema)
(raise e)))) (raise e))))
(put/bytes p cb mt h) (put/bytes p cb mt h)
(semaphore-post put-bytes-sema))))) (semaphore-post put-bytes-sema)))
(string->symbol (format "~v" (list 'put/bytes^ p))))))
(define (aws-put-file! index absolute-path content-bytes mime-type [headers '()]) (define (aws-put-file! index absolute-path content-bytes mime-type [headers '()])
(define relative-path (absolute-path->relative-path absolute-path)) (define relative-path (absolute-path->relative-path absolute-path))

84
src/users.rkt Normal file
View File

@ -0,0 +1,84 @@
#lang racket/base
;; User management - userdb, plus registration and emailing
(provide login-password-correct?
send-registration-or-reset-email!
registration-code-correct?
register-or-update-user!)
(require net/sendmail)
(require reloadable)
(require infrastructure-userdb)
(require "config.rkt")
(require "hash-utils.rkt")
(define-logger racket-pkg-website/users)
(define userdb (userdb-config (@ (config) user-directory)
#t ;; writeable!
))
(define *codes*
(make-persistent-state '*codes* (lambda () (make-registration-state))))
(define (login-password-correct? email given-password)
(log-racket-pkg-website/users-info "Checking password for ~v" email)
(user-password-correct? (lookup-user userdb email) given-password))
(define (send-registration-or-reset-email! email)
(if (user-exists? userdb email)
(send-password-reset-email! email)
(send-account-registration-email! email)))
(define (sender-address)
(or (@ (config) email-sender-address)
"pkgs@racket-lang.org"))
(log-racket-pkg-website/users-info "Will use sender address ~v" (sender-address))
(define (send-password-reset-email! email)
(log-racket-pkg-website/users-info "Sending password reset email to ~v" email)
(send-mail-message
(sender-address)
"Account password reset for Racket Package Catalog"
(list email)
'()
'()
(list
"Someone tried to login with your email address for an account on the Racket Package Catalog, but failed."
"If this was you, please use this code to reset your password:"
""
(generate-registration-code! (*codes*) email)
""
"This code will expire, so if it is not available, you'll have to try again.")))
(define (send-account-registration-email! email)
(log-racket-pkg-website/users-info "Sending account registration email to ~v" email)
(send-mail-message
(sender-address)
"Account confirmation for Racket Package Catalog"
(list email)
'()
'()
(list
"Someone tried to register your email address for an account on the Racket Package Catalog."
"If you want to proceed, use this code:"
""
(generate-registration-code! (*codes*) email)
""
"This code will expire, so if it is not available, you'll have to try to register again.")))
(define (registration-code-correct? email given-code)
(log-racket-pkg-website/users-info "Checking registration code for ~v" email)
(check-registration-code (*codes*)
email
given-code
(lambda () #t)
(lambda () #f)))
(define (register-or-update-user! email password)
(log-racket-pkg-website/users-info "Updating user record ~v" email)
(save-user! userdb
(user-password-set (or (lookup-user userdb email)
(make-user email password))
password)))

View File

@ -34,7 +34,7 @@ function preenSourceType(e) {
case "git": case "git":
previewGroup.show(); previewGroup.show();
pieces = showhide(false, true, true, true, true, true); pieces = showhide(false, true, true, true, true, true);
previewUrl = pieces[1] + "://" + pieces[2] + "/" + pieces[3] + previewUrl = "https" + "://" + pieces[2] + "/" + pieces[3] +
(pieces[5] ? "?path=" + pieces[5] : "") + (pieces[5] ? "?path=" + pieces[5] : "") +
(pieces[4] && (pieces[4] !== 'master') ? '#' + pieces[4] : ""); (pieces[4] && (pieces[4] !== 'master') ? '#' + pieces[4] : "");
break; break;

View File

@ -1,3 +1,12 @@
// Functions related to package listings as produced by `package-summary-table` in site.rkt
function toggleBulkOperationSelections() {
var checkboxes = Array.from(document.querySelectorAll("input.selected-packages"));
var anySelected = checkboxes.some(function (n) { return n.checked; });
var newState = anySelected ? false : true;
checkboxes.forEach(function (n) { n.checked = newState; });
}
$(function() { $(function() {
"use strict"; "use strict";

View File

@ -83,6 +83,10 @@ input#new_version {
width: 6em; width: 6em;
} }
.not-shown-to-humans {
display: none;
}
.confirm-package-deletion { .confirm-package-deletion {
background-color: red; background-color: red;
padding: 2em; padding: 2em;
@ -98,6 +102,20 @@ input#new_version {
margin: 0; margin: 0;
} }
.confirm-bulk-operation {
background-color: #ffdddd;
padding: 2em;
display: block;
border: solid black 1rem;
font-size: 120%;
}
.confirm-bulk-operation h2 {
margin: 0;
}
.confirm-bulk-operation ul {
margin: 2em;
}
.package-count { .package-count {
font-size: 120%; font-size: 120%;
} }