├── .gitignore ├── README.md ├── golang ├── README.md ├── cmd │ ├── go.mod │ └── notify │ │ └── main.go └── pkg │ ├── go.mod │ └── notify.go ├── node-express ├── .gitignore ├── README.md ├── package.json ├── src │ ├── envHelpers.ts │ ├── index.ts │ └── webhooksUtil.ts ├── tsconfig.json ├── tslint.json └── yarn.lock ├── python-django ├── .gitignore ├── README.md ├── requirements.txt └── webhook_server │ ├── backend │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── middleware.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── urls.py │ └── views.py │ ├── manage.py │ └── webhook_server │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── rust-actix ├── Cargo.lock ├── Cargo.toml ├── README.md └── src ├── env_helpers.rs ├── main.rs └── notify.rs /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | target 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example Alchemy Notify Webhook Server 2 | 3 | A simple set of examples to set up a webhook server using Alchemy Notify. 4 | 5 | ## Getting started 6 | 7 | To get started, follow these 4 simple steps: 8 | 9 | 1. :wave: New to Alchemy? Get access to Alchemy for free [here](https://dashboard.alchemyapi.io/signup/?a=b4823c8466) 10 | 2. :link: Create a local webhook proxy using ngrok, you can follow the steps [here](https://docs.alchemy.com/alchemy/enhanced-apis/notify-api/using-notify#test-out-webhooks) 11 | 3. :loudspeaker: Using your ngrok link above and the webhook path you want (ex https://your-link-from-step-2.ngrok.io/webhook-path), set up the Alchemy Notify webhook of your choice on your dashboard [here](https://dashboard.alchemyapi.io/notify) 12 | 4. :rocket: Pick your language/framework of choice: 13 | - [node-express](https://github.com/alchemyplatform/webhook-examples/tree/master/node-express) 14 | - [python-django](https://github.com/alchemyplatform/webhook-examples/tree/master/python-django) 15 | - [golang](https://github.com/alchemyplatform/webhook-examples/tree/master/golang) 16 | - [rust-actix](https://github.com/alchemyplatform/webhook-examples/tree/master/rust-actix) 17 | -------------------------------------------------------------------------------- /golang/README.md: -------------------------------------------------------------------------------- 1 | # Example Alchemy Notify Webhooks Server in Go 2 | 3 | A simple example webhook server for using Alchemy Notify that uses golang. 4 | 5 | ## Run 6 | 7 | First, access the command directory: 8 | 9 | ``` 10 | cd cmd 11 | ``` 12 | 13 | To run on localhost:8080: 14 | 15 | ``` 16 | PORT=8080 HOST=localhost SIGNING_KEY=whsec_your_key_here go run notify/main.go 17 | ``` 18 | 19 | Please change SIGNING_KEY to the signing key corresponding to your webhook, which you can find [here](https://docs.alchemy.com/alchemy/enhanced-apis/notify-api/using-notify#1.-find-your-signing-key) 20 | 21 | And just like that, you're done! 22 | 23 | NOTE: Your webhook path is currently set to "/webhook-path" in `cmd/notify/main.go`, but feel free to change it to whatever path you'd like. 24 | 25 | ## Debugging 26 | 27 | If you aren't receiving any webhooks, be sure you followed the steps [here](https://github.com/alchemyplatform/#readme). 28 | -------------------------------------------------------------------------------- /golang/cmd/go.mod: -------------------------------------------------------------------------------- 1 | module notify_example 2 | 3 | go 1.18 4 | 5 | require github.com/alchemyplatform/alchemy-notify-sdk-go v1.0.0 6 | 7 | replace github.com/alchemyplatform/alchemy-notify-sdk-go v1.0.0 => ../pkg 8 | -------------------------------------------------------------------------------- /golang/cmd/notify/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "os" 8 | 9 | notify "github.com/alchemyplatform/alchemy-notify-sdk-go" 10 | ) 11 | 12 | func setDefaultEnvVar(k string, v string) { 13 | if _, ok := os.LookupEnv(k); !ok { 14 | os.Setenv(k, v) 15 | } 16 | } 17 | 18 | func handleWebhook(w http.ResponseWriter, req *http.Request, event *notify.AlchemyWebhookEvent) { 19 | // Do stuff with with webhook event here! 20 | log.Printf("Processing webhook event id: %s\n", event.Id) 21 | // Be sure to respond with 200 when you successfully process the event 22 | w.Write([]byte("Alchemy Notify is the best!")) 23 | } 24 | 25 | func main() { 26 | setDefaultEnvVar("PORT", "8080") 27 | setDefaultEnvVar("HOST", "127.0.0.1") 28 | setDefaultEnvVar("SIGNING_KEY", "whsec_test") 29 | 30 | port := os.Getenv("PORT") 31 | host := os.Getenv("HOST") 32 | signingKey := os.Getenv("SIGNING_KEY") 33 | 34 | mux := http.NewServeMux() 35 | 36 | // Register handler for Alchemy Notify webhook events 37 | mux.Handle( 38 | // TODO: update to your own webhook path 39 | "/webhook-path", 40 | // Middleware needed to validate the alchemy signature 41 | notify.NewAlchemyRequestHandlerMiddleware(handleWebhook, signingKey), 42 | ) 43 | 44 | // Listen to Alchemy Notify webhook events 45 | addr := fmt.Sprintf("%s:%s", host, port) 46 | log.Printf("Example Alchemy Notify app listening at %s\n", addr) 47 | err := http.ListenAndServe(addr, mux) 48 | log.Fatal(err) 49 | } 50 | -------------------------------------------------------------------------------- /golang/pkg/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alchemyplatform/alchemy-notify-sdk-go 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /golang/pkg/notify.go: -------------------------------------------------------------------------------- 1 | package notify 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha256" 6 | "encoding/hex" 7 | "encoding/json" 8 | "io/ioutil" 9 | "log" 10 | "net/http" 11 | "time" 12 | ) 13 | 14 | type AlchemyWebhookEvent struct { 15 | WebhookId string 16 | Id string 17 | CreatedAt time.Time 18 | Type string 19 | Event map[string]interface{} 20 | } 21 | 22 | func jsonToAlchemyWebhookEvent(body []byte) (*AlchemyWebhookEvent, error) { 23 | event := new(AlchemyWebhookEvent) 24 | if err := json.Unmarshal(body, &event); err != nil { 25 | return nil, err 26 | } 27 | return event, nil 28 | } 29 | 30 | // Middleware helpers for handling an Alchemy Notify webhook request 31 | type AlchemyRequestHandler func(http.ResponseWriter, *http.Request, *AlchemyWebhookEvent) 32 | 33 | type AlchemyRequestHandlerMiddleware struct { 34 | handler AlchemyRequestHandler 35 | signingKey string 36 | } 37 | 38 | func NewAlchemyRequestHandlerMiddleware(handler AlchemyRequestHandler, signingKey string) *AlchemyRequestHandlerMiddleware { 39 | return &AlchemyRequestHandlerMiddleware{handler, signingKey} 40 | } 41 | 42 | func isValidSignatureForStringBody( 43 | body []byte, 44 | signature string, 45 | signingKey []byte, 46 | ) bool { 47 | h := hmac.New(sha256.New, signingKey) 48 | h.Write([]byte(body)) 49 | digest := hex.EncodeToString(h.Sum(nil)) 50 | return digest == signature 51 | } 52 | 53 | func (arh *AlchemyRequestHandlerMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { 54 | signature := r.Header.Get("x-alchemy-signature") 55 | body, err := ioutil.ReadAll(r.Body) 56 | if err != nil { 57 | http.Error(w, err.Error(), http.StatusInternalServerError) 58 | log.Panic(err) 59 | return 60 | } 61 | r.Body.Close() 62 | 63 | isValidSignature := isValidSignatureForStringBody(body, signature, []byte(arh.signingKey)) 64 | if !isValidSignature { 65 | errMsg := "Signature validation failed, unauthorized!" 66 | http.Error(w, errMsg, http.StatusForbidden) 67 | log.Panic(errMsg) 68 | return 69 | } 70 | 71 | event, err := jsonToAlchemyWebhookEvent(body) 72 | if err != nil { 73 | http.Error(w, err.Error(), http.StatusBadRequest) 74 | log.Panic(err) 75 | return 76 | } 77 | arh.handler(w, r, event) 78 | } 79 | -------------------------------------------------------------------------------- /node-express/.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # NPM 5 | node_modules 6 | -------------------------------------------------------------------------------- /node-express/README.md: -------------------------------------------------------------------------------- 1 | # Example Alchemy Notify Webhooks Server in Node 2 | 3 | A simple example webhook server for using Alchemy Notify that uses node express. 4 | 5 | ## Installation 6 | 7 | #### Simple setup 8 | 9 | First, install Yarn if you don't have it: 10 | 11 | ``` 12 | npm install -g yarn 13 | ``` 14 | 15 | Then, install the dependencies of all packages: 16 | 17 | ``` 18 | yarn 19 | ``` 20 | 21 | ## Run 22 | 23 | To run on localhost:8080: 24 | 25 | ``` 26 | PORT=8080 HOST=localhost SIGNING_KEY=whsec_your_key_here yarn start 27 | ``` 28 | 29 | Please change SIGNING_KEY to the signing key corresponding to your webhook, which you can find [here](https://docs.alchemy.com/alchemy/enhanced-apis/notify-api/using-notify#1.-find-your-signing-key) 30 | 31 | And just like that, you're done! 32 | 33 | NOTE: Your webhook path is currently set to "/webhook-path" in `src/index.ts`, but feel free to change it to whatever path you'd like. 34 | 35 | ## Debugging 36 | 37 | If you aren't receiving any webhooks, be sure you followed the steps [here first](https://github.com/alchemyplatform/#readme). 38 | -------------------------------------------------------------------------------- /node-express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webhooks-example", 3 | "version": "1.0.0", 4 | "private": true, 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "ts-node --transpile-only src/index.ts" 8 | }, 9 | "devDependencies": { 10 | "@types/express": "^4.17.13", 11 | "@types/node": "^17.0.35", 12 | "typescript": "^4.7.2" 13 | }, 14 | "dependencies": { 15 | "express": "^4.17.3", 16 | "ts-node": "^10.8.0", 17 | "tslib": "^1.11.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /node-express/src/envHelpers.ts: -------------------------------------------------------------------------------- 1 | export function setDefaultEnvVar( 2 | key: string, 3 | defaultValue: string | undefined 4 | ) { 5 | const value = process.env[key]; 6 | if (value === undefined) { 7 | process.env[key] = defaultValue; 8 | } 9 | } 10 | 11 | export function getRequiredEnvVar(key: string): string { 12 | const value = process.env[key]; 13 | if (value === undefined) { 14 | throw Error(`${key} env var does not exist!`); 15 | } 16 | return value; 17 | } 18 | -------------------------------------------------------------------------------- /node-express/src/index.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { getRequiredEnvVar, setDefaultEnvVar } from "./envHelpers"; 3 | import { 4 | addAlchemyContextToRequest, 5 | validateAlchemySignature, 6 | AlchemyWebhookEvent, 7 | } from "./webhooksUtil"; 8 | 9 | async function main(): Promise { 10 | const app = express(); 11 | 12 | setDefaultEnvVar("PORT", "8080"); 13 | setDefaultEnvVar("HOST", "127.0.0.1"); 14 | setDefaultEnvVar("SIGNING_KEY", "whsec_test"); 15 | 16 | const port = +getRequiredEnvVar("PORT"); 17 | const host = getRequiredEnvVar("HOST"); 18 | const signingKey = getRequiredEnvVar("SIGNING_KEY"); 19 | 20 | // Middleware needed to validate the alchemy signature 21 | app.use( 22 | express.json({ 23 | verify: addAlchemyContextToRequest, 24 | }) 25 | ); 26 | app.use(validateAlchemySignature(signingKey)); 27 | 28 | // Register handler for Alchemy Notify webhook events 29 | // TODO: update to your own webhook path 30 | app.post("/webhook-path", (req, res) => { 31 | const webhookEvent = req.body as AlchemyWebhookEvent; 32 | // Do stuff with with webhook event here! 33 | console.log(`Processing webhook event id: ${webhookEvent.id}`); 34 | // Be sure to respond with 200 when you successfully process the event 35 | res.send("Alchemy Notify is the best!"); 36 | }); 37 | 38 | // Listen to Alchemy Notify webhook events 39 | app.listen(port, host, () => { 40 | console.log(`Example Alchemy Notify app listening at ${host}:${port}`); 41 | }); 42 | } 43 | 44 | main(); 45 | -------------------------------------------------------------------------------- /node-express/src/webhooksUtil.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction } from "express"; 2 | import { Request, Response } from "express-serve-static-core"; 3 | 4 | import * as crypto from "crypto"; 5 | import { IncomingMessage, ServerResponse } from "http"; 6 | 7 | export interface AlchemyRequest extends Request { 8 | alchemy: { 9 | rawBody: string; 10 | signature: string; 11 | }; 12 | } 13 | 14 | export function isValidSignatureForAlchemyRequest( 15 | request: AlchemyRequest, 16 | signingKey: string 17 | ): boolean { 18 | return isValidSignatureForStringBody( 19 | request.alchemy.rawBody, 20 | request.alchemy.signature, 21 | signingKey 22 | ); 23 | } 24 | 25 | export function isValidSignatureForStringBody( 26 | body: string, 27 | signature: string, 28 | signingKey: string 29 | ): boolean { 30 | const hmac = crypto.createHmac("sha256", signingKey); // Create a HMAC SHA256 hash using the signing key 31 | hmac.update(body, "utf8"); // Update the token hash with the request body using utf8 32 | const digest = hmac.digest("hex"); 33 | return signature === digest; 34 | } 35 | 36 | export function addAlchemyContextToRequest( 37 | req: IncomingMessage, 38 | _res: ServerResponse, 39 | buf: Buffer, 40 | encoding: BufferEncoding 41 | ): void { 42 | const signature = req.headers["x-alchemy-signature"]; 43 | // Signature must be validated against the raw string 44 | var body = buf.toString(encoding || "utf8"); 45 | (req as AlchemyRequest).alchemy = { 46 | rawBody: body, 47 | signature: signature as string, 48 | }; 49 | } 50 | 51 | export function validateAlchemySignature(signingKey: string) { 52 | return (req: Request, res: Response, next: NextFunction) => { 53 | if (!isValidSignatureForAlchemyRequest(req as AlchemyRequest, signingKey)) { 54 | const errMessage = "Signature validation failed, unauthorized!"; 55 | res.status(403).send(errMessage); 56 | throw new Error(errMessage); 57 | } else { 58 | next(); 59 | } 60 | }; 61 | } 62 | 63 | export interface AlchemyWebhookEvent { 64 | webhookId: string; 65 | id: string; 66 | createdAt: Date; 67 | type: AlchemyWebhookType; 68 | event: Record; 69 | } 70 | 71 | export type AlchemyWebhookType = 72 | | "MINED_TRANSACTION" 73 | | "DROPPED_TRANSACTION" 74 | | "ADDRESS_ACTIVITY"; 75 | -------------------------------------------------------------------------------- /node-express/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "forceConsistentCasingInFileNames": true, 5 | "lib": ["es6"], 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "noEmit": true, 9 | "noFallthroughCasesInSwitch": true, 10 | "noImplicitReturns": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "rootDir": "src", 14 | "skipLibCheck": true, 15 | "strict": true, 16 | "target": "es5", 17 | "importHelpers": false 18 | }, 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /node-express/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-config-prettier"], 3 | "rules": { 4 | "interface-name": [true, "never-prefix"], 5 | "no-bitwise": false, 6 | "no-console": false, 7 | "no-this-assignment": [true, { "allow-destructuring": true }], 8 | "object-literal-sort-keys": false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /node-express/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@cspotcode/source-map-support@^0.8.0": 6 | version "0.8.1" 7 | resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" 8 | integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== 9 | dependencies: 10 | "@jridgewell/trace-mapping" "0.3.9" 11 | 12 | "@jridgewell/resolve-uri@^3.0.3": 13 | version "3.0.7" 14 | resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe" 15 | integrity sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA== 16 | 17 | "@jridgewell/sourcemap-codec@^1.4.10": 18 | version "1.4.13" 19 | resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" 20 | integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== 21 | 22 | "@jridgewell/trace-mapping@0.3.9": 23 | version "0.3.9" 24 | resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" 25 | integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== 26 | dependencies: 27 | "@jridgewell/resolve-uri" "^3.0.3" 28 | "@jridgewell/sourcemap-codec" "^1.4.10" 29 | 30 | "@tsconfig/node10@^1.0.7": 31 | version "1.0.8" 32 | resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" 33 | integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== 34 | 35 | "@tsconfig/node12@^1.0.7": 36 | version "1.0.9" 37 | resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" 38 | integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== 39 | 40 | "@tsconfig/node14@^1.0.0": 41 | version "1.0.1" 42 | resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" 43 | integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== 44 | 45 | "@tsconfig/node16@^1.0.2": 46 | version "1.0.2" 47 | resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" 48 | integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== 49 | 50 | "@types/body-parser@*": 51 | version "1.19.2" 52 | resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" 53 | integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== 54 | dependencies: 55 | "@types/connect" "*" 56 | "@types/node" "*" 57 | 58 | "@types/connect@*": 59 | version "3.4.35" 60 | resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" 61 | integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== 62 | dependencies: 63 | "@types/node" "*" 64 | 65 | "@types/express-serve-static-core@^4.17.18": 66 | version "4.17.28" 67 | resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" 68 | integrity sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== 69 | dependencies: 70 | "@types/node" "*" 71 | "@types/qs" "*" 72 | "@types/range-parser" "*" 73 | 74 | "@types/express@^4.17.13": 75 | version "4.17.13" 76 | resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" 77 | integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== 78 | dependencies: 79 | "@types/body-parser" "*" 80 | "@types/express-serve-static-core" "^4.17.18" 81 | "@types/qs" "*" 82 | "@types/serve-static" "*" 83 | 84 | "@types/mime@^1": 85 | version "1.3.2" 86 | resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" 87 | integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== 88 | 89 | "@types/node@*", "@types/node@^17.0.35": 90 | version "17.0.35" 91 | resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.35.tgz#635b7586086d51fb40de0a2ec9d1014a5283ba4a" 92 | integrity sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg== 93 | 94 | "@types/qs@*": 95 | version "6.9.7" 96 | resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" 97 | integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== 98 | 99 | "@types/range-parser@*": 100 | version "1.2.4" 101 | resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" 102 | integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== 103 | 104 | "@types/serve-static@*": 105 | version "1.13.10" 106 | resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" 107 | integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== 108 | dependencies: 109 | "@types/mime" "^1" 110 | "@types/node" "*" 111 | 112 | accepts@~1.3.8: 113 | version "1.3.8" 114 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" 115 | integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== 116 | dependencies: 117 | mime-types "~2.1.34" 118 | negotiator "0.6.3" 119 | 120 | acorn-walk@^8.1.1: 121 | version "8.2.0" 122 | resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" 123 | integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== 124 | 125 | acorn@^8.4.1: 126 | version "8.7.1" 127 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" 128 | integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== 129 | 130 | arg@^4.1.0: 131 | version "4.1.3" 132 | resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" 133 | integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== 134 | 135 | array-flatten@1.1.1: 136 | version "1.1.1" 137 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 138 | integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== 139 | 140 | body-parser@1.20.0: 141 | version "1.20.0" 142 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" 143 | integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== 144 | dependencies: 145 | bytes "3.1.2" 146 | content-type "~1.0.4" 147 | debug "2.6.9" 148 | depd "2.0.0" 149 | destroy "1.2.0" 150 | http-errors "2.0.0" 151 | iconv-lite "0.4.24" 152 | on-finished "2.4.1" 153 | qs "6.10.3" 154 | raw-body "2.5.1" 155 | type-is "~1.6.18" 156 | unpipe "1.0.0" 157 | 158 | bytes@3.1.2: 159 | version "3.1.2" 160 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" 161 | integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== 162 | 163 | call-bind@^1.0.0: 164 | version "1.0.2" 165 | resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" 166 | integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== 167 | dependencies: 168 | function-bind "^1.1.1" 169 | get-intrinsic "^1.0.2" 170 | 171 | content-disposition@0.5.4: 172 | version "0.5.4" 173 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" 174 | integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== 175 | dependencies: 176 | safe-buffer "5.2.1" 177 | 178 | content-type@~1.0.4: 179 | version "1.0.4" 180 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 181 | integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== 182 | 183 | cookie-signature@1.0.6: 184 | version "1.0.6" 185 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 186 | integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= 187 | 188 | cookie@0.5.0: 189 | version "0.5.0" 190 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" 191 | integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== 192 | 193 | create-require@^1.1.0: 194 | version "1.1.1" 195 | resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" 196 | integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== 197 | 198 | debug@2.6.9: 199 | version "2.6.9" 200 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 201 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 202 | dependencies: 203 | ms "2.0.0" 204 | 205 | depd@2.0.0: 206 | version "2.0.0" 207 | resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" 208 | integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== 209 | 210 | destroy@1.2.0: 211 | version "1.2.0" 212 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" 213 | integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== 214 | 215 | diff@^4.0.1: 216 | version "4.0.2" 217 | resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" 218 | integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== 219 | 220 | ee-first@1.1.1: 221 | version "1.1.1" 222 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 223 | integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= 224 | 225 | encodeurl@~1.0.2: 226 | version "1.0.2" 227 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 228 | integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= 229 | 230 | escape-html@~1.0.3: 231 | version "1.0.3" 232 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 233 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= 234 | 235 | etag@~1.8.1: 236 | version "1.8.1" 237 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 238 | integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= 239 | 240 | express@^4.17.3: 241 | version "4.18.1" 242 | resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" 243 | integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== 244 | dependencies: 245 | accepts "~1.3.8" 246 | array-flatten "1.1.1" 247 | body-parser "1.20.0" 248 | content-disposition "0.5.4" 249 | content-type "~1.0.4" 250 | cookie "0.5.0" 251 | cookie-signature "1.0.6" 252 | debug "2.6.9" 253 | depd "2.0.0" 254 | encodeurl "~1.0.2" 255 | escape-html "~1.0.3" 256 | etag "~1.8.1" 257 | finalhandler "1.2.0" 258 | fresh "0.5.2" 259 | http-errors "2.0.0" 260 | merge-descriptors "1.0.1" 261 | methods "~1.1.2" 262 | on-finished "2.4.1" 263 | parseurl "~1.3.3" 264 | path-to-regexp "0.1.7" 265 | proxy-addr "~2.0.7" 266 | qs "6.10.3" 267 | range-parser "~1.2.1" 268 | safe-buffer "5.2.1" 269 | send "0.18.0" 270 | serve-static "1.15.0" 271 | setprototypeof "1.2.0" 272 | statuses "2.0.1" 273 | type-is "~1.6.18" 274 | utils-merge "1.0.1" 275 | vary "~1.1.2" 276 | 277 | finalhandler@1.2.0: 278 | version "1.2.0" 279 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" 280 | integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== 281 | dependencies: 282 | debug "2.6.9" 283 | encodeurl "~1.0.2" 284 | escape-html "~1.0.3" 285 | on-finished "2.4.1" 286 | parseurl "~1.3.3" 287 | statuses "2.0.1" 288 | unpipe "~1.0.0" 289 | 290 | forwarded@0.2.0: 291 | version "0.2.0" 292 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" 293 | integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== 294 | 295 | fresh@0.5.2: 296 | version "0.5.2" 297 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 298 | integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= 299 | 300 | function-bind@^1.1.1: 301 | version "1.1.1" 302 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 303 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 304 | 305 | get-intrinsic@^1.0.2: 306 | version "1.1.1" 307 | resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" 308 | integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== 309 | dependencies: 310 | function-bind "^1.1.1" 311 | has "^1.0.3" 312 | has-symbols "^1.0.1" 313 | 314 | has-symbols@^1.0.1: 315 | version "1.0.3" 316 | resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" 317 | integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== 318 | 319 | has@^1.0.3: 320 | version "1.0.3" 321 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 322 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 323 | dependencies: 324 | function-bind "^1.1.1" 325 | 326 | http-errors@2.0.0: 327 | version "2.0.0" 328 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" 329 | integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== 330 | dependencies: 331 | depd "2.0.0" 332 | inherits "2.0.4" 333 | setprototypeof "1.2.0" 334 | statuses "2.0.1" 335 | toidentifier "1.0.1" 336 | 337 | iconv-lite@0.4.24: 338 | version "0.4.24" 339 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 340 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 341 | dependencies: 342 | safer-buffer ">= 2.1.2 < 3" 343 | 344 | inherits@2.0.4: 345 | version "2.0.4" 346 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 347 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 348 | 349 | ipaddr.js@1.9.1: 350 | version "1.9.1" 351 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" 352 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== 353 | 354 | make-error@^1.1.1: 355 | version "1.3.6" 356 | resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" 357 | integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== 358 | 359 | media-typer@0.3.0: 360 | version "0.3.0" 361 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 362 | integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= 363 | 364 | merge-descriptors@1.0.1: 365 | version "1.0.1" 366 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 367 | integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= 368 | 369 | methods@~1.1.2: 370 | version "1.1.2" 371 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 372 | integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= 373 | 374 | mime-db@1.52.0: 375 | version "1.52.0" 376 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" 377 | integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== 378 | 379 | mime-types@~2.1.24, mime-types@~2.1.34: 380 | version "2.1.35" 381 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" 382 | integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== 383 | dependencies: 384 | mime-db "1.52.0" 385 | 386 | mime@1.6.0: 387 | version "1.6.0" 388 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 389 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== 390 | 391 | ms@2.0.0: 392 | version "2.0.0" 393 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 394 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 395 | 396 | ms@2.1.3: 397 | version "2.1.3" 398 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 399 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 400 | 401 | negotiator@0.6.3: 402 | version "0.6.3" 403 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" 404 | integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== 405 | 406 | object-inspect@^1.9.0: 407 | version "1.12.1" 408 | resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.1.tgz#28a661153bad7e470e4b01479ef1cb91ce511191" 409 | integrity sha512-Y/jF6vnvEtOPGiKD1+q+X0CiUYRQtEHp89MLLUJ7TUivtH8Ugn2+3A7Rynqk7BRsAoqeOQWnFnjpDrKSxDgIGA== 410 | 411 | on-finished@2.4.1: 412 | version "2.4.1" 413 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" 414 | integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== 415 | dependencies: 416 | ee-first "1.1.1" 417 | 418 | parseurl@~1.3.3: 419 | version "1.3.3" 420 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 421 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== 422 | 423 | path-to-regexp@0.1.7: 424 | version "0.1.7" 425 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 426 | integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= 427 | 428 | proxy-addr@~2.0.7: 429 | version "2.0.7" 430 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" 431 | integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== 432 | dependencies: 433 | forwarded "0.2.0" 434 | ipaddr.js "1.9.1" 435 | 436 | qs@6.10.3: 437 | version "6.10.3" 438 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" 439 | integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== 440 | dependencies: 441 | side-channel "^1.0.4" 442 | 443 | range-parser@~1.2.1: 444 | version "1.2.1" 445 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" 446 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== 447 | 448 | raw-body@2.5.1: 449 | version "2.5.1" 450 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" 451 | integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== 452 | dependencies: 453 | bytes "3.1.2" 454 | http-errors "2.0.0" 455 | iconv-lite "0.4.24" 456 | unpipe "1.0.0" 457 | 458 | safe-buffer@5.2.1: 459 | version "5.2.1" 460 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 461 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 462 | 463 | "safer-buffer@>= 2.1.2 < 3": 464 | version "2.1.2" 465 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 466 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 467 | 468 | send@0.18.0: 469 | version "0.18.0" 470 | resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" 471 | integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== 472 | dependencies: 473 | debug "2.6.9" 474 | depd "2.0.0" 475 | destroy "1.2.0" 476 | encodeurl "~1.0.2" 477 | escape-html "~1.0.3" 478 | etag "~1.8.1" 479 | fresh "0.5.2" 480 | http-errors "2.0.0" 481 | mime "1.6.0" 482 | ms "2.1.3" 483 | on-finished "2.4.1" 484 | range-parser "~1.2.1" 485 | statuses "2.0.1" 486 | 487 | serve-static@1.15.0: 488 | version "1.15.0" 489 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" 490 | integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== 491 | dependencies: 492 | encodeurl "~1.0.2" 493 | escape-html "~1.0.3" 494 | parseurl "~1.3.3" 495 | send "0.18.0" 496 | 497 | setprototypeof@1.2.0: 498 | version "1.2.0" 499 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" 500 | integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== 501 | 502 | side-channel@^1.0.4: 503 | version "1.0.4" 504 | resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" 505 | integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== 506 | dependencies: 507 | call-bind "^1.0.0" 508 | get-intrinsic "^1.0.2" 509 | object-inspect "^1.9.0" 510 | 511 | statuses@2.0.1: 512 | version "2.0.1" 513 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" 514 | integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== 515 | 516 | toidentifier@1.0.1: 517 | version "1.0.1" 518 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" 519 | integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== 520 | 521 | ts-node@^10.8.0: 522 | version "10.8.0" 523 | resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.8.0.tgz#3ceb5ac3e67ae8025c1950626aafbdecb55d82ce" 524 | integrity sha512-/fNd5Qh+zTt8Vt1KbYZjRHCE9sI5i7nqfD/dzBBRDeVXZXS6kToW6R7tTU6Nd4XavFs0mAVCg29Q//ML7WsZYA== 525 | dependencies: 526 | "@cspotcode/source-map-support" "^0.8.0" 527 | "@tsconfig/node10" "^1.0.7" 528 | "@tsconfig/node12" "^1.0.7" 529 | "@tsconfig/node14" "^1.0.0" 530 | "@tsconfig/node16" "^1.0.2" 531 | acorn "^8.4.1" 532 | acorn-walk "^8.1.1" 533 | arg "^4.1.0" 534 | create-require "^1.1.0" 535 | diff "^4.0.1" 536 | make-error "^1.1.1" 537 | v8-compile-cache-lib "^3.0.1" 538 | yn "3.1.1" 539 | 540 | tslib@^1.11.1: 541 | version "1.14.1" 542 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" 543 | integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== 544 | 545 | type-is@~1.6.18: 546 | version "1.6.18" 547 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 548 | integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== 549 | dependencies: 550 | media-typer "0.3.0" 551 | mime-types "~2.1.24" 552 | 553 | typescript@^4.7.2: 554 | version "4.7.2" 555 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.2.tgz#1f9aa2ceb9af87cca227813b4310fff0b51593c4" 556 | integrity sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A== 557 | 558 | unpipe@1.0.0, unpipe@~1.0.0: 559 | version "1.0.0" 560 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 561 | integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= 562 | 563 | utils-merge@1.0.1: 564 | version "1.0.1" 565 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 566 | integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= 567 | 568 | v8-compile-cache-lib@^3.0.1: 569 | version "3.0.1" 570 | resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" 571 | integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== 572 | 573 | vary@~1.1.2: 574 | version "1.1.2" 575 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 576 | integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= 577 | 578 | yn@3.1.1: 579 | version "3.1.1" 580 | resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" 581 | integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== 582 | -------------------------------------------------------------------------------- /python-django/.gitignore: -------------------------------------------------------------------------------- 1 | env 2 | __pycache__ -------------------------------------------------------------------------------- /python-django/README.md: -------------------------------------------------------------------------------- 1 | # Example Alchemy Notify Webhooks Server 2 | 3 | A simple example webhook server for using Alchemy Notify that uses python django. 4 | 5 | ## Installation 6 | 7 | #### Simple setup 8 | 9 | With this command, create & source your virtual environment and install your dependencies: 10 | 11 | ``` 12 | python3 -m venv env && source env/bin/activate && pip install -r requirements.txt 13 | ``` 14 | 15 | ## Run 16 | 17 | First, access the server directory: 18 | 19 | ``` 20 | cd webhook_server 21 | ``` 22 | 23 | To run on localhost:8080: 24 | 25 | ``` 26 | cd webhook_server 27 | 28 | SIGNING_KEY=whsec_your_key_here python manage.py runserver localhost:8080 29 | ``` 30 | 31 | And just like that, you're done! 32 | 33 | NOTE: Your webhook path is currently set to "webhook-path" in `webhook_server/backend/urls.py`, but feel free to change it to whatever path you'd like. 34 | 35 | ## Debugging 36 | 37 | If you aren't receiving any webhooks, be sure you followed the steps [here first](https://github.com/alchemyplatform/#readme). 38 | -------------------------------------------------------------------------------- /python-django/requirements.txt: -------------------------------------------------------------------------------- 1 | django==3.2.13 -------------------------------------------------------------------------------- /python-django/webhook_server/backend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alchemyplatform/webhook-examples/28a2888346b4143ff30c227ee01ded3dfa0f93ed/python-django/webhook_server/backend/__init__.py -------------------------------------------------------------------------------- /python-django/webhook_server/backend/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /python-django/webhook_server/backend/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BackendConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "backend" 7 | -------------------------------------------------------------------------------- /python-django/webhook_server/backend/middleware.py: -------------------------------------------------------------------------------- 1 | import hmac 2 | import hashlib 3 | from django.core.exceptions import PermissionDenied 4 | from webhook_server.settings import SIGNING_KEY 5 | import json 6 | from types import SimpleNamespace 7 | 8 | 9 | def is_valid_signature_for_string_body( 10 | body: str, signature: str, signing_key: str 11 | ) -> bool: 12 | digest = hmac.new( 13 | bytes(signing_key, "utf-8"), 14 | msg=bytes(body, "utf-8"), 15 | digestmod=hashlib.sha256, 16 | ).hexdigest() 17 | 18 | return signature == digest 19 | 20 | 21 | class AlchemyWebhookEvent: 22 | def __init__(self, webhookId, id, createdAt, type, event): 23 | self.webhook_id = webhookId 24 | self.id = id 25 | self.created_at = createdAt 26 | self.type = type 27 | self.event = event 28 | 29 | 30 | class AlchemyRequestHandlerMiddleware: 31 | def __init__(self, get_response): 32 | self.get_response = get_response 33 | 34 | def __call__(self, request): 35 | str_body = str(request.body, request.encoding or "utf-8") 36 | signature = request.headers["x-alchemy-signature"] 37 | if not is_valid_signature_for_string_body(str_body, signature, SIGNING_KEY): 38 | raise PermissionDenied("Signature validation failed, unauthorized!") 39 | 40 | webhook_event = json.loads(str_body) 41 | request.alchemy_webhook_event = AlchemyWebhookEvent(**webhook_event) 42 | response = self.get_response(request) 43 | return response 44 | -------------------------------------------------------------------------------- /python-django/webhook_server/backend/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alchemyplatform/webhook-examples/28a2888346b4143ff30c227ee01ded3dfa0f93ed/python-django/webhook_server/backend/migrations/__init__.py -------------------------------------------------------------------------------- /python-django/webhook_server/backend/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /python-django/webhook_server/backend/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /python-django/webhook_server/backend/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | # Register handler for Alchemy Notify webhook events 6 | # TODO: update to your own webhook path 7 | urlpatterns = [ 8 | path("webhook-path", views.webhook_path, name="webhook_path"), 9 | ] 10 | -------------------------------------------------------------------------------- /python-django/webhook_server/backend/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | 5 | from django.views.decorators.csrf import csrf_exempt 6 | 7 | from django.http import HttpResponse 8 | from django.core.exceptions import PermissionDenied 9 | import json 10 | 11 | 12 | @csrf_exempt 13 | def webhook_path(request): 14 | # Do stuff with with webhook event here! 15 | print("Processing webhook event id: {}".format(request.alchemy_webhook_event.id)) 16 | # Be sure to respond with 200 when you successfully process the event 17 | return HttpResponse("Alchemy Notify is the best!", status=200) 18 | -------------------------------------------------------------------------------- /python-django/webhook_server/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webhook_server.settings") 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /python-django/webhook_server/webhook_server/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alchemyplatform/webhook-examples/28a2888346b4143ff30c227ee01ded3dfa0f93ed/python-django/webhook_server/webhook_server/__init__.py -------------------------------------------------------------------------------- /python-django/webhook_server/webhook_server/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for webhook_server project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webhook_server.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /python-django/webhook_server/webhook_server/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for webhook_server project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.2.13. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.2/ref/settings/ 11 | """ 12 | 13 | import os 14 | from pathlib import Path 15 | 16 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 17 | BASE_DIR = Path(__file__).resolve().parent.parent 18 | 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = "django-insecure-k8)#j)0kw48xtftysxrh04k%hs)*+jvkd&y&vjx3zhc!0v8_na" 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = True 28 | 29 | # TODO: add the name of allowed hosts here 30 | ALLOWED_HOSTS = ["*"] 31 | 32 | # Application definition 33 | 34 | INSTALLED_APPS = [ 35 | "django.contrib.admin", 36 | "django.contrib.auth", 37 | "django.contrib.contenttypes", 38 | "django.contrib.sessions", 39 | "django.contrib.messages", 40 | "django.contrib.staticfiles", 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | "django.middleware.security.SecurityMiddleware", 45 | "django.contrib.sessions.middleware.SessionMiddleware", 46 | "django.middleware.common.CommonMiddleware", 47 | "django.middleware.csrf.CsrfViewMiddleware", 48 | "django.contrib.auth.middleware.AuthenticationMiddleware", 49 | "django.contrib.messages.middleware.MessageMiddleware", 50 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 51 | # Middleware needed to validate the alchemy signature 52 | "backend.middleware.AlchemyRequestHandlerMiddleware", 53 | ] 54 | 55 | SIGNING_KEY = os.environ.get("SIGNING_KEY", "whsec_test") 56 | 57 | ROOT_URLCONF = "webhook_server.urls" 58 | 59 | TEMPLATES = [ 60 | { 61 | "BACKEND": "django.template.backends.django.DjangoTemplates", 62 | "DIRS": [], 63 | "APP_DIRS": True, 64 | "OPTIONS": { 65 | "context_processors": [ 66 | "django.template.context_processors.debug", 67 | "django.template.context_processors.request", 68 | "django.contrib.auth.context_processors.auth", 69 | "django.contrib.messages.context_processors.messages", 70 | ], 71 | }, 72 | }, 73 | ] 74 | 75 | WSGI_APPLICATION = "webhook_server.wsgi.application" 76 | 77 | 78 | """ TODO: Uncomment if you want to start working with a database 79 | # Database 80 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases 81 | 82 | DATABASES = { 83 | 'default': { 84 | 'ENGINE': 'django.db.backends.sqlite3', 85 | 'NAME': BASE_DIR / 'db.sqlite3', 86 | } 87 | } 88 | """ 89 | 90 | 91 | # Password validation 92 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 93 | 94 | AUTH_PASSWORD_VALIDATORS = [ 95 | { 96 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 97 | }, 98 | { 99 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 100 | }, 101 | { 102 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 103 | }, 104 | { 105 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 106 | }, 107 | ] 108 | 109 | 110 | # Internationalization 111 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 112 | 113 | LANGUAGE_CODE = "en-us" 114 | 115 | TIME_ZONE = "UTC" 116 | 117 | USE_I18N = True 118 | 119 | USE_L10N = True 120 | 121 | USE_TZ = True 122 | 123 | 124 | # Static files (CSS, JavaScript, Images) 125 | # https://docs.djangoproject.com/en/3.2/howto/static-files/ 126 | 127 | STATIC_URL = "/static/" 128 | 129 | # Default primary key field type 130 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 131 | 132 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 133 | -------------------------------------------------------------------------------- /python-django/webhook_server/webhook_server/urls.py: -------------------------------------------------------------------------------- 1 | """webhook_server URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.2/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | 19 | urlpatterns = [ 20 | path("", include("backend.urls")), 21 | path("admin/", admin.site.urls), 22 | ] 23 | -------------------------------------------------------------------------------- /python-django/webhook_server/webhook_server/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for webhook_server project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webhook_server.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /rust-actix/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "actix-codec" 7 | version = "0.5.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" 10 | dependencies = [ 11 | "bitflags", 12 | "bytes", 13 | "futures-core", 14 | "futures-sink", 15 | "log", 16 | "memchr", 17 | "pin-project-lite", 18 | "tokio", 19 | "tokio-util", 20 | ] 21 | 22 | [[package]] 23 | name = "actix-http" 24 | version = "3.0.4" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "a5885cb81a0d4d0d322864bea1bb6c2a8144626b4fdc625d4c51eba197e7797a" 27 | dependencies = [ 28 | "actix-codec", 29 | "actix-rt", 30 | "actix-service", 31 | "actix-utils", 32 | "ahash", 33 | "base64", 34 | "bitflags", 35 | "brotli", 36 | "bytes", 37 | "bytestring", 38 | "derive_more", 39 | "encoding_rs", 40 | "flate2", 41 | "futures-core", 42 | "h2", 43 | "http", 44 | "httparse", 45 | "httpdate", 46 | "itoa", 47 | "language-tags", 48 | "local-channel", 49 | "log", 50 | "mime", 51 | "percent-encoding", 52 | "pin-project-lite", 53 | "rand", 54 | "sha-1", 55 | "smallvec", 56 | "zstd", 57 | ] 58 | 59 | [[package]] 60 | name = "actix-macros" 61 | version = "0.2.3" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" 64 | dependencies = [ 65 | "quote", 66 | "syn", 67 | ] 68 | 69 | [[package]] 70 | name = "actix-router" 71 | version = "0.5.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "eb60846b52c118f2f04a56cc90880a274271c489b2498623d58176f8ca21fa80" 74 | dependencies = [ 75 | "bytestring", 76 | "firestorm", 77 | "http", 78 | "log", 79 | "regex", 80 | "serde", 81 | ] 82 | 83 | [[package]] 84 | name = "actix-rt" 85 | version = "2.7.0" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "7ea16c295198e958ef31930a6ef37d0fb64e9ca3b6116e6b93a8bdae96ee1000" 88 | dependencies = [ 89 | "futures-core", 90 | "tokio", 91 | ] 92 | 93 | [[package]] 94 | name = "actix-server" 95 | version = "2.1.1" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "0da34f8e659ea1b077bb4637948b815cd3768ad5a188fdcd74ff4d84240cd824" 98 | dependencies = [ 99 | "actix-rt", 100 | "actix-service", 101 | "actix-utils", 102 | "futures-core", 103 | "futures-util", 104 | "mio", 105 | "num_cpus", 106 | "socket2", 107 | "tokio", 108 | "tracing", 109 | ] 110 | 111 | [[package]] 112 | name = "actix-service" 113 | version = "2.0.2" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" 116 | dependencies = [ 117 | "futures-core", 118 | "paste", 119 | "pin-project-lite", 120 | ] 121 | 122 | [[package]] 123 | name = "actix-utils" 124 | version = "3.0.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "e491cbaac2e7fc788dfff99ff48ef317e23b3cf63dbaf7aaab6418f40f92aa94" 127 | dependencies = [ 128 | "local-waker", 129 | "pin-project-lite", 130 | ] 131 | 132 | [[package]] 133 | name = "actix-web" 134 | version = "4.0.1" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "f4e5ebffd51d50df56a3ae0de0e59487340ca456f05dd0b90c0a7a6dd6a74d31" 137 | dependencies = [ 138 | "actix-codec", 139 | "actix-http", 140 | "actix-macros", 141 | "actix-router", 142 | "actix-rt", 143 | "actix-server", 144 | "actix-service", 145 | "actix-utils", 146 | "actix-web-codegen", 147 | "ahash", 148 | "bytes", 149 | "bytestring", 150 | "cfg-if", 151 | "cookie", 152 | "derive_more", 153 | "encoding_rs", 154 | "futures-core", 155 | "futures-util", 156 | "itoa", 157 | "language-tags", 158 | "log", 159 | "mime", 160 | "once_cell", 161 | "pin-project-lite", 162 | "regex", 163 | "serde", 164 | "serde_json", 165 | "serde_urlencoded", 166 | "smallvec", 167 | "socket2", 168 | "time 0.3.9", 169 | "url", 170 | ] 171 | 172 | [[package]] 173 | name = "actix-web-codegen" 174 | version = "4.0.0" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "7525bedf54704abb1d469e88d7e7e9226df73778798a69cea5022d53b2ae91bc" 177 | dependencies = [ 178 | "actix-router", 179 | "proc-macro2", 180 | "quote", 181 | "syn", 182 | ] 183 | 184 | [[package]] 185 | name = "adler" 186 | version = "1.0.2" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 189 | 190 | [[package]] 191 | name = "ahash" 192 | version = "0.7.6" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 195 | dependencies = [ 196 | "getrandom", 197 | "once_cell", 198 | "version_check", 199 | ] 200 | 201 | [[package]] 202 | name = "aho-corasick" 203 | version = "0.7.18" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 206 | dependencies = [ 207 | "memchr", 208 | ] 209 | 210 | [[package]] 211 | name = "alchemy-notify-example" 212 | version = "0.1.0" 213 | dependencies = [ 214 | "actix-web", 215 | "chrono", 216 | "env_logger", 217 | "futures-util", 218 | "hex", 219 | "hex-literal", 220 | "hmac", 221 | "log", 222 | "serde", 223 | "serde_json", 224 | "sha2", 225 | ] 226 | 227 | [[package]] 228 | name = "alloc-no-stdlib" 229 | version = "2.0.3" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3" 232 | 233 | [[package]] 234 | name = "alloc-stdlib" 235 | version = "0.2.1" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2" 238 | dependencies = [ 239 | "alloc-no-stdlib", 240 | ] 241 | 242 | [[package]] 243 | name = "atty" 244 | version = "0.2.14" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 247 | dependencies = [ 248 | "hermit-abi", 249 | "libc", 250 | "winapi", 251 | ] 252 | 253 | [[package]] 254 | name = "autocfg" 255 | version = "1.1.0" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 258 | 259 | [[package]] 260 | name = "base64" 261 | version = "0.13.0" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 264 | 265 | [[package]] 266 | name = "bitflags" 267 | version = "1.3.2" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 270 | 271 | [[package]] 272 | name = "block-buffer" 273 | version = "0.10.2" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" 276 | dependencies = [ 277 | "generic-array", 278 | ] 279 | 280 | [[package]] 281 | name = "brotli" 282 | version = "3.3.4" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" 285 | dependencies = [ 286 | "alloc-no-stdlib", 287 | "alloc-stdlib", 288 | "brotli-decompressor", 289 | ] 290 | 291 | [[package]] 292 | name = "brotli-decompressor" 293 | version = "2.3.2" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" 296 | dependencies = [ 297 | "alloc-no-stdlib", 298 | "alloc-stdlib", 299 | ] 300 | 301 | [[package]] 302 | name = "bytes" 303 | version = "1.1.0" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 306 | 307 | [[package]] 308 | name = "bytestring" 309 | version = "1.0.0" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "90706ba19e97b90786e19dc0d5e2abd80008d99d4c0c5d1ad0b5e72cec7c494d" 312 | dependencies = [ 313 | "bytes", 314 | ] 315 | 316 | [[package]] 317 | name = "cc" 318 | version = "1.0.73" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 321 | dependencies = [ 322 | "jobserver", 323 | ] 324 | 325 | [[package]] 326 | name = "cfg-if" 327 | version = "1.0.0" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 330 | 331 | [[package]] 332 | name = "chrono" 333 | version = "0.4.19" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 336 | dependencies = [ 337 | "libc", 338 | "num-integer", 339 | "num-traits", 340 | "time 0.1.43", 341 | "winapi", 342 | ] 343 | 344 | [[package]] 345 | name = "convert_case" 346 | version = "0.4.0" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 349 | 350 | [[package]] 351 | name = "cookie" 352 | version = "0.16.0" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" 355 | dependencies = [ 356 | "percent-encoding", 357 | "time 0.3.9", 358 | "version_check", 359 | ] 360 | 361 | [[package]] 362 | name = "cpufeatures" 363 | version = "0.2.2" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" 366 | dependencies = [ 367 | "libc", 368 | ] 369 | 370 | [[package]] 371 | name = "crc32fast" 372 | version = "1.3.2" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 375 | dependencies = [ 376 | "cfg-if", 377 | ] 378 | 379 | [[package]] 380 | name = "crypto-common" 381 | version = "0.1.3" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" 384 | dependencies = [ 385 | "generic-array", 386 | "typenum", 387 | ] 388 | 389 | [[package]] 390 | name = "derive_more" 391 | version = "0.99.17" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" 394 | dependencies = [ 395 | "convert_case", 396 | "proc-macro2", 397 | "quote", 398 | "rustc_version", 399 | "syn", 400 | ] 401 | 402 | [[package]] 403 | name = "digest" 404 | version = "0.10.3" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" 407 | dependencies = [ 408 | "block-buffer", 409 | "crypto-common", 410 | "subtle", 411 | ] 412 | 413 | [[package]] 414 | name = "encoding_rs" 415 | version = "0.8.31" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" 418 | dependencies = [ 419 | "cfg-if", 420 | ] 421 | 422 | [[package]] 423 | name = "env_logger" 424 | version = "0.9.0" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" 427 | dependencies = [ 428 | "atty", 429 | "humantime", 430 | "log", 431 | "regex", 432 | "termcolor", 433 | ] 434 | 435 | [[package]] 436 | name = "firestorm" 437 | version = "0.5.1" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "2c5f6c2c942da57e2aaaa84b8a521489486f14e75e7fa91dab70aba913975f98" 440 | 441 | [[package]] 442 | name = "flate2" 443 | version = "1.0.23" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af" 446 | dependencies = [ 447 | "cfg-if", 448 | "crc32fast", 449 | "libc", 450 | "miniz_oxide", 451 | ] 452 | 453 | [[package]] 454 | name = "fnv" 455 | version = "1.0.7" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 458 | 459 | [[package]] 460 | name = "form_urlencoded" 461 | version = "1.0.1" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 464 | dependencies = [ 465 | "matches", 466 | "percent-encoding", 467 | ] 468 | 469 | [[package]] 470 | name = "futures-core" 471 | version = "0.3.21" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 474 | 475 | [[package]] 476 | name = "futures-macro" 477 | version = "0.3.21" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" 480 | dependencies = [ 481 | "proc-macro2", 482 | "quote", 483 | "syn", 484 | ] 485 | 486 | [[package]] 487 | name = "futures-sink" 488 | version = "0.3.21" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" 491 | 492 | [[package]] 493 | name = "futures-task" 494 | version = "0.3.21" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" 497 | 498 | [[package]] 499 | name = "futures-util" 500 | version = "0.3.21" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" 503 | dependencies = [ 504 | "futures-core", 505 | "futures-macro", 506 | "futures-task", 507 | "pin-project-lite", 508 | "pin-utils", 509 | "slab", 510 | ] 511 | 512 | [[package]] 513 | name = "generic-array" 514 | version = "0.14.5" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" 517 | dependencies = [ 518 | "typenum", 519 | "version_check", 520 | ] 521 | 522 | [[package]] 523 | name = "getrandom" 524 | version = "0.2.6" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" 527 | dependencies = [ 528 | "cfg-if", 529 | "libc", 530 | "wasi 0.10.2+wasi-snapshot-preview1", 531 | ] 532 | 533 | [[package]] 534 | name = "h2" 535 | version = "0.3.13" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" 538 | dependencies = [ 539 | "bytes", 540 | "fnv", 541 | "futures-core", 542 | "futures-sink", 543 | "futures-util", 544 | "http", 545 | "indexmap", 546 | "slab", 547 | "tokio", 548 | "tokio-util", 549 | "tracing", 550 | ] 551 | 552 | [[package]] 553 | name = "hashbrown" 554 | version = "0.11.2" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 557 | 558 | [[package]] 559 | name = "hermit-abi" 560 | version = "0.1.19" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 563 | dependencies = [ 564 | "libc", 565 | ] 566 | 567 | [[package]] 568 | name = "hex" 569 | version = "0.4.3" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 572 | 573 | [[package]] 574 | name = "hex-literal" 575 | version = "0.3.4" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" 578 | 579 | [[package]] 580 | name = "hmac" 581 | version = "0.12.1" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 584 | dependencies = [ 585 | "digest", 586 | ] 587 | 588 | [[package]] 589 | name = "http" 590 | version = "0.2.7" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" 593 | dependencies = [ 594 | "bytes", 595 | "fnv", 596 | "itoa", 597 | ] 598 | 599 | [[package]] 600 | name = "httparse" 601 | version = "1.7.1" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" 604 | 605 | [[package]] 606 | name = "httpdate" 607 | version = "1.0.2" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 610 | 611 | [[package]] 612 | name = "humantime" 613 | version = "2.1.0" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 616 | 617 | [[package]] 618 | name = "idna" 619 | version = "0.2.3" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 622 | dependencies = [ 623 | "matches", 624 | "unicode-bidi", 625 | "unicode-normalization", 626 | ] 627 | 628 | [[package]] 629 | name = "indexmap" 630 | version = "1.8.1" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" 633 | dependencies = [ 634 | "autocfg", 635 | "hashbrown", 636 | ] 637 | 638 | [[package]] 639 | name = "itoa" 640 | version = "1.0.2" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" 643 | 644 | [[package]] 645 | name = "jobserver" 646 | version = "0.1.24" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" 649 | dependencies = [ 650 | "libc", 651 | ] 652 | 653 | [[package]] 654 | name = "language-tags" 655 | version = "0.3.2" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" 658 | 659 | [[package]] 660 | name = "lazy_static" 661 | version = "1.4.0" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 664 | 665 | [[package]] 666 | name = "libc" 667 | version = "0.2.126" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 670 | 671 | [[package]] 672 | name = "local-channel" 673 | version = "0.1.3" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" 676 | dependencies = [ 677 | "futures-core", 678 | "futures-sink", 679 | "futures-util", 680 | "local-waker", 681 | ] 682 | 683 | [[package]] 684 | name = "local-waker" 685 | version = "0.1.3" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" 688 | 689 | [[package]] 690 | name = "lock_api" 691 | version = "0.4.7" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" 694 | dependencies = [ 695 | "autocfg", 696 | "scopeguard", 697 | ] 698 | 699 | [[package]] 700 | name = "log" 701 | version = "0.4.17" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 704 | dependencies = [ 705 | "cfg-if", 706 | ] 707 | 708 | [[package]] 709 | name = "matches" 710 | version = "0.1.9" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 713 | 714 | [[package]] 715 | name = "memchr" 716 | version = "2.5.0" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 719 | 720 | [[package]] 721 | name = "mime" 722 | version = "0.3.16" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 725 | 726 | [[package]] 727 | name = "miniz_oxide" 728 | version = "0.5.1" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" 731 | dependencies = [ 732 | "adler", 733 | ] 734 | 735 | [[package]] 736 | name = "mio" 737 | version = "0.8.3" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" 740 | dependencies = [ 741 | "libc", 742 | "log", 743 | "wasi 0.11.0+wasi-snapshot-preview1", 744 | "windows-sys", 745 | ] 746 | 747 | [[package]] 748 | name = "num-integer" 749 | version = "0.1.45" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 752 | dependencies = [ 753 | "autocfg", 754 | "num-traits", 755 | ] 756 | 757 | [[package]] 758 | name = "num-traits" 759 | version = "0.2.15" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 762 | dependencies = [ 763 | "autocfg", 764 | ] 765 | 766 | [[package]] 767 | name = "num_cpus" 768 | version = "1.13.1" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 771 | dependencies = [ 772 | "hermit-abi", 773 | "libc", 774 | ] 775 | 776 | [[package]] 777 | name = "num_threads" 778 | version = "0.1.6" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" 781 | dependencies = [ 782 | "libc", 783 | ] 784 | 785 | [[package]] 786 | name = "once_cell" 787 | version = "1.12.0" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" 790 | 791 | [[package]] 792 | name = "parking_lot" 793 | version = "0.12.0" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" 796 | dependencies = [ 797 | "lock_api", 798 | "parking_lot_core", 799 | ] 800 | 801 | [[package]] 802 | name = "parking_lot_core" 803 | version = "0.9.3" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" 806 | dependencies = [ 807 | "cfg-if", 808 | "libc", 809 | "redox_syscall", 810 | "smallvec", 811 | "windows-sys", 812 | ] 813 | 814 | [[package]] 815 | name = "paste" 816 | version = "1.0.7" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" 819 | 820 | [[package]] 821 | name = "percent-encoding" 822 | version = "2.1.0" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 825 | 826 | [[package]] 827 | name = "pin-project-lite" 828 | version = "0.2.9" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 831 | 832 | [[package]] 833 | name = "pin-utils" 834 | version = "0.1.0" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 837 | 838 | [[package]] 839 | name = "ppv-lite86" 840 | version = "0.2.16" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 843 | 844 | [[package]] 845 | name = "proc-macro2" 846 | version = "1.0.39" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" 849 | dependencies = [ 850 | "unicode-ident", 851 | ] 852 | 853 | [[package]] 854 | name = "quote" 855 | version = "1.0.18" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" 858 | dependencies = [ 859 | "proc-macro2", 860 | ] 861 | 862 | [[package]] 863 | name = "rand" 864 | version = "0.8.5" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 867 | dependencies = [ 868 | "libc", 869 | "rand_chacha", 870 | "rand_core", 871 | ] 872 | 873 | [[package]] 874 | name = "rand_chacha" 875 | version = "0.3.1" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 878 | dependencies = [ 879 | "ppv-lite86", 880 | "rand_core", 881 | ] 882 | 883 | [[package]] 884 | name = "rand_core" 885 | version = "0.6.3" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 888 | dependencies = [ 889 | "getrandom", 890 | ] 891 | 892 | [[package]] 893 | name = "redox_syscall" 894 | version = "0.2.13" 895 | source = "registry+https://github.com/rust-lang/crates.io-index" 896 | checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" 897 | dependencies = [ 898 | "bitflags", 899 | ] 900 | 901 | [[package]] 902 | name = "regex" 903 | version = "1.5.6" 904 | source = "registry+https://github.com/rust-lang/crates.io-index" 905 | checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" 906 | dependencies = [ 907 | "aho-corasick", 908 | "memchr", 909 | "regex-syntax", 910 | ] 911 | 912 | [[package]] 913 | name = "regex-syntax" 914 | version = "0.6.26" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" 917 | 918 | [[package]] 919 | name = "rustc_version" 920 | version = "0.4.0" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 923 | dependencies = [ 924 | "semver", 925 | ] 926 | 927 | [[package]] 928 | name = "ryu" 929 | version = "1.0.10" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" 932 | 933 | [[package]] 934 | name = "scopeguard" 935 | version = "1.1.0" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 938 | 939 | [[package]] 940 | name = "semver" 941 | version = "1.0.9" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" 944 | 945 | [[package]] 946 | name = "serde" 947 | version = "1.0.137" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" 950 | dependencies = [ 951 | "serde_derive", 952 | ] 953 | 954 | [[package]] 955 | name = "serde_derive" 956 | version = "1.0.137" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" 959 | dependencies = [ 960 | "proc-macro2", 961 | "quote", 962 | "syn", 963 | ] 964 | 965 | [[package]] 966 | name = "serde_json" 967 | version = "1.0.81" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" 970 | dependencies = [ 971 | "itoa", 972 | "ryu", 973 | "serde", 974 | ] 975 | 976 | [[package]] 977 | name = "serde_urlencoded" 978 | version = "0.7.1" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 981 | dependencies = [ 982 | "form_urlencoded", 983 | "itoa", 984 | "ryu", 985 | "serde", 986 | ] 987 | 988 | [[package]] 989 | name = "sha-1" 990 | version = "0.10.0" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" 993 | dependencies = [ 994 | "cfg-if", 995 | "cpufeatures", 996 | "digest", 997 | ] 998 | 999 | [[package]] 1000 | name = "sha2" 1001 | version = "0.10.2" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" 1004 | dependencies = [ 1005 | "cfg-if", 1006 | "cpufeatures", 1007 | "digest", 1008 | ] 1009 | 1010 | [[package]] 1011 | name = "signal-hook-registry" 1012 | version = "1.4.0" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 1015 | dependencies = [ 1016 | "libc", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "slab" 1021 | version = "0.4.6" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" 1024 | 1025 | [[package]] 1026 | name = "smallvec" 1027 | version = "1.8.0" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" 1030 | 1031 | [[package]] 1032 | name = "socket2" 1033 | version = "0.4.4" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" 1036 | dependencies = [ 1037 | "libc", 1038 | "winapi", 1039 | ] 1040 | 1041 | [[package]] 1042 | name = "subtle" 1043 | version = "2.4.1" 1044 | source = "registry+https://github.com/rust-lang/crates.io-index" 1045 | checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" 1046 | 1047 | [[package]] 1048 | name = "syn" 1049 | version = "1.0.95" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" 1052 | dependencies = [ 1053 | "proc-macro2", 1054 | "quote", 1055 | "unicode-ident", 1056 | ] 1057 | 1058 | [[package]] 1059 | name = "termcolor" 1060 | version = "1.1.3" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 1063 | dependencies = [ 1064 | "winapi-util", 1065 | ] 1066 | 1067 | [[package]] 1068 | name = "time" 1069 | version = "0.1.43" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 1072 | dependencies = [ 1073 | "libc", 1074 | "winapi", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "time" 1079 | version = "0.3.9" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" 1082 | dependencies = [ 1083 | "itoa", 1084 | "libc", 1085 | "num_threads", 1086 | "time-macros", 1087 | ] 1088 | 1089 | [[package]] 1090 | name = "time-macros" 1091 | version = "0.2.4" 1092 | source = "registry+https://github.com/rust-lang/crates.io-index" 1093 | checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" 1094 | 1095 | [[package]] 1096 | name = "tinyvec" 1097 | version = "1.6.0" 1098 | source = "registry+https://github.com/rust-lang/crates.io-index" 1099 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1100 | dependencies = [ 1101 | "tinyvec_macros", 1102 | ] 1103 | 1104 | [[package]] 1105 | name = "tinyvec_macros" 1106 | version = "0.1.0" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1109 | 1110 | [[package]] 1111 | name = "tokio" 1112 | version = "1.18.2" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395" 1115 | dependencies = [ 1116 | "bytes", 1117 | "libc", 1118 | "memchr", 1119 | "mio", 1120 | "once_cell", 1121 | "parking_lot", 1122 | "pin-project-lite", 1123 | "signal-hook-registry", 1124 | "socket2", 1125 | "winapi", 1126 | ] 1127 | 1128 | [[package]] 1129 | name = "tokio-util" 1130 | version = "0.7.2" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" 1133 | dependencies = [ 1134 | "bytes", 1135 | "futures-core", 1136 | "futures-sink", 1137 | "pin-project-lite", 1138 | "tokio", 1139 | "tracing", 1140 | ] 1141 | 1142 | [[package]] 1143 | name = "tracing" 1144 | version = "0.1.34" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" 1147 | dependencies = [ 1148 | "cfg-if", 1149 | "log", 1150 | "pin-project-lite", 1151 | "tracing-attributes", 1152 | "tracing-core", 1153 | ] 1154 | 1155 | [[package]] 1156 | name = "tracing-attributes" 1157 | version = "0.1.21" 1158 | source = "registry+https://github.com/rust-lang/crates.io-index" 1159 | checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" 1160 | dependencies = [ 1161 | "proc-macro2", 1162 | "quote", 1163 | "syn", 1164 | ] 1165 | 1166 | [[package]] 1167 | name = "tracing-core" 1168 | version = "0.1.26" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" 1171 | dependencies = [ 1172 | "lazy_static", 1173 | ] 1174 | 1175 | [[package]] 1176 | name = "typenum" 1177 | version = "1.15.0" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" 1180 | 1181 | [[package]] 1182 | name = "unicode-bidi" 1183 | version = "0.3.8" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" 1186 | 1187 | [[package]] 1188 | name = "unicode-ident" 1189 | version = "1.0.0" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" 1192 | 1193 | [[package]] 1194 | name = "unicode-normalization" 1195 | version = "0.1.19" 1196 | source = "registry+https://github.com/rust-lang/crates.io-index" 1197 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 1198 | dependencies = [ 1199 | "tinyvec", 1200 | ] 1201 | 1202 | [[package]] 1203 | name = "url" 1204 | version = "2.2.2" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 1207 | dependencies = [ 1208 | "form_urlencoded", 1209 | "idna", 1210 | "matches", 1211 | "percent-encoding", 1212 | ] 1213 | 1214 | [[package]] 1215 | name = "version_check" 1216 | version = "0.9.4" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1219 | 1220 | [[package]] 1221 | name = "wasi" 1222 | version = "0.10.2+wasi-snapshot-preview1" 1223 | source = "registry+https://github.com/rust-lang/crates.io-index" 1224 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 1225 | 1226 | [[package]] 1227 | name = "wasi" 1228 | version = "0.11.0+wasi-snapshot-preview1" 1229 | source = "registry+https://github.com/rust-lang/crates.io-index" 1230 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1231 | 1232 | [[package]] 1233 | name = "winapi" 1234 | version = "0.3.9" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1237 | dependencies = [ 1238 | "winapi-i686-pc-windows-gnu", 1239 | "winapi-x86_64-pc-windows-gnu", 1240 | ] 1241 | 1242 | [[package]] 1243 | name = "winapi-i686-pc-windows-gnu" 1244 | version = "0.4.0" 1245 | source = "registry+https://github.com/rust-lang/crates.io-index" 1246 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1247 | 1248 | [[package]] 1249 | name = "winapi-util" 1250 | version = "0.1.5" 1251 | source = "registry+https://github.com/rust-lang/crates.io-index" 1252 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1253 | dependencies = [ 1254 | "winapi", 1255 | ] 1256 | 1257 | [[package]] 1258 | name = "winapi-x86_64-pc-windows-gnu" 1259 | version = "0.4.0" 1260 | source = "registry+https://github.com/rust-lang/crates.io-index" 1261 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1262 | 1263 | [[package]] 1264 | name = "windows-sys" 1265 | version = "0.36.1" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 1268 | dependencies = [ 1269 | "windows_aarch64_msvc", 1270 | "windows_i686_gnu", 1271 | "windows_i686_msvc", 1272 | "windows_x86_64_gnu", 1273 | "windows_x86_64_msvc", 1274 | ] 1275 | 1276 | [[package]] 1277 | name = "windows_aarch64_msvc" 1278 | version = "0.36.1" 1279 | source = "registry+https://github.com/rust-lang/crates.io-index" 1280 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 1281 | 1282 | [[package]] 1283 | name = "windows_i686_gnu" 1284 | version = "0.36.1" 1285 | source = "registry+https://github.com/rust-lang/crates.io-index" 1286 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 1287 | 1288 | [[package]] 1289 | name = "windows_i686_msvc" 1290 | version = "0.36.1" 1291 | source = "registry+https://github.com/rust-lang/crates.io-index" 1292 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 1293 | 1294 | [[package]] 1295 | name = "windows_x86_64_gnu" 1296 | version = "0.36.1" 1297 | source = "registry+https://github.com/rust-lang/crates.io-index" 1298 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 1299 | 1300 | [[package]] 1301 | name = "windows_x86_64_msvc" 1302 | version = "0.36.1" 1303 | source = "registry+https://github.com/rust-lang/crates.io-index" 1304 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 1305 | 1306 | [[package]] 1307 | name = "zstd" 1308 | version = "0.10.2+zstd.1.5.2" 1309 | source = "registry+https://github.com/rust-lang/crates.io-index" 1310 | checksum = "5f4a6bd64f22b5e3e94b4e238669ff9f10815c27a5180108b849d24174a83847" 1311 | dependencies = [ 1312 | "zstd-safe", 1313 | ] 1314 | 1315 | [[package]] 1316 | name = "zstd-safe" 1317 | version = "4.1.6+zstd.1.5.2" 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" 1319 | checksum = "94b61c51bb270702d6167b8ce67340d2754b088d0c091b06e593aa772c3ee9bb" 1320 | dependencies = [ 1321 | "libc", 1322 | "zstd-sys", 1323 | ] 1324 | 1325 | [[package]] 1326 | name = "zstd-sys" 1327 | version = "1.6.3+zstd.1.5.2" 1328 | source = "registry+https://github.com/rust-lang/crates.io-index" 1329 | checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8" 1330 | dependencies = [ 1331 | "cc", 1332 | "libc", 1333 | ] 1334 | -------------------------------------------------------------------------------- /rust-actix/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alchemy-notify-example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | actix-web = "4" 10 | env_logger = "0.9.0" 11 | log = "0.4.17" 12 | futures-util = "0.3.21" 13 | hmac = "0.12.1" 14 | hex-literal = "0.3.4" 15 | sha2 = "0.10.2" 16 | hex = "0.4.3" 17 | serde = { version = "1.0", features = ["derive"] } 18 | serde_json = "1.0.81" 19 | chrono = "0.4.0" 20 | -------------------------------------------------------------------------------- /rust-actix/README.md: -------------------------------------------------------------------------------- 1 | # Example Alchemy Notify Webhooks Server in Rust 2 | 3 | A simple example webhook server for using Alchemy Notify that uses rust [actix](https://actix.rs/docs/getting-started/). 4 | 5 | ## Run 6 | 7 | To run on localhost:8080: 8 | 9 | ``` 10 | PORT=8080 HOST=localhost SIGNING_KEY=whsec_your_key_here cargo run 11 | ``` 12 | 13 | Please change SIGNING_KEY to the signing key corresponding to your webhook, which you can find [here](https://docs.alchemy.com/alchemy/enhanced-apis/notify-api/using-notify#1.-find-your-signing-key) 14 | 15 | And just like that, you're done! 16 | 17 | NOTE: Your webhook path is currently set to "/webhook-path" in `src/main.rs`, but feel free to change it to whatever path you'd like. 18 | 19 | ## Debugging 20 | 21 | If you aren't receiving any webhooks, be sure you followed the steps [here first](https://github.com/alchemyplatform/#readme). 22 | -------------------------------------------------------------------------------- /rust-actix/src/env_helpers.rs: -------------------------------------------------------------------------------- 1 | use std::{env, str::FromStr}; 2 | 3 | pub fn set_default_env_var(key: &str, value: &str) { 4 | if env::var(key).is_err() { 5 | env::set_var(key, value); 6 | } 7 | } 8 | 9 | pub fn cast_required_env_var(key: &str) -> F 10 | where 11 | ::Err: std::fmt::Debug, 12 | { 13 | env::var(key) 14 | .expect(&format!("{} env var does not exist!", key)) 15 | .parse::() 16 | .expect(&format!("Error casting {} env var!", key)) 17 | } 18 | -------------------------------------------------------------------------------- /rust-actix/src/main.rs: -------------------------------------------------------------------------------- 1 | mod env_helpers; 2 | mod notify; 3 | 4 | use log::info; 5 | 6 | use actix_web::{middleware, web, App, HttpMessage, HttpRequest, HttpResponse, HttpServer}; 7 | use std::{io, rc::Rc}; 8 | 9 | use crate::env_helpers::cast_required_env_var; 10 | use crate::env_helpers::set_default_env_var; 11 | use crate::notify::AlchemyWebhookEvent; 12 | 13 | async fn webhook_handler(req: HttpRequest) -> HttpResponse { 14 | let extensions = req.extensions(); 15 | let event = extensions 16 | .get::>() 17 | .unwrap() 18 | .as_ref(); 19 | // Do stuff with with webhook event here! 20 | info!("Processing webhook event id: {:?}", event.id); 21 | // Be sure to respond with 200 when you successfully process the event 22 | HttpResponse::Ok().body("Alchemy Notify is the best!") 23 | } 24 | 25 | #[actix_web::main] 26 | async fn main() -> io::Result<()> { 27 | set_default_env_var("RUST_LOG", "info"); 28 | env_logger::init(); 29 | 30 | set_default_env_var("PORT", "8080"); 31 | set_default_env_var("HOST", "127.0.0.1"); 32 | set_default_env_var("SIGNING_KEY", "whsec_test"); 33 | 34 | let port = cast_required_env_var::("PORT"); 35 | let host = cast_required_env_var::("HOST"); 36 | let signing_key = cast_required_env_var::("SIGNING_KEY"); 37 | 38 | HttpServer::new(move || { 39 | App::new() 40 | .wrap(middleware::Logger::default()) 41 | // Middleware needed to validate the alchemy signature 42 | .wrap(notify::AlchemyRequestHandlerMiddlewareFactory::new( 43 | signing_key.clone(), 44 | )) 45 | // Register handler for Alchemy Notify webhook events 46 | .service( 47 | web::resource( 48 | // TODO: update to your own webhook path 49 | "/webhook-path", 50 | ) 51 | .to(webhook_handler), 52 | ) 53 | }) 54 | // Listen to Alchemy Notify webhook events 55 | .bind((host, port))? 56 | .run() 57 | .await 58 | } 59 | -------------------------------------------------------------------------------- /rust-actix/src/notify.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, FixedOffset}; 2 | use hex; 3 | use hmac::{Hmac, Mac}; 4 | use sha2::Sha256; 5 | use std::{ 6 | future::{ready, Ready}, 7 | rc::Rc, 8 | }; 9 | 10 | use serde::{de, Deserialize, Deserializer}; 11 | 12 | use actix_web::{ 13 | dev::{self, Service, ServiceRequest, ServiceResponse, Transform}, 14 | error::ErrorBadRequest, 15 | error::ErrorUnauthorized, 16 | web::BytesMut, 17 | Error, HttpMessage, 18 | }; 19 | use futures_util::{future::LocalBoxFuture, stream::StreamExt}; 20 | 21 | #[derive(Deserialize)] 22 | #[serde(rename_all = "camelCase")] 23 | 24 | pub struct AlchemyWebhookEvent { 25 | pub webhook_id: String, 26 | pub id: String, 27 | #[serde(deserialize_with = "deserialize_date_time")] 28 | pub created_at: DateTime, 29 | #[serde(rename = "type")] 30 | pub webhook_type: String, 31 | pub event: serde_json::Value, 32 | } 33 | 34 | fn deserialize_date_time<'a, D>(deserializer: D) -> Result, D::Error> 35 | where 36 | D: Deserializer<'a>, 37 | { 38 | let date_time_string: String = Deserialize::deserialize(deserializer)?; 39 | let date_time = DateTime::::parse_from_rfc3339(&date_time_string) 40 | .map_err(|e| de::Error::custom(e.to_string()))?; 41 | Ok(date_time) 42 | } 43 | 44 | fn is_valid_signature_for_string_body( 45 | body: &[u8], 46 | signature: &str, 47 | signing_key: &str, 48 | ) -> Result> { 49 | let signing_key_bytes: Vec = signing_key.bytes().collect(); 50 | let mut mac = Hmac::::new_from_slice(&signing_key_bytes)?; 51 | mac.update(&body); 52 | let hex_decode_signature = hex::decode(signature)?; 53 | let verification = mac.verify_slice(&hex_decode_signature).is_ok(); 54 | Ok(verification) 55 | } 56 | 57 | pub struct AlchemyRequestHandlerMiddleware { 58 | signing_key: String, 59 | service: Rc, 60 | } 61 | 62 | impl Service for AlchemyRequestHandlerMiddleware 63 | where 64 | S: Service, Error = Error> + 'static, 65 | S::Future: 'static, 66 | B: 'static, 67 | { 68 | type Response = ServiceResponse; 69 | type Error = Error; 70 | type Future = LocalBoxFuture<'static, Result>; 71 | 72 | dev::forward_ready!(service); 73 | 74 | fn call(&self, mut req: ServiceRequest) -> Self::Future { 75 | let svc = self.service.clone(); 76 | let signing_key = self.signing_key.clone(); 77 | 78 | Box::pin(async move { 79 | let mut body = BytesMut::new(); 80 | let mut stream = req.take_payload(); 81 | while let Some(chunk) = stream.next().await { 82 | body.extend_from_slice(&chunk?); 83 | } 84 | 85 | let signature = req 86 | .headers() 87 | .get("x-alchemy-signature") 88 | .ok_or(ErrorBadRequest( 89 | "Signature validation failed, missing x-alchemy-signature header!", 90 | ))? 91 | .to_str() 92 | .map_err(|_| { 93 | ErrorBadRequest( 94 | "Signature validation failed, x-alchemy-signature header is not a string!", 95 | ) 96 | })?; 97 | 98 | let is_valid_signature = 99 | is_valid_signature_for_string_body(&body, signature, &signing_key)?; 100 | 101 | if !is_valid_signature { 102 | return Err(ErrorUnauthorized( 103 | "Signature validation failed, signature and body do not match!", 104 | )); 105 | } 106 | 107 | let webhook: AlchemyWebhookEvent = serde_json::from_slice(&body).map_err(|_| { 108 | ErrorBadRequest("Bad format, body could not be mapped to AlchemyWebhookEvent") 109 | })?; 110 | 111 | req.extensions_mut() 112 | .insert::>(Rc::new(webhook)); 113 | 114 | let res = svc.call(req).await?; 115 | 116 | Ok(res) 117 | }) 118 | } 119 | } 120 | 121 | pub struct AlchemyRequestHandlerMiddlewareFactory { 122 | signing_key: String, 123 | } 124 | 125 | impl AlchemyRequestHandlerMiddlewareFactory { 126 | pub fn new(signing_key: String) -> Self { 127 | AlchemyRequestHandlerMiddlewareFactory { signing_key } 128 | } 129 | } 130 | 131 | impl Transform for AlchemyRequestHandlerMiddlewareFactory 132 | where 133 | B: 'static, 134 | S: Service, Error = Error> + 'static, 135 | { 136 | type Response = ServiceResponse; 137 | type Error = Error; 138 | type Transform = AlchemyRequestHandlerMiddleware; 139 | type InitError = (); 140 | type Future = Ready>; 141 | 142 | fn new_transform(&self, service: S) -> Self::Future { 143 | ready(Ok(AlchemyRequestHandlerMiddleware { 144 | signing_key: self.signing_key.clone(), 145 | service: Rc::new(service), 146 | })) 147 | } 148 | } 149 | --------------------------------------------------------------------------------