WIP 1
This commit is contained in:
parent
c095fa7042
commit
43bfdaa1e3
186 changed files with 276 additions and 37155 deletions
|
|
@ -1,13 +0,0 @@
|
|||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12
|
||||
},
|
||||
"rules": {
|
||||
}
|
||||
};
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [0.2.3](https://gitlab.com/digiresilience.org/link/babel-preset-amigo/compare/0.2.2...0.2.3) (2021-10-08)
|
||||
|
||||
### [0.2.2](https://gitlab.com/digiresilience.org/link/babel-preset-amigo/compare/0.2.1...0.2.2) (2021-05-25)
|
||||
|
||||
### [0.2.1](https://gitlab.com/digiresilience.org/link/babel-preset-amigo/compare/0.2.0...0.2.1) (2021-05-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* bump babel to 7.14 ([fec59d5](https://gitlab.com/digiresilience.org/link/babel-preset-amigo/commit/fec59d563dc0b0f1c3ace754d88091f0bdbf1afc))
|
||||
|
||||
## [0.2.0](https://gitlab.com/digiresilience.org/link/babel-preset-amigo/compare/0.1.0...0.2.0) (2020-11-20)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* upgrade deps
|
||||
|
||||
### Features
|
||||
|
||||
* upgrade deps ([46a9ff0](https://gitlab.com/digiresilience.org/link/babel-preset-amigo/commit/46a9ff0883e1f99ab0e918fcbe8c90f4545d58cf))
|
||||
|
||||
## 0.1.0 (2020-10-09)
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
# babel-preset-amigo
|
||||
|
||||
A shared babel config for [CDR Tech][cdrtech].
|
||||
|
||||
# Install
|
||||
|
||||
We recommend using [@digiresilience/amigo-dev][amigo-dev] to manage your dev dependencies.
|
||||
|
||||
[amigo-dev]: https://gitlab.com/digiresilience/link/amigo-dev
|
||||
|
||||
But if you want to do it manually, then:
|
||||
|
||||
```console
|
||||
$ npm install --save-dev @digiresilience/babel-preset-amigo
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
**`babel.config.json`**
|
||||
|
||||
```json
|
||||
{
|
||||
"presets": [
|
||||
"@digiresilience/babel-preset-amigo"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
# Credits
|
||||
|
||||
Copyright © 2020-present [Center for Digital Resilience][cdr]
|
||||
|
||||
### Contributors
|
||||
|
||||
| [![Abel Luck][abelxluck_avatar]][abelxluck_homepage]<br/>[Abel Luck][abelxluck_homepage] |
|
||||
|---|
|
||||
|
||||
[abelxluck_homepage]: https://gitlab.com/abelxluck
|
||||
[abelxluck_avatar]: https://secure.gravatar.com/avatar/0f605397e0ead93a68e1be26dc26481a?s=100&d=identicon
|
||||
|
||||
### License
|
||||
|
||||
[](https://www.gnu.org/licenses/agpl-3.0.en.html)
|
||||
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
[cdrtech]: https://digiresilience.org/tech/
|
||||
[cdr]: https://digiresilience.org
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
module.exports = () => ({
|
||||
presets: [
|
||||
[require("@babel/preset-env"), { targets: { node: "current" } }],
|
||||
require("@babel/preset-typescript"),
|
||||
],
|
||||
});
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"name": "babel-preset-link",
|
||||
"version": "0.2.3",
|
||||
"description": "amigo's babel preset",
|
||||
"author": "Abel Luck <abel@guardianproject.info>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"lint": "eslint index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "7.24.0",
|
||||
"@babel/preset-env": "7.24.0",
|
||||
"@babel/preset-typescript": "7.23.3"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.57.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "eslint-config-link",
|
||||
"name": "eslint-config",
|
||||
"version": "0.3.10",
|
||||
"description": "amigo's eslint config",
|
||||
"author": "Abel Luck <abel@guardianproject.info>",
|
||||
|
|
@ -10,8 +10,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@rushstack/eslint-patch": "^1.7.2",
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.1",
|
||||
"@typescript-eslint/parser": "^7.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||
"@typescript-eslint/parser": "^7.2.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-xo-space": "^0.35.0",
|
||||
"eslint-plugin-cypress": "^2.15.1",
|
||||
|
|
@ -30,6 +30,6 @@
|
|||
"devDependencies": {
|
||||
"eslint": "^8.57.0",
|
||||
"jest": "^29.7.0",
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "^5.4.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
require('eslint-config-link/patch/modern-module-resolution');
|
||||
module.exports = {
|
||||
extends: [
|
||||
"eslint-config-link/profile/node",
|
||||
"eslint-config-link/profile/typescript"
|
||||
],
|
||||
parserOptions: { tsconfigRootDir: __dirname }
|
||||
};
|
||||
|
||||
11
packages/hapi-nextauth/.gitignore
vendored
11
packages/hapi-nextauth/.gitignore
vendored
|
|
@ -1,11 +0,0 @@
|
|||
.idea/*
|
||||
.nyc_output
|
||||
build
|
||||
node_modules
|
||||
test
|
||||
src/*/*.js
|
||||
coverage
|
||||
*.log
|
||||
package-lock.json
|
||||
.npmrc
|
||||
junit.xml
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
.eslintrc.js
|
||||
.editorconfig
|
||||
.prettierignore
|
||||
.versionrc
|
||||
Makefile
|
||||
.gitlab-ci.yml
|
||||
coverage
|
||||
jest*
|
||||
tsconfig*
|
||||
*.log
|
||||
test*
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# package.json is formatted by package managers, so we ignore it here
|
||||
package.json
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [0.2.1](https://digiresilience.org/link/hapi-users/compare/0.2.0...0.2.1) (2021-10-08)
|
||||
|
||||
## [0.2.0](https://digiresilience.org/link/hapi-users/compare/0.1.0...0.2.0) (2021-05-03)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* update deps
|
||||
|
||||
### Features
|
||||
|
||||
* update deps ([4fdf4d0](https://digiresilience.org/link/hapi-users/commit/4fdf4d0a2a25f76f1d3c27868145b0362e819195))
|
||||
|
||||
## [0.1.0](https://digiresilience.org/link/hapi-users/compare/0.0.3...0.1.0) (2021-04-30)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* upgrade next-auth to 3.19.3
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* upgrade amigo-dev to 0.2.3 ([3fc9eaa](https://digiresilience.org/link/hapi-users/commit/3fc9eaa44658982887d6b8e6b6dc89044a7357de))
|
||||
* upgrade next-auth to 3.19.3 ([cfb19b5](https://digiresilience.org/link/hapi-users/commit/cfb19b5ef43dd493fa1795c28b47c2d973f40132))
|
||||
|
||||
### [0.0.3](https://digiresilience.org/link/hapi-users/compare/0.0.2...0.0.3) (2020-11-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't require package.json ([d9ca860](https://digiresilience.org/link/hapi-users/commit/d9ca860e8feeb46e9f19a1295313fe8f1efb45b5))
|
||||
|
||||
### [0.0.2](https://digiresilience.org/link/hapi-users/compare/0.0.1...0.0.2) (2020-11-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* do not register @hapi/basic, but declare a dependency on it ([8775d01](https://digiresilience.org/link/hapi-users/commit/8775d01778c42711d0b4aec15b0d25c0c7c040b8))
|
||||
* implement basic auth for endpoint authorization ([0834f2e](https://digiresilience.org/link/hapi-users/commit/0834f2e9f2a618287767c18797b1ad7665b22bb1))
|
||||
|
||||
### 0.0.1 (2020-11-20)
|
||||
|
|
@ -1,616 +0,0 @@
|
|||
### GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc.
|
||||
<https://fsf.org/>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
### Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains
|
||||
free software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing
|
||||
under this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
### TERMS AND CONDITIONS
|
||||
|
||||
#### 0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public
|
||||
License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds
|
||||
of works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of
|
||||
an exact copy. The resulting work is called a "modified version" of
|
||||
the earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user
|
||||
through a computer network, with no transfer of a copy, is not
|
||||
conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices" to
|
||||
the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
#### 1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work for
|
||||
making modifications to it. "Object code" means any non-source form of
|
||||
a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users can
|
||||
regenerate automatically from other parts of the Corresponding Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that same
|
||||
work.
|
||||
|
||||
#### 2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not convey,
|
||||
without conditions so long as your license otherwise remains in force.
|
||||
You may convey covered works to others for the sole purpose of having
|
||||
them make modifications exclusively for you, or provide you with
|
||||
facilities for running those works, provided that you comply with the
|
||||
terms of this License in conveying all material for which you do not
|
||||
control copyright. Those thus making or running the covered works for
|
||||
you must do so exclusively on your behalf, under your direction and
|
||||
control, on terms that prohibit them from making any copies of your
|
||||
copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under the
|
||||
conditions stated below. Sublicensing is not allowed; section 10 makes
|
||||
it unnecessary.
|
||||
|
||||
#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such
|
||||
circumvention is effected by exercising rights under this License with
|
||||
respect to the covered work, and you disclaim any intention to limit
|
||||
operation or modification of the work as a means of enforcing, against
|
||||
the work's users, your or third parties' legal rights to forbid
|
||||
circumvention of technological measures.
|
||||
|
||||
#### 4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
#### 5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these
|
||||
conditions:
|
||||
|
||||
- a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
- b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under
|
||||
section 7. This requirement modifies the requirement in section 4
|
||||
to "keep intact all notices".
|
||||
- c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
- d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
#### 6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms of
|
||||
sections 4 and 5, provided that you also convey the machine-readable
|
||||
Corresponding Source under the terms of this License, in one of these
|
||||
ways:
|
||||
|
||||
- a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
- b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the Corresponding
|
||||
Source from a network server at no charge.
|
||||
- c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
- d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
- e) Convey the object code using peer-to-peer transmission,
|
||||
provided you inform other peers where the object code and
|
||||
Corresponding Source of the work are being offered to the general
|
||||
public at no charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal,
|
||||
family, or household purposes, or (2) anything designed or sold for
|
||||
incorporation into a dwelling. In determining whether a product is a
|
||||
consumer product, doubtful cases shall be resolved in favor of
|
||||
coverage. For a particular product received by a particular user,
|
||||
"normally used" refers to a typical or common use of that class of
|
||||
product, regardless of the status of the particular user or of the way
|
||||
in which the particular user actually uses, or expects or is expected
|
||||
to use, the product. A product is a consumer product regardless of
|
||||
whether the product has substantial commercial, industrial or
|
||||
non-consumer uses, unless such uses represent the only significant
|
||||
mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to
|
||||
install and execute modified versions of a covered work in that User
|
||||
Product from a modified version of its Corresponding Source. The
|
||||
information must suffice to ensure that the continued functioning of
|
||||
the modified object code is in no case prevented or interfered with
|
||||
solely because modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or
|
||||
updates for a work that has been modified or installed by the
|
||||
recipient, or for the User Product in which it has been modified or
|
||||
installed. Access to a network may be denied when the modification
|
||||
itself materially and adversely affects the operation of the network
|
||||
or violates the rules and protocols for communication across the
|
||||
network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
#### 7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders
|
||||
of that material) supplement the terms of this License with terms:
|
||||
|
||||
- a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
- b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
- c) Prohibiting misrepresentation of the origin of that material,
|
||||
or requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
- d) Limiting the use for publicity purposes of names of licensors
|
||||
or authors of the material; or
|
||||
- e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
- f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions
|
||||
of it) with contractual assumptions of liability to the recipient,
|
||||
for any liability that these contractual assumptions directly
|
||||
impose on those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions; the
|
||||
above requirements apply either way.
|
||||
|
||||
#### 8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your license
|
||||
from a particular copyright holder is reinstated (a) provisionally,
|
||||
unless and until the copyright holder explicitly and finally
|
||||
terminates your license, and (b) permanently, if the copyright holder
|
||||
fails to notify you of the violation by some reasonable means prior to
|
||||
60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
#### 9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or run
|
||||
a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
#### 10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
#### 11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims owned
|
||||
or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within the
|
||||
scope of its coverage, prohibits the exercise of, or is conditioned on
|
||||
the non-exercise of one or more of the rights that are specifically
|
||||
granted under this License. You may not convey a covered work if you
|
||||
are a party to an arrangement with a third party that is in the
|
||||
business of distributing software, under which you make payment to the
|
||||
third party based on the extent of your activity of conveying the
|
||||
work, and under which the third party grants, to any of the parties
|
||||
who would receive the covered work from you, a discriminatory patent
|
||||
license (a) in connection with copies of the covered work conveyed by
|
||||
you (or copies made from those copies), or (b) primarily for and in
|
||||
connection with specific products or compilations that contain the
|
||||
covered work, unless you entered into that arrangement, or that patent
|
||||
license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
#### 12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under
|
||||
this License and any other pertinent obligations, then as a
|
||||
consequence you may not convey it at all. For example, if you agree to
|
||||
terms that obligate you to collect a royalty for further conveying
|
||||
from those to whom you convey the Program, the only way you could
|
||||
satisfy both those terms and this License would be to refrain entirely
|
||||
from conveying the Program.
|
||||
|
||||
#### 13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your
|
||||
version supports such interaction) an opportunity to receive the
|
||||
Corresponding Source of your version by providing access to the
|
||||
Corresponding Source from a network server at no charge, through some
|
||||
standard or customary means of facilitating copying of software. This
|
||||
Corresponding Source shall include the Corresponding Source for any
|
||||
work covered by version 3 of the GNU General Public License that is
|
||||
incorporated pursuant to the following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
#### 14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Affero General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever
|
||||
published by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future versions
|
||||
of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
#### 15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
|
||||
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
|
||||
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
|
||||
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
|
||||
CORRECTION.
|
||||
|
||||
#### 16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
|
||||
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
|
||||
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
|
||||
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
|
||||
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
|
||||
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
|
||||
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
#### 17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
# hapi-nextauth
|
||||
|
||||
This is a plugin for hapi.js that exposes [NextAuth's database adapter](https://next-auth.js.org/tutorials/creating-a-database-adapter) via HTTP. Bring your own database.
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
import * as Hapi from "@hapi/hapi";
|
||||
import Joi from "joi";
|
||||
import NextAuthPlugin from "@digiresilience/hapi-nextauth";
|
||||
import type { AdapterInstance } from "next-auth/adapters";
|
||||
|
||||
|
||||
const server = new Hapi.Server();
|
||||
|
||||
// the validator must be registered before registering the plugin
|
||||
await server.validator(Joi);
|
||||
|
||||
const nextAuthAdapterFactory: AdapterInstance = (request: Hapi.Request) => {
|
||||
... instantiate your next auth adapter ...
|
||||
}
|
||||
|
||||
|
||||
// register the plugin
|
||||
await server.register({
|
||||
plugin: NextAuthPlugin,
|
||||
options: {
|
||||
// the only required parameter is a function that returns your implementation of the NextAuthAdapter
|
||||
nextAuthAdapterFactory,
|
||||
}});
|
||||
```
|
||||
|
||||
Reference the [next-auth typings](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/next-auth/adapters.d.ts#L38-L77) for the adapter interface.
|
||||
|
||||
Options consist of:
|
||||
|
||||
- `nextAuthAdapterFactory` - a function that returns your implementation of the NextAuthAdapter, it takes the Hapi Request as the sole argument.
|
||||
- `basePath` - a string that all next auth endpoints will be served from
|
||||
- `sharedSecret` - the secret used for basic authentication to the nextauth endpoints
|
||||
- `validators` - an object containing
|
||||
- `profile` - a Joi schema that validates a profile
|
||||
- `user` - a Joi schema that validates a user
|
||||
- `userId` - a Joi schema that validates a userId
|
||||
- `session` - a Joi schema that validates a session
|
||||
- `tags` - tags to add to the endpoints
|
||||
|
||||
Defaults are defined in [`src/index.ts`](src/index.ts)
|
||||
|
||||
## Credits
|
||||
|
||||
Copyright © 2020-present [Center for Digital Resilience][cdr]
|
||||
|
||||
### Contributors
|
||||
|
||||
| [![Abel Luck][abelxluck_avatar]][abelxluck_homepage]<br/>[Abel Luck][abelxluck_homepage] |
|
||||
| ---------------------------------------------------------------------------------------- |
|
||||
|
||||
[abelxluck_homepage]: https://gitlab.com/abelxluck
|
||||
[abelxluck_avatar]: https://secure.gravatar.com/avatar/0f605397e0ead93a68e1be26dc26481a?s=100&d=identicon
|
||||
|
||||
### License
|
||||
|
||||
[](https://www.gnu.org/licenses/agpl-3.0.en.html)
|
||||
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
[cdrtech]: https://digiresilience.org/tech/
|
||||
[cdr]: https://digiresilience.org
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"presets": [
|
||||
"babel-preset-link"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"preset": "jest-config-link"
|
||||
}
|
||||
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
{
|
||||
"name": "@digiresilience/hapi-nextauth",
|
||||
"version": "1.0.0",
|
||||
"description": "a plugin for hapi.js that exposes NextAuth's database adapter via HTTP",
|
||||
"main": "build/main/index.js",
|
||||
"type": "module",
|
||||
"author": "Abel Luck <abel@guardianproject.info>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"private": false,
|
||||
"devDependencies": {
|
||||
"@hapi/basic": "^7.0.2",
|
||||
"@types/jest": "^29.5.12",
|
||||
"babel-preset-link": "*",
|
||||
"eslint-config-link": "*",
|
||||
"jest-config-link": "*",
|
||||
"tsc-watch": "^6.0.4",
|
||||
"tsconfig-link": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hapi/hapi": "^21.3.3",
|
||||
"@hapi/hoek": "^11.0.4",
|
||||
"joi": "^17.12.2",
|
||||
"next-auth": "4.24.6"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"fix:lint": "eslint src --ext .ts --fix",
|
||||
"fmt": "prettier \"src/**/*.ts\" --write",
|
||||
"test": "jest --coverage --forceExit --detectOpenHandles --reporters=default --reporters=jest-junit",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"lint-fmt": "prettier \"src/**/*.ts\" --list-different",
|
||||
"doc": "typedoc src/ --exclude '**/*.test.ts' --exclude '**/*.spec.ts' --name $npm_package_name --readme README.md --target es2019 --mode file --out build/docs",
|
||||
"dev": "tsc-watch --build --noClear"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,363 +0,0 @@
|
|||
import * as Hapi from "@hapi/hapi";
|
||||
import HapiBasic from "@hapi/basic";
|
||||
import NextAuthPlugin from ".";
|
||||
|
||||
describe("plugin option validation", () => {
|
||||
let server;
|
||||
beforeEach(async () => {
|
||||
server = new Hapi.Server();
|
||||
});
|
||||
|
||||
it("should throw when options contain no next auth adapter", async () => {
|
||||
expect(server.register(NextAuthPlugin)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("plugin runtime", () => {
|
||||
let server;
|
||||
const user = { id: "abc", email: "abc@abc.abc" };
|
||||
const session = {
|
||||
id: "zyx",
|
||||
userId: "abc",
|
||||
expires: Date.now(),
|
||||
sessionToken: "foo",
|
||||
accessToken: "bar",
|
||||
};
|
||||
|
||||
const start = async (mock) => {
|
||||
await server.register(HapiBasic);
|
||||
await server.register({
|
||||
plugin: NextAuthPlugin,
|
||||
options: {
|
||||
nextAuthAdapterFactory: () => mock,
|
||||
},
|
||||
});
|
||||
|
||||
await server.start();
|
||||
return server;
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
server = new Hapi.Server({ port: 0 });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it("createUser", async () => {
|
||||
const createUser = jest.fn(() => user);
|
||||
const profile = { email: "abc@abc.abc" };
|
||||
|
||||
await start({ createUser });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "post",
|
||||
url: "/api/nextauth/createUser",
|
||||
payload: profile,
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(createUser).toHaveBeenCalledWith(profile);
|
||||
expect(result).toStrictEqual(user);
|
||||
});
|
||||
|
||||
it("createUser fails with invalid payload", async () => {
|
||||
const createUser = jest.fn(() => user);
|
||||
const profile = { name: "name" };
|
||||
|
||||
await start({ createUser });
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: "post",
|
||||
url: "/api/nextauth/createUser",
|
||||
payload: profile,
|
||||
});
|
||||
expect(statusCode).toBe(400);
|
||||
});
|
||||
|
||||
it("getUser", async () => {
|
||||
const getUser = jest.fn(() => user);
|
||||
|
||||
await start({ getUser });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "get",
|
||||
url: "/api/nextauth/getUser/abc",
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(getUser).toHaveBeenCalledWith("abc");
|
||||
expect(result).toBe(user);
|
||||
});
|
||||
|
||||
it("getUserByEmail", async () => {
|
||||
const getUserByEmail = jest.fn(() => user);
|
||||
|
||||
await start({ getUserByEmail });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "get",
|
||||
url: "/api/nextauth/getUserByEmail/abc@abc.abc",
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(getUserByEmail).toHaveBeenCalledWith("abc@abc.abc");
|
||||
expect(result).toBe(user);
|
||||
});
|
||||
|
||||
it("getUserByEmail fails with invalid email", async () => {
|
||||
const getUserByEmail = jest.fn(() => user);
|
||||
|
||||
await start({ getUserByEmail });
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: "get",
|
||||
url: "/api/nextauth/getUserByEmail/notanemail@foo",
|
||||
});
|
||||
expect(statusCode).toBe(400);
|
||||
});
|
||||
|
||||
it("getUserByProviderAccountId", async () => {
|
||||
const getUserByProviderAccountId = jest.fn(() => user);
|
||||
|
||||
await start({ getUserByProviderAccountId });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "get",
|
||||
url: "/api/nextauth/getUserByProviderAccountId/foo/bar",
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(getUserByProviderAccountId).toHaveBeenCalledWith("foo", "bar");
|
||||
expect(result).toBe(user);
|
||||
});
|
||||
|
||||
it("updateUser", async () => {
|
||||
const updateUser = jest.fn(() => user);
|
||||
|
||||
await start({ updateUser });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "put",
|
||||
url: "/api/nextauth/updateUser",
|
||||
payload: user,
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(updateUser).toHaveBeenCalledWith(user);
|
||||
expect(result).toStrictEqual(user);
|
||||
});
|
||||
|
||||
it("updateUser fails with invalid payload", async () => {
|
||||
const updateUser = jest.fn(() => user);
|
||||
|
||||
await start({ updateUser });
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: "put",
|
||||
url: "/api/nextauth/updateUser",
|
||||
payload: {
|
||||
// id not specified
|
||||
email: "abc@abc.abc",
|
||||
},
|
||||
});
|
||||
expect(statusCode).toBe(400);
|
||||
});
|
||||
|
||||
it("linkUser", async () => {
|
||||
const linkAccount = jest.fn(() => undefined);
|
||||
const args = {
|
||||
userId: "abc",
|
||||
providerId: "foo",
|
||||
providerType: "something",
|
||||
providerAccountId: "bar",
|
||||
refreshToken: "refreshToken",
|
||||
accessToken: "accessToken",
|
||||
accessTokenExpires: 10,
|
||||
};
|
||||
|
||||
await start({ linkAccount });
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: "put",
|
||||
url: "/api/nextauth/linkAccount",
|
||||
payload: args,
|
||||
});
|
||||
expect(statusCode).toBe(204);
|
||||
expect(linkAccount.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it("createSession", async () => {
|
||||
const createSession = jest.fn(() => session);
|
||||
|
||||
await start({ createSession });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "post",
|
||||
url: "/api/nextauth/createSession",
|
||||
payload: user,
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(createSession).toHaveBeenCalledWith(user);
|
||||
expect(result).toStrictEqual(session);
|
||||
});
|
||||
|
||||
it("getSession", async () => {
|
||||
const getSession = jest.fn(() => session);
|
||||
|
||||
await start({ getSession });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "get",
|
||||
url: "/api/nextauth/getSession/xyz",
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(getSession).toHaveBeenCalledWith("xyz");
|
||||
expect(result).toBe(session);
|
||||
});
|
||||
|
||||
it("updateSession", async () => {
|
||||
const updateSession = jest.fn(() => session);
|
||||
|
||||
await start({ updateSession });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "put",
|
||||
url: "/api/nextauth/updateSession",
|
||||
payload: session,
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(updateSession).toHaveBeenCalledWith(
|
||||
{
|
||||
...session,
|
||||
expires: new Date(session.expires),
|
||||
},
|
||||
false
|
||||
);
|
||||
expect(result).toStrictEqual(session);
|
||||
});
|
||||
|
||||
it("updateSession - force", async () => {
|
||||
const updateSession = jest.fn(() => session);
|
||||
|
||||
await start({ updateSession });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "put",
|
||||
url: "/api/nextauth/updateSession?force=true",
|
||||
payload: session,
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(updateSession).toHaveBeenCalledWith(
|
||||
{
|
||||
...session,
|
||||
expires: new Date(session.expires),
|
||||
},
|
||||
true
|
||||
);
|
||||
expect(result).toStrictEqual(session);
|
||||
});
|
||||
|
||||
it("deleteSession", async () => {
|
||||
const deleteSession = jest.fn(() => undefined);
|
||||
|
||||
await start({ deleteSession });
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: "delete",
|
||||
url: "/api/nextauth/deleteSession/xyz",
|
||||
});
|
||||
expect(statusCode).toBe(204);
|
||||
expect(deleteSession).toHaveBeenCalledWith("xyz");
|
||||
});
|
||||
});
|
||||
|
||||
describe("plugin authentication", () => {
|
||||
const user = { id: "abc", email: "abc@abc.abc" };
|
||||
const sharedSecret = "secret";
|
||||
let server;
|
||||
|
||||
const start = async (mock) => {
|
||||
await server.register(HapiBasic);
|
||||
await server.register({
|
||||
plugin: NextAuthPlugin,
|
||||
options: {
|
||||
nextAuthAdapterFactory: () => mock,
|
||||
sharedSecret,
|
||||
},
|
||||
});
|
||||
|
||||
await server.start();
|
||||
return server;
|
||||
};
|
||||
|
||||
const basicHeader = (username, password) =>
|
||||
"Basic " +
|
||||
Buffer.from(username + ":" + password, "utf8").toString("base64");
|
||||
|
||||
beforeEach(async () => {
|
||||
server = new Hapi.Server({ port: 0 });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it("getUser - no auth header fails", async () => {
|
||||
const getUser = jest.fn(() => user);
|
||||
|
||||
await start({ getUser });
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: "get",
|
||||
url: "/api/nextauth/getUser/abc",
|
||||
});
|
||||
expect(statusCode).toBe(401);
|
||||
expect(getUser).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it("getUser - with auth header suceeds", async () => {
|
||||
const getUser = jest.fn(() => user);
|
||||
|
||||
await start({ getUser });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "get",
|
||||
url: "/api/nextauth/getUser/abc",
|
||||
headers: {
|
||||
authorization: basicHeader(sharedSecret, ""),
|
||||
},
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(getUser).toHaveBeenCalledWith("abc");
|
||||
expect(result).toBe(user);
|
||||
});
|
||||
|
||||
it("getUser - with invalid credentials fails", async () => {
|
||||
const getUser = jest.fn(() => user);
|
||||
|
||||
await start({ getUser });
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: "get",
|
||||
url: "/api/nextauth/getUser/abc",
|
||||
headers: {
|
||||
authorization: basicHeader("wrong secret", ""),
|
||||
},
|
||||
});
|
||||
expect(statusCode).toBe(401);
|
||||
expect(getUser).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it("getUser - with secret in password field fails", async () => {
|
||||
const getUser = jest.fn(() => user);
|
||||
|
||||
await start({ getUser });
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: "get",
|
||||
url: "/api/nextauth/getUser/abc",
|
||||
headers: {
|
||||
authorization: basicHeader("", "sharedSecret"),
|
||||
},
|
||||
});
|
||||
expect(statusCode).toBe(401);
|
||||
expect(getUser).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
import * as Hapi from "@hapi/hapi";
|
||||
import * as Hoek from "@hapi/hoek";
|
||||
import Joi from "joi";
|
||||
|
||||
import { NextAuthPluginOptions } from "./types.js";
|
||||
import * as Routes from "./routes.js";
|
||||
|
||||
const minimumProfileSchema = Joi.object()
|
||||
.keys({
|
||||
email: Joi.string().email().required(),
|
||||
})
|
||||
.unknown(true);
|
||||
|
||||
const minimumUserSchema = Joi.object()
|
||||
.keys({
|
||||
userId: Joi.string().required(),
|
||||
email: Joi.string().email().required(),
|
||||
})
|
||||
.unknown(true);
|
||||
|
||||
const minimumSessionSchema = Joi.object()
|
||||
.keys({
|
||||
id: Joi.string().required(),
|
||||
userId: Joi.string().required(),
|
||||
expires: Joi.number().required(),
|
||||
sessionToken: Joi.string().required(),
|
||||
accessToken: Joi.string().required(),
|
||||
})
|
||||
.unknown(true);
|
||||
|
||||
const defaultOptions = {
|
||||
basePath: "/api/nextauth",
|
||||
validators: {
|
||||
userId: Joi.string().required(),
|
||||
profile: minimumProfileSchema,
|
||||
user: minimumUserSchema,
|
||||
session: minimumSessionSchema,
|
||||
},
|
||||
tags: [],
|
||||
};
|
||||
|
||||
const validateAuth = (sharedSecret) => (request, username, password) => {
|
||||
// we follow stripe's lead here for authenticating with basic auth
|
||||
// the shared secret should be bassed as the basic auth username, the password should be empty
|
||||
if (password !== "") {
|
||||
console.error(
|
||||
"hapi-nextauth: attempted authentication with basic auth password. only the username should be defined."
|
||||
);
|
||||
|
||||
return { isValid: false, credentials: {} };
|
||||
}
|
||||
|
||||
const isValid = username === sharedSecret;
|
||||
const credentials = {
|
||||
id: "nextauth-frontend",
|
||||
};
|
||||
|
||||
return { isValid, credentials };
|
||||
};
|
||||
|
||||
const register = async (
|
||||
server: Hapi.Server,
|
||||
pluginOpts?: any
|
||||
): Promise<void> => {
|
||||
const options: any = Hoek.applyToDefaults(
|
||||
// a little type gymnastics here to workaround poor typing
|
||||
defaultOptions as any,
|
||||
pluginOpts
|
||||
) as any;
|
||||
|
||||
if (!options.nextAuthAdapterFactory) {
|
||||
throw new Error(
|
||||
"You must pass a NextAuthAdapterFactory instance to hapi-nextauth."
|
||||
);
|
||||
}
|
||||
|
||||
server.validator(Joi as any);
|
||||
let auth = "hapi-nextauth";
|
||||
if (options.sharedSecret) {
|
||||
server.dependency(["@hapi/basic"]);
|
||||
server.auth.strategy(auth, "basic", {
|
||||
validate: validateAuth(options.sharedSecret),
|
||||
});
|
||||
} else {
|
||||
console.warn(
|
||||
"hapi-nextauth: AUTHENTICATION OF FRONTEND TO NEXTAUTH ENDPOINTS DISABLED!"
|
||||
);
|
||||
auth = undefined;
|
||||
}
|
||||
|
||||
await Routes.register(server, options, auth);
|
||||
};
|
||||
|
||||
const nextAuthPlugin = {
|
||||
register,
|
||||
name: "@digiresilience/hapi-nextauth",
|
||||
version: "0.0.3",
|
||||
};
|
||||
|
||||
export * from "./types.js";
|
||||
export default nextAuthPlugin;
|
||||
|
|
@ -1,293 +0,0 @@
|
|||
/* eslint-disable unicorn/no-null */
|
||||
import Joi from "joi";
|
||||
import * as Hapi from "@hapi/hapi";
|
||||
import { ResponseToolkit, ResponseObject } from "@hapi/hapi";
|
||||
|
||||
export interface LinkAccountPayload {
|
||||
userId: string;
|
||||
providerType: string;
|
||||
providerId: string;
|
||||
providerAccountId: string;
|
||||
refreshToken: string;
|
||||
accessToken: string;
|
||||
accessTokenExpires?: null;
|
||||
}
|
||||
|
||||
export const register = async <TUser, TProfile>(
|
||||
server: Hapi.Server,
|
||||
opts: any,
|
||||
auth?: string
|
||||
): Promise<void> => {
|
||||
const { tags, basePath, validators } = opts;
|
||||
const { session, user, userId, profile } = validators;
|
||||
server.route([
|
||||
{
|
||||
method: "POST",
|
||||
path: `${basePath}/createUser`,
|
||||
options: {
|
||||
auth,
|
||||
tags,
|
||||
validate: {
|
||||
payload: profile,
|
||||
},
|
||||
async handler(
|
||||
request: Hapi.Request,
|
||||
h: ResponseToolkit
|
||||
): Promise<Hapi.ResponseObject> {
|
||||
const payload: TProfile = request.payload as TProfile;
|
||||
const r = await opts
|
||||
.nextAuthAdapterFactory(request)
|
||||
.createUser(payload);
|
||||
return h.response(r as object);
|
||||
},
|
||||
description: "Create a user from a profile",
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
path: `${basePath}/getUser/{userId}`,
|
||||
options: {
|
||||
auth,
|
||||
tags,
|
||||
validate: {
|
||||
params: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
async handler(
|
||||
request: Hapi.Request,
|
||||
h: ResponseToolkit
|
||||
): Promise<ResponseObject> {
|
||||
const id = request.params.userId;
|
||||
const r = await opts.nextAuthAdapterFactory(request).getUser(id);
|
||||
if (!r) return h.response().code(404);
|
||||
return h.response(r as object);
|
||||
},
|
||||
description: "Get a user by id",
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
path: `${basePath}/getUserByEmail/{userEmail}`,
|
||||
options: {
|
||||
auth,
|
||||
tags,
|
||||
validate: {
|
||||
params: {
|
||||
userEmail: Joi.string().email(),
|
||||
},
|
||||
},
|
||||
async handler(
|
||||
request: Hapi.Request,
|
||||
h: ResponseToolkit
|
||||
): Promise<ResponseObject> {
|
||||
const email = request.params.userEmail;
|
||||
const r = await opts
|
||||
.nextAuthAdapterFactory(request)
|
||||
.getUserByEmail(email);
|
||||
if (!r) return h.response().code(404);
|
||||
return h.response(r as object);
|
||||
},
|
||||
description: "Get a user by email",
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
path: `${basePath}/getUserByAccount/{provider}/{providerAccountId}`,
|
||||
options: {
|
||||
auth,
|
||||
tags,
|
||||
validate: {
|
||||
params: {
|
||||
provider: Joi.string(),
|
||||
providerAccountId: Joi.string(),
|
||||
},
|
||||
},
|
||||
async handler(
|
||||
request: Hapi.Request,
|
||||
h: ResponseToolkit
|
||||
): Promise<ResponseObject> {
|
||||
const { provider, providerAccountId } = request.params;
|
||||
const r = await opts
|
||||
.nextAuthAdapterFactory(request)
|
||||
.getUserByAccount(provider, providerAccountId);
|
||||
if (!r) return h.response().code(404);
|
||||
return h.response(r as object);
|
||||
},
|
||||
description: "Get a user by provider id and provider account id",
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "PUT",
|
||||
path: `${basePath}/updateUser`,
|
||||
options: {
|
||||
auth,
|
||||
tags,
|
||||
validate: {
|
||||
payload: user,
|
||||
},
|
||||
async handler(
|
||||
request: Hapi.Request,
|
||||
h: ResponseToolkit
|
||||
): Promise<ResponseObject> {
|
||||
const payload: TUser = request.payload as TUser;
|
||||
const r = await opts
|
||||
.nextAuthAdapterFactory(request)
|
||||
.updateUser(payload);
|
||||
if (!r) return h.response().code(404);
|
||||
return h.response(r as object);
|
||||
},
|
||||
description: "Update a user's data",
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "PUT",
|
||||
path: `${basePath}/linkAccount`,
|
||||
options: {
|
||||
auth,
|
||||
tags,
|
||||
validate: {
|
||||
payload: Joi.object({
|
||||
// https://next-auth.js.org/getting-started/upgrade-v4#schema-changes
|
||||
userId: Joi.string().required(),
|
||||
provider: Joi.string().required(),
|
||||
type: Joi.string().required(),
|
||||
providerAccountId: Joi.string().required(),
|
||||
refresh_token: Joi.string().optional().allow(null),
|
||||
access_token: Joi.string().optional().allow(null),
|
||||
expires_at: Joi.number().optional().allow(null),
|
||||
}).unknown(true),
|
||||
},
|
||||
async handler(
|
||||
request: Hapi.Request,
|
||||
h: ResponseToolkit
|
||||
): Promise<ResponseObject> {
|
||||
const {
|
||||
userId,
|
||||
providerId,
|
||||
providerType,
|
||||
providerAccountId,
|
||||
refreshToken,
|
||||
accessToken,
|
||||
accessTokenExpires,
|
||||
} = request.payload as LinkAccountPayload;
|
||||
await opts
|
||||
.nextAuthAdapterFactory(request)
|
||||
.linkAccount(
|
||||
userId,
|
||||
providerId,
|
||||
providerType,
|
||||
providerAccountId,
|
||||
refreshToken,
|
||||
accessToken,
|
||||
accessTokenExpires
|
||||
);
|
||||
return h.response().code(204);
|
||||
},
|
||||
description: "Link a provider account with a user",
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "POST",
|
||||
path: `${basePath}/createSession`,
|
||||
options: {
|
||||
auth,
|
||||
tags,
|
||||
validate: {
|
||||
payload: Joi.object({
|
||||
userId: Joi.string().required(),
|
||||
sessionToken: Joi.string().required(),
|
||||
expires: Joi.string().isoDate().required(),
|
||||
}),
|
||||
},
|
||||
async handler(
|
||||
request: Hapi.Request,
|
||||
h: ResponseToolkit
|
||||
): Promise<ResponseObject> {
|
||||
const payload: TUser = request.payload as TUser;
|
||||
const r = await opts
|
||||
.nextAuthAdapterFactory(request)
|
||||
.createSession(payload);
|
||||
return h.response(r as object);
|
||||
},
|
||||
description: "Create a new session for a user",
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
path: `${basePath}/getSessionAndUser/{sessionToken}`,
|
||||
options: {
|
||||
auth,
|
||||
tags,
|
||||
validate: {
|
||||
params: {
|
||||
sessionToken: Joi.string(),
|
||||
},
|
||||
},
|
||||
async handler(
|
||||
request: Hapi.Request,
|
||||
h: ResponseToolkit
|
||||
): Promise<ResponseObject> {
|
||||
const token = request.params.sessionToken;
|
||||
const r = await opts
|
||||
.nextAuthAdapterFactory(request)
|
||||
.getSessionAndUser(token);
|
||||
if (!r) return h.response().code(404);
|
||||
return h.response(r as object);
|
||||
},
|
||||
description: "Get a session by its token",
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "PUT",
|
||||
path: `${basePath}/updateSession`,
|
||||
options: {
|
||||
auth,
|
||||
tags,
|
||||
validate: {
|
||||
payload: session,
|
||||
},
|
||||
async handler(
|
||||
request: Hapi.Request,
|
||||
h: ResponseToolkit
|
||||
): Promise<ResponseObject> {
|
||||
const inputPayload = request.payload as any;
|
||||
const { expires } = inputPayload;
|
||||
const payload = {
|
||||
...inputPayload,
|
||||
expires: new Date(expires),
|
||||
};
|
||||
const force = Boolean(request.query.force);
|
||||
const r = await opts
|
||||
.nextAuthAdapterFactory(request)
|
||||
.updateSession(payload, force);
|
||||
if (!r) return h.response().code(204);
|
||||
return h.response(r as object);
|
||||
},
|
||||
description: "Update a session for a user",
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "DELETE",
|
||||
path: `${basePath}/deleteSession/{sessionToken}`,
|
||||
options: {
|
||||
auth,
|
||||
tags,
|
||||
validate: {
|
||||
params: {
|
||||
sessionToken: Joi.string(),
|
||||
},
|
||||
},
|
||||
async handler(
|
||||
request: Hapi.Request,
|
||||
h: ResponseToolkit
|
||||
): Promise<ResponseObject> {
|
||||
const token = request.params.sessionToken;
|
||||
await opts.nextAuthAdapterFactory(request).deleteSession(token);
|
||||
return h.response().code(204);
|
||||
},
|
||||
description: "Delete a user's session",
|
||||
},
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import type { Adapter } from "next-auth/adapters";
|
||||
import type { NumberSchema, StringSchema, ObjectSchema } from "joi";
|
||||
import type { Request } from "@hapi/hapi";
|
||||
|
||||
export type AdapterFactory = (request: Request) => Adapter;
|
||||
|
||||
export interface NextAuthPluginOptions {
|
||||
nextAuthAdapterFactory: Adapter;
|
||||
|
||||
validators?: {
|
||||
profile: ObjectSchema;
|
||||
userId: StringSchema | NumberSchema;
|
||||
user: ObjectSchema;
|
||||
session: ObjectSchema;
|
||||
};
|
||||
sharedSecret?: string;
|
||||
basePath?: string;
|
||||
tags?: string[];
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"extends": "tsconfig-link",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"incremental": true,
|
||||
"outDir": "build/main",
|
||||
"rootDir": "src",
|
||||
"baseUrl": "./",
|
||||
"skipLibCheck": true,
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules/**"]
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
max_line_length = 80
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = 0
|
||||
trim_trailing_whitespace = false
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
require('eslint-config-link/patch/modern-module-resolution');
|
||||
module.exports = {
|
||||
extends: [
|
||||
"eslint-config-link/profile/node",
|
||||
"eslint-config-link/profile/typescript"
|
||||
],
|
||||
parserOptions: { tsconfigRootDir: __dirname }
|
||||
};
|
||||
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# package.json is formatted by package managers, so we ignore it here
|
||||
package.json
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [0.0.3](https://digiresilience.org/link/hapi-pg-promise/compare/0.0.2...0.0.3) (2021-10-08)
|
||||
|
||||
### [0.0.2](https://digiresilience.org/link/hapi-pg-promise/compare/0.0.1...0.0.2) (2021-05-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* update deps ([3d8609a](https://digiresilience.org/link/hapi-pg-promise/commit/3d8609ae069d6c43da8a2a7d3a6b7c7e0e7db014))
|
||||
|
||||
### 0.0.1 (2020-11-20)
|
||||
|
|
@ -1,616 +0,0 @@
|
|||
### GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc.
|
||||
<https://fsf.org/>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
### Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains
|
||||
free software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing
|
||||
under this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
### TERMS AND CONDITIONS
|
||||
|
||||
#### 0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public
|
||||
License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds
|
||||
of works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of
|
||||
an exact copy. The resulting work is called a "modified version" of
|
||||
the earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user
|
||||
through a computer network, with no transfer of a copy, is not
|
||||
conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices" to
|
||||
the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
#### 1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work for
|
||||
making modifications to it. "Object code" means any non-source form of
|
||||
a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users can
|
||||
regenerate automatically from other parts of the Corresponding Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that same
|
||||
work.
|
||||
|
||||
#### 2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not convey,
|
||||
without conditions so long as your license otherwise remains in force.
|
||||
You may convey covered works to others for the sole purpose of having
|
||||
them make modifications exclusively for you, or provide you with
|
||||
facilities for running those works, provided that you comply with the
|
||||
terms of this License in conveying all material for which you do not
|
||||
control copyright. Those thus making or running the covered works for
|
||||
you must do so exclusively on your behalf, under your direction and
|
||||
control, on terms that prohibit them from making any copies of your
|
||||
copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under the
|
||||
conditions stated below. Sublicensing is not allowed; section 10 makes
|
||||
it unnecessary.
|
||||
|
||||
#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such
|
||||
circumvention is effected by exercising rights under this License with
|
||||
respect to the covered work, and you disclaim any intention to limit
|
||||
operation or modification of the work as a means of enforcing, against
|
||||
the work's users, your or third parties' legal rights to forbid
|
||||
circumvention of technological measures.
|
||||
|
||||
#### 4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
#### 5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these
|
||||
conditions:
|
||||
|
||||
- a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
- b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under
|
||||
section 7. This requirement modifies the requirement in section 4
|
||||
to "keep intact all notices".
|
||||
- c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
- d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
#### 6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms of
|
||||
sections 4 and 5, provided that you also convey the machine-readable
|
||||
Corresponding Source under the terms of this License, in one of these
|
||||
ways:
|
||||
|
||||
- a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
- b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the Corresponding
|
||||
Source from a network server at no charge.
|
||||
- c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
- d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
- e) Convey the object code using peer-to-peer transmission,
|
||||
provided you inform other peers where the object code and
|
||||
Corresponding Source of the work are being offered to the general
|
||||
public at no charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal,
|
||||
family, or household purposes, or (2) anything designed or sold for
|
||||
incorporation into a dwelling. In determining whether a product is a
|
||||
consumer product, doubtful cases shall be resolved in favor of
|
||||
coverage. For a particular product received by a particular user,
|
||||
"normally used" refers to a typical or common use of that class of
|
||||
product, regardless of the status of the particular user or of the way
|
||||
in which the particular user actually uses, or expects or is expected
|
||||
to use, the product. A product is a consumer product regardless of
|
||||
whether the product has substantial commercial, industrial or
|
||||
non-consumer uses, unless such uses represent the only significant
|
||||
mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to
|
||||
install and execute modified versions of a covered work in that User
|
||||
Product from a modified version of its Corresponding Source. The
|
||||
information must suffice to ensure that the continued functioning of
|
||||
the modified object code is in no case prevented or interfered with
|
||||
solely because modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or
|
||||
updates for a work that has been modified or installed by the
|
||||
recipient, or for the User Product in which it has been modified or
|
||||
installed. Access to a network may be denied when the modification
|
||||
itself materially and adversely affects the operation of the network
|
||||
or violates the rules and protocols for communication across the
|
||||
network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
#### 7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders
|
||||
of that material) supplement the terms of this License with terms:
|
||||
|
||||
- a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
- b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
- c) Prohibiting misrepresentation of the origin of that material,
|
||||
or requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
- d) Limiting the use for publicity purposes of names of licensors
|
||||
or authors of the material; or
|
||||
- e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
- f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions
|
||||
of it) with contractual assumptions of liability to the recipient,
|
||||
for any liability that these contractual assumptions directly
|
||||
impose on those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions; the
|
||||
above requirements apply either way.
|
||||
|
||||
#### 8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your license
|
||||
from a particular copyright holder is reinstated (a) provisionally,
|
||||
unless and until the copyright holder explicitly and finally
|
||||
terminates your license, and (b) permanently, if the copyright holder
|
||||
fails to notify you of the violation by some reasonable means prior to
|
||||
60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
#### 9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or run
|
||||
a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
#### 10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
#### 11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims owned
|
||||
or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within the
|
||||
scope of its coverage, prohibits the exercise of, or is conditioned on
|
||||
the non-exercise of one or more of the rights that are specifically
|
||||
granted under this License. You may not convey a covered work if you
|
||||
are a party to an arrangement with a third party that is in the
|
||||
business of distributing software, under which you make payment to the
|
||||
third party based on the extent of your activity of conveying the
|
||||
work, and under which the third party grants, to any of the parties
|
||||
who would receive the covered work from you, a discriminatory patent
|
||||
license (a) in connection with copies of the covered work conveyed by
|
||||
you (or copies made from those copies), or (b) primarily for and in
|
||||
connection with specific products or compilations that contain the
|
||||
covered work, unless you entered into that arrangement, or that patent
|
||||
license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
#### 12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under
|
||||
this License and any other pertinent obligations, then as a
|
||||
consequence you may not convey it at all. For example, if you agree to
|
||||
terms that obligate you to collect a royalty for further conveying
|
||||
from those to whom you convey the Program, the only way you could
|
||||
satisfy both those terms and this License would be to refrain entirely
|
||||
from conveying the Program.
|
||||
|
||||
#### 13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your
|
||||
version supports such interaction) an opportunity to receive the
|
||||
Corresponding Source of your version by providing access to the
|
||||
Corresponding Source from a network server at no charge, through some
|
||||
standard or customary means of facilitating copying of software. This
|
||||
Corresponding Source shall include the Corresponding Source for any
|
||||
work covered by version 3 of the GNU General Public License that is
|
||||
incorporated pursuant to the following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
#### 14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Affero General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever
|
||||
published by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future versions
|
||||
of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
#### 15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
|
||||
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
|
||||
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
|
||||
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
|
||||
CORRECTION.
|
||||
|
||||
#### 16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
|
||||
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
|
||||
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
|
||||
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
|
||||
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
|
||||
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
|
||||
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
#### 17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
# hapi-pg-promise
|
||||
|
||||
This is a plugin for hapi.js that decorates server and request with a [pg-promise](https://github.com/vitaly-t/pg-promise) database instance.
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
import * as Hapi from "@hapi/hapi";
|
||||
import PgPromisePlugin from "@digiresilience/hapi-pg-promise";
|
||||
|
||||
const server = new Hapi.Server();
|
||||
|
||||
// the validator must be registered before registering the plugin
|
||||
await server.validator(Joi);
|
||||
|
||||
// register the plugin
|
||||
await server.register({
|
||||
plugin: PgPromisePlugin,
|
||||
options: {
|
||||
// the only required parameter is the connection string
|
||||
connection: "postgresql://....",
|
||||
// ... and the pg-promise initialization options
|
||||
pgpInit: {...}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Reference the [pg-promise initialization options](http://vitaly-t.github.io/pg-promise/module-pg-promise.html)
|
||||
|
||||
Options consist of:
|
||||
|
||||
- `connection` - pg-promise/pg connection string or object
|
||||
- `pgpInit` - the pg-promise initialization options (mutually exclusive with `pgp`)
|
||||
- `pgp` - an existing pre-initialized pg-promise instance (mutually exclusive with `pgpInit`)
|
||||
- `logSql` - a boolean that when true, causes pgp monitor to print all sql statements. !! WARNING !! setting to true could cause data leaks
|
||||
- `decorateAs` - an object containing..
|
||||
- `pgp` - a string. the plugin will decorate server and request with the pgp instance under this key (default: pgp)
|
||||
- `db` - a string. the plugin will decorate server and request with the db instance under this key (default: db)
|
||||
- if either of these is falsey, then the plugin will not decorate
|
||||
|
||||
Defaults are defined in [`src/index.ts`](src/index.ts)
|
||||
|
||||
## Credits
|
||||
|
||||
Copyright © 2020-present [Center for Digital Resilience][cdr]
|
||||
|
||||
### Contributors
|
||||
|
||||
| [![Abel Luck][abelxluck_avatar]][abelxluck_homepage]<br/>[Abel Luck][abelxluck_homepage] |
|
||||
| ---------------------------------------------------------------------------------------- |
|
||||
|
||||
|
||||
[abelxluck_homepage]: https://gitlab.com/abelxluck
|
||||
[abelxluck_avatar]: https://secure.gravatar.com/avatar/0f605397e0ead93a68e1be26dc26481a?s=100&d=identicon
|
||||
|
||||
### License
|
||||
|
||||
[](https://www.gnu.org/licenses/agpl-3.0.en.html)
|
||||
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
[cdrtech]: https://digiresilience.org/tech/
|
||||
[cdr]: https://digiresilience.org
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"presets": [
|
||||
"babel-preset-link"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"preset": "jest-config-link"
|
||||
}
|
||||
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
"name": "@digiresilience/hapi-pg-promise",
|
||||
"version": "1.0.0",
|
||||
"description": "a hapi.js plugin for pg-promise",
|
||||
"main": "build/main/index.js",
|
||||
"type": "module",
|
||||
"author": "Abel Luck <abel@guardianproject.info>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"private": false,
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.12",
|
||||
"tsc-watch": "^6.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hapi/hapi": "^21.3.3",
|
||||
"pg-monitor": "^2.0.0",
|
||||
"pg-promise": "^11.5.4"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"fix:lint": "eslint src --ext .ts --fix",
|
||||
"fmt": "prettier \"src/**/*.ts\" --write",
|
||||
"test": "jest --coverage --forceExit --detectOpenHandles --reporters=default --reporters=jest-junit",
|
||||
"lint": "eslint src --ext .ts && prettier \"src/**/*.ts\" --list-different",
|
||||
"doc": "typedoc src/ --exclude '**/*.test.ts' --exclude '**/*.spec.ts' --name $npm_package_name --readme README.md --target es2019 --mode file --out build/docs",
|
||||
"dev": "tsc-watch --build --noClear"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
import * as Hapi from "@hapi/hapi";
|
||||
import { makePlugin } from ".";
|
||||
|
||||
const plugin = makePlugin();
|
||||
|
||||
describe("plugin option validation", () => {
|
||||
let server;
|
||||
beforeEach(async () => {
|
||||
server = new Hapi.Server();
|
||||
});
|
||||
|
||||
it("should throw when no connection details defined", async () => {
|
||||
expect(server.register(plugin)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
const defaultOpts = {
|
||||
connection: "postgresql://amigo:amigo@127.0.0.1:5432/postgres",
|
||||
};
|
||||
describe("basic plugin runtime", () => {
|
||||
let server;
|
||||
beforeEach(async () => {
|
||||
server = new Hapi.Server({ port: 0 });
|
||||
await server.register({
|
||||
plugin,
|
||||
options: defaultOpts,
|
||||
});
|
||||
await server.start();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it("should decorate db and pgp into server and request", async () => {
|
||||
expect.assertions(5);
|
||||
server.route({
|
||||
method: "GET",
|
||||
path: "/",
|
||||
handler(req) {
|
||||
expect(req.db).toBeInstanceOf(Function);
|
||||
expect(req.server.db).toBeInstanceOf(Function);
|
||||
expect(req.pgp).toBeInstanceOf(Function);
|
||||
expect(req.server.pgp).toBeInstanceOf(Function);
|
||||
return "OK";
|
||||
},
|
||||
});
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: "get",
|
||||
url: "/",
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe("plugin runtime", () => {
|
||||
let server;
|
||||
beforeEach(async () => {
|
||||
server = new Hapi.Server({ port: 0 });
|
||||
});
|
||||
afterEach(async () => {
|
||||
await server.stop();
|
||||
});
|
||||
it("should decorate db and pgp into server and request with custom name", async () => {
|
||||
expect.assertions(5);
|
||||
|
||||
await server.register({
|
||||
plugin,
|
||||
options: {
|
||||
...defaultOpts,
|
||||
logSql: true,
|
||||
decorateAs: {
|
||||
pgp: "foobar",
|
||||
db: "poprocks",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await server.start();
|
||||
|
||||
await server.route({
|
||||
method: "GET",
|
||||
path: "/",
|
||||
handler(req) {
|
||||
expect(req.poprocks).toBeInstanceOf(Function);
|
||||
expect(req.server.poprocks).toBeInstanceOf(Function);
|
||||
expect(req.foobar).toBeInstanceOf(Function);
|
||||
expect(req.server.foobar).toBeInstanceOf(Function);
|
||||
return "OK";
|
||||
},
|
||||
});
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: "get",
|
||||
url: "/",
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
import * as Hapi from "@hapi/hapi";
|
||||
import pgPromise from "pg-promise";
|
||||
import pgMonitor from "pg-monitor";
|
||||
import type { IConnectionParameters } from "pg-promise/typescript/pg-subset";
|
||||
import type { IMain, IInitOptions } from "pg-promise";
|
||||
import { IPGPPluginOptions, ExtendedProtocol } from "./types.js";
|
||||
import { Plugin } from "@hapi/hapi/lib/types/plugin";
|
||||
|
||||
export * from "./types.js";
|
||||
|
||||
export const startDiagnostics = <T>(
|
||||
logSql: boolean,
|
||||
initOpts: IInitOptions<T>
|
||||
): void => {
|
||||
if (logSql) {
|
||||
pgMonitor.attach(initOpts);
|
||||
} else {
|
||||
pgMonitor.attach(initOpts, ["error"]);
|
||||
}
|
||||
};
|
||||
|
||||
export const stopDiagnostics = (): void => pgMonitor.detach();
|
||||
|
||||
const startPgp = async <T>(initOptions: IInitOptions<T>): Promise<IMain> => {
|
||||
const pgp: IMain = pgPromise(initOptions);
|
||||
return pgp;
|
||||
};
|
||||
|
||||
const startDb = async <T>(
|
||||
pgp,
|
||||
connection: string | IConnectionParameters
|
||||
): Promise<ExtendedProtocol<T>> => {
|
||||
const db: ExtendedProtocol<T> = pgp(connection);
|
||||
|
||||
return db;
|
||||
};
|
||||
|
||||
export function makePlugin<T>(): Plugin<IPGPPluginOptions<T>, void> {
|
||||
return {
|
||||
version: "1.0.0",
|
||||
name: "pg-promise",
|
||||
async register(
|
||||
server: Hapi.Server,
|
||||
userOpts?: IPGPPluginOptions<T>
|
||||
): Promise<void> {
|
||||
if (userOpts.logSql === undefined) userOpts.logSql = false;
|
||||
if (!userOpts.decorateAs) userOpts.decorateAs = { pgp: "pgp", db: "db" };
|
||||
|
||||
const options = userOpts;
|
||||
|
||||
if (!options.connection) {
|
||||
throw new Error(
|
||||
"hapi-pg-promise: connection details are not defined. You must specify a valid connection for the plugin to boot."
|
||||
);
|
||||
}
|
||||
|
||||
if ("pgp" in options && "pgpInit" in options) {
|
||||
throw new Error(
|
||||
"hapi-pg-promise: options pgp and pgpInit are mutually exclusive"
|
||||
);
|
||||
}
|
||||
|
||||
let pgp: IMain;
|
||||
if ("pgp" in options) {
|
||||
pgp = options.pgp;
|
||||
} else {
|
||||
pgp = await startPgp(options.pgpInit || {});
|
||||
startDiagnostics(options.logSql, options.pgpInit || {});
|
||||
}
|
||||
|
||||
const db = await startDb(pgp, options.connection);
|
||||
|
||||
if (options.decorateAs) {
|
||||
if (options.decorateAs) {
|
||||
server.decorate("request", options.decorateAs.pgp, pgp);
|
||||
server.decorate("server", options.decorateAs.pgp, pgp);
|
||||
}
|
||||
|
||||
if (options.decorateAs.db) {
|
||||
server.decorate("server", options.decorateAs.db, () => db);
|
||||
server.decorate("request", options.decorateAs.db, () => db);
|
||||
}
|
||||
}
|
||||
|
||||
server.ext("onPostStop", async () => {
|
||||
stopDiagnostics();
|
||||
await db.$pool.end();
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import type { IMain, IInitOptions, IDatabase } from "pg-promise";
|
||||
import type { IConnectionParameters } from "pg-promise/typescript/pg-subset";
|
||||
|
||||
export type ExtendedProtocol<T> = IDatabase<T> & T;
|
||||
|
||||
export type IPGPPluginOptions<T> = {
|
||||
connection: string | IConnectionParameters;
|
||||
logSql?: boolean;
|
||||
decorateAs?: {
|
||||
pgp: string;
|
||||
db: string;
|
||||
};
|
||||
} & ({ pgpInit: IInitOptions<T> } | { pgp: IMain<T> });
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"extends": "tsconfig-link",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"incremental": true,
|
||||
"outDir": "build/main",
|
||||
"rootDir": "src",
|
||||
"baseUrl": "./",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules/**"]
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "jest-config-link",
|
||||
"name": "jest-config",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"author": "Abel Luck <abel@guardianproject.info>",
|
||||
|
|
@ -14,4 +14,4 @@
|
|||
"jest-junit": "^16.0.0"
|
||||
},
|
||||
"peerDependencies": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,22 +9,22 @@
|
|||
"@emotion/react": "^11.11.4",
|
||||
"@emotion/server": "^11.11.0",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@fontsource/playfair-display": "^5.0.21",
|
||||
"@fontsource/playfair-display": "^5.0.23",
|
||||
"@fontsource/poppins": "^5.0.12",
|
||||
"@fontsource/roboto": "^5.0.12",
|
||||
"@mui/icons-material": "^5",
|
||||
"@mui/lab": "^5.0.0-alpha.167",
|
||||
"@mui/lab": "^5.0.0-alpha.168",
|
||||
"@mui/material": "^5",
|
||||
"@mui/x-data-grid-pro": "^6.19.6",
|
||||
"@mui/x-date-pickers-pro": "^6.19.6",
|
||||
"@opensearch-project/opensearch": "^2.5.0",
|
||||
"date-fns": "^3.3.1",
|
||||
"@mui/x-date-pickers-pro": "^6.19.7",
|
||||
"@opensearch-project/opensearch": "^2.6.0",
|
||||
"date-fns": "^3.5.0",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"material-ui-popup-state": "^5.0.10",
|
||||
"next": "14.1.2",
|
||||
"next-auth": "^4.24.6",
|
||||
"next": "14.1.3",
|
||||
"next-auth": "^4.24.7",
|
||||
"next-http-proxy-middleware": "^1.2.6",
|
||||
"nodemailer": "^6.9.11",
|
||||
"nodemailer": "^6.9.12",
|
||||
"react": "18.2.0",
|
||||
"react-cookie": "^7.1.0",
|
||||
"react-cookie-consent": "^9.0.0",
|
||||
|
|
@ -39,19 +39,19 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.0",
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/react": "18.2.63",
|
||||
"@types/node": "^20.11.28",
|
||||
"@types/react": "18.2.66",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"babel-loader": "^9.1.3",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-next": "^14.1.2",
|
||||
"eslint-config-next": "^14.1.3",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-react": "^7.34.0",
|
||||
"eslint-plugin-react": "^7.34.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"typescript": "5.3.3"
|
||||
"typescript": "5.4.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -18,20 +18,20 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/figlet": "^1.5.8",
|
||||
"@types/lodash": "^4.14.202",
|
||||
"@types/lodash": "^4.17.0",
|
||||
"@types/node": "*",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"camelcase-keys": "^9.1.3",
|
||||
"pg-monitor": "^2.0.0",
|
||||
"tsc-watch": "^6.0.4",
|
||||
"typedoc": "^0.25.11",
|
||||
"typescript": "^5.3.3"
|
||||
"typedoc": "^0.25.12",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@digiresilience/hapi-nextauth": "*",
|
||||
"@hapi/boom": "^10.0.1",
|
||||
"@hapi/glue": "^9.0.1",
|
||||
"@hapi/hapi": "^21.3.3",
|
||||
"@hapi/hapi": "^21.3.6",
|
||||
"@hapi/hoek": "^11.0.4",
|
||||
"@hapi/inert": "^7.1.0",
|
||||
"@hapi/vision": "^7.0.3",
|
||||
|
|
@ -54,7 +54,7 @@
|
|||
"http-terminator": "^3.2.0",
|
||||
"joi": "^17.12.2",
|
||||
"lodash": "^4.17.21",
|
||||
"next-auth": "^4.24.6",
|
||||
"next-auth": "^4.24.7",
|
||||
"pg-promise": "^11.5.4",
|
||||
"pino": "^8.19.0",
|
||||
"pino-pretty": "^10.3.1",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"extends": "tsconfig-link",
|
||||
"extends": "tsconfig",
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"outDir": "build/main",
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
require('eslint-config-link/patch/modern-module-resolution');
|
||||
module.exports = {
|
||||
extends: [
|
||||
"eslint-config-link/profile/node",
|
||||
"eslint-config-link/profile/typescript"
|
||||
],
|
||||
parserOptions: { tsconfigRootDir: __dirname },
|
||||
rules: {
|
||||
"import/no-extraneous-dependencies": [
|
||||
// enable this when this is fixed
|
||||
// https://github.com/benmosher/eslint-plugin-import/pull/1696
|
||||
"off",
|
||||
{ packageDir: [".", "node_modules/@digiresilience/metamigo", "node_modules/@digiresilience/metamigo-dev"] },
|
||||
],
|
||||
// TODO: enable this after jest fixes this issue https://github.com/nodejs/node/issues/38343
|
||||
"unicorn/prefer-node-protocol": "off"
|
||||
}
|
||||
};
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
{
|
||||
"name": "@digiresilience/metamigo-config",
|
||||
"version": "0.2.0",
|
||||
"main": "build/main/index.js",
|
||||
"type": "module",
|
||||
"author": "Abel Luck <abel@guardianproject.info>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@digiresilience/metamigo-common": "*",
|
||||
"@digiresilience/montar": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.24.0",
|
||||
"@babel/preset-env": "7.24.0",
|
||||
"@babel/preset-typescript": "7.23.3",
|
||||
"eslint": "^8.57.0",
|
||||
"pino-pretty": "^10.3.1",
|
||||
"prettier": "^3.2.5",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsc-watch": "^6.0.4",
|
||||
"typedoc": "^0.25.11",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"files": [
|
||||
"build",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"doc": "typedoc src/ --exclude '**/*.test.ts' --exclude '**/*.spec.ts' --name $npm_package_name --readme README.md --target es2019 --mode file --out build/docs",
|
||||
"fix:lint": "eslint src --ext .ts --fix",
|
||||
"fmt": "prettier \"src/**/*.ts\" --write",
|
||||
"lint": "eslint src --ext .ts && prettier \"src/**/*.ts\" --list-different",
|
||||
"test": "echo no tests",
|
||||
"dev": "tsc-watch --build --noClear"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,453 +0,0 @@
|
|||
import * as process from "process";
|
||||
import * as convict from "convict";
|
||||
import * as Metamigo from "@digiresilience/metamigo-common";
|
||||
import { defState } from "@digiresilience/montar";
|
||||
|
||||
export const configSchema = {
|
||||
db: {
|
||||
connection: {
|
||||
doc: "The postgres connection url.",
|
||||
format: "uri",
|
||||
default: "postgresql://metamigo:metamigo@127.0.0.1:5433/metamigo_dev",
|
||||
env: "METAMIGO_DATABASE_URL",
|
||||
sensitive: true,
|
||||
},
|
||||
name: {
|
||||
doc: "The name of the postgres database",
|
||||
format: String,
|
||||
default: "metamigo_dev",
|
||||
env: "METAMIGO_DATABASE_NAME",
|
||||
},
|
||||
owner: {
|
||||
doc: "The username of the postgres database owner",
|
||||
format: String,
|
||||
default: "metamigo",
|
||||
env: "METAMIGO_DATABASE_OWNER",
|
||||
},
|
||||
},
|
||||
worker: {
|
||||
connection: {
|
||||
doc: "The postgres connection url for the worker database.",
|
||||
format: "uri",
|
||||
default: "postgresql://metamigo:metamigo@127.0.0.1:5433/metamigo_dev",
|
||||
env: "METAMIGO_WORKER_DATABASE_URL",
|
||||
},
|
||||
concurrency: {
|
||||
doc: "The number of jobs to run concurrently",
|
||||
default: 1,
|
||||
format: "positiveInt",
|
||||
env: "METAMIGO_WORKER_CONCURRENT_JOBS",
|
||||
},
|
||||
pollInterval: {
|
||||
doc: "How long to wait between polling for jobs in milliseconds (for jobs scheduled in the future/retries)",
|
||||
default: 2000,
|
||||
format: "positiveInt",
|
||||
env: "METAMIGO_WORKER_POLL_INTERVAL_MS",
|
||||
},
|
||||
},
|
||||
postgraphile: {
|
||||
auth: {
|
||||
doc: "The postgres role that postgraphile logs in with",
|
||||
format: String,
|
||||
default: "metamigo_graphile_auth",
|
||||
env: "METAMIGO_DATABASE_AUTHENTICATOR",
|
||||
},
|
||||
appRootConnection: {
|
||||
doc: "The postgres root/superuser connection url for development mode so PG can watch the schema changes, this is strangely named in the postgraphile API 'ownerConnectionString'",
|
||||
format: String,
|
||||
default: "postgresql://postgres:metamigo@127.0.0.1:5433/metamigo_dev",
|
||||
env: "METAMIGO_APP_ROOT_DATABASE_URL",
|
||||
},
|
||||
authConnection: {
|
||||
doc: "The postgres connection URL for postgraphile, must not be superuser and must have limited privs.",
|
||||
format: String,
|
||||
default:
|
||||
"postgresql://metamigo_graphile_auth:metamigo@127.0.0.1:5433/metamigo_dev",
|
||||
env: "METAMIGO_DATABASE_AUTH_URL",
|
||||
},
|
||||
visitor: {
|
||||
doc: "The postgres role that postgraphile switches to",
|
||||
format: String,
|
||||
default: "app_postgraphile",
|
||||
env: "METAMIGO_DATABASE_VISITOR",
|
||||
},
|
||||
schema: {
|
||||
doc: "The schema postgraphile should expose with graphql",
|
||||
format: String,
|
||||
default: "app_public",
|
||||
},
|
||||
enableGraphiql: {
|
||||
doc: "Whether to enable the graphiql web interface or not",
|
||||
format: "Boolean",
|
||||
default: false,
|
||||
env: "METAMIGO_ENABLE_GRAPHIQL",
|
||||
},
|
||||
},
|
||||
|
||||
dev: {
|
||||
shadowConnection: {
|
||||
doc: "The shadow databse connection url used by postgraphile-migrate. Not needed in production.",
|
||||
format: "uri",
|
||||
default: "postgresql://metamigo:metamigo@127.0.0.1:5433/metamigo_shadow",
|
||||
env: "METAMIGO_SHADOW_DATABASE_URL",
|
||||
sensitive: true,
|
||||
},
|
||||
rootConnection: {
|
||||
doc: "The postgres root/superuser connection url for testing only, database must NOT be the app database. Not needed in production.",
|
||||
format: "uri",
|
||||
default: "postgresql://postgres:metamigo@127.0.0.1:5433/template1",
|
||||
env: "METAMIGO_ROOT_DATABASE_URL",
|
||||
sensitive: true,
|
||||
},
|
||||
},
|
||||
frontend: {
|
||||
url: {
|
||||
doc: "The url the frontend can be accessed at",
|
||||
format: "url",
|
||||
default: "http://localhost:3000",
|
||||
env: "METAMIGO_FRONTEND_URL",
|
||||
},
|
||||
apiUrl: {
|
||||
doc: "The url the api backend can be accessed at from the frontend server",
|
||||
format: "url",
|
||||
default: "http://localhost:3001",
|
||||
env: "METAMIGO_API_URL",
|
||||
},
|
||||
},
|
||||
nextAuth: {
|
||||
secret: {
|
||||
doc: "A random string used to hash tokens, sign cookies and generate crytographic keys. Shared with the api backend.",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "NEXTAUTH_SECRET",
|
||||
sensitive: true,
|
||||
},
|
||||
audience: {
|
||||
doc: "We will add this string as the `aud` claim to our JWT token, if empty or not present defaults to `frontend.url`",
|
||||
format: String,
|
||||
default: "",
|
||||
env: "NEXTAUTH_AUDIENCE",
|
||||
},
|
||||
signingKeyB64: {
|
||||
doc: "A base64 encoded JWK.Key used for JWT signing",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "NEXTAUTH_SIGNING_KEY_B64",
|
||||
sensitive: true,
|
||||
},
|
||||
encryptionKeyB64: {
|
||||
doc: "A base64 encoded JWK.Key used for JWT encryption",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "NEXTAUTH_ENCRYPTION_KEY_B64",
|
||||
sensitive: true,
|
||||
},
|
||||
signingKey: {
|
||||
doc: "",
|
||||
format: String,
|
||||
default: undefined,
|
||||
sensitive: true,
|
||||
skipGenerate: true,
|
||||
},
|
||||
encryptionKey: {
|
||||
doc: "",
|
||||
format: String,
|
||||
default: undefined,
|
||||
sensitive: true,
|
||||
skipGenerate: true,
|
||||
},
|
||||
google: {
|
||||
id: {
|
||||
doc: "reference https://next-auth.js.org/providers/google",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "GOOGLE_ID",
|
||||
sensitive: true,
|
||||
},
|
||||
secret: {
|
||||
doc: "reference https://next-auth.js.org/providers/google",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "GOOGLE_SECRET",
|
||||
sensitive: true,
|
||||
},
|
||||
},
|
||||
github: {
|
||||
id: {
|
||||
doc: "reference https://next-auth.js.org/providers/github",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "GITHUB_ID",
|
||||
sensitive: true,
|
||||
},
|
||||
secret: {
|
||||
doc: "reference https://next-auth.js.org/providers/github",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "GITHUB_SECRET",
|
||||
sensitive: true,
|
||||
},
|
||||
},
|
||||
gitlab: {
|
||||
id: {
|
||||
doc: "reference https://next-auth.js.org/providers/gitlab",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "GITLAB_ID",
|
||||
sensitive: true,
|
||||
},
|
||||
secret: {
|
||||
doc: "reference https://next-auth.js.org/providers/gitlab",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "GITLAB_SECRET",
|
||||
sensitive: true,
|
||||
},
|
||||
},
|
||||
cognito: {
|
||||
id: {
|
||||
doc: "reference https://next-auth.js.org/providers/cognito",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "COGNITO_ID",
|
||||
sensitive: true,
|
||||
},
|
||||
secret: {
|
||||
doc: "reference https://next-auth.js.org/providers/cognito",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "COGNITO_SECRET",
|
||||
sensitive: true,
|
||||
},
|
||||
domain: {
|
||||
doc: "reference https://next-auth.js.org/providers/cognito",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "COGNITO_DOMAIN",
|
||||
sensitive: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
cfaccess: {
|
||||
audience: {
|
||||
doc: "the cloudflare access audience id",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "CFACCESS_AUDIENCE",
|
||||
},
|
||||
|
||||
domain: {
|
||||
doc: "the cloudflare access domain, something like `YOURAPP.cloudflareaccess.com`",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "CFACCESS_DOMAIN",
|
||||
},
|
||||
},
|
||||
signald: {
|
||||
enabled: {
|
||||
doc: "Whether to enable the signald signal backend",
|
||||
format: "Boolean",
|
||||
default: false,
|
||||
env: "SIGNALD_ENABLED",
|
||||
},
|
||||
socket: {
|
||||
doc: "the unix domain socket signald is listening on",
|
||||
format: String,
|
||||
default: `${process.cwd()}/signald/signald.sock`,
|
||||
env: "SIGNALD_SOCKET",
|
||||
},
|
||||
},
|
||||
leafcutter: {
|
||||
enabled: {
|
||||
doc: "Whether to enable leafcutter functionality",
|
||||
format: "Boolean",
|
||||
default: false,
|
||||
env: "LEAFCUTTER_ENABLED",
|
||||
},
|
||||
zammadApiUrl: {
|
||||
doc: "The full base zammad api url",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "ZAMMAD_API_URL",
|
||||
},
|
||||
zammadApiKey: {
|
||||
doc: "The zammad api key",
|
||||
format: String,
|
||||
default: undefined,
|
||||
sensitive: true,
|
||||
env: "ZAMMAD_API_KEY",
|
||||
},
|
||||
labelStudioApiUrl: {
|
||||
doc: "The full base label studio api url",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "LABEL_STUDIO_API_URL",
|
||||
},
|
||||
labelStudioApiKey: {
|
||||
doc: "The label studio api key",
|
||||
format: String,
|
||||
default: undefined,
|
||||
sensitive: true,
|
||||
env: "LABEL_STUDIO_API_KEY",
|
||||
},
|
||||
contributorId: {
|
||||
doc: "The leafcutter contributor id",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "LEAFCUTTER_CONTRIBUTOR_ID",
|
||||
},
|
||||
contributorName: {
|
||||
doc: "The leafcutter contributor name",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "LEAFCUTTER_CONTRIBUTOR_NAME",
|
||||
},
|
||||
opensearchApiUrl: {
|
||||
doc: "The opensearch api url",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "OPENSEARCH_API_URL",
|
||||
},
|
||||
opensearchUsername: {
|
||||
doc: "The opensearch username",
|
||||
format: String,
|
||||
default: undefined,
|
||||
env: "OPENSEARCH_USERNAME",
|
||||
},
|
||||
opensearchPassword: {
|
||||
doc: "The opensearch password",
|
||||
format: String,
|
||||
default: undefined,
|
||||
sensative: true,
|
||||
env: "OPENSEARCH_PASSWORD",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// define the interfaces for the concrete config objects
|
||||
export interface IDBConfig {
|
||||
connection: string;
|
||||
name: string;
|
||||
owner: string;
|
||||
}
|
||||
|
||||
export interface IWorkerConfig {
|
||||
connection: string;
|
||||
concurrency: number;
|
||||
pollInterval: number;
|
||||
}
|
||||
|
||||
export interface IPostgraphileConfig {
|
||||
auth: string;
|
||||
visitor: string;
|
||||
appRootConnection: string;
|
||||
authConnection: string;
|
||||
schema: string;
|
||||
enableGraphiql: boolean;
|
||||
}
|
||||
|
||||
export interface IDevConfig {
|
||||
shadowConnection: string;
|
||||
rootConnection: string;
|
||||
}
|
||||
|
||||
export interface IFrontendConfig {
|
||||
url: string;
|
||||
apiUrl: string;
|
||||
}
|
||||
|
||||
export interface INextAuthConfig {
|
||||
secret: string;
|
||||
audience: string;
|
||||
signingKey: string;
|
||||
encryptionKey: string;
|
||||
signingKeyB64: string;
|
||||
encryptionKeyB64: string;
|
||||
google?: { id: string; secret: string; };
|
||||
github?: { id: string; secret: string; };
|
||||
gitlab?: { id: string; secret: string; };
|
||||
cognito?: { id: string; secret: string; domain: string; };
|
||||
}
|
||||
|
||||
export interface ICFAccessConfig {
|
||||
audience: string;
|
||||
domain: string;
|
||||
}
|
||||
|
||||
export interface ISignaldConifg {
|
||||
enabled: boolean;
|
||||
socket: string;
|
||||
}
|
||||
|
||||
export interface ILeafcutterConfig {
|
||||
enabled: boolean;
|
||||
zammadApiUrl: string;
|
||||
zammadApiKey: string;
|
||||
labelStudioApiUrl: string;
|
||||
labelStudioApiKey: string;
|
||||
contributorId: string;
|
||||
contributorName: string;
|
||||
opensearchApiUrl: string;
|
||||
opensearchUsername: string;
|
||||
opensearchPassword: string;
|
||||
}
|
||||
|
||||
// Extend the metamigo base type to add your app's custom config along side the out
|
||||
// of the box Metamigo config
|
||||
export interface IAppConfig extends Metamigo.IMetamigoConfig {
|
||||
db: IDBConfig;
|
||||
worker: IWorkerConfig;
|
||||
postgraphile: IPostgraphileConfig;
|
||||
dev: IDevConfig;
|
||||
frontend: IFrontendConfig;
|
||||
nextAuth: INextAuthConfig;
|
||||
cfaccess: ICFAccessConfig;
|
||||
signald: ISignaldConifg;
|
||||
leafcutter: ILeafcutterConfig;
|
||||
}
|
||||
|
||||
export type IAppConvict = Metamigo.ExtendedConvict<IAppConfig>;
|
||||
|
||||
// Merge the Metamigo base schema with your app's schmea
|
||||
export const schema: convict.Schema<IAppConfig> = {
|
||||
...Metamigo.configBaseSchema,
|
||||
...configSchema,
|
||||
};
|
||||
|
||||
export const loadConfig = async (): Promise<IAppConfig> => {
|
||||
const config = await Metamigo.loadConfiguration(schema);
|
||||
|
||||
if (!config.frontend.url || config.frontend.url === "")
|
||||
throw new Error(
|
||||
"configuration value frontend.url is missing. Add to config or set NEXTAUTH_URL env var"
|
||||
);
|
||||
|
||||
// nextauth expects the url to be provided with this environment variable, so we will munge it in place here
|
||||
process.env.NEXTAUTH_URL = config.frontend.url;
|
||||
|
||||
if (config.nextAuth.signingKeyB64)
|
||||
config.nextAuth.signingKey = Buffer.from(
|
||||
config.nextAuth.signingKeyB64,
|
||||
"base64"
|
||||
).toString("utf-8");
|
||||
|
||||
if (config.nextAuth.encryptionKeyB64)
|
||||
config.nextAuth.encryptionKey = Buffer.from(
|
||||
config.nextAuth.encryptionKeyB64,
|
||||
"base64"
|
||||
).toString("utf-8");
|
||||
|
||||
if (!config.nextAuth.audience || config.nextAuth.audience === "")
|
||||
config.nextAuth.audience = config.frontend.url;
|
||||
|
||||
return config as IAppConfig;
|
||||
};
|
||||
|
||||
export const loadConfigRaw = async (): Promise<IAppConvict> =>
|
||||
Metamigo.loadConfigurationRaw(schema);
|
||||
|
||||
const config = defState("config", {
|
||||
start: loadConfig,
|
||||
});
|
||||
|
||||
export default config;
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"extends": "tsconfig-link",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"incremental": true,
|
||||
"outDir": "build/main",
|
||||
"rootDir": "src",
|
||||
"baseUrl": "./",
|
||||
"skipLibCheck": true,
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules/**"]
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
require('eslint-config-link/patch/modern-module-resolution');
|
||||
module.exports = {
|
||||
extends: [
|
||||
"eslint-config-link/profile/node",
|
||||
"eslint-config-link/profile/typescript"
|
||||
],
|
||||
parserOptions: { tsconfigRootDir: __dirname }
|
||||
};
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
/*
|
||||
* Graphile Migrate configuration.
|
||||
*
|
||||
* MUST NOT CONTAIN SECRETS/PASSWORDS
|
||||
|
||||
* This file is in JSON5 format.
|
||||
*/
|
||||
|
||||
{
|
||||
/*
|
||||
* Database connections strings are sourced from the DATABASE_URL,
|
||||
* SHADOW_DATABASE_URL and ROOT_DATABASE_URL environmental variables.
|
||||
*/
|
||||
|
||||
/*
|
||||
* pgSettings: key-value settings to be automatically loaded into PostgreSQL
|
||||
* before running migrations, using an equivalent of `SET LOCAL <key> TO
|
||||
* <value>`
|
||||
*/
|
||||
"pgSettings": {
|
||||
"search_path": "public",
|
||||
},
|
||||
|
||||
/*
|
||||
* placeholders: substituted in SQL files when compiled/executed. Placeholder
|
||||
* keys should be prefixed with a colon and in all caps, like
|
||||
* `:COLON_PREFIXED_ALL_CAPS`. Placeholder values should be strings. They
|
||||
* will be replaced verbatim with NO ESCAPING AT ALL (this differs from how
|
||||
* psql handles placeholders) so should only be used with "safe" values. This
|
||||
* is useful for committing migrations where certain parameters can change
|
||||
* between environments (development, staging, production) but you wish to
|
||||
* use the same signed migration files for all.
|
||||
*
|
||||
* The special value "!ENV" can be used to indicate an environmental variable
|
||||
* of the same name should be used.
|
||||
*
|
||||
* Graphile Migrate automatically sets the `:DATABASE_NAME` and
|
||||
* `:DATABASE_OWNER` placeholders, and you should not attempt to override
|
||||
* these.
|
||||
*/
|
||||
"placeholders": {
|
||||
":DATABASE_VISITOR": "!ENV",
|
||||
":DATABASE_AUTHENTICATOR": "!ENV",
|
||||
},
|
||||
|
||||
/*
|
||||
* Actions allow you to run scripts or commands at certain points in the
|
||||
* migration lifecycle. SQL files are ran against the database directly.
|
||||
* "command" actions are ran with the following environmental variables set:
|
||||
*
|
||||
* - GM_DBURL: the PostgreSQL URL of the database being migrated
|
||||
* - GM_DBNAME: the name of the database from GM_DBURL
|
||||
* - GM_DBUSER: the user from GM_DBURL
|
||||
* - GM_SHADOW: set to 1 if the shadow database is being migrated, left unset
|
||||
* otherwise
|
||||
*
|
||||
* If "shadow" is unspecified, the actions will run on events to both shadow
|
||||
* and normal databases. If "shadow" is true the action will only run on
|
||||
* actions to the shadow DB, and if false only on actions to the main DB.
|
||||
*/
|
||||
|
||||
/*
|
||||
* afterReset: actions executed after a `graphile-migrate reset` command.
|
||||
*/
|
||||
|
||||
"afterReset": [
|
||||
"!../scripts/afterReset.sql",
|
||||
],
|
||||
|
||||
/*
|
||||
* afterAllMigrations: actions executed once all migrations are complete.
|
||||
*/
|
||||
"afterAllMigrations": [
|
||||
{
|
||||
"_": "command",
|
||||
"shadow": true,
|
||||
"command": "node scripts/dump-db.js"
|
||||
},
|
||||
],
|
||||
|
||||
/*
|
||||
* afterCurrent: actions executed once the current migration has been
|
||||
* evaluated (i.e. in watch mode).
|
||||
*/
|
||||
"afterCurrent": [
|
||||
{
|
||||
"_": "command",
|
||||
"command": "./scripts/afterCurrent.sh",
|
||||
}
|
||||
],
|
||||
|
||||
/*
|
||||
* blankMigrationContent: content to be written to the current migration
|
||||
* after commit. NOTE: this should only contain comments.
|
||||
*/
|
||||
// "blankMigrationContent": "-- Write your migration here\n",
|
||||
|
||||
/****************************************************************************\
|
||||
*** ***
|
||||
*** You probably don't want to edit anything below here. ***
|
||||
*** ***
|
||||
\****************************************************************************/
|
||||
|
||||
/*
|
||||
* manageGraphileMigrateSchema: if you set this false, you must be sure to
|
||||
* keep the graphile_migrate schema up to date yourself. We recommend you
|
||||
* leave it at its default.
|
||||
*/
|
||||
// "manageGraphileMigrateSchema": true,
|
||||
|
||||
/*
|
||||
* migrationsFolder: path to the folder in which to store your migrations.
|
||||
*/
|
||||
// migrationsFolder: "./migrations",
|
||||
|
||||
"//generatedWith": "1.0.2"
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
FROM postgres:13
|
||||
COPY scripts/bootstrap.sh /docker-entrypoint-initdb.d/bootstrap.sh
|
||||
|
|
@ -1,650 +0,0 @@
|
|||
--! Previous: -
|
||||
--! Hash: sha1:b13a5217288f5d349d8d9e3afbd7bb30c0dbad21
|
||||
|
||||
-- region Bootstrap
|
||||
drop schema if exists app_public cascade;
|
||||
alter default privileges revoke all on sequences from public;
|
||||
alter default privileges revoke all on functions from public;
|
||||
|
||||
-- By default the public schema is owned by `postgres`; we need superuser privileges to change this :(
|
||||
-- alter schema public owner to waterbear;
|
||||
revoke all on schema public from public;
|
||||
grant all on schema public to :DATABASE_OWNER;
|
||||
|
||||
|
||||
create schema app_public;
|
||||
grant usage on schema
|
||||
public,
|
||||
app_public
|
||||
to
|
||||
:DATABASE_VISITOR,
|
||||
app_admin,
|
||||
app_anonymous,
|
||||
app_user;
|
||||
|
||||
/**********/
|
||||
|
||||
drop schema if exists app_hidden cascade;
|
||||
create schema app_hidden;
|
||||
grant usage on schema app_hidden to :DATABASE_VISITOR;
|
||||
|
||||
alter default privileges in schema app_hidden grant usage, select on sequences to :DATABASE_VISITOR;
|
||||
|
||||
/**********/
|
||||
|
||||
alter default privileges in schema public, app_public, app_hidden grant usage, select on sequences to :DATABASE_VISITOR;
|
||||
alter default privileges in schema public, app_public, app_hidden
|
||||
grant execute on functions to
|
||||
:DATABASE_VISITOR,
|
||||
app_admin,
|
||||
app_user;
|
||||
|
||||
/**********/
|
||||
|
||||
drop schema if exists app_private cascade;
|
||||
create schema app_private;
|
||||
|
||||
|
||||
-- endregion
|
||||
-- region UtilFunctions
|
||||
create function app_private.tg__add_job() returns trigger as
|
||||
$$
|
||||
begin
|
||||
perform graphile_worker.add_job(tg_argv[0], json_build_object('id', NEW.id),
|
||||
coalesce(tg_argv[1], public.gen_random_uuid()::text));
|
||||
return NEW;
|
||||
end;
|
||||
$$ language plpgsql volatile
|
||||
security definer
|
||||
set search_path to pg_catalog, public, pg_temp;
|
||||
comment on function app_private.tg__add_job() is
|
||||
E'Useful shortcut to create a job on insert/update. Pass the task name as the first trigger argument, and optionally the queue name as the second argument. The record id will automatically be available on the JSON payload.';
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
create function app_private.tg__timestamps() returns trigger as
|
||||
$$
|
||||
begin
|
||||
NEW.created_at = (case when TG_OP = 'INSERT' then NOW() else OLD.created_at end);
|
||||
NEW.updated_at = (case
|
||||
when TG_OP = 'UPDATE' and OLD.updated_at >= NOW()
|
||||
then OLD.updated_at + interval '1 millisecond'
|
||||
else NOW() end);
|
||||
return NEW;
|
||||
end;
|
||||
$$ language plpgsql volatile
|
||||
set search_path to pg_catalog, public, pg_temp;
|
||||
comment on function app_private.tg__timestamps() is
|
||||
E'This trigger should be called on all tables with created_at, updated_at - it ensures that they cannot be manipulated and that updated_at will always be larger than the previous updated_at.';
|
||||
|
||||
-- endregion
|
||||
|
||||
-- region Users, Sessions, and Accounts
|
||||
/* ------------------------------------------------------------------ */
|
||||
create table app_private.sessions
|
||||
(
|
||||
id uuid not null default gen_random_uuid() primary key,
|
||||
user_id uuid not null,
|
||||
expires timestamptz not null,
|
||||
session_token text not null,
|
||||
access_token text not null,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
last_active_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create unique index session_token on app_private.sessions(session_token);
|
||||
create unique index access_token on app_private.sessions(access_token);
|
||||
|
||||
alter table app_private.sessions
|
||||
enable row level security;
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
create function app_public.current_session_id() returns uuid as
|
||||
$$
|
||||
-- note the jwt.claims.session_id doesn't mean you have to use jwt, it is just where this function will always look for the session id.
|
||||
select nullif(pg_catalog.current_setting('jwt.claims.session_id', true), '')::uuid;
|
||||
$$ language sql stable;
|
||||
comment on function app_public.current_session_id() is
|
||||
E'Handy method to get the current session ID.';
|
||||
|
||||
/*
|
||||
* A less secure but more performant version of this function would be just:
|
||||
*
|
||||
* select nullif(pg_catalog.current_setting('jwt.claims.user_id', true), '')::int;
|
||||
*
|
||||
* The increased security of this implementation is because even if someone gets
|
||||
* the ability to run SQL within this transaction they cannot impersonate
|
||||
* another user without knowing their session_id (which should be closely
|
||||
* guarded).
|
||||
*/
|
||||
create function app_public.current_user_id() returns uuid as
|
||||
$$
|
||||
select user_id
|
||||
from app_private.sessions
|
||||
where id = app_public.current_session_id();
|
||||
$$ language sql stable
|
||||
security definer
|
||||
set search_path to pg_catalog, public, pg_temp;
|
||||
comment on function app_public.current_user_id() is
|
||||
E'Handy method to get the current user ID for use in RLS policies, etc; in GraphQL, use `currentUser{id}` instead.';
|
||||
-- We've put this in public, but omitted it, because it's often useful for debugging auth issues.
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
-- These are the user roles for our application
|
||||
create type app_public.role_type as
|
||||
ENUM ('none','admin', 'user');
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
create table app_public.users
|
||||
(
|
||||
id uuid not null default uuid_generate_v1mc() primary key,
|
||||
email citext not null,
|
||||
email_verified timestamptz,
|
||||
name text not null,
|
||||
avatar text,
|
||||
user_role app_public.role_type not null default 'none',
|
||||
is_active boolean not null default false,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
created_by text not null,
|
||||
constraint users_email_validity check (email ~* '^[A-Za-z0-9._%-]+@[A-Za-z0-9.-]+[.][A-Za-z]+$'),
|
||||
constraint users_avatar_validity check (avatar ~ '^https?://[^/]+'),
|
||||
constraint users_email_unique unique (email)
|
||||
);
|
||||
comment on table app_public.users is
|
||||
E'A user who can log in to the application.';
|
||||
comment on column app_public.users.id is
|
||||
E'Unique identifier for the user.';
|
||||
comment on column app_public.users.email is
|
||||
E'The email address of the user.';
|
||||
comment on column app_public.users.email_verified is
|
||||
E'The time at which the email address was verified';
|
||||
comment on column app_public.users.name is
|
||||
E'Public-facing name (or pseudonym) of the user.';
|
||||
comment on column app_public.users.avatar is
|
||||
E'Optional avatar URL.';
|
||||
comment on column app_public.users.user_role is
|
||||
E'The role that defines the user''s privileges.';
|
||||
comment on column app_public.users.is_active is
|
||||
E'If false, the user is not allowed to login or access the application';
|
||||
|
||||
alter table app_public.users
|
||||
enable row level security;
|
||||
|
||||
alter table app_private.sessions
|
||||
add constraint sessions_user_id_fkey foreign key ("user_id") references app_public.users on delete cascade;
|
||||
|
||||
create index on app_private.sessions (user_id);
|
||||
|
||||
-- app_public perms default
|
||||
create policy access_self on app_public.users to app_anonymous using (id = app_public.current_user_id());
|
||||
|
||||
--create policy update_self on app_public.users for update using (id = app_public.current_user_id());
|
||||
grant select on app_public.users to app_anonymous;
|
||||
grant update (name, avatar) on app_public.users to :DATABASE_VISITOR, app_user;
|
||||
|
||||
-- app_public perms for app_admin
|
||||
create policy access_all on app_public.users to app_admin using (true);
|
||||
grant update (email, name, avatar, is_active, user_role) on app_public.users to app_admin;
|
||||
grant select on app_public.users to app_admin;
|
||||
grant insert (email, name, avatar, user_role, is_active, created_by) on app_public.users to app_admin;
|
||||
grant update (email, name, avatar, user_role, is_active, created_by) on app_public.users to app_admin;
|
||||
|
||||
create trigger _100_timestamps
|
||||
before insert or update
|
||||
on app_public.users
|
||||
for each row
|
||||
execute procedure app_private.tg__timestamps();
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
create function app_public.current_user() returns app_public.users as
|
||||
$$
|
||||
select users.*
|
||||
from app_public.users
|
||||
where id = app_public.current_user_id();
|
||||
$$ language sql stable;
|
||||
comment on function app_public.current_user() is
|
||||
E'The currently logged in user (or null if not logged in).';
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
create function app_public.logout() returns void as
|
||||
$$
|
||||
begin
|
||||
-- Delete the session
|
||||
delete from app_private.sessions where id = app_public.current_session_id();
|
||||
-- Clear the identifier from the transaction
|
||||
perform set_config('jwt.claims.session_id', '', true);
|
||||
end;
|
||||
$$ language plpgsql security definer
|
||||
volatile
|
||||
set search_path to pg_catalog, public, pg_temp;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
create table app_public.accounts
|
||||
(
|
||||
id uuid not null default uuid_generate_v1mc() primary key,
|
||||
compound_id text not null,
|
||||
user_id uuid not null,
|
||||
provider_type text not null,
|
||||
provider_id text not null,
|
||||
provider_account_id text not null,
|
||||
refresh_token text,
|
||||
access_token text,
|
||||
access_token_expires timestamptz,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
alter table app_public.accounts
|
||||
enable row level security;
|
||||
|
||||
alter table app_public.accounts
|
||||
add constraint accounts_user_id_fkey foreign key ("user_id") references app_public.users on delete cascade;
|
||||
|
||||
create unique index accounts_compound_id on app_public.accounts(compound_id);
|
||||
create index accounts_provider_account_id on app_public.accounts(provider_account_id);
|
||||
create index accounts_provider_id on app_public.accounts(provider_id);
|
||||
create index accounts_user_id on app_public.accounts (user_id);
|
||||
|
||||
create policy access_self on app_public.accounts to app_anonymous using (user_id = app_public.current_user_id());
|
||||
|
||||
grant select on app_public.accounts to app_anonymous;
|
||||
grant update (compound_id, provider_type, provider_id, provider_account_id, refresh_token, access_token, access_token_expires) on app_public.accounts to app_user;
|
||||
|
||||
create policy access_all on app_public.accounts to app_admin using (true);
|
||||
grant update (compound_id, provider_type, provider_id, provider_account_id, refresh_token, access_token, access_token_expires) on app_public.accounts to app_admin;
|
||||
grant select on app_public.accounts to app_admin;
|
||||
grant insert (user_id, compound_id, provider_type, provider_id, provider_account_id, refresh_token, access_token, access_token_expires) on app_public.accounts to app_admin;
|
||||
grant update (compound_id, provider_type, provider_id, provider_account_id, refresh_token, access_token, access_token_expires) on app_public.accounts to app_admin;
|
||||
|
||||
create trigger _100_timestamps
|
||||
before insert or update
|
||||
on app_public.accounts
|
||||
for each row
|
||||
execute procedure app_private.tg__timestamps();
|
||||
|
||||
-- endregion
|
||||
|
||||
-- region Create first user function
|
||||
|
||||
create or replace function app_public.create_first_user (user_email text, user_name text)
|
||||
returns setof app_public.users
|
||||
as
|
||||
$$
|
||||
declare
|
||||
user_count int;
|
||||
begin
|
||||
|
||||
user_count := (select count(id) from app_public.users);
|
||||
|
||||
if (user_count != 0) then
|
||||
raise exception 'Admin user already created';
|
||||
end if;
|
||||
|
||||
return query insert into app_public.users (email, email_verified, name, user_role, is_active, created_by)
|
||||
values (user_email, now(), user_name, 'admin', true, 'first user hook') returning *;
|
||||
end ;
|
||||
$$ LANGUAGE plpgsql VOLATILE
|
||||
SECURITY DEFINER;
|
||||
|
||||
|
||||
comment on function app_public.create_first_user(user_email text, user_name text) is
|
||||
E'Creates the first user with an admin role. Only possible when there are no other users in the database.';
|
||||
|
||||
grant execute on function app_public.create_first_user(user_email text, user_name text) to app_anonymous;
|
||||
|
||||
create function app_private.tg__first_user() returns trigger as
|
||||
$$
|
||||
declare
|
||||
user_count int;
|
||||
begin
|
||||
user_count := (select count(id) from app_public.users);
|
||||
|
||||
if (user_count = 0) then
|
||||
NEW.user_role = 'admin';
|
||||
end if;
|
||||
return NEW;
|
||||
end;
|
||||
$$ language plpgsql volatile
|
||||
set search_path to pg_catalog, public, pg_temp;
|
||||
comment on function app_private.tg__first_user() is
|
||||
E'This trigger is called to ensure the first user created is an admin';
|
||||
|
||||
|
||||
create trigger _101_first_user
|
||||
before insert
|
||||
on app_public.users
|
||||
for each row
|
||||
execute procedure app_private.tg__first_user();
|
||||
-- endregion
|
||||
|
||||
-- region Settings
|
||||
|
||||
create table app_public.settings
|
||||
(
|
||||
id uuid not null default uuid_generate_v1mc() primary key,
|
||||
name text not null,
|
||||
value jsonb,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
|
||||
);
|
||||
|
||||
create unique index setting_name on app_public.settings(name);
|
||||
|
||||
alter table app_public.settings
|
||||
enable row level security;
|
||||
|
||||
create policy access_all on app_public.settings to app_admin using (true);
|
||||
grant update (name, value) on app_public.settings to app_admin;
|
||||
grant select on app_public.settings to app_admin;
|
||||
grant insert (name, value) on app_public.settings to app_admin;
|
||||
|
||||
create trigger _100_timestamps
|
||||
before insert or update
|
||||
on app_public.settings
|
||||
for each row
|
||||
execute procedure app_private.tg__timestamps();
|
||||
|
||||
-- endregion
|
||||
|
||||
-- region Provider
|
||||
|
||||
create table app_public.voice_providers
|
||||
(
|
||||
id uuid not null default uuid_generate_v1mc() primary key,
|
||||
kind text not null,
|
||||
name text not null,
|
||||
credentials jsonb not null,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create unique index voice_providers_number on app_public.voice_providers(name);
|
||||
|
||||
alter table app_public.voice_providers
|
||||
enable row level security;
|
||||
|
||||
create policy access_all on app_public.voice_providers to app_admin using (true);
|
||||
grant update (name, credentials) on app_public.voice_providers to app_admin;
|
||||
grant select on app_public.voice_providers to app_admin;
|
||||
grant insert (kind, name, credentials) on app_public.voice_providers to app_admin;
|
||||
grant delete on app_public.voice_providers to app_admin;
|
||||
|
||||
create trigger _100_timestamps
|
||||
before insert or update
|
||||
on app_public.voice_providers
|
||||
for each row
|
||||
execute procedure app_private.tg__timestamps();
|
||||
-- endregion
|
||||
|
||||
-- region Voice Line
|
||||
|
||||
create table app_public.voice_lines
|
||||
(
|
||||
id uuid not null default uuid_generate_v1mc() primary key,
|
||||
provider_id uuid not null,
|
||||
provider_line_sid text not null,
|
||||
number text not null,
|
||||
language text not null,
|
||||
voice text not null,
|
||||
prompt_text text,
|
||||
prompt_audio jsonb,
|
||||
audio_prompt_enabled boolean not null default false,
|
||||
audio_converted_at timestamptz,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
alter table app_public.voice_lines
|
||||
add constraint voice_lines_provider_id_fkey foreign key ("provider_id") references app_public.voice_providers on delete cascade;
|
||||
|
||||
create index on app_public.voice_lines (provider_id);
|
||||
create index on app_public.voice_lines (provider_line_sid);
|
||||
create unique index voice_lines_number on app_public.voice_lines(number);
|
||||
|
||||
alter table app_public.voice_lines
|
||||
enable row level security;
|
||||
|
||||
create policy access_all on app_public.voice_lines to app_admin using (true);
|
||||
grant update (prompt_text, prompt_audio, audio_prompt_enabled, language, voice) on app_public.voice_lines to app_admin;
|
||||
grant select on app_public.voice_lines to app_admin;
|
||||
grant insert (provider_id, provider_line_sid, number, prompt_text, prompt_audio, audio_prompt_enabled, language, voice) on app_public.voice_lines to app_admin;
|
||||
grant delete on app_public.voice_lines to app_admin;
|
||||
|
||||
create trigger _100_timestamps
|
||||
before insert or update
|
||||
on app_public.voice_lines
|
||||
for each row
|
||||
execute procedure app_private.tg__timestamps();
|
||||
|
||||
|
||||
create function app_private.tg__voice_line_provider_update() returns trigger as $$
|
||||
begin
|
||||
if (TG_OP = 'DELETE') then
|
||||
perform graphile_worker.add_job('voice-line-delete', json_build_object('voiceLineId', OLD.id, 'providerId', OLD.provider_id, 'providerLineSid', OLD.provider_line_sid));
|
||||
else
|
||||
perform graphile_worker.add_job('voice-line-provider-update', json_build_object('voiceLineId', NEW.id));
|
||||
end if;
|
||||
|
||||
return null;
|
||||
end;
|
||||
$$ language plpgsql volatile security definer set search_path to pg_catalog, public, pg_temp;
|
||||
|
||||
comment on function app_private.tg__voice_line_provider_update() is
|
||||
E'This trigger is called to ensure a voice line is connected to twilio properly';
|
||||
|
||||
|
||||
create trigger _101_voice_line_provider_update
|
||||
after insert or update of provider_line_sid or delete
|
||||
on app_public.voice_lines
|
||||
for each row
|
||||
execute procedure app_private.tg__voice_line_provider_update();
|
||||
|
||||
create function app_private.tg__voice_line_prompt_audio_update() returns trigger as $$
|
||||
begin
|
||||
perform graphile_worker.add_job('voice-line-audio-update', json_build_object('voiceLineId', NEW.id));
|
||||
return null;
|
||||
end;
|
||||
$$ language plpgsql volatile security definer set search_path to pg_catalog, public, pg_temp;
|
||||
|
||||
comment on function app_private.tg__voice_line_prompt_audio_update() is
|
||||
E'This trigger is called to ensure a voice line is connected to twilio properly';
|
||||
|
||||
|
||||
create trigger _101_voice_line_prompt_audio_update
|
||||
after insert or update of prompt_audio
|
||||
on app_public.voice_lines
|
||||
for each row
|
||||
execute procedure app_private.tg__voice_line_prompt_audio_update();
|
||||
-- endregion
|
||||
|
||||
-- region Webhooks
|
||||
create table app_public.webhooks
|
||||
(
|
||||
id uuid not null default uuid_generate_v1mc() primary key,
|
||||
backend_type text not null,
|
||||
backend_id uuid not null,
|
||||
name text not null,
|
||||
endpoint_url text not null,
|
||||
http_method text not null default 'post',
|
||||
headers jsonb,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint webhook_http_method_validity check (http_method in ('post', 'put')),
|
||||
constraint webhook_endpoint_url_validity check (endpoint_url ~ '^https?://[^/]+')
|
||||
);
|
||||
|
||||
create index on app_public.webhooks (backend_type, backend_id);
|
||||
|
||||
alter table app_public.webhooks
|
||||
enable row level security;
|
||||
|
||||
create policy access_all on app_public.webhooks to app_admin using (true);
|
||||
grant update (name, endpoint_url, http_method, headers) on app_public.webhooks to app_admin;
|
||||
grant select on app_public.webhooks to app_admin;
|
||||
grant insert (backend_type, backend_id, name, endpoint_url, http_method, headers) on app_public.webhooks to app_admin;
|
||||
grant delete on app_public.webhooks to app_admin;
|
||||
|
||||
create trigger _100_timestamps
|
||||
before insert or update
|
||||
on app_public.webhooks
|
||||
for each row
|
||||
execute procedure app_private.tg__timestamps();
|
||||
-- endregion
|
||||
|
||||
-- region WhatsappBots
|
||||
set transform_null_equals to true;
|
||||
create table app_public.whatsapp_bots
|
||||
(
|
||||
id uuid not null default uuid_generate_v1mc() primary key,
|
||||
phone_number text not null,
|
||||
token uuid not null default uuid_generate_v1mc(),
|
||||
user_id uuid not null,
|
||||
description text,
|
||||
auth_info text,
|
||||
qr_code text,
|
||||
is_verified boolean not null default false,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
|
||||
);
|
||||
|
||||
create unique index whatsapp_bot_token on app_public.whatsapp_bots(token);
|
||||
|
||||
alter table app_public.whatsapp_bots
|
||||
add constraint whatsapp_bots_user_id_fkey foreign key ("user_id") references app_public.users on delete cascade;
|
||||
|
||||
alter table app_public.whatsapp_bots
|
||||
enable row level security;
|
||||
|
||||
create policy access_all on app_public.whatsapp_bots to app_admin using (true);
|
||||
grant update (phone_number, token, user_id, description, auth_info, qr_code, is_verified) on app_public.whatsapp_bots to app_admin;
|
||||
grant select on app_public.whatsapp_bots to app_admin;
|
||||
grant insert (phone_number, token, user_id, description, auth_info, qr_code, is_verified) on app_public.whatsapp_bots to app_admin;
|
||||
|
||||
create trigger _100_timestamps
|
||||
before insert or update
|
||||
on app_public.whatsapp_bots
|
||||
for each row
|
||||
execute procedure app_private.tg__timestamps();
|
||||
|
||||
-- endregion
|
||||
-- region WhatsappMessages
|
||||
|
||||
create table app_public.whatsapp_messages
|
||||
(
|
||||
id uuid not null default uuid_generate_v1mc() primary key,
|
||||
whatsapp_bot_id uuid not null,
|
||||
wa_message_id text,
|
||||
wa_message text,
|
||||
wa_timestamp timestamptz,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
|
||||
);
|
||||
|
||||
create unique index whatsapp_message_whatsapp_bot_id on app_public.whatsapp_messages(whatsapp_bot_id);
|
||||
|
||||
alter table app_public.whatsapp_messages
|
||||
add constraint whatsapp_messages_whatsapp_bot_id_fkey foreign key ("whatsapp_bot_id") references app_public.whatsapp_bots on delete cascade;
|
||||
|
||||
alter table app_public.whatsapp_messages
|
||||
enable row level security;
|
||||
|
||||
create policy access_all on app_public.whatsapp_messages to app_admin using (true);
|
||||
grant update (whatsapp_bot_id, wa_message_id, wa_message, wa_timestamp) on app_public.whatsapp_messages to app_admin;
|
||||
grant select on app_public.whatsapp_messages to app_admin;
|
||||
grant insert (whatsapp_bot_id, wa_message_id, wa_message, wa_timestamp) on app_public.whatsapp_messages to app_admin;
|
||||
|
||||
create trigger _100_timestamps
|
||||
before insert or update
|
||||
on app_public.whatsapp_messages
|
||||
for each row
|
||||
execute procedure app_private.tg__timestamps();
|
||||
|
||||
-- endregion
|
||||
-- region WhatsappAttachments
|
||||
|
||||
create table app_public.whatsapp_attachments
|
||||
(
|
||||
id uuid not null default uuid_generate_v1mc() primary key,
|
||||
whatsapp_bot_id uuid not null,
|
||||
whatsapp_message_id uuid,
|
||||
attachment bytea,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
|
||||
);
|
||||
|
||||
create unique index whatsapp_attachment_whatsapp_bot_id on app_public.whatsapp_attachments(whatsapp_bot_id);
|
||||
create unique index whatsapp_attachment_whatsapp_message_id on app_public.whatsapp_attachments(whatsapp_message_id);
|
||||
|
||||
alter table app_public.whatsapp_attachments
|
||||
add constraint whatsapp_attachments_whatsapp_bot_id_fkey foreign key ("whatsapp_bot_id") references app_public.whatsapp_bots on delete cascade;
|
||||
alter table app_public.whatsapp_attachments
|
||||
add constraint whatsapp_attachments_whatsapp_message_id_fkey foreign key ("whatsapp_message_id") references app_public.whatsapp_messages on delete cascade;
|
||||
|
||||
alter table app_public.whatsapp_attachments
|
||||
enable row level security;
|
||||
|
||||
create policy access_all on app_public.whatsapp_attachments to app_admin using (true);
|
||||
grant update (whatsapp_bot_id, whatsapp_message_id, attachment) on app_public.whatsapp_attachments to app_admin;
|
||||
grant select on app_public.whatsapp_attachments to app_admin;
|
||||
grant insert (whatsapp_bot_id, whatsapp_message_id, attachment) on app_public.whatsapp_attachments to app_admin;
|
||||
|
||||
create trigger _100_timestamps
|
||||
before insert or update
|
||||
on app_public.whatsapp_attachments
|
||||
for each row
|
||||
execute procedure app_private.tg__timestamps();
|
||||
|
||||
-- endregion
|
||||
|
||||
-- region SignalBots
|
||||
set transform_null_equals to true;
|
||||
create table app_public.signal_bots
|
||||
(
|
||||
id uuid not null default uuid_generate_v1mc() primary key,
|
||||
phone_number text not null,
|
||||
token uuid not null default uuid_generate_v1mc(),
|
||||
user_id uuid not null,
|
||||
description text,
|
||||
auth_info text,
|
||||
is_verified boolean not null default false,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
|
||||
);
|
||||
|
||||
create unique index signal_bot_token on app_public.signal_bots(token);
|
||||
|
||||
alter table app_public.signal_bots
|
||||
add constraint signal_bots_user_id_fkey foreign key ("user_id") references app_public.users on delete cascade;
|
||||
|
||||
alter table app_public.signal_bots
|
||||
enable row level security;
|
||||
|
||||
create policy access_all on app_public.signal_bots to app_admin using (true);
|
||||
grant update (phone_number, token, user_id, description, auth_info, is_verified) on app_public.signal_bots to app_admin;
|
||||
grant select on app_public.signal_bots to app_admin;
|
||||
grant insert (phone_number, token, user_id, description, auth_info, is_verified) on app_public.signal_bots to app_admin;
|
||||
|
||||
create trigger _100_timestamps
|
||||
before insert or update
|
||||
on app_public.signal_bots
|
||||
for each row
|
||||
execute procedure app_private.tg__timestamps();
|
||||
|
||||
|
||||
-- endregion
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
--! Previous: sha1:b13a5217288f5d349d8d9e3afbd7bb30c0dbad21
|
||||
--! Hash: sha1:8659f815ff013a793f2e01113a9a61a98c7bd8d5
|
||||
|
||||
-- Enter migration here
|
||||
|
||||
drop table if exists app_public.whatsapp_attachments cascade;
|
||||
drop table if exists app_public.whatsapp_messages cascade;
|
||||
|
||||
grant delete on app_public.whatsapp_bots to app_admin;
|
||||
grant delete on app_public.signal_bots to app_admin;
|
||||
|
|
@ -1 +0,0 @@
|
|||
-- Enter migration here
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
{
|
||||
"name": "@digiresilience/metamigo-db",
|
||||
"private": true,
|
||||
"version": "0.2.0",
|
||||
"main": "build/main/index.js",
|
||||
"type": "module",
|
||||
"author": "Abel Luck <abel@guardianproject.info>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@digiresilience/metamigo-common": "*",
|
||||
"@digiresilience/metamigo-config": "^0.2.0",
|
||||
"@graphile-contrib/pg-many-to-many": "^1.0.2",
|
||||
"camelcase-keys": "^9.1.3",
|
||||
"graphile-migrate": "^1.4.1",
|
||||
"graphql": "15.8.0",
|
||||
"pg-promise": "^11.5.4",
|
||||
"postgraphile": "4.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.24.0",
|
||||
"@babel/preset-env": "7.24.0",
|
||||
"@babel/preset-typescript": "7.23.3",
|
||||
"@types/jest": "^29.5.12",
|
||||
"eslint": "^8.57.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-junit": "^16.0.0",
|
||||
"pino-pretty": "^10.3.1",
|
||||
"prettier": "^3.2.5",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsc-watch": "^6.0.4",
|
||||
"typedoc": "^0.25.11",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"doc": "typedoc src/ --exclude '**/*.test.ts' --exclude '**/*.spec.ts' --name $npm_package_name --readme README.md --target es2019 --mode file --out build/docs",
|
||||
"fix:lint": "eslint src --ext .ts --fix",
|
||||
"fmt": "prettier \"src/**/*.ts\" --write",
|
||||
"lint": "eslint src --ext .ts && prettier \"src/**/*.ts\" --list-different",
|
||||
"worker": "NODE_ENV=development yarn cli worker",
|
||||
"dev": "tsc-watch --build --noClear "
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
psql -Xv ON_ERROR_STOP=1 "${GM_DBURL}" <<EOF
|
||||
|
||||
INSERT INTO app_public.users(email, name, user_role, is_active, created_by)
|
||||
VALUES('abel@guardianproject.info', 'Abel', 'admin'::app_public.role_type, true, 'afterCurrent Hook')
|
||||
on conflict (email) do nothing;
|
||||
|
||||
INSERT INTO app_public.users(email, name, user_role, is_active, created_by)
|
||||
VALUES('darren@redaranj.com', 'Darren', 'admin'::app_public.role_type, true, 'afterCurrent Hook')
|
||||
on conflict (email) do nothing;
|
||||
|
||||
INSERT INTO app_public.settings(name, value)
|
||||
VALUES('app-setting', to_jsonb('this is a setting value stored as json text'::text))
|
||||
on conflict (name) do nothing;
|
||||
|
||||
EOF
|
||||
|
||||
if [[ -f "${PWD}/scripts/afterCurrent-private.sh" ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "${PWD}/scripts/afterCurrent-private.sh"
|
||||
fi
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
REVOKE ALL ON DATABASE :DATABASE_NAME FROM PUBLIC;
|
||||
GRANT CONNECT ON DATABASE :DATABASE_NAME TO :DATABASE_OWNER;
|
||||
GRANT CONNECT ON DATABASE :DATABASE_NAME TO :DATABASE_AUTHENTICATOR;
|
||||
GRANT ALL ON DATABASE :DATABASE_NAME TO :DATABASE_OWNER;
|
||||
grant app_anonymous to :DATABASE_VISITOR;
|
||||
grant app_user to :DATABASE_VISITOR;
|
||||
grant app_admin to :DATABASE_VISITOR;
|
||||
CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA public;
|
||||
CREATE EXTENSION IF NOT EXISTS citext WITH SCHEMA public;
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public;
|
||||
CREATE EXTENSION IF NOT EXISTS tablefunc WITH SCHEMA public;
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
DATABASE_HOST=${POSTGRES_HOST:-}
|
||||
DATABASE_PORT=${POSTGRES_PORT:-5432}
|
||||
DATABASE_SUPERUSER=${POSTGRES_USER:-postgres}
|
||||
DATABASE_SUPERUSER_PASSWORD=${POSTGRES_PASSWORD:-metamigo}
|
||||
|
||||
export PGPASSWORD=$DATABASE_SUPERUSER_PASSWORD
|
||||
|
||||
# this script is run under two circumstances: with a local postgres and a remote postgres
|
||||
# local postgres: we should use the unix domain socket to connect
|
||||
# remote postgres: we should pass the --host param
|
||||
|
||||
HOST_PARAM="--host="
|
||||
if [[ ! -z ${DATABASE_HOST} ]]; then
|
||||
HOST_PARAM="--host=${DATABASE_HOST}"
|
||||
fi
|
||||
|
||||
# wait for postgres process to settle
|
||||
set +e
|
||||
echo "pg_isready $HOST_PARAM --username $POSTGRES_USER --dbname template1"
|
||||
pg_isready "$HOST_PARAM" --username "$POSTGRES_USER" --dbname template1
|
||||
while ! pg_isready "$HOST_PARAM" --username "$POSTGRES_USER" --dbname template1; do
|
||||
echo "$(date) - waiting for database to start"
|
||||
sleep 10
|
||||
done
|
||||
set -e
|
||||
|
||||
echo
|
||||
echo
|
||||
echo "Creating the database and the roles"
|
||||
# We're using 'template1' because we know it should exist. We should not actually change this database.
|
||||
psql -Xv ON_ERROR_STOP=1 "$HOST_PARAM" --username "$POSTGRES_USER" --dbname template1 <<EOF
|
||||
|
||||
CREATE ROLE ${DATABASE_OWNER} WITH LOGIN PASSWORD '${DATABASE_OWNER_PASSWORD}';
|
||||
GRANT ${DATABASE_OWNER} TO ${DATABASE_SUPERUSER};
|
||||
CREATE ROLE ${DATABASE_AUTHENTICATOR} WITH LOGIN PASSWORD '${DATABASE_AUTHENTICATOR_PASSWORD}' NOINHERIT;
|
||||
CREATE ROLE ${DATABASE_VISITOR};
|
||||
GRANT ${DATABASE_VISITOR} TO ${DATABASE_AUTHENTICATOR};
|
||||
-- Create database
|
||||
CREATE DATABASE ${DATABASE_NAME} OWNER ${DATABASE_OWNER};
|
||||
-- Database permissions
|
||||
REVOKE ALL ON DATABASE ${DATABASE_NAME} FROM PUBLIC;
|
||||
GRANT ALL ON DATABASE ${DATABASE_NAME} TO ${DATABASE_OWNER};
|
||||
GRANT CONNECT ON DATABASE ${DATABASE_NAME} TO ${DATABASE_AUTHENTICATOR};
|
||||
EOF
|
||||
|
||||
echo
|
||||
echo
|
||||
echo "Installing extensions into the database"
|
||||
psql -Xv ON_ERROR_STOP=1 "$HOST_PARAM" --username "$POSTGRES_USER" --dbname "$DATABASE_NAME" <<EOF
|
||||
CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA public;
|
||||
CREATE EXTENSION IF NOT EXISTS citext WITH SCHEMA public;
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public;
|
||||
CREATE EXTENSION IF NOT EXISTS tablefunc WITH SCHEMA public;
|
||||
EOF
|
||||
|
||||
echo
|
||||
echo
|
||||
echo "Creating roles in the database"
|
||||
psql -Xv ON_ERROR_STOP=1 "$HOST_PARAM" --username "$POSTGRES_USER" --dbname "$DATABASE_NAME" <<EOF
|
||||
CREATE ROLE app_anonymous;
|
||||
CREATE ROLE app_user WITH IN ROLE app_anonymous;
|
||||
CREATE ROLE app_admin WITH IN ROLE app_user;
|
||||
GRANT app_anonymous TO ${DATABASE_AUTHENTICATOR};
|
||||
GRANT app_admin TO ${DATABASE_AUTHENTICATOR};
|
||||
EOF
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
if [ "$GM_DBURL" = "" ]; then
|
||||
echo "This script should only be ran from inside graphile-migrate";
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
export COMPOSE_PROJECT_NAME
|
||||
|
||||
# When ran inside docker-compose we need to be able to run a different pg_dump binary
|
||||
${PG_DUMP:-pg_dump} \
|
||||
--no-sync \
|
||||
--schema-only \
|
||||
--no-owner \
|
||||
--exclude-schema=graphile_migrate \
|
||||
--exclude-schema=graphile_worker \
|
||||
--file=../../data/schema.sql \
|
||||
"$GM_DBURL"
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
const { spawn } = require("child_process");
|
||||
|
||||
if (process.env.CI) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const connectionString = process.env.GM_DBURL;
|
||||
if (!connectionString) {
|
||||
console.error(
|
||||
"This script should only be called from a graphile-migrate action."
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
spawn(
|
||||
process.env.PG_DUMP || "pg_dump",
|
||||
[
|
||||
"--no-sync",
|
||||
"--schema-only",
|
||||
"--no-owner",
|
||||
"--exclude-schema=graphile_migrate",
|
||||
"--exclude-schema=graphile_worker",
|
||||
`--file=../../data/schema.sql`,
|
||||
connectionString,
|
||||
],
|
||||
{
|
||||
stdio: "inherit",
|
||||
shell: true,
|
||||
}
|
||||
);
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
import process from "node:process";
|
||||
import { existsSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { exec } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type { IAppConfig } from "@digiresilience/metamigo-config";
|
||||
|
||||
/**
|
||||
* We use graphile-migrate for managing database migrations.
|
||||
*
|
||||
* However we also use convict as the sole source of truth for our app's configuration. We do not want to have to configure
|
||||
* separate env files or config files for graphile-migrate and yet again others for convict.
|
||||
*
|
||||
* So we wrap the graphile-migrate cli tool here. We parse our convict config, set necessary env vars, and then shell out to
|
||||
* graphile-migrate.
|
||||
*
|
||||
* Commander eats all args starting with --, so you must use the -- escape to indicate the arguments have finished
|
||||
*
|
||||
* Example:
|
||||
* ./cli db -- --help // will show graphile migrate help
|
||||
* ./cli db -- watch // will watch the current sql for changes
|
||||
* ./cli db -- watch --once // will apply the current sql once
|
||||
*/
|
||||
export const migrateWrapper = async (
|
||||
commands: string[],
|
||||
config: IAppConfig,
|
||||
silent = false
|
||||
): Promise<void> => {
|
||||
const env = {
|
||||
DATABASE_URL: config.db.connection,
|
||||
SHADOW_DATABASE_URL: config.dev.shadowConnection,
|
||||
ROOT_DATABASE_URL: config.dev.rootConnection,
|
||||
DATABASE_NAME: config.db.name,
|
||||
DATABASE_OWNER: config.db.owner,
|
||||
DATABASE_AUTHENTICATOR: config.postgraphile.auth,
|
||||
DATABASE_VISITOR: config.postgraphile.visitor,
|
||||
};
|
||||
const cmd = `npx --no-install graphile-migrate ${commands.join(" ")}`;
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const dbDir = path.resolve(__dirname, "../../");
|
||||
const gmrcPath = path.resolve(__dirname, "../../.gmrc");
|
||||
if (!existsSync(gmrcPath)) {
|
||||
throw new Error(`graphile migrate config not found at ${gmrcPath}`);
|
||||
}
|
||||
|
||||
if (!silent) console.log("executing:", cmd);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const proc = exec(cmd, {
|
||||
env: { ...process.env, ...env },
|
||||
cwd: dbDir,
|
||||
});
|
||||
|
||||
proc.stdout.on("data", (data) => {
|
||||
if (!silent) console.log("MIGRATE:", data);
|
||||
});
|
||||
|
||||
proc.stderr.on("data", (data) => {
|
||||
console.error("MIGRATE", data);
|
||||
});
|
||||
proc.on("close", (code) => {
|
||||
if (code !== 0) {
|
||||
reject(new Error(`graphile-migrate exited with code ${code}`));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
import type { IAppConfig } from "@digiresilience/metamigo-config";
|
||||
import camelcaseKeys from "camelcase-keys";
|
||||
import PgSimplifyInflectorPlugin from "@graphile-contrib/pg-simplify-inflector";
|
||||
import PgManyToManyPlugin from "@graphile-contrib/pg-many-to-many";
|
||||
import ConnectionFilterPlugin from "postgraphile-plugin-connection-filter";
|
||||
import type { PostGraphileOptions } from "postgraphile";
|
||||
|
||||
import {
|
||||
UserRecordRepository,
|
||||
AccountRecordRepository,
|
||||
SessionRecordRepository,
|
||||
} from "@digiresilience/metamigo-common";
|
||||
|
||||
import {
|
||||
SettingRecordRepository,
|
||||
VoiceProviderRecordRepository,
|
||||
VoiceLineRecordRepository,
|
||||
WebhookRecordRepository,
|
||||
WhatsappBotRecordRepository,
|
||||
WhatsappMessageRecordRepository,
|
||||
WhatsappAttachmentRecordRepository,
|
||||
SignalBotRecordRepository,
|
||||
} from "./records/index.js";
|
||||
|
||||
import type { IInitOptions, IDatabase } from "pg-promise";
|
||||
|
||||
export interface IRepositories {
|
||||
users: UserRecordRepository;
|
||||
sessions: SessionRecordRepository;
|
||||
accounts: AccountRecordRepository;
|
||||
settings: SettingRecordRepository;
|
||||
voiceLines: VoiceLineRecordRepository;
|
||||
voiceProviders: VoiceProviderRecordRepository;
|
||||
webhooks: WebhookRecordRepository;
|
||||
whatsappBots: WhatsappBotRecordRepository;
|
||||
whatsappMessages: WhatsappMessageRecordRepository;
|
||||
whatsappAttachments: WhatsappAttachmentRecordRepository;
|
||||
signalBots: SignalBotRecordRepository;
|
||||
}
|
||||
|
||||
export type AppDatabase = IDatabase<IRepositories> & IRepositories;
|
||||
|
||||
export const dbInitOptions = (
|
||||
_config: IAppConfig
|
||||
): IInitOptions<IRepositories> => ({
|
||||
noWarnings: true,
|
||||
receive(e) {
|
||||
const { data, result } = e;
|
||||
if (result) result.rows = camelcaseKeys(data);
|
||||
},
|
||||
|
||||
// Extending the database protocol with our custom repositories;
|
||||
// API: http://vitaly-t.github.io/pg-promise/global.html#event:extend
|
||||
extend(obj: any, _dc) {
|
||||
// AppDatase was obj type
|
||||
// Database Context (_dc) is mainly needed for extending multiple databases with different access API.
|
||||
|
||||
// NOTE:
|
||||
// This event occurs for every task and transaction being executed (which could be every request!)
|
||||
// so it should be as fast as possible. Do not use 'require()' or do any other heavy lifting.
|
||||
obj.users = new UserRecordRepository(obj);
|
||||
obj.sessions = new SessionRecordRepository(obj);
|
||||
obj.accounts = new AccountRecordRepository(obj);
|
||||
obj.settings = new SettingRecordRepository(obj);
|
||||
obj.voiceLines = new VoiceLineRecordRepository(obj);
|
||||
obj.voiceProviders = new VoiceProviderRecordRepository(obj);
|
||||
obj.webhooks = new WebhookRecordRepository(obj);
|
||||
obj.whatsappBots = new WhatsappBotRecordRepository(obj);
|
||||
obj.whatsappMessages = new WhatsappMessageRecordRepository(obj);
|
||||
obj.whatsappAttachments = new WhatsappAttachmentRecordRepository(obj);
|
||||
obj.signalBots = new SignalBotRecordRepository(obj);
|
||||
},
|
||||
});
|
||||
|
||||
export const getPostGraphileOptions = (): PostGraphileOptions => ({
|
||||
ignoreRBAC: false,
|
||||
dynamicJson: true,
|
||||
ignoreIndexes: false,
|
||||
appendPlugins: [
|
||||
PgSimplifyInflectorPlugin,
|
||||
PgManyToManyPlugin,
|
||||
ConnectionFilterPlugin as any,
|
||||
],
|
||||
});
|
||||
|
||||
export * from "./helpers.js";
|
||||
export * from "./records/index.js";
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
export * from "./settings.js";
|
||||
export * from "./signal/bots.js";
|
||||
export * from "./whatsapp/bots.js";
|
||||
export * from "./whatsapp/messages.js";
|
||||
export * from "./whatsapp/attachments.js";
|
||||
export * from "./settings.js";
|
||||
export * from "./voice/voice-line.js";
|
||||
export * from "./voice/voice-provider.js";
|
||||
export * from "./webhooks.js";
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars,@typescript-eslint/no-explicit-any,prefer-destructuring */
|
||||
import {
|
||||
RepositoryBase,
|
||||
recordInfo,
|
||||
UUID,
|
||||
Flavor,
|
||||
} from "@digiresilience/metamigo-common";
|
||||
|
||||
export type SettingId = Flavor<UUID, "Setting Id">;
|
||||
|
||||
export interface UnsavedSetting<T> {
|
||||
name: string;
|
||||
value: T;
|
||||
}
|
||||
|
||||
export interface SavedSetting<T> extends UnsavedSetting<T> {
|
||||
id: SettingId;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export const SettingRecord = recordInfo<UnsavedSetting<any>, SavedSetting<any>>(
|
||||
"app_public",
|
||||
"settings"
|
||||
);
|
||||
|
||||
export class SettingRecordRepository extends RepositoryBase(SettingRecord) {
|
||||
async findByName<T>(name: string): Promise<SavedSetting<T> | null> {
|
||||
return this.db.oneOrNone("SELECT * FROM $1 $2:raw LIMIT 1", [
|
||||
this.schemaTable,
|
||||
this.where({ name }),
|
||||
]);
|
||||
}
|
||||
|
||||
async upsert<T>(name: string, value: T): Promise<SavedSetting<T>> {
|
||||
return this.db.one(
|
||||
`INSERT INTO $1 ($2:name) VALUES ($2:csv)
|
||||
ON CONFLICT (name)
|
||||
DO UPDATE SET value = EXCLUDED.value RETURNING *`,
|
||||
[this.schemaTable, this.columnize({ name, value })]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// these helpers let us create type safe setting constants
|
||||
export interface SettingType<T = any> {
|
||||
_type: T;
|
||||
}
|
||||
|
||||
export interface SettingInfo<T = any> extends SettingType<T> {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function castToSettingInfo(
|
||||
runtimeData: Omit<SettingInfo, "_type">
|
||||
): SettingInfo {
|
||||
return runtimeData as SettingInfo;
|
||||
}
|
||||
|
||||
export function settingInfo<T>(name: string): SettingInfo<T>;
|
||||
|
||||
// don't use this signature, use the explicit typed signature
|
||||
export function settingInfo(name: string) {
|
||||
return castToSettingInfo({
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
export interface ISettingsService {
|
||||
name: string;
|
||||
lookup<T>(settingInfo: SettingInfo<T>): Promise<T>;
|
||||
save<T>(settingInfo: SettingInfo<T>, value: T): Promise<T>;
|
||||
}
|
||||
|
||||
export const SettingsService = (
|
||||
repo: SettingRecordRepository
|
||||
): ISettingsService => ({
|
||||
name: "settingService",
|
||||
async lookup<T>(settingInfo: SettingInfo<T>): Promise<T> {
|
||||
const s = await repo.findByName<T>(settingInfo.name);
|
||||
return s.value;
|
||||
},
|
||||
|
||||
async save<T>(settingInfo: SettingInfo<T>, value: T): Promise<T> {
|
||||
const s = await repo.upsert(settingInfo.name, value);
|
||||
return s.value;
|
||||
},
|
||||
});
|
||||
|
||||
const _test = async () => {
|
||||
// here is an example of how to use this module
|
||||
// it also serves as a compile-time test case
|
||||
const repo = new SettingRecordRepository({} as any);
|
||||
|
||||
// create your own custom setting types!
|
||||
// the value is serialized as json in the database
|
||||
type Custom = { foo: string; bar: string };
|
||||
type CustomUnsavedSetting = UnsavedSetting<Custom>;
|
||||
type CustomSetting = SavedSetting<Custom>;
|
||||
|
||||
const s3: CustomSetting = await repo.findByName("test");
|
||||
|
||||
const customValue = { foo: "monkeys", bar: "eggplants" };
|
||||
let customSetting = { name: "custom", value: customValue };
|
||||
customSetting = await repo.insert(customSetting);
|
||||
const value: Custom = customSetting.value;
|
||||
|
||||
const MySetting = settingInfo<string>("my-setting");
|
||||
};
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
import {
|
||||
RepositoryBase,
|
||||
recordInfo,
|
||||
UUID,
|
||||
Flavor,
|
||||
} from "@digiresilience/metamigo-common";
|
||||
|
||||
export type SignalBotId = Flavor<UUID, "Signal Bot Id">;
|
||||
|
||||
export interface UnsavedSignalBot {
|
||||
phoneNumber: string;
|
||||
userId: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface SavedSignalBot extends UnsavedSignalBot {
|
||||
id: SignalBotId;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
token: string;
|
||||
authInfo: string;
|
||||
isVerified: boolean;
|
||||
}
|
||||
|
||||
export const SignalBotRecord = recordInfo<UnsavedSignalBot, SavedSignalBot>(
|
||||
"app_public",
|
||||
"signal_bots"
|
||||
);
|
||||
|
||||
export class SignalBotRecordRepository extends RepositoryBase(SignalBotRecord) {
|
||||
async updateAuthInfo(
|
||||
bot: SavedSignalBot,
|
||||
authInfo: string | undefined
|
||||
): Promise<SavedSignalBot> {
|
||||
return this.db.one(
|
||||
"UPDATE $1 SET (auth_info, is_verified) = ROW($2, true) WHERE id = $3 RETURNING *",
|
||||
[this.schemaTable, authInfo, bot.id]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
import {
|
||||
RepositoryBase,
|
||||
recordInfo,
|
||||
UUID,
|
||||
Flavor,
|
||||
} from "@digiresilience/metamigo-common";
|
||||
import type {} from "pg-promise";
|
||||
|
||||
export type VoiceLineId = Flavor<UUID, "VoiceLine Id">;
|
||||
|
||||
export type VoiceLineAudio = {
|
||||
"audio/webm": string;
|
||||
"audio/mpeg"?: string;
|
||||
checksum?: string;
|
||||
};
|
||||
|
||||
export interface UnsavedVoiceLine {
|
||||
providerId: string;
|
||||
providerLineSid: string;
|
||||
number: string;
|
||||
language: string;
|
||||
voice: string;
|
||||
promptText?: string;
|
||||
promptAudio?: VoiceLineAudio;
|
||||
audioPromptEnabled: boolean;
|
||||
audioConvertedAt?: Date;
|
||||
}
|
||||
|
||||
export interface SavedVoiceLine extends UnsavedVoiceLine {
|
||||
id: VoiceLineId;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export const VoiceLineRecord = recordInfo<UnsavedVoiceLine, SavedVoiceLine>(
|
||||
"app_public",
|
||||
"voice_lines"
|
||||
);
|
||||
|
||||
export class VoiceLineRecordRepository extends RepositoryBase(VoiceLineRecord) {
|
||||
/**
|
||||
* Fetch all voice lines given the numbers
|
||||
* @param numbers
|
||||
*/
|
||||
async findAllByNumbers(numbers: string[]): Promise<SavedVoiceLine[]> {
|
||||
return this.db.any(
|
||||
"SELECT id,provider_id,provider_line_sid,number FROM $1 WHERE number in ($2:csv)",
|
||||
[this.schemaTable, numbers]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all voice lines given a list of provider line ids
|
||||
* @param ids
|
||||
*/
|
||||
async findAllByProviderLineSids(ids: string[]): Promise<SavedVoiceLine[]> {
|
||||
return this.db.any(
|
||||
"SELECT id,provider_id,provider_line_sid,number FROM $1 WHERE provider_line_sid in ($2:csv)",
|
||||
[this.schemaTable, ids]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
import {
|
||||
RepositoryBase,
|
||||
recordInfo,
|
||||
UUID,
|
||||
Flavor,
|
||||
} from "@digiresilience/metamigo-common";
|
||||
|
||||
/*
|
||||
* VoiceProvider
|
||||
*
|
||||
* A provider is a company that provides incoming voice call services
|
||||
*/
|
||||
|
||||
export type VoiceProviderId = Flavor<UUID, "VoiceProvider Id">;
|
||||
|
||||
export enum VoiceProviderKinds {
|
||||
TWILIO = "TWILIO",
|
||||
}
|
||||
|
||||
export type TwilioCredentials = {
|
||||
accountSid: string;
|
||||
apiKeySid: string;
|
||||
apiKeySecret: string;
|
||||
};
|
||||
|
||||
// expand this type later when we support more providers
|
||||
export type VoiceProviderCredentials = TwilioCredentials;
|
||||
|
||||
export interface UnsavedVoiceProvider {
|
||||
kind: VoiceProviderKinds;
|
||||
name: string;
|
||||
credentials: VoiceProviderCredentials;
|
||||
}
|
||||
|
||||
export interface SavedVoiceProvider extends UnsavedVoiceProvider {
|
||||
id: VoiceProviderId;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export const VoiceProviderRecord = recordInfo<
|
||||
UnsavedVoiceProvider,
|
||||
SavedVoiceProvider
|
||||
>("app_public", "voice_providers");
|
||||
|
||||
export class VoiceProviderRecordRepository extends RepositoryBase(
|
||||
VoiceProviderRecord
|
||||
) {
|
||||
async findByTwilioAccountSid(
|
||||
accountSid: string
|
||||
): Promise<SavedVoiceProvider | null> {
|
||||
return this.db.oneOrNone(
|
||||
"select * from $1 where credentials->>'accountSid' = $2",
|
||||
[this.schemaTable, accountSid]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
import {
|
||||
RepositoryBase,
|
||||
recordInfo,
|
||||
UUID,
|
||||
Flavor,
|
||||
} from "@digiresilience/metamigo-common";
|
||||
|
||||
/*
|
||||
* Webhook
|
||||
*
|
||||
* A webhook allows external services to be notified when a recorded call is available
|
||||
*/
|
||||
|
||||
export type WebhookId = Flavor<UUID, "Webhook Id">;
|
||||
|
||||
export interface HttpHeaders {
|
||||
header: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface UnsavedWebhook {
|
||||
name: string;
|
||||
voiceLineId: string;
|
||||
endpointUrl: string;
|
||||
httpMethod: "post" | "put";
|
||||
headers?: HttpHeaders[];
|
||||
}
|
||||
|
||||
export interface SavedWebhook extends UnsavedWebhook {
|
||||
id: WebhookId;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export const WebhookRecord = recordInfo<UnsavedWebhook, SavedWebhook>(
|
||||
"app_public",
|
||||
"webhooks"
|
||||
);
|
||||
|
||||
export class WebhookRecordRepository extends RepositoryBase(WebhookRecord) {
|
||||
async findAllByBackendId(
|
||||
backendType: string,
|
||||
backendId: string
|
||||
): Promise<SavedWebhook[]> {
|
||||
return this.db.any(
|
||||
"select * from $1 where backend_type = $2 and backend_id = $3",
|
||||
[this.schemaTable, backendType, backendId]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import {
|
||||
RepositoryBase,
|
||||
recordInfo,
|
||||
UUID,
|
||||
Flavor,
|
||||
} from "@digiresilience/metamigo-common";
|
||||
import type { } from "pg-promise/typescript/pg-subset";
|
||||
|
||||
export type WhatsappAttachmentId = Flavor<UUID, "Whatsapp Attachment Id">;
|
||||
|
||||
export interface UnsavedWhatsappAttachment {
|
||||
whatsappBotId: string;
|
||||
whatsappMessageId: string;
|
||||
attachment: Buffer;
|
||||
}
|
||||
|
||||
export interface SavedWhatsappAttachment extends UnsavedWhatsappAttachment {
|
||||
id: WhatsappAttachmentId;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export const WhatsappAttachmentRecord = recordInfo<
|
||||
UnsavedWhatsappAttachment,
|
||||
SavedWhatsappAttachment
|
||||
>("app_public", "whatsapp_attachments");
|
||||
|
||||
export class WhatsappAttachmentRecordRepository extends RepositoryBase(
|
||||
WhatsappAttachmentRecord
|
||||
) { };
|
||||
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
import {
|
||||
RepositoryBase,
|
||||
recordInfo,
|
||||
UUID,
|
||||
Flavor,
|
||||
} from "@digiresilience/metamigo-common";
|
||||
import type { } from "pg-promise/typescript/pg-subset";
|
||||
|
||||
export type WhatsappBotId = Flavor<UUID, "Whatsapp Bot Id">;
|
||||
|
||||
export interface UnsavedWhatsappBot {
|
||||
phoneNumber: string;
|
||||
userId: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface SavedWhatsappBot extends UnsavedWhatsappBot {
|
||||
id: WhatsappBotId;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
token: string;
|
||||
authInfo: string;
|
||||
qrCode: string;
|
||||
isVerified: boolean;
|
||||
}
|
||||
|
||||
export const WhatsappBotRecord = recordInfo<
|
||||
UnsavedWhatsappBot,
|
||||
SavedWhatsappBot
|
||||
>("app_public", "whatsapp_bots");
|
||||
|
||||
export class WhatsappBotRecordRepository extends RepositoryBase(
|
||||
WhatsappBotRecord
|
||||
) {
|
||||
async updateQR(
|
||||
bot: SavedWhatsappBot,
|
||||
qrCode: string | undefined
|
||||
): Promise<SavedWhatsappBot> {
|
||||
return this.db.one(
|
||||
"UPDATE $1 SET (qr_code) = ROW($2) WHERE id = $3 RETURNING *",
|
||||
[this.schemaTable, qrCode, bot.id]
|
||||
);
|
||||
}
|
||||
|
||||
async updateVerified(
|
||||
bot: SavedWhatsappBot,
|
||||
verified: boolean
|
||||
): Promise<SavedWhatsappBot> {
|
||||
return this.db.one(
|
||||
"UPDATE $1 SET (is_verified) = ROW($2) WHERE id = $3 RETURNING *",
|
||||
[this.schemaTable, verified, bot.id]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import {
|
||||
RepositoryBase,
|
||||
recordInfo,
|
||||
UUID,
|
||||
Flavor,
|
||||
} from "@digiresilience/metamigo-common";
|
||||
import type { } from "pg-promise/typescript/pg-subset";
|
||||
|
||||
export type WhatsappMessageId = Flavor<UUID, "Whatsapp Message Id">;
|
||||
|
||||
export interface UnsavedWhatsappMessage {
|
||||
whatsappBotId: string;
|
||||
waMessageId: string;
|
||||
waTimestamp: Date;
|
||||
waMessage: string;
|
||||
attachments?: string[];
|
||||
}
|
||||
|
||||
export interface SavedWhatsappMessage extends UnsavedWhatsappMessage {
|
||||
id: WhatsappMessageId;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export const WhatsappMessageRecord = recordInfo<
|
||||
UnsavedWhatsappMessage,
|
||||
SavedWhatsappMessage
|
||||
>("app_public", "whatsapp_messages");
|
||||
|
||||
export class WhatsappMessageRecordRepository extends RepositoryBase(
|
||||
WhatsappMessageRecord
|
||||
) { }
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"extends": "tsconfig-link",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"incremental": true,
|
||||
"outDir": "build/main",
|
||||
"rootDir": "src",
|
||||
"baseUrl": "./",
|
||||
"skipLibCheck": true,
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules/**"]
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
max_line_length = 80
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = 0
|
||||
trim_trailing_whitespace = false
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
require("eslint-config-link/patch/modern-module-resolution");
|
||||
module.exports = {
|
||||
extends: [
|
||||
"eslint-config-link/profile/node",
|
||||
"eslint-config-link/profile/typescript"
|
||||
],
|
||||
parserOptions: { tsconfigRootDir: __dirname, project: "./tsconfig.spec.json" },
|
||||
rules: {
|
||||
"no-prototype-builtins": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# package.json is formatted by package managers, so we ignore it here
|
||||
package.json
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [0.1.6](https://gitlab.com/digiresilience/link/montar/compare/0.1.5...0.1.6) (2021-10-11)
|
||||
|
||||
### [0.1.5](https://gitlab.com/digiresilience/link/montar/compare/0.1.4...0.1.5) (2021-10-08)
|
||||
|
||||
### [0.1.4](https://gitlab.com/digiresilience/link/montar/compare/0.1.3...0.1.4) (2021-10-08)
|
||||
|
||||
### [0.1.3](https://gitlab.com/digiresilience/link/montar/compare/0.1.2...0.1.3) (2021-10-08)
|
||||
|
||||
### [0.1.2](https://gitlab.com/digiresilience/link/montar/compare/0.1.1...0.1.2) (2020-11-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add startOnly function to start only certain states ([b776b66](https://gitlab.com/digiresilience/link/montar/commit/b776b6640efe52f934b8afecc64f5e8c2b1be0f3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* only stop states that were started ([178bbd2](https://gitlab.com/digiresilience/link/montar/commit/178bbd2296671ed480bcc8d22c5f72736f6335b8))
|
||||
|
||||
### [0.1.1](https://gitlab.com/digiresilience/link/montar/compare/0.1.0...0.1.1) (2020-11-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Use debug module for optional debugging ([0cb617c](https://gitlab.com/digiresilience/link/montar/commit/0cb617cf4eac9178cd56af8834bc429f656beba5))
|
||||
|
||||
## 0.1.0 (2020-11-10)
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
# montar
|
||||
|
||||
manage state in typescript. inspired by [tolitius/mount](https://github.com/tolitius/mount) from clojure.
|
||||
|
||||
What's this all about? Watch this [video from Stuart Sierra](https://www.youtube.com/watch?v=13cmHf_kt-Q) to learn more about the background of component, mount, and montar.
|
||||
|
||||
## Install
|
||||
|
||||
```console
|
||||
$ npm install --save-dev @digiresilience/montar
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
import { defState } from "@digiresilience/montar"
|
||||
```
|
||||
|
||||
### Creating State
|
||||
|
||||
```
|
||||
// db.ts
|
||||
const db = defState("db", {
|
||||
start: createDbConnection
|
||||
})
|
||||
|
||||
export default db
|
||||
```
|
||||
|
||||
where the createDbConnection function creates a connection (for example to a database) and is defined elsewhere.
|
||||
|
||||
### Starting state
|
||||
|
||||
```
|
||||
import { start } from "@digiresilience/montar"
|
||||
|
||||
const bootYourApp = async () => {
|
||||
// .. prepare for app boot
|
||||
// ..
|
||||
|
||||
// boot!
|
||||
return start()
|
||||
}
|
||||
```
|
||||
|
||||
### Using State
|
||||
|
||||
But wait, there is more.. this state is a top level being, which means it can be simply imported by other namespaces:
|
||||
|
||||
For example let's say an app needs a connection above. No problem:
|
||||
|
||||
```
|
||||
// server.ts
|
||||
import db from "./db"
|
||||
|
||||
|
||||
async function doStuff() {
|
||||
return db.executeSql("foo")
|
||||
}
|
||||
```
|
||||
|
||||
Note that before `start()` is called, `db` will be uninitialized and therefore unusable. You must call `start()` before accessing any states.
|
||||
|
||||
### Starting/Stopping State
|
||||
|
||||
`montar` has start and stop functions that will walk all the states created with `defState` and start / stop them accordingly: i.e. will call their start and stop defined functions.
|
||||
|
||||
When testing you might be interested in starting only certain states. You can do this with
|
||||
|
||||
- `startOnly(string[])` - only start the given states. WARNING: dependencies are not auto started, so you must ensure all dependencies are passed.
|
||||
- `startWithout(string[])` - start all states _except_ those passed
|
||||
|
||||
### Start and Stop Order
|
||||
|
||||
Since dependencies are "injected" by requiring on the namespace level, montar trusts the typescript compiler and node.js runtime to maintain the start and stop order for all the defStates.
|
||||
|
||||
The "start" order is then recorded and replayed on each subsequent start
|
||||
|
||||
The "stop" order is simply the reverse of the start order.
|
||||
|
||||
### Troubleshooting / Debugging
|
||||
|
||||
Set the `DEBUG=montar` environment variable for runtime debug logs.
|
||||
|
||||
## Differences to mount
|
||||
|
||||
montar was created to bring management of top-level application state under
|
||||
control. It steals greedily from
|
||||
[tolitius/mount](https://github.com/tolitius/mount) a well-known library in the
|
||||
clojure and clojurescript community.
|
||||
|
||||
The problem solved is: how to load different sub-systems of your application while respecting dependency order? Also, how do achieve this without complicated dependency injection containers that make the code harder to read.
|
||||
|
||||
With montar once you define your state, then every other module can import and
|
||||
use your state without special syntax or wrappers.
|
||||
|
||||
montar differs from mount in these ways:
|
||||
|
||||
- **start/stop are async** - the start and stop functions are async
|
||||
- **no var rebinding** - instead we use the little-known [`Proxy`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
|
||||
- this comes with limitations: only functions, arrays, objects can be defined as state. scalars do not work.
|
||||
- **not concerned with reloading** - in clj/cljs you have the repl and great hot-code reloading (thanks to immutable and persistent data structures). we do not have this in javascript.
|
||||
|
||||
## Credits
|
||||
|
||||
Copyright © 2020-present [Center for Digital Resilience][cdr]
|
||||
|
||||
### Contributors
|
||||
|
||||
| [![Abel Luck][abelxluck_avatar]][abelxluck_homepage]<br/>[Abel Luck][abelxluck_homepage] |
|
||||
| ---------------------------------------------------------------------------------------- |
|
||||
|
||||
|
||||
[abelxluck_homepage]: https://gitlab.com/abelxluck
|
||||
[abelxluck_avatar]: https://secure.gravatar.com/avatar/0f605397e0ead93a68e1be26dc26481a?s=100&d=identicon
|
||||
|
||||
### License
|
||||
|
||||
[](https://www.gnu.org/licenses/agpl-3.0.en.html)
|
||||
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
[cdrtech]: https://digiresilience.org/tech/
|
||||
[cdr]: https://digiresilience.org
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"presets": [
|
||||
"babel-preset-link"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"preset": "jest-config-link"
|
||||
}
|
||||
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
{
|
||||
"name": "@digiresilience/montar",
|
||||
"version": "0.1.7",
|
||||
"description": "manage typescript state",
|
||||
"main": "build/main/index.js",
|
||||
"type": "module",
|
||||
"typings": "build/main/index.d.ts",
|
||||
"module": "build/module/index.js",
|
||||
"author": "Abel Luck <abel@guardianproject.info>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"fix:lint": "eslint src --ext .ts --fix",
|
||||
"fmt": "prettier \"src/**/*.ts\" --write",
|
||||
"test": "DEBUG=montar jest --config jest.config.json",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"lint-fmt": "prettier \"src/**/*.ts\" --list-different",
|
||||
"doc": "typedoc src/ --exclude '**/*.test.ts' --exclude '**/*.spec.ts' --name $npm_package_name --readme README.md --target es2019 --mode file --out build/docs",
|
||||
"dev": "tsc-watch --build --noClear"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.12",
|
||||
"babel-preset-link": "*",
|
||||
"eslint-config-link": "*",
|
||||
"jest-config-link": "*",
|
||||
"tsc-watch": "^6.0.4",
|
||||
"tsconfig-link": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
import { defState, start, stop, isStarted } from ".";
|
||||
|
||||
const _inc41 = (): number => 41 + 1;
|
||||
const _inc = (n: number): number => n + 1;
|
||||
|
||||
const obj = defState("obj", {
|
||||
start: async () => ({
|
||||
value: 42,
|
||||
}),
|
||||
});
|
||||
|
||||
const mutableObj = defState("mutableObj", {
|
||||
start: async () => ({
|
||||
value: 41,
|
||||
}),
|
||||
});
|
||||
|
||||
type FortyOneAdder = () => number;
|
||||
|
||||
type Incrementer = (n: number) => number;
|
||||
|
||||
const inc41 = defState<FortyOneAdder>("inc41", {
|
||||
isFunction: true,
|
||||
start: async () => _inc41,
|
||||
});
|
||||
const inc = defState<Incrementer>("inc", {
|
||||
isFunction: true,
|
||||
start: async () => _inc,
|
||||
});
|
||||
|
||||
describe("defstate", () => {
|
||||
beforeEach(async () => {
|
||||
await start();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await stop();
|
||||
});
|
||||
|
||||
test("obj", async () => {
|
||||
expect(obj.value).toBe(42);
|
||||
});
|
||||
|
||||
test("mutable obj", async () => {
|
||||
expect(mutableObj.value).toBe(41);
|
||||
mutableObj.value++;
|
||||
expect(mutableObj.value).toBe(42);
|
||||
});
|
||||
|
||||
test("inc41", async () => {
|
||||
expect(inc41()).toBe(42);
|
||||
expect(isStarted("inc41")).toBe(true);
|
||||
});
|
||||
test("inc", async () => {
|
||||
expect(inc(41)).toBe(42);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("doesn't exist", () => {
|
||||
expect(() => isStarted("not-real")).toThrow();
|
||||
});
|
||||
test("invalid type", () => {
|
||||
defState("invalid", { start: () => 42 as any });
|
||||
expect(start()).rejects.toThrow();
|
||||
});
|
||||
|
||||
test("multiple defs", () => {
|
||||
defState("foo", { async start() {} });
|
||||
expect(() => {
|
||||
defState("foo", { async start() {} });
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import Debug from "debug";
|
||||
import { mutableProxyFactory } from "./proxy.js";
|
||||
|
||||
const debug = Debug("montar");
|
||||
|
||||
export type Start<T> = () => Promise<T>;
|
||||
|
||||
interface StateMeta<T> {
|
||||
name: string;
|
||||
start: Start<T>;
|
||||
stop(): Promise<void>;
|
||||
isFunction: boolean;
|
||||
setHandler<T extends object>(handler: ProxyHandler<T>): void;
|
||||
setTarget<T>(target: T): void;
|
||||
}
|
||||
|
||||
interface DefState<T> {
|
||||
start: Start<T>;
|
||||
stop?(): Promise<void>;
|
||||
isFunction?: boolean;
|
||||
}
|
||||
interface StateMetaMap<T> {
|
||||
[name: string]: StateMeta<T>;
|
||||
}
|
||||
|
||||
interface StateMap {
|
||||
[name: string]: any;
|
||||
}
|
||||
|
||||
const defaultStop = async () => {};
|
||||
const states: StateMap = {};
|
||||
const statesMeta: StateMetaMap<any> = {};
|
||||
const statesOrder: string[] = [];
|
||||
|
||||
function getState(name: string) {
|
||||
if (!statesMeta.hasOwnProperty(name)) {
|
||||
throw new Error(`State ${name} not started.`);
|
||||
}
|
||||
|
||||
return states[name];
|
||||
}
|
||||
|
||||
function setState(name: string, state: any) {
|
||||
states[name] = state;
|
||||
}
|
||||
|
||||
function getStateMeta(name: string) {
|
||||
return statesMeta[name];
|
||||
}
|
||||
|
||||
function setStateMeta(name: string, meta: StateMeta<any>) {
|
||||
statesMeta[name] = meta;
|
||||
}
|
||||
|
||||
export function isStarted(name: string): boolean {
|
||||
return getState(name) !== undefined;
|
||||
}
|
||||
|
||||
const canary = {
|
||||
error:
|
||||
"I am the bare proxy. You shouldn't see me. If you see me, then a montar state was not started.",
|
||||
};
|
||||
export function defState<T>(name: string, meta: DefState<T>): any {
|
||||
if (statesMeta.hasOwnProperty(name)) {
|
||||
throw new Error(`Already registered ${name}`);
|
||||
}
|
||||
|
||||
const { start, stop = defaultStop, isFunction = false } = meta;
|
||||
|
||||
let initialTarget: any = canary;
|
||||
if (isFunction) initialTarget = () => canary.error;
|
||||
const { proxy, setTarget, setHandler } = mutableProxyFactory(initialTarget);
|
||||
|
||||
setStateMeta(name, {
|
||||
name,
|
||||
start,
|
||||
stop,
|
||||
setTarget,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
setHandler,
|
||||
isFunction,
|
||||
});
|
||||
statesOrder.push(name);
|
||||
|
||||
debug(`defined: ${name}`);
|
||||
return proxy;
|
||||
}
|
||||
|
||||
async function _start(toStart?: string[]): Promise<void> {
|
||||
for (const name of toStart) {
|
||||
debug(` >> starting.. ${name}`);
|
||||
const meta = getStateMeta(name);
|
||||
const state = await meta.start();
|
||||
const stateType = typeof state;
|
||||
if (!["object", "function"].includes(stateType)) {
|
||||
throw new Error(
|
||||
`error ${name}'s start() returned a non-object, non-function type`
|
||||
);
|
||||
}
|
||||
|
||||
meta.setTarget(state);
|
||||
setState(name, state);
|
||||
}
|
||||
}
|
||||
|
||||
export async function start(): Promise<void> {
|
||||
return _start(statesOrder);
|
||||
}
|
||||
|
||||
export async function startWithout(excluded: string[]): Promise<void> {
|
||||
const toStart = statesOrder.filter((name) => !excluded.includes(name));
|
||||
return _start(toStart);
|
||||
}
|
||||
|
||||
export async function startOnly(included: string[]): Promise<void> {
|
||||
const toStart = statesOrder.filter((name) => included.includes(name));
|
||||
debug(" startOnly ", included);
|
||||
return _start(toStart);
|
||||
}
|
||||
|
||||
export async function stop(): Promise<void> {
|
||||
for (let i = statesOrder.length - 1; i >= 0; i--) {
|
||||
const name = statesOrder[i];
|
||||
if (states[name] === undefined) continue;
|
||||
const meta = statesMeta[name];
|
||||
await meta.stop();
|
||||
delete states[name];
|
||||
debug(`<< stopping.. ${name}`);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
// @ts-nocheck PITA for this file.. revisit later.
|
||||
import { mutableProxyFactory, PProxyHandler } from "./proxy";
|
||||
|
||||
describe("mutable proxy types", () => {
|
||||
test("function", async () => {
|
||||
const { proxy, setTarget } = mutableProxyFactory(() => 42);
|
||||
expect(proxy()).toBe(42);
|
||||
setTarget(() => 43);
|
||||
expect(proxy()).toBe(43);
|
||||
});
|
||||
|
||||
test("object", async () => {
|
||||
const { proxy, setTarget } = mutableProxyFactory({ value: 42 });
|
||||
expect(proxy.value).toBe(42);
|
||||
setTarget({ value: 43 });
|
||||
expect(proxy.value).toBe(43);
|
||||
});
|
||||
|
||||
test("array", async () => {
|
||||
const { proxy, setTarget } = mutableProxyFactory([42]);
|
||||
expect(proxy[0]).toBe(42);
|
||||
setTarget([43]);
|
||||
expect(proxy[0]).toBe(43);
|
||||
});
|
||||
|
||||
test("object to function", async () => {
|
||||
const { proxy, setTarget } = mutableProxyFactory({ value: 42 });
|
||||
expect(proxy.value).toBe(42);
|
||||
setTarget(() => 43);
|
||||
expect(() => proxy()).toThrow();
|
||||
});
|
||||
test("scalar", async () => {
|
||||
expect(() => {
|
||||
mutableProxyFactory(42);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test("boolean", async () => {
|
||||
expect(() => {
|
||||
mutableProxyFactory(false);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test("null", async () => {
|
||||
expect(() => {
|
||||
mutableProxyFactory(null); // eslint-disable-line unicorn/no-null
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test("undefined", async () => {
|
||||
expect(() => {
|
||||
mutableProxyFactory();
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test("setHandler", async () => {
|
||||
const { proxy, setHandler } = mutableProxyFactory({ value: 42 });
|
||||
setHandler(new PProxyHandler());
|
||||
expect(proxy.value).toBe(42);
|
||||
});
|
||||
|
||||
test("setTarget", async () => {
|
||||
const { proxy, setTarget } = mutableProxyFactory({ value: 42 });
|
||||
setTarget([43]);
|
||||
expect(proxy[0]).toBe(43);
|
||||
});
|
||||
|
||||
test("setHandler simple", async () => {
|
||||
const { proxy, setHandler } = mutableProxyFactory({ value: 41 });
|
||||
expect(proxy.value).toBe(41);
|
||||
setHandler({
|
||||
get: () => 42,
|
||||
});
|
||||
expect(proxy.value).toBe(42);
|
||||
});
|
||||
|
||||
test("getHandler", async () => {
|
||||
const { getHandler, setHandler, proxy } = mutableProxyFactory({
|
||||
value: 42,
|
||||
});
|
||||
const handler = new PProxyHandler();
|
||||
setHandler(handler);
|
||||
expect(getHandler()).toBe(handler);
|
||||
expect(proxy.value).toBe(42);
|
||||
});
|
||||
|
||||
test("getTarget", async () => {
|
||||
const { getTarget } = mutableProxyFactory({ value: 42 });
|
||||
expect(getTarget().value).toBe(42);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
/* eslint-disable no-new,no-useless-call */
|
||||
// mutableProxyFactory from https://stackoverflow.com/a/54460544
|
||||
// (C) Alex Hall https://stackoverflow.com/users/2482744/alex-hall
|
||||
// License CC BY-SA 3.0
|
||||
/* eslint-disable @typescript-eslint/ban-types */
|
||||
|
||||
export class PProxyHandler<T extends object> implements ProxyHandler<T> {
|
||||
getPrototypeOf?(target: T): object | null {
|
||||
return Reflect.getPrototypeOf(target);
|
||||
}
|
||||
|
||||
setPrototypeOf?(target: T, v: any): boolean {
|
||||
return Reflect.setPrototypeOf(target, v);
|
||||
}
|
||||
|
||||
isExtensible?(target: T): boolean {
|
||||
return Reflect.isExtensible(target);
|
||||
}
|
||||
|
||||
preventExtensions?(target: T): boolean {
|
||||
return Reflect.preventExtensions(target);
|
||||
}
|
||||
|
||||
getOwnPropertyDescriptor?(
|
||||
target: T,
|
||||
p: PropertyKey
|
||||
): PropertyDescriptor | undefined {
|
||||
return Reflect.getOwnPropertyDescriptor(target, p);
|
||||
}
|
||||
|
||||
has?(target: T, p: PropertyKey): boolean {
|
||||
return Reflect.has(target, p);
|
||||
}
|
||||
|
||||
get?(target: T, p: PropertyKey, receiver: any): any {
|
||||
return Reflect.get(target, p, receiver);
|
||||
}
|
||||
|
||||
set?(target: T, p: PropertyKey, value: any, receiver: any): boolean {
|
||||
return Reflect.set(target, p, value, receiver);
|
||||
}
|
||||
|
||||
deleteProperty?(target: T, p: PropertyKey): boolean {
|
||||
return Reflect.deleteProperty(target, p);
|
||||
}
|
||||
|
||||
defineProperty?(
|
||||
target: T,
|
||||
p: PropertyKey,
|
||||
attributes: PropertyDescriptor
|
||||
): boolean {
|
||||
return Reflect.defineProperty(target, p, attributes);
|
||||
}
|
||||
|
||||
enumerate?(target: T): PropertyKey[] {
|
||||
return Reflect.ownKeys(target);
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
ownKeys?(target: T): PropertyKey[] {
|
||||
return Reflect.ownKeys(target);
|
||||
}
|
||||
|
||||
apply?(target: T, thisArg: any, argArray?: any): any {
|
||||
return Reflect.apply(target as Function, thisArg, argArray);
|
||||
}
|
||||
|
||||
construct?(target: T, argArray: any, newTarget?: any): object {
|
||||
return Reflect.construct(target as Function, argArray, newTarget);
|
||||
}
|
||||
}
|
||||
|
||||
interface MutableProxy<T extends object> {
|
||||
setTarget(target: T): void;
|
||||
setHandler(handler: PProxyHandler<T>): void;
|
||||
getTarget(): T;
|
||||
getHandler(): ProxyHandler<T>;
|
||||
proxy: T;
|
||||
}
|
||||
|
||||
export function mutableProxyFactory<T extends object>(
|
||||
mutableTarget: T,
|
||||
mutableHandler?: ProxyHandler<T>
|
||||
): MutableProxy<T> {
|
||||
if (!mutableHandler) mutableHandler = new PProxyHandler() as any;
|
||||
return {
|
||||
setTarget(target: T): void {
|
||||
new Proxy(target, {}); // test target validity
|
||||
mutableTarget = target;
|
||||
},
|
||||
setHandler(handler: PProxyHandler<T>): void {
|
||||
new Proxy({}, handler as any); // test handler validity
|
||||
Object.keys(handler).forEach((key) => {
|
||||
const value = handler[key];
|
||||
if (Reflect[key] && typeof value !== "function") {
|
||||
throw new Error(`Trap "${key}: ${value}" is not a function`);
|
||||
}
|
||||
});
|
||||
mutableHandler = handler as any;
|
||||
},
|
||||
getTarget(): T {
|
||||
return mutableTarget;
|
||||
},
|
||||
// @ts-expect-error
|
||||
getHandler(): PProxyHandler<T> {
|
||||
return mutableHandler as any;
|
||||
},
|
||||
proxy: new Proxy(
|
||||
mutableTarget,
|
||||
new Proxy(
|
||||
{},
|
||||
{
|
||||
// Dynamically forward all the traps to the associated methods on the mutable handler
|
||||
get(target, property) {
|
||||
return (_target, ...args) =>
|
||||
mutableHandler[property].apply(mutableHandler, [
|
||||
mutableTarget,
|
||||
...args,
|
||||
]);
|
||||
},
|
||||
}
|
||||
)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import { defState, stop, startOnly, startWithout } from ".";
|
||||
|
||||
const startOnlyState = defState("startOnlyState", {
|
||||
start: async () => ({
|
||||
value: 42,
|
||||
}),
|
||||
});
|
||||
|
||||
const startedState = defState("startState", {
|
||||
start: async () => ({
|
||||
value: 42,
|
||||
}),
|
||||
});
|
||||
|
||||
const neverStartedState = defState("neverStartedState", {
|
||||
start: async () => ({
|
||||
value: 42,
|
||||
}),
|
||||
});
|
||||
|
||||
describe("starting", () => {
|
||||
afterEach(async () => stop());
|
||||
|
||||
test("startOnly", async () => {
|
||||
await startOnly(["startOnlyState"]);
|
||||
expect(startOnlyState.value).toBe(42);
|
||||
expect(startedState.value).toBe(undefined);
|
||||
expect(neverStartedState.value).toBe(undefined);
|
||||
});
|
||||
|
||||
test("startWithout", async () => {
|
||||
await startWithout(["neverStartedState"]);
|
||||
expect(startOnlyState.value).toBe(42);
|
||||
expect(startedState.value).toBe(42);
|
||||
expect(neverStartedState.value).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"extends": "tsconfig-link",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"incremental": true,
|
||||
"outDir": "build/main",
|
||||
"rootDir": "src",
|
||||
"baseUrl": "./",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules/**"]
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"exclude": ["node_modules"],
|
||||
"extends": "./tsconfig.json"
|
||||
}
|
||||
2
packages/node-signald/.gitignore
vendored
2
packages/node-signald/.gitignore
vendored
|
|
@ -1,2 +0,0 @@
|
|||
node_modules
|
||||
dist
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [0.0.3](https://gitlab.com/digiresilience/link/node-signald/compare/v0.0.2...v0.0.3) (2022-01-03)
|
||||
|
||||
### [0.0.2](https://gitlab.com/digiresilience/link/node-signald/compare/v0.0.1...v0.0.2) (2021-10-08)
|
||||
|
||||
### 0.0.1 (2021-10-08)
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
import { SignaldAPI, CaptchaRequiredException } from "node-signald";
|
||||
import * as process from "process";
|
||||
import * as prompt from "prompt";
|
||||
|
||||
const SOCKETFILE =
|
||||
process.env.SIGNALD_SOCKET || "/run/user/1000/signald/signald.sock";
|
||||
|
||||
const validate = () => {
|
||||
if (!process.env.NUMBER)
|
||||
throw new Error(
|
||||
"Please set the NUMBER env var to the number you want to test with."
|
||||
);
|
||||
};
|
||||
const main = async () => {
|
||||
validate();
|
||||
const signald = new SignaldAPI();
|
||||
await signald.connectAsync(SOCKETFILE);
|
||||
try {
|
||||
await signald.register(process.env.NUMBER);
|
||||
} catch (e) {
|
||||
console.log("GOT A error", e.name);
|
||||
if (e.name === "CaptchaRequiredException") {
|
||||
console.log(`
|
||||
|
||||
CAPTCHA REQUIRED
|
||||
-----------------
|
||||
|
||||
1. Visit https://signalcaptchas.org/registration/generate.html
|
||||
2. Appease the machine
|
||||
3. Bring back your captcha gobbly-gook here
|
||||
`);
|
||||
console.log("captcha required");
|
||||
|
||||
prompt.start();
|
||||
const { captcha } = await prompt.get(["captcha"]);
|
||||
await signald.register(process.env.NUMBER, false, captcha);
|
||||
} else {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
const { code } = await prompt.get(["code"]);
|
||||
await signald.verify(process.env.NUMBER, code);
|
||||
};
|
||||
|
||||
const main2 = async () => {
|
||||
validate();
|
||||
const signald = new SignaldAPI();
|
||||
await signald.connectAsync(SOCKETFILE);
|
||||
let result = await signald.listAccounts();
|
||||
console.log(JSON.stringify(result));
|
||||
signald.on("messagev0", (envelope) => {
|
||||
const source = envelope.source.number;
|
||||
const body = envelope.dataMessage.body;
|
||||
const when = new Date(envelope.timestamp).toDateString();
|
||||
console.log(`${when} [${source}]: ${body}`);
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
result.accounts.map(
|
||||
async (account: any) => await signald.requestSync(account.address.uuid)
|
||||
)
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
result.accounts.map(
|
||||
async (account: any) => await signald.subscribev0(account.address.uuid)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
// main();
|
||||
main2();
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"name": "node-signald-example",
|
||||
"version": "0.0.1",
|
||||
"description": "example usage",
|
||||
"main": "index.js",
|
||||
"license": "AGPL-3.0-only",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"example": "node --unhandled-rejections=strict -r ts-node/register --unhandled-rejections=strict example.ts",
|
||||
"linklib": "cd ../ && yarn build && yarn link && cd example && yarn link node-signald"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"prompt": "^1.1.0",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"module": "commonjs",
|
||||
"target": "es2019",
|
||||
"lib": ["es2020"],
|
||||
"declaration": true,
|
||||
"esModuleInterop": true,
|
||||
"outDir": "./dist",
|
||||
"types": ["node", "jest"],
|
||||
|
||||
"moduleResolution": "node",
|
||||
"inlineSourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"traceResolution": false,
|
||||
"listEmittedFiles": false,
|
||||
"listFiles": false,
|
||||
"pretty": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
},
|
||||
"include": ["example.ts"]
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
{
|
||||
"name": "@digiresilience/node-signald",
|
||||
"version": "1.0.0",
|
||||
"description": "signald bindings for node.js",
|
||||
"author": "Abel Luck <abel@guardianproject.info>",
|
||||
"license": "AGPL-3.0-only",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
"main": "build/main/index.js",
|
||||
"types": "types/main/index.d.ts",
|
||||
"files": [
|
||||
"/dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12.9.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --build --verbose",
|
||||
"dev": "tsc-watch --build --noClear",
|
||||
"generate": "node util/generate.js && prettier src/generated.ts -w --loglevel error && npm run build",
|
||||
"doc": "typedoc src/ --exclude '**/*.test.ts' --exclude '**/*.spec.ts' --name $npm_package_name --readme README.md --out dist/docs",
|
||||
"fix": "echo n/a",
|
||||
"lint": "echo n/a",
|
||||
"fix:lint": "echo n/a",
|
||||
"fmt": "prettier \"src/**/*.ts\" --write",
|
||||
"test": "echo n/a"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/backoff": "^2.5.5",
|
||||
"babel-preset-link": "*",
|
||||
"camelcase": "^8.0.0",
|
||||
"eslint-config-link": "*",
|
||||
"jest-config-link": "*",
|
||||
"tsc-watch": "^6.0.4",
|
||||
"tsconfig-link": "*",
|
||||
"typedoc": "^0.25.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"backoff": "^2.5.0",
|
||||
"camelcase-keys": "^9.1.3",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"snakecase-keys": "^6.0.0",
|
||||
"ts-custom-error": "^3.3.1",
|
||||
"uuid": "^9.0.1"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
import { SignaldGeneratedApi, JsonMessageEnvelopev1 } from "./generated.js";
|
||||
|
||||
export class SignaldAPI extends SignaldGeneratedApi {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public async subscribev0(account: string): Promise<void> {
|
||||
return this.getResponse({
|
||||
type: "subscribe",
|
||||
username: account,
|
||||
}) as Promise<void>;
|
||||
}
|
||||
|
||||
public async unsubscribev0(account: string): Promise<void> {
|
||||
return this.getResponse({
|
||||
type: "unsubscribe",
|
||||
username: account,
|
||||
}) as Promise<void>;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue