34 Commits

Author SHA1 Message Date
59cd6083a6 chore(release): 1.2.8 [skip ci] 2023-07-22 14:31:14 +00:00
9a1684e22b fix: update dependencies to latest 2023-07-22 16:26:27 +02:00
23d2a9da71 chore(release): 1.2.7 [skip ci] 2023-07-02 16:58:58 +00:00
8e238f80c5 docs: use ssh to clone the repository 2023-07-02 18:54:02 +02:00
ca7de85e4b chore: enable source maps for easier debugging 2023-07-02 18:52:01 +02:00
ab19598edd fix: update author - Théo LUDWIG 2023-07-02 18:46:58 +02:00
1c1644f243 fix: update dependencies to latest 2023-07-02 18:45:54 +02:00
10110d1a36 chore(release): 1.2.6 [skip ci] 2023-05-13 18:12:41 +00:00
73d2da66b2 fix: update dependencies to latest 2023-05-13 20:09:02 +02:00
b07a62de8b chore(release): 1.2.5 [skip ci] 2023-04-02 21:49:01 +00:00
8a327eb7c7 fix: update dependencies to latest 2023-04-02 23:45:47 +02:00
78d7dbdb3f chore(release): 1.2.4 [skip ci] 2023-01-11 17:04:21 +00:00
e30a66eeb6 fix: update dependencies to latest 2023-01-11 18:02:38 +01:00
12dcabccb3 chore(release): 1.2.3 [skip ci] 2022-12-13 10:40:30 +00:00
224d3b3764 fix: dependencies security vulnerabilities (fastify) 2022-12-13 10:38:59 +00:00
fdecf5ce1a chore(release): 1.2.2 [skip ci] 2022-11-08 11:22:53 +00:00
6926132a1b fix: dependencies security vulnerabilities 2022-11-08 11:16:24 +00:00
e1543becc5 chore(release): 1.2.1 [skip ci] 2022-10-04 13:06:50 +00:00
b985172cd0 fix: schemaValidationMessages errors in Swagger docs 2022-10-04 14:59:14 +02:00
8ac1696ca0 docs: add information for development 2022-10-04 14:58:12 +02:00
de34618a7c chore: simplify Docker setup 2022-08-30 17:57:29 +02:00
400dc7ec2a chore: fix Dockerfile 2022-08-30 17:53:57 +02:00
49ac4f6ca4 chore(release): 1.2.0 [skip ci] 2022-08-29 17:47:08 +00:00
8e69511e3e docs: add oauth2 tag 2022-08-29 17:37:00 +00:00
7e305429b4 feat: make JWT refreshTokens more secure
Don't store the token itself in the database, store a UUID, and when refreshing the accessToken, verify the token and verify that in the payload there is a corresponding UUID stored in the database
2022-08-29 17:26:43 +00:00
b71da7dcc9 fix: on password reset, delete all refresh tokens 2022-08-29 16:32:24 +00:00
a6dd112e4a refactor: minor changes 2022-08-29 16:10:17 +00:00
ab94d1e656 ci: fix prisma:validate error in CI 2022-08-23 23:58:38 +02:00
8483cd4772 ci: usage of ubuntu-latest 2022-08-23 23:57:52 +02:00
46745e1b7e build(deps): update latest 2022-08-23 21:53:07 +00:00
50dbab7dfe chore(release): 1.1.0 [skip ci] 2022-06-29 04:34:56 +00:00
2f78604116 fix: sort public guilds with descending members count 2022-06-29 04:16:48 +00:00
4d565e4f1f build(deps): update latest 2022-06-29 03:59:30 +00:00
183377afc3 feat: update file-uploads-api to v1.1.0 2022-06-29 03:58:26 +00:00
149 changed files with 9504 additions and 20786 deletions

View File

@ -1 +1,3 @@
{ "extends": ["@commitlint/config-conventional"] } {
"extends": ["@commitlint/config-conventional"]
}

View File

@ -1,2 +1 @@
ARG VARIANT="16" FROM mcr.microsoft.com/devcontainers/javascript-node:20
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}

View File

@ -3,19 +3,22 @@
"dockerComposeFile": "./docker-compose.yml", "dockerComposeFile": "./docker-compose.yml",
"service": "workspace", "service": "workspace",
"workspaceFolder": "/workspace", "workspaceFolder": "/workspace",
"settings": { "customizations": {
"remote.autoForwardPorts": false "vscode": {
"settings": {
"remote.autoForwardPorts": false,
"remote.localPortHost": "allInterfaces"
}
},
"extensions": [
"editorconfig.editorconfig",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"davidanson.vscode-markdownlint",
"prisma.prisma",
"mikestead.dotenv",
"ms-azuretools.vscode-docker"
]
}, },
"extensions": [
"editorconfig.editorconfig",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"davidanson.vscode-markdownlint",
"prisma.prisma",
"mikestead.dotenv",
"ms-azuretools.vscode-docker"
],
"forwardPorts": [8080, 5555, 5432, 1080],
"postAttachCommand": ["npm", "install"],
"remoteUser": "node" "remoteUser": "node"
} }

View File

@ -1,5 +1,3 @@
version: '3.0'
services: services:
workspace: workspace:
build: build:
@ -8,23 +6,18 @@ services:
volumes: volumes:
- '..:/workspace:cached' - '..:/workspace:cached'
command: 'sleep infinity' command: 'sleep infinity'
extra_hosts: network_mode: 'host'
- 'host.docker.internal:host-gateway'
thream-database: thream-database:
image: 'postgres:14.2' image: 'postgres:15.3'
environment: environment:
POSTGRES_USER: 'user' POSTGRES_USER: 'thream_user'
POSTGRES_PASSWORD: 'password' POSTGRES_PASSWORD: 'password'
POSTGRES_DB: 'thream' POSTGRES_DB: 'thream'
volumes: volumes:
- 'postgres-data:/var/lib/postgresql/data' - 'thream-postgres-data:/var/lib/postgresql/data'
restart: 'unless-stopped' restart: 'unless-stopped'
network_mode: 'host'
thream-maildev:
image: 'maildev/maildev:1.1.0'
ports:
- '1080:80'
volumes: volumes:
postgres-data: thream-postgres-data:

View File

@ -1,9 +1,6 @@
.vscode .*
.git !.npmrc
.env !.swcrc
build build
coverage coverage
.nyc_output
node_modules node_modules
tmp
temp

View File

@ -1,20 +1,29 @@
API_URL=http://localhost:8080 API_URL=http://127.0.0.1:8080
COMPOSE_PROJECT_NAME=thream-api COMPOSE_PROJECT_NAME=thream-api
DATABASE_URL=postgresql://user:password@thream-database:5432/thream
DATABASE_USER=thream_user
DATABASE_PASSWORD=password
DATABASE_NAME=thream
DATABASE_URL=postgresql://thream_user:password@127.0.0.1:5432/thream
EMAIL_HOST=0.0.0.0
EMAIL_PASSWORD=password
EMAIL_PORT=1025
EMAIL_USER=no-reply@thream.fr
DISCORD_CLIENT_ID= DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET= DISCORD_CLIENT_SECRET=
EMAIL_HOST=thream-maildev
EMAIL_PASSWORD=password
EMAIL_PORT=25
EMAIL_USER=no-reply@thream.fr
FILE_UPLOADS_API_KEY=apiKeySecret FILE_UPLOADS_API_KEY=apiKeySecret
FILE_UPLOADS_API_URL=http://host.docker.internal:8000 FILE_UPLOADS_API_URL=http://127.0.0.1:8000
GITHUB_CLIENT_ID= GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET= GITHUB_CLIENT_SECRET=
GOOGLE_CLIENT_ID= GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET= GOOGLE_CLIENT_SECRET=
HOST=0.0.0.0 HOST=0.0.0.0
JWT_ACCESS_EXPIRES_IN=15 minutes JWT_ACCESS_EXPIRES_IN=15 minutes
# You can generate JWT secrets with the `npm run generate:jwt-secret` command.
JWT_ACCESS_SECRET=accessTokenSecret JWT_ACCESS_SECRET=accessTokenSecret
JWT_REFRESH_SECRET=refreshTokenSecret JWT_REFRESH_SECRET=refreshTokenSecret
NODE_ENV=development NODE_ENV=development

View File

@ -1,16 +1,13 @@
{ {
"extends": ["conventions", "prettier"], "extends": ["conventions", "prettier"],
"plugins": ["prettier", "import", "unicorn"], "plugins": ["prettier", "import", "unicorn"],
"parser": "@typescript-eslint/parser",
"parserOptions": { "parserOptions": {
"project": "./tsconfig.json" "project": "./tsconfig.json"
}, },
"env": {
"node": true
},
"rules": { "rules": {
"prettier/prettier": "error", "prettier/prettier": "error",
"import/extensions": ["error", "always"], "import/extensions": ["error", "always"],
"unicorn/prevent-abbreviations": "error", "unicorn/prevent-abbreviations": "error"
"unicorn/prefer-node-protocol": "error"
} }
} }

View File

@ -1,6 +1,6 @@
<!-- Please first discuss the change you wish to make via issue before making a change. It might avoid a waste of your time. --> <!-- Please first discuss the change you wish to make via issue before making a change. It might avoid a waste of your time. -->
## What changes this PR introduce? # What changes this PR introduce?
## List any relevant issue numbers ## List any relevant issue numbers

View File

@ -16,10 +16,10 @@ jobs:
language: ['javascript'] language: ['javascript']
steps: steps:
- uses: 'actions/checkout@v3.0.0' - uses: 'actions/checkout@v3.5.3'
- name: 'Initialize CodeQL' - name: 'Initialize CodeQL'
uses: 'github/codeql-action/init@v1' uses: 'github/codeql-action/init@v2'
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}

View File

@ -10,16 +10,18 @@ jobs:
build: build:
runs-on: 'ubuntu-latest' runs-on: 'ubuntu-latest'
steps: steps:
- uses: 'actions/checkout@v3.0.0' - uses: 'actions/checkout@v3.5.3'
- name: 'Use Node.js' - name: 'Setup Node.js'
uses: 'actions/setup-node@v3.0.0' uses: 'actions/setup-node@v3.6.0'
with: with:
node-version: '16.x' node-version: '20.x'
cache: 'npm' cache: 'npm'
- name: 'Install' - name: 'Install dependencies'
run: 'npm install' run: 'npm clean-install'
- name: 'Build' - name: 'Build'
run: 'npm run build' run: 'npm run build'
- run: 'npm run build:typescript'

View File

@ -10,16 +10,16 @@ jobs:
lint: lint:
runs-on: 'ubuntu-latest' runs-on: 'ubuntu-latest'
steps: steps:
- uses: 'actions/checkout@v3.0.0' - uses: 'actions/checkout@v3.5.3'
- name: 'Use Node.js' - name: 'Setup Node.js'
uses: 'actions/setup-node@v3.0.0' uses: 'actions/setup-node@v3.6.0'
with: with:
node-version: '16.x' node-version: '20.x'
cache: 'npm' cache: 'npm'
- name: 'Install' - name: 'Install dependencies'
run: 'npm install' run: 'npm clean-install'
- name: 'lint:commit' - name: 'lint:commit'
run: 'npm run lint:commit -- --to "${{ github.sha }}"' run: 'npm run lint:commit -- --to "${{ github.sha }}"'
@ -30,18 +30,11 @@ jobs:
- name: 'lint:markdown' - name: 'lint:markdown'
run: 'npm run lint:markdown' run: 'npm run lint:markdown'
- name: 'lint:typescript' - name: 'lint:eslint'
run: 'npm run lint:typescript' run: 'npm run lint:eslint'
- name: 'lint:prettier' - name: 'lint:prettier'
run: 'npm run lint:prettier' run: 'npm run lint:prettier'
- name: 'lint:dotenv' - name: 'prisma:validate'
uses: 'dotenv-linter/action-dotenv-linter@v2' run: 'cp .env.example .env && npm run prisma:validate'
with:
github_token: ${{ secrets.github_token }}
- name: 'lint:docker'
uses: 'hadolint/hadolint-action@v1.6.0'
with:
dockerfile: './Dockerfile'

View File

@ -8,30 +8,32 @@ jobs:
release: release:
runs-on: 'ubuntu-latest' runs-on: 'ubuntu-latest'
steps: steps:
- uses: 'actions/checkout@v3.0.0' - uses: 'actions/checkout@v3.5.3'
with: with:
fetch-depth: 0 fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: 'Import GPG key' - name: 'Import GPG key'
uses: 'crazy-max/ghaction-import-gpg@v3.2.0' uses: 'crazy-max/ghaction-import-gpg@v5.3.0'
with: with:
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
git-user-signingkey: true git_user_signingkey: true
git-commit-gpgsign: true git_commit_gpgsign: true
- name: 'Use Node.js' - name: 'Setup Node.js'
uses: 'actions/setup-node@v3.0.0' uses: 'actions/setup-node@v3.6.0'
with: with:
node-version: '16.x' node-version: '20.x'
cache: 'npm' cache: 'npm'
- name: 'Install' - name: 'Install dependencies'
run: 'npm install' run: 'npm clean-install'
- name: 'Build' - name: 'Build'
run: 'npm run build' run: 'npm run build'
- run: 'npm run build:typescript'
- name: 'Release' - name: 'Release'
run: 'npm run release' run: 'npm run release'
env: env:

View File

@ -8,18 +8,18 @@ on:
jobs: jobs:
test: test:
runs-on: 'macos-latest' runs-on: 'ubuntu-latest'
steps: steps:
- uses: 'actions/checkout@v3.0.0' - uses: 'actions/checkout@v3.5.3'
- name: 'Use Node.js' - name: 'Setup Node.js'
uses: 'actions/setup-node@v3.0.0' uses: 'actions/setup-node@v3.6.0'
with: with:
node-version: '16.x' node-version: '20.x'
cache: 'npm' cache: 'npm'
- name: 'Install' - name: 'Install dependencies'
run: 'npm install' run: 'npm clean-install'
- name: 'Build' - name: 'Build'
run: 'npm run build' run: 'npm run build'

1
.gitignore vendored
View File

@ -35,3 +35,4 @@ npm-debug.log*
# misc # misc
.DS_Store .DS_Store
*.hbs

View File

@ -3,3 +3,4 @@
npm run lint:staged npm run lint:staged
npm run build npm run build
npm run build:typescript

View File

@ -2,5 +2,6 @@
"*": ["editorconfig-checker"], "*": ["editorconfig-checker"],
"*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix"], "*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix"],
"*.{json,jsonc,yml,yaml}": ["prettier --write"], "*.{json,jsonc,yml,yaml}": ["prettier --write"],
"*.md": ["prettier --write", "markdownlint --dot --fix"] "*.{md,mdx}": ["prettier --write", "markdownlint-cli2 --fix"],
"prisma/schema.prisma": ["prisma validate"]
} }

11
.markdownlint-cli2.jsonc Normal file
View File

@ -0,0 +1,11 @@
{
"config": {
"extends": "markdownlint/style/prettier",
"relative-links": true,
"default": true,
"MD033": false
},
"globs": ["**/*.{md,mdx}"],
"ignores": ["**/node_modules"],
"customRules": ["markdownlint-rule-relative-links"]
}

View File

@ -1,6 +0,0 @@
{
"default": true,
"MD013": false,
"MD033": false,
"MD041": false
}

View File

@ -1,5 +0,0 @@
{
"reporter": ["text", "cobertura"],
"src": "./build",
"all": true
}

View File

@ -1,6 +0,0 @@
build
node_modules
coverage
package.json
package-lock.json
*.hbs

15
.swcrc
View File

@ -1,22 +1,13 @@
{ {
"sourceMaps": true,
"jsc": { "jsc": {
"parser": { "parser": {
"syntax": "typescript", "syntax": "typescript",
"decorators": true,
"dynamicImport": true "dynamicImport": true
}, },
"transform": { "target": "es2022"
"legacyDecorator": true,
"decoratorMetadata": true
},
"target": "es2022",
"loose": true
}, },
"module": { "module": {
"type": "es6", "type": "es6"
"strict": false,
"strictMode": true,
"lazy": false,
"noInterop": false
} }
} }

8
.taprc
View File

@ -1,8 +0,0 @@
ts: false
jsx: false
flow: false
check-coverage: false
coverage: false
files:
- 'build/**/*.test.js'

View File

@ -60,7 +60,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at reported to the community leaders responsible for enforcement at
contact@divlo.fr. <contact@theoludwig.fr>.
All complaints will be reviewed and investigated promptly and fairly. All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the All community leaders are obligated to respect the privacy and security of the

View File

@ -29,38 +29,14 @@ If you're adding new features to **Thream/api**, please include tests.
## Commits ## Commits
The commit message guidelines respect The commit message guidelines adheres to [Conventional Commits](https://www.conventionalcommits.org/) and [Semantic Versioning](https://semver.org/) for releases.
[@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional)
and [Semantic Versioning](https://semver.org/) for releases.
### Types
Types define which kind of changes you made to the project.
| Types | Description |
| -------- | ------------------------------------------------------------------------------------------------------------ |
| feat | A new feature. |
| fix | A bug fix. |
| docs | Documentation only changes. |
| style | Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc). |
| refactor | A code change that neither fixes a bug nor adds a feature. |
| perf | A code change that improves performance. |
| test | Adding missing tests or correcting existing tests. |
| build | Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm). |
| ci | Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs). |
| chore | Other changes that don't modify src or test files. |
| revert | Reverts a previous commit. |
### Scopes
Scopes define what part of the code changed.
### Examples ### Examples
```sh ```sh
git commit -m "feat(services): add POST /users/signup" git commit -m "feat: add POST /users/signup"
git commit -m "docs(readme): update installation process" git commit -m "docs(readme): update installation process"
git commit -m "fix(services): should emit events to connected users" git commit -m "fix: should emit events to connected users"
``` ```
## Directory Structure ## Directory Structure
@ -86,13 +62,12 @@ git commit -m "fix(services): should emit events to connected users"
- `services` : all REST API endpoints - `services` : all REST API endpoints
- `tools` : configs and utilities - `tools` : configs and utilities
- `typings` : types gloablly used in the project - `typings` : types gloablly used in the project
- `uploads` : uploaded files by users
### Services folder explained with an example ### Services folder explained with an example
We have API REST services for the `channels`. We have API REST services for the `channels`.
Here is what potentially look like a folder structure for this service : Here is what potentially look like a folder structure for this service:
```text ```text
└── src └── src
@ -111,7 +86,7 @@ Here is what potentially look like a folder structure for this service :
└── index.ts └── index.ts
``` ```
This folder structure will map to these REST API routes : This folder structure will map to these REST API routes:
- GET `/channels` - GET `/channels`
- DELETE `/channels/:channelId` - DELETE `/channels/:channelId`
@ -121,3 +96,5 @@ The folders after `src/services` : is the real path of the routes in the API exc
folders starting and ending with `__` like `__test__` or `__utils__`. folders starting and ending with `__` like `__test__` or `__utils__`.
The filenames correspond to the HTTP methods used (`get`, `post`, `put`, `delete`). The filenames correspond to the HTTP methods used (`get`, `post`, `put`, `delete`).
You can generate the boilerplate code for a new service with the `npm run generate` command.

View File

@ -1,23 +1,22 @@
FROM node:16.14.2 AS dependencies FROM node:20.5.0 AS dependencies
WORKDIR /usr/src/app WORKDIR /usr/src/app
COPY ./package*.json ./ COPY ./package*.json ./
RUN npm install RUN npm clean-install
FROM node:16.14.2 AS builder FROM node:20.5.0 AS builder
WORKDIR /usr/src/app WORKDIR /usr/src/app
COPY --from=dependencies /usr/src/app/node_modules ./node_modules COPY --from=dependencies /usr/src/app/node_modules ./node_modules
COPY ./ ./ COPY ./ ./
RUN npm run prisma:generate && npm run build RUN npm run prisma:generate && npm run build
FROM node:16.14.2 AS runner FROM node:20.5.0 AS runner
WORKDIR /usr/src/app WORKDIR /usr/src/app
ENV NODE_ENV=production ENV NODE_ENV=production
ENV NODE_OPTIONS=--enable-source-maps
COPY --from=builder /usr/src/app/node_modules ./node_modules COPY --from=builder /usr/src/app/node_modules ./node_modules
COPY --from=builder /usr/src/app/start.sh ./docker-start.sh
COPY --from=builder /usr/src/app/package.json ./package.json COPY --from=builder /usr/src/app/package.json ./package.json
COPY --from=builder /usr/src/app/email ./email COPY --from=builder /usr/src/app/email ./email
COPY --from=builder /usr/src/app/build ./build COPY --from=builder /usr/src/app/build ./build
COPY --from=builder /usr/src/app/prisma ./prisma COPY --from=builder /usr/src/app/prisma ./prisma
COPY --from=builder /usr/src/app/uploads ./uploads
USER node USER node
CMD ["./docker-start.sh"] CMD npm run prisma:migrate:deploy && node build/index.js

View File

@ -1,4 +1,4 @@
<h1 align="center"><a href="https://api.thream.divlo.fr/documentation">Thream/api</a></h1> <h1 align="center"><a href="https://api.thream.theoludwig.fr/documentation">Thream/api</a></h1>
<p align="center"> <p align="center">
<a href="./CONTRIBUTING.md"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" /></a> <a href="./CONTRIBUTING.md"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" /></a>
@ -18,7 +18,7 @@
Thream's Application Programming Interface (API) to stay close with your friends and communities. Thream's Application Programming Interface (API) to stay close with your friends and communities.
It uses [Thream/file-uploads-api](https://github.com/Thream/file-uploads-api) [v1.0.0](https://github.com/Thream/file-uploads-api/releases/tag/v1.0.0). It uses [Thream/file-uploads-api](https://github.com/Thream/file-uploads-api) [v1.1.6](https://github.com/Thream/file-uploads-api/releases/tag/v1.1.6).
## ⚙️ Getting Started ## ⚙️ Getting Started
@ -32,61 +32,81 @@ It uses [Thream/file-uploads-api](https://github.com/Thream/file-uploads-api) [v
```sh ```sh
# Clone the repository # Clone the repository
git clone https://github.com/Thream/api.git git clone git@github.com:Thream/api.git
# Go to the project root # Go to the project root
cd api cd api
# Install dependencies
npm clean-install
# Configure environment variables # Configure environment variables
cp .env.example .env cp .env.example .env
# Install # Generate Prisma client types
npm install npm run prisma:generate
``` ```
You will need to configure the environment variables by creating an `.env` file at ### Database Setup
the root of the project (see `.env.example`).
### Local Development environment
#### Setup the database
```sh ```sh
# Create a new user and database # Create a new user and database
psql psql
create database thream_database; CREATE DATABASE thream;
create user thream_user with encrypted password 'password'; CREATE USER thream_user with encrypted password 'password';
ALTER USER thream_user WITH SUPERUSER; ALTER USER thream_user WITH SUPERUSER;
``` ```
Replace `DATABASE_URL` inside `.env` with `postgresql://thream_user:password@localhost:5432/thream_database` ### Database Production migration
```sh
npm run prisma:migrate:deploy
```
### Local Development environment
Recommended to use [VSCode: Remote development in Containers](https://code.visualstudio.com/docs/remote/containers-tutorial).
#### Database Development migration
```sh ```sh
# Run Prisma migrations # Run Prisma migrations
npm run prisma:migrate:dev npm run prisma:migrate:dev
# Reset the database (WARNING: This will delete all data)
npm run prisma:migrate:reset
``` ```
#### Usage #### Usage
```sh ```sh
# Run API
npm run dev npm run dev
# Run Prisma Studio
npm run prisma:studio
``` ```
### Production environment with [Docker](https://www.docker.com/) ##### Services started
- `api`: <http://127.0.0.1:8080>
- [Maildev](https://maildev.github.io/maildev/): <http://127.0.0.1:1080>
- [Prisma Studio](https://www.prisma.io/studio): <http://127.0.0.1:5555>
##### Commands
```sh ```sh
# Setup and run all the services for you # Build, Lint and Test
docker-compose up --build npm run build
npm run build:typescript
npm run lint:editorconfig
npm run lint:markdown
npm run lint:eslint
npm run lint:prettier
npm run test
``` ```
#### Services started ### Production environment (with [Docker](https://www.docker.com/))
- API : `http://localhost:8080` ```sh
- [PostgreSQL database](https://www.postgresql.org/) docker compose up --build
```
## 💡 Contributing ## 💡 Contributing

View File

@ -1,29 +1,27 @@
version: '3.0'
services: services:
thream-api: thream-api:
container_name: ${COMPOSE_PROJECT_NAME} container_name: 'thream-api'
image: 'thream-api'
restart: 'unless-stopped'
network_mode: 'host'
build: build:
context: './' context: './'
env_file: env_file: '.env'
- '.env'
ports:
- '${PORT}:${PORT}'
depends_on: depends_on:
- 'thream-database' - 'thream-database'
volumes:
- './uploads:/usr/src/app/uploads'
restart: 'unless-stopped'
thream-database: thream-database:
container_name: 'thream-database' container_name: 'thream-database'
image: 'postgres:14.2' image: 'postgres:15.3'
environment:
POSTGRES_USER: 'user'
POSTGRES_PASSWORD: 'password'
POSTGRES_DB: 'thream'
volumes:
- 'database-volume:/var/lib/postgresql/data'
restart: 'unless-stopped' restart: 'unless-stopped'
network_mode: 'host'
env_file: '.env'
environment:
POSTGRES_USER: ${DATABASE_USER}
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
POSTGRES_DB: ${DATABASE_NAME}
volumes:
- 'thream-postgres-data:/var/lib/postgresql/data'
volumes: volumes:
database-volume: thream-postgres-data:

View File

@ -1,4 +0,0 @@
#!/bin/bash
npm run prisma:migrate:deploy
node build/index.js

View File

@ -22,7 +22,7 @@ export const serviceGenerator = {
type: 'list', type: 'list',
name: 'tag', name: 'tag',
message: 'tag', message: 'tag',
choices: ['users', 'guilds', 'channels', 'messages', 'members', 'uploads'] choices: ['users', 'oauth2', 'guilds', 'channels', 'messages', 'members']
}, },
{ {
type: 'confirm', type: 'confirm',

View File

@ -1,18 +1,20 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from 'application.js' import { application } from '#src/application.js'
{{#if shouldBeAuthenticated}} {{#if shouldBeAuthenticated}}
import { authenticateUserTest } from '__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
{{/if}} {{/if}}
import prisma from 'tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
await tap.test('{{httpMethod}} {{url}}', async (t) => { await test('{{httpMethod}} {{url}}', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
{{#if shouldBeAuthenticated}} {{#if shouldBeAuthenticated}}
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
{{/if}} {{/if}}
@ -32,6 +34,6 @@ await tap.test('{{httpMethod}} {{url}}', async (t) => {
payload: {} payload: {}
}) })
// const responseJson = response.json() // const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
}) })
}) })

View File

@ -1,10 +1,10 @@
import { Static, Type } from '@sinclair/typebox' import { Static, Type } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from 'tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from 'models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
{{#if shouldBeAuthenticated}} {{#if shouldBeAuthenticated}}
import authenticateUser from 'tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
{{/if}} {{/if}}
const body{{sentenceCase httpMethod}}ServiceSchema = Type.Object({ const body{{sentenceCase httpMethod}}ServiceSchema = Type.Object({

27048
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,101 +1,110 @@
{ {
"name": "@thream/api", "name": "@thream/api",
"version": "1.0.1", "version": "1.2.8",
"description": "Thream's application programming interface to stay close with your friends and communities.", "description": "Thream's application programming interface to stay close with your friends and communities.",
"private": true, "private": true,
"type": "module", "type": "module",
"repository": { "imports": {
"type": "git", "#src/*": "./build/*"
"url": "https://github.com/Thream/api"
}, },
"engines": { "engines": {
"node": ">=16.0.0", "node": ">=16.0.0",
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"repository": {
"type": "git",
"url": "https://github.com/Thream/api"
},
"scripts": { "scripts": {
"build": "rimraf ./build && swc ./src --out-dir ./build && tsc", "build": "rimraf ./build && swc ./src --out-dir ./build",
"build:dev": "swc ./src --out-dir ./build --watch", "build:typescript": "tsc",
"start": "node build/index.js", "start": "node --enable-source-maps build/index.js",
"dev": "concurrently -k -n \"TypeScript,Node\" -p \"[{name}]\" -c \"blue,green\" \"npm run build:dev\" \"cross-env NODE_ENV=development nodemon build/index.js\"", "dev:build": "swc ./src --out-dir ./build --watch",
"dev": "concurrently --kill-others --names \"TypeScript,Node,Maildev,Prisma Studio\" \"npm run dev:build\" \"cross-env NODE_ENV=development node --watch --enable-source-maps build/index.js\" \"npm run maildev\" \"npm run prisma:studio\"",
"maildev": "maildev",
"generate": "plop", "generate": "plop",
"generate:jwt-secret": "node --enable-source-maps ./build/scripts/generate-jwt-secret.js",
"lint:commit": "commitlint", "lint:commit": "commitlint",
"lint:editorconfig": "editorconfig-checker", "lint:editorconfig": "editorconfig-checker",
"lint:markdown": "markdownlint \"**/*.md\" --dot --ignore-path \".gitignore\"", "lint:markdown": "markdownlint-cli2",
"lint:typescript": "eslint \"**/*.{js,jsx,ts,tsx}\" --ignore-path \".gitignore\"", "lint:eslint": "eslint . --max-warnings 0 --report-unused-disable-directives --ignore-path .gitignore",
"lint:prettier": "prettier \".\" --check", "lint:prettier": "prettier . --check",
"lint:staged": "lint-staged", "lint:staged": "lint-staged",
"test": "cross-env NODE_ENV=test c8 tap", "test": "cross-env NODE_ENV=test node --enable-source-maps --test build/",
"prisma:validate": "prisma validate",
"prisma:generate": "prisma generate", "prisma:generate": "prisma generate",
"prisma:studio": "prisma studio", "prisma:studio": "prisma studio --browser=none",
"prisma:migrate:dev": "prisma migrate dev", "prisma:migrate:dev": "prisma migrate dev",
"prisma:migrate:deploy": "prisma migrate deploy", "prisma:migrate:deploy": "prisma migrate deploy",
"release": "semantic-release", "release": "semantic-release",
"postinstall": "husky install" "postinstall": "husky install"
}, },
"dependencies": { "dependencies": {
"@prisma/client": "3.12.0", "@fastify/cors": "8.3.0",
"@sinclair/typebox": "0.23.4", "@fastify/helmet": "11.0.0",
"@thream/socketio-jwt": "3.0.0", "@fastify/multipart": "7.6.0",
"axios": "0.26.1", "@fastify/rate-limit": "8.0.3",
"@fastify/sensible": "5.2.0",
"@fastify/swagger": "8.8.0",
"@fastify/swagger-ui": "1.9.2",
"@prisma/client": "5.0.0",
"@sinclair/typebox": "0.29.6",
"@thream/socketio-jwt": "3.1.2",
"axios": "1.4.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"dotenv": "16.0.0", "dotenv": "16.3.1",
"ejs": "3.1.6", "ejs": "3.1.9",
"fastify": "3.28.0", "fastify": "4.20.0",
"fastify-cors": "6.0.3", "fastify-plugin": "4.5.1",
"fastify-helmet": "7.0.1",
"fastify-multipart": "5.3.1",
"fastify-plugin": "3.0.1",
"fastify-rate-limit": "5.8.0",
"fastify-sensible": "3.1.2",
"fastify-swagger": "5.1.0",
"form-data": "4.0.0", "form-data": "4.0.0",
"http-errors": "2.0.0", "http-errors": "2.0.0",
"jsonwebtoken": "8.5.1", "jsonwebtoken": "9.0.1",
"ms": "2.1.3", "ms": "2.1.3",
"nodemailer": "6.7.3", "nodemailer": "6.9.4",
"read-pkg": "7.1.0", "read-pkg": "8.0.0",
"socket.io": "4.4.1" "socket.io": "4.7.1"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "16.2.3", "@commitlint/cli": "17.6.7",
"@commitlint/config-conventional": "16.2.1", "@commitlint/config-conventional": "17.6.7",
"@saithodev/semantic-release-backmerge": "2.1.2", "@saithodev/semantic-release-backmerge": "3.2.0",
"@semantic-release/git": "10.0.1", "@semantic-release/git": "10.0.1",
"@swc/cli": "0.1.57", "@swc/cli": "0.1.62",
"@swc/core": "1.2.164", "@swc/core": "1.3.70",
"@tsconfig/strictest": "2.0.1",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/busboy": "1.5.0", "@types/busboy": "1.5.0",
"@types/ejs": "3.1.0", "@types/ejs": "3.1.2",
"@types/http-errors": "1.8.2", "@types/http-errors": "2.0.1",
"@types/jsonwebtoken": "8.5.8", "@types/jsonwebtoken": "9.0.2",
"@types/ms": "0.7.31", "@types/ms": "0.7.31",
"@types/node": "17.0.23", "@types/node": "20.4.3",
"@types/nodemailer": "6.4.4", "@types/nodemailer": "6.4.8",
"@types/sinon": "10.0.11", "@types/sinon": "10.0.15",
"@types/tap": "15.0.6", "@typescript-eslint/eslint-plugin": "6.1.0",
"@typescript-eslint/eslint-plugin": "5.18.0", "@typescript-eslint/parser": "6.1.0",
"c8": "7.11.0", "chokidar": "3.5.3",
"concurrently": "7.1.0", "concurrently": "8.2.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"editorconfig-checker": "4.0.2", "editorconfig-checker": "5.1.1",
"eslint": "8.12.0", "eslint": "8.45.0",
"eslint-config-conventions": "2.0.0", "eslint-config-conventions": "11.0.1",
"eslint-config-prettier": "8.5.0", "eslint-config-prettier": "8.8.0",
"eslint-plugin-import": "2.26.0", "eslint-plugin-import": "2.27.5",
"eslint-plugin-prettier": "4.0.0", "eslint-plugin-prettier": "5.0.0",
"eslint-plugin-promise": "6.0.0", "eslint-plugin-promise": "6.1.1",
"eslint-plugin-unicorn": "42.0.0", "eslint-plugin-unicorn": "48.0.0",
"husky": "7.0.4", "husky": "8.0.3",
"lint-staged": "12.3.7", "lint-staged": "13.2.3",
"markdownlint-cli": "0.31.1", "maildev": "2.1.0",
"nodemon": "2.0.15", "markdownlint-cli2": "0.8.1",
"plop": "3.0.5", "markdownlint-rule-relative-links": "2.1.0",
"prettier": "2.6.2", "plop": "3.1.2",
"prisma": "3.12.0", "prettier": "3.0.0",
"rimraf": "3.0.2", "prisma": "5.0.0",
"semantic-release": "19.0.2", "rimraf": "5.0.1",
"sinon": "13.0.1", "semantic-release": "21.0.7",
"tap": "16.0.1", "sinon": "15.2.0",
"typescript": "4.6.3" "typescript": "5.1.6"
} }
} }

View File

@ -1,44 +1,46 @@
import { User } from '@prisma/client' import type { User } from '@prisma/client'
import sinon from 'sinon' import sinon from 'sinon'
import { refreshTokenExample } from '../../models/RefreshToken.js' import { refreshTokenExample } from '#src/models/RefreshToken.js'
import { userExample, UserJWT } from '../../models/User.js' import type { UserJWT } from '#src/models/User.js'
import { userSettingsExample } from '../../models/UserSettings.js' import { userExample } from '#src/models/User.js'
import { userSettingsExample } from '#src/models/UserSettings.js'
import { import {
generateAccessToken, generateAccessToken,
generateRefreshToken generateRefreshToken
} from '../../tools/utils/jwtToken.js' } from '#src/tools/utils/jwtToken.js'
import prisma from '../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
const userStubValue = {
findUnique: async () => {
return userExample
}
}
const userSettingStubValue = {
findFirst: async () => {
return userSettingsExample
}
}
const oAuthStubValue = {
findMany: async () => {
return []
}
}
const refreshTokenStubValue = {
create: async () => {
return refreshTokenExample
}
}
export const authenticateUserTest = async (): Promise<{ export const authenticateUserTest = async (): Promise<{
accessToken: string accessToken: string
refreshToken: string refreshToken: string
user: User user: User
userStubValue: any userStubValue: typeof userStubValue
userSettingStubValue: any userSettingStubValue: typeof userSettingStubValue
oAuthStubValue: any oAuthStubValue: typeof oAuthStubValue
refreshTokenStubValue: any refreshTokenStubValue: typeof refreshTokenStubValue
}> => { }> => {
const userStubValue = {
findUnique: async () => {
return userExample
}
}
const userSettingStubValue = {
findFirst: async () => {
return userSettingsExample
}
}
const oAuthStubValue = {
findMany: async () => {
return []
}
}
const refreshTokenStubValue = {
create: async () => {
return refreshTokenExample
}
}
sinon.stub(prisma, 'user').value(userStubValue) sinon.stub(prisma, 'user').value(userStubValue)
sinon.stub(prisma, 'userSetting').value(userSettingStubValue) sinon.stub(prisma, 'userSetting').value(userSettingStubValue)
sinon.stub(prisma, 'oAuth').value(oAuthStubValue) sinon.stub(prisma, 'oAuth').value(oAuthStubValue)

View File

@ -1,21 +1,27 @@
import dotenv from 'dotenv' import dotenv from 'dotenv'
import fastify from 'fastify' import fastify from 'fastify'
import fastifyCors from 'fastify-cors' import fastifyCors from '@fastify/cors'
import fastifySwagger from 'fastify-swagger' import fastifySwagger from '@fastify/swagger'
import fastifyHelmet from 'fastify-helmet' import fastifySwaggerUI from '@fastify/swagger-ui'
import fastifyRateLimit from 'fastify-rate-limit' import fastifyHelmet from '@fastify/helmet'
import fastifySensible from 'fastify-sensible' import fastifyRateLimit from '@fastify/rate-limit'
import fastifySensible from '@fastify/sensible'
import { readPackage } from 'read-pkg'
import { services } from './services/index.js' import { services } from '#src/services/index.js'
import { swaggerOptions } from './tools/configurations/swaggerOptions.js' import fastifySocketIo from '#src/tools/plugins/socket-io.js'
import fastifySocketIo from './tools/plugins/socket-io.js'
dotenv.config() dotenv.config()
const packageJSON = await readPackage()
export const application = fastify({ export const application = fastify({
logger: process.env.NODE_ENV === 'development', logger: process.env['NODE_ENV'] === 'development',
ajv: { ajv: {
customOptions: { customOptions: {
format: 'full' strict: 'log',
keywords: ['kind', 'modifier'],
formats: {
full: true
}
} }
} }
}) })
@ -35,5 +41,35 @@ await application.register(fastifyRateLimit, {
max: 200, max: 200,
timeWindow: '1 minute' timeWindow: '1 minute'
}) })
await application.register(fastifySwagger, swaggerOptions) await application.register(fastifySwagger, {
openapi: {
info: {
title: packageJSON.name,
description: packageJSON.description,
version: packageJSON.version
},
tags: [
{ name: 'users' },
{ name: 'oauth2' },
{ name: 'guilds' },
{ name: 'channels' },
{ name: 'messages' },
{ name: 'members' }
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
}
}
}
},
hideUntagged: true
})
await application.register(fastifySwaggerUI, {
routePrefix: '/documentation',
staticCSP: true
})
await application.register(services) await application.register(services)

View File

@ -1,5 +1,8 @@
import { application } from './application.js' import { application } from '#src/application.js'
import { HOST, PORT } from './tools/configurations/index.js' import { HOST, PORT } from '#src/tools/configurations.js'
const address = await application.listen(PORT, HOST) const address = await application.listen({
console.log('\u001B[36m%s\u001B[0m', `🚀 Server listening at ${address}`) port: PORT,
host: HOST
})
console.log(`Server listening at ${address}`)

View File

@ -1,8 +1,8 @@
import { Type } from '@sinclair/typebox' import { Type } from '@sinclair/typebox'
import { Channel } from '@prisma/client' import type { Channel } from '@prisma/client'
import { date, id } from './utils.js' import { date, id } from '#src/models/utils.js'
import { guildExample } from './Guild.js' import { guildExample } from '#src/models/Guild.js'
export const types = [Type.Literal('text')] export const types = [Type.Literal('text')]

View File

@ -1,7 +1,7 @@
import { Guild } from '@prisma/client' import type { Guild } from '@prisma/client'
import { Type } from '@sinclair/typebox' import { Type } from '@sinclair/typebox'
import { date, id } from './utils.js' import { date, id } from '#src/models/utils.js'
export const guildSchema = { export const guildSchema = {
id, id,

View File

@ -1,9 +1,9 @@
import { Type } from '@sinclair/typebox' import { Type } from '@sinclair/typebox'
import { Member } from '@prisma/client' import type { Member } from '@prisma/client'
import { date, id } from './utils.js' import { date, id } from '#src/models/utils.js'
import { guildExample } from './Guild.js' import { guildExample } from '#src/models/Guild.js'
import { userExample } from './User.js' import { userExample } from '#src/models/User.js'
export const memberSchema = { export const memberSchema = {
id, id,

View File

@ -1,7 +1,7 @@
import { Message } from '@prisma/client' import type { Message } from '@prisma/client'
import { Type } from '@sinclair/typebox' import { Type } from '@sinclair/typebox'
import { date, id } from './utils.js' import { date, id } from '#src/models/utils.js'
export const types = [Type.Literal('text'), Type.Literal('file')] export const types = [Type.Literal('text'), Type.Literal('file')]
@ -14,8 +14,7 @@ export const messageSchema = {
type: Type.Union(types, { default: 'text' }), type: Type.Union(types, { default: 'text' }),
mimetype: Type.String({ mimetype: Type.String({
maxLength: 127, maxLength: 127,
default: 'text/plain', default: 'text/plain'
format: 'mimetype'
}), }),
createdAt: date.createdAt, createdAt: date.createdAt,
updatedAt: date.updatedAt, updatedAt: date.updatedAt,

View File

@ -1,19 +1,19 @@
import { Type } from '@sinclair/typebox' import { Type } from '@sinclair/typebox'
import { date, id } from './utils.js' import { date, id } from '#src/models/utils.js'
export const providers = ['Google', 'GitHub', 'Discord'] as const export const providers = ['Google', 'GitHub', 'Discord'] as const
export const strategies = [...providers, 'Local'] as const export const strategies = [...providers, 'Local'] as const
export const strategiesTypebox = strategies.map((strategy) => export const strategiesTypebox = strategies.map((strategy) => {
Type.Literal(strategy) return Type.Literal(strategy)
) })
export const providersTypebox = providers.map((provider) => export const providersTypebox = providers.map((provider) => {
Type.Literal(provider) return Type.Literal(provider)
) })
export type ProviderOAuth = typeof providers[number] export type ProviderOAuth = (typeof providers)[number]
export type AuthenticationStrategy = typeof strategies[number] export type AuthenticationStrategy = (typeof strategies)[number]
export const oauthSchema = { export const oauthSchema = {
id, id,

View File

@ -1,12 +1,12 @@
import { RefreshToken } from '@prisma/client' import type { RefreshToken } from '@prisma/client'
import { Type } from '@sinclair/typebox' import { Type } from '@sinclair/typebox'
import { userExample } from './User.js' import { userExample } from '#src/models/User.js'
import { date, id } from './utils.js' import { date, id } from '#src/models/utils.js'
export const refreshTokensSchema = { export const refreshTokensSchema = {
id, id,
token: Type.String(), token: Type.String({ format: 'uuid' }),
createdAt: date.createdAt, createdAt: date.createdAt,
updatedAt: date.updatedAt, updatedAt: date.updatedAt,
userId: id userId: id
@ -15,7 +15,7 @@ export const refreshTokensSchema = {
export const refreshTokenExample: RefreshToken = { export const refreshTokenExample: RefreshToken = {
id: 1, id: 1,
userId: userExample.id, userId: userExample.id,
token: 'sometoken', token: 'sometokenUUID',
createdAt: new Date(), createdAt: new Date(),
updatedAt: new Date() updatedAt: new Date()
} }

View File

@ -1,15 +1,21 @@
import { User } from '@prisma/client' import type { User } from '@prisma/client'
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox'
import { AuthenticationStrategy, strategiesTypebox } from './OAuth.js' import type { AuthenticationStrategy } from '#src/models/OAuth.js'
import { userSettingsSchema } from './UserSettings.js' import { strategiesTypebox } from '#src/models/OAuth.js'
import { date, id } from './utils.js' import { userSettingsSchema } from '#src/models/UserSettings.js'
import { date, id } from '#src/models/utils.js'
export interface UserJWT { export interface UserJWT {
id: number id: number
currentStrategy: AuthenticationStrategy currentStrategy: AuthenticationStrategy
} }
export interface UserRefreshJWT extends UserJWT {
tokenUUID: string
}
export interface UserRequest { export interface UserRequest {
current: User current: User
currentStrategy: AuthenticationStrategy currentStrategy: AuthenticationStrategy

View File

@ -1,7 +1,8 @@
import { UserSetting } from '@prisma/client' import type { UserSetting } from '@prisma/client'
import { Type, Static } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox'
import { date, id } from './utils.js' import { date, id } from '#src/models/utils.js'
export const languages = [Type.Literal('fr'), Type.Literal('en')] export const languages = [Type.Literal('fr'), Type.Literal('en')]
export const themes = [Type.Literal('light'), Type.Literal('dark')] export const themes = [Type.Literal('light'), Type.Literal('dark')]

View File

@ -34,7 +34,7 @@ export const fastifyErrorsSchema = {
404: { 404: {
statusCode: Type.Literal(404), statusCode: Type.Literal(404),
error: Type.Literal('Not Found'), error: Type.Literal('Not Found'),
message: Type.Literal('Not Found') message: Type.String()
}, },
431: { 431: {
statusCode: Type.Literal(431), statusCode: Type.Literal(431),

View File

@ -0,0 +1,3 @@
import crypto from 'node:crypto'
console.log(crypto.randomBytes(256).toString('base64'))

View File

@ -1,18 +1,20 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { channelExample } from '../../../../models/Channel.js' import { channelExample } from '#src/models/Channel.js'
import { memberExample } from '../../../../models/Member.js' import { memberExample } from '#src/models/Member.js'
await tap.test('DELETE /channels/[channelId]', async (t) => { await test('DELETE /channels/[channelId]', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
const defaultChannelId = 5 const defaultChannelId = 5
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'channel').value({ sinon.stub(prisma, 'channel').value({
@ -45,14 +47,14 @@ await tap.test('DELETE /channels/[channelId]', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.id, channelExample.id) assert.strictEqual(responseJson.id, channelExample.id)
t.equal(responseJson.name, channelExample.name) assert.strictEqual(responseJson.name, channelExample.name)
t.equal(responseJson.guildId, channelExample.guildId) assert.strictEqual(responseJson.guildId, channelExample.guildId)
t.equal(responseJson.defaultChannelId, defaultChannelId) assert.strictEqual(responseJson.defaultChannelId, defaultChannelId)
}) })
await t.test('fails if there is only one channel', async (t) => { await t.test('fails if there is only one channel', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'channel').value({ sinon.stub(prisma, 'channel').value({
findUnique: async () => { findUnique: async () => {
@ -74,10 +76,10 @@ await tap.test('DELETE /channels/[channelId]', async (t) => {
authorization: `Bearer ${accessToken}` authorization: `Bearer ${accessToken}`
} }
}) })
t.equal(response.statusCode, 400) assert.strictEqual(response.statusCode, 400)
}) })
await t.test('fails if the channel is not found', async (t) => { await t.test('fails if the channel is not found', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'channel').value({ sinon.stub(prisma, 'channel').value({
findUnique: async () => { findUnique: async () => {
@ -91,10 +93,10 @@ await tap.test('DELETE /channels/[channelId]', async (t) => {
authorization: `Bearer ${accessToken}` authorization: `Bearer ${accessToken}`
} }
}) })
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
}) })
await t.test('fails if the member is not found', async (t) => { await t.test('fails if the member is not found', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'channel').value({ sinon.stub(prisma, 'channel').value({
findUnique: async () => { findUnique: async () => {
@ -113,10 +115,10 @@ await tap.test('DELETE /channels/[channelId]', async (t) => {
authorization: `Bearer ${accessToken}` authorization: `Bearer ${accessToken}`
} }
}) })
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
}) })
await t.test('fails if the member is not owner', async (t) => { await t.test('fails if the member is not owner', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'channel').value({ sinon.stub(prisma, 'channel').value({
findUnique: async () => { findUnique: async () => {
@ -138,6 +140,6 @@ await tap.test('DELETE /channels/[channelId]', async (t) => {
authorization: `Bearer ${accessToken}` authorization: `Bearer ${accessToken}`
} }
}) })
t.equal(response.statusCode, 400) assert.strictEqual(response.statusCode, 400)
}) })
}) })

View File

@ -1,18 +1,20 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { channelExample } from '../../../../models/Channel.js' import { channelExample } from '#src/models/Channel.js'
import { memberExample } from '../../../../models/Member.js' import { memberExample } from '#src/models/Member.js'
await tap.test('GET /channels/[channelId]', async (t) => { await test('GET /channels/[channelId]', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'channel').value({ sinon.stub(prisma, 'channel').value({
findUnique: async () => { findUnique: async () => {
@ -32,13 +34,13 @@ await tap.test('GET /channels/[channelId]', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.channel.id, channelExample.id) assert.strictEqual(responseJson.channel.id, channelExample.id)
t.equal(responseJson.channel.name, channelExample.name) assert.strictEqual(responseJson.channel.name, channelExample.name)
t.equal(responseJson.channel.guildId, channelExample.guildId) assert.strictEqual(responseJson.channel.guildId, channelExample.guildId)
}) })
await t.test('fails with not found member', async (t) => { await t.test('fails with not found member', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'channel').value({ sinon.stub(prisma, 'channel').value({
findUnique: async () => { findUnique: async () => {
@ -58,11 +60,11 @@ await tap.test('GET /channels/[channelId]', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
t.equal(responseJson.message, 'Channel not found') assert.strictEqual(responseJson.message, 'Channel not found')
}) })
await t.test('fails with not found channel', async (t) => { await t.test('fails with not found channel', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'channel').value({ sinon.stub(prisma, 'channel').value({
findUnique: async () => { findUnique: async () => {
@ -82,15 +84,15 @@ await tap.test('GET /channels/[channelId]', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
t.equal(responseJson.message, 'Channel not found') assert.strictEqual(responseJson.message, 'Channel not found')
}) })
await t.test('fails with unauthenticated user', async (t) => { await t.test('fails with unauthenticated user', async () => {
const response = await application.inject({ const response = await application.inject({
method: 'GET', method: 'GET',
url: '/channels/1' url: '/channels/1'
}) })
t.equal(response.statusCode, 401) assert.strictEqual(response.statusCode, 401)
}) })
}) })

View File

@ -1,20 +1,22 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { channelExample } from '../../../../models/Channel.js' import { channelExample } from '#src/models/Channel.js'
import { memberExample } from '../../../../models/Member.js' import { memberExample } from '#src/models/Member.js'
const newName = 'new channel name' const newName = 'new channel name'
await tap.test('PUT /channels/[channelId]', async (t) => { await test('PUT /channels/[channelId]', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
const defaultChannelId = 5 const defaultChannelId = 5
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'channel').value({ sinon.stub(prisma, 'channel').value({
@ -48,14 +50,14 @@ await tap.test('PUT /channels/[channelId]', async (t) => {
payload: { name: newName } payload: { name: newName }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.id, channelExample.id) assert.strictEqual(responseJson.id, channelExample.id)
t.equal(responseJson.name, newName) assert.strictEqual(responseJson.name, newName)
t.equal(responseJson.guildId, channelExample.guildId) assert.strictEqual(responseJson.guildId, channelExample.guildId)
t.equal(responseJson.defaultChannelId, defaultChannelId) assert.strictEqual(responseJson.defaultChannelId, defaultChannelId)
}) })
await t.test('fails if the channel is not found', async (t) => { await t.test('fails if the channel is not found', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'channel').value({ sinon.stub(prisma, 'channel').value({
findUnique: async () => { findUnique: async () => {
@ -75,10 +77,10 @@ await tap.test('PUT /channels/[channelId]', async (t) => {
}, },
payload: { name: newName } payload: { name: newName }
}) })
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
}) })
await t.test('fails if the member is not found', async (t) => { await t.test('fails if the member is not found', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'channel').value({ sinon.stub(prisma, 'channel').value({
findUnique: async () => { findUnique: async () => {
@ -98,10 +100,10 @@ await tap.test('PUT /channels/[channelId]', async (t) => {
}, },
payload: { name: newName } payload: { name: newName }
}) })
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
}) })
await t.test('fails if the member is not owner', async (t) => { await t.test('fails if the member is not owner', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'channel').value({ sinon.stub(prisma, 'channel').value({
findUnique: async () => { findUnique: async () => {
@ -124,6 +126,6 @@ await tap.test('PUT /channels/[channelId]', async (t) => {
}, },
payload: { name: newName } payload: { name: newName }
}) })
t.equal(response.statusCode, 400) assert.strictEqual(response.statusCode, 400)
}) })
}) })

View File

@ -1,10 +1,11 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { channelSchema } from '../../../models/Channel.js' import { channelSchema } from '#src/models/Channel.js'
const parametersSchema = Type.Object({ const parametersSchema = Type.Object({
channelId: channelSchema.id channelId: channelSchema.id
@ -47,8 +48,8 @@ export const deleteChannelService: FastifyPluginAsync = async (fastify) => {
if (request.user == null) { if (request.user == null) {
throw fastify.httpErrors.forbidden() throw fastify.httpErrors.forbidden()
} }
const { user } = request const { user, params } = request
const { channelId } = request.params const { channelId } = params
const channelCheck = await prisma.channel.findUnique({ const channelCheck = await prisma.channel.findUnique({
where: { id: channelId } where: { id: channelId }
}) })

View File

@ -1,10 +1,11 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { channelSchema } from '../../../models/Channel.js' import { channelSchema } from '#src/models/Channel.js'
const parametersSchema = Type.Object({ const parametersSchema = Type.Object({
channelId: channelSchema.id channelId: channelSchema.id

View File

@ -1,20 +1,22 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
import prisma from '../../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { channelExample } from '../../../../../models/Channel.js' import { channelExample } from '#src/models/Channel.js'
import { memberExample } from '../../../../../models/Member.js' import { memberExample } from '#src/models/Member.js'
import { userExample } from '../../../../../models/User.js' import { userExample } from '#src/models/User.js'
import { messageExample } from '../../../../../models/Message.js' import { messageExample } from '#src/models/Message.js'
await tap.test('GET /channels/[channelId]/messages', async (t) => { await test('GET /channels/[channelId]/messages', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'channel').value({ sinon.stub(prisma, 'channel').value({
findUnique: async () => { findUnique: async () => {
@ -42,19 +44,19 @@ await tap.test('GET /channels/[channelId]/messages', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.length, 1) assert.strictEqual(responseJson.length, 1)
t.equal(responseJson[0].id, messageExample.id) assert.strictEqual(responseJson[0].id, messageExample.id)
t.equal(responseJson[0].value, messageExample.value) assert.strictEqual(responseJson[0].value, messageExample.value)
t.equal(responseJson[0].type, messageExample.type) assert.strictEqual(responseJson[0].type, messageExample.type)
t.equal(responseJson[0].mimetype, messageExample.mimetype) assert.strictEqual(responseJson[0].mimetype, messageExample.mimetype)
t.equal(responseJson[0].member.id, memberExample.id) assert.strictEqual(responseJson[0].member.id, memberExample.id)
t.equal(responseJson[0].member.isOwner, memberExample.isOwner) assert.strictEqual(responseJson[0].member.isOwner, memberExample.isOwner)
t.equal(responseJson[0].member.user.id, userExample.id) assert.strictEqual(responseJson[0].member.user.id, userExample.id)
t.equal(responseJson[0].member.user.name, userExample.name) assert.strictEqual(responseJson[0].member.user.name, userExample.name)
}) })
await t.test('fails with not found channel', async (t) => { await t.test('fails with not found channel', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'channel').value({ sinon.stub(prisma, 'channel').value({
findUnique: async () => { findUnique: async () => {
@ -77,11 +79,11 @@ await tap.test('GET /channels/[channelId]/messages', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
t.equal(responseJson.message, 'Channel not found') assert.strictEqual(responseJson.message, 'Channel not found')
}) })
await t.test('fails with not found member', async (t) => { await t.test('fails with not found member', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'channel').value({ sinon.stub(prisma, 'channel').value({
findUnique: async () => { findUnique: async () => {
@ -101,15 +103,15 @@ await tap.test('GET /channels/[channelId]/messages', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
t.equal(responseJson.message, 'Channel not found') assert.strictEqual(responseJson.message, 'Channel not found')
}) })
await t.test('fails with unauthenticated user', async (t) => { await t.test('fails with unauthenticated user', async () => {
const response = await application.inject({ const response = await application.inject({
method: 'GET', method: 'GET',
url: `/channels/1/messages` url: `/channels/1/messages`
}) })
t.equal(response.statusCode, 401) assert.strictEqual(response.statusCode, 401)
}) })
}) })

View File

@ -1,20 +1,22 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
import prisma from '../../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { channelExample } from '../../../../../models/Channel.js' import { channelExample } from '#src/models/Channel.js'
import { memberExample } from '../../../../../models/Member.js' import { memberExample } from '#src/models/Member.js'
import { userExample } from '../../../../../models/User.js' import { userExample } from '#src/models/User.js'
import { messageExample } from '../../../../../models/Message.js' import { messageExample } from '#src/models/Message.js'
await tap.test('POST /channels/[channelId]/messages', async (t) => { await test('POST /channels/[channelId]/messages', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'channel').value({ sinon.stub(prisma, 'channel').value({
findUnique: async () => { findUnique: async () => {
@ -43,18 +45,18 @@ await tap.test('POST /channels/[channelId]/messages', async (t) => {
payload: { value: messageExample.value } payload: { value: messageExample.value }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 201) assert.strictEqual(response.statusCode, 201)
t.equal(responseJson.id, messageExample.id) assert.strictEqual(responseJson.id, messageExample.id)
t.equal(responseJson.value, messageExample.value) assert.strictEqual(responseJson.value, messageExample.value)
t.equal(responseJson.type, messageExample.type) assert.strictEqual(responseJson.type, messageExample.type)
t.equal(responseJson.mimetype, messageExample.mimetype) assert.strictEqual(responseJson.mimetype, messageExample.mimetype)
t.equal(responseJson.member.id, memberExample.id) assert.strictEqual(responseJson.member.id, memberExample.id)
t.equal(responseJson.member.isOwner, memberExample.isOwner) assert.strictEqual(responseJson.member.isOwner, memberExample.isOwner)
t.equal(responseJson.member.user.id, userExample.id) assert.strictEqual(responseJson.member.user.id, userExample.id)
t.equal(responseJson.member.user.name, userExample.name) assert.strictEqual(responseJson.member.user.name, userExample.name)
}) })
await t.test('fails with no message value', async (t) => { await t.test('fails with no message value', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'channel').value({ sinon.stub(prisma, 'channel').value({
findUnique: async () => { findUnique: async () => {
@ -77,10 +79,10 @@ await tap.test('POST /channels/[channelId]/messages', async (t) => {
}, },
payload: {} payload: {}
}) })
t.equal(response.statusCode, 400) assert.strictEqual(response.statusCode, 400)
}) })
await t.test('fails with not found channel', async (t) => { await t.test('fails with not found channel', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'channel').value({ sinon.stub(prisma, 'channel').value({
findUnique: async () => { findUnique: async () => {
@ -104,11 +106,11 @@ await tap.test('POST /channels/[channelId]/messages', async (t) => {
payload: { value: messageExample.value } payload: { value: messageExample.value }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
t.equal(responseJson.message, 'Channel not found') assert.strictEqual(responseJson.message, 'Channel not found')
}) })
await t.test('fails with not found member', async (t) => { await t.test('fails with not found member', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'channel').value({ sinon.stub(prisma, 'channel').value({
findUnique: async () => { findUnique: async () => {
@ -129,7 +131,7 @@ await tap.test('POST /channels/[channelId]/messages', async (t) => {
payload: { value: messageExample.value } payload: { value: messageExample.value }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
t.equal(responseJson.message, 'Channel not found') assert.strictEqual(responseJson.message, 'Channel not found')
}) })
}) })

View File

@ -1,17 +1,18 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { messageSchema } from '../../../../models/Message.js' import { messageSchema } from '#src/models/Message.js'
import { memberSchema } from '../../../../models/Member.js' import { memberSchema } from '#src/models/Member.js'
import { userPublicWithoutSettingsSchema } from '../../../../models/User.js' import { userPublicWithoutSettingsSchema } from '#src/models/User.js'
import { import {
getPaginationOptions, getPaginationOptions,
queryPaginationObjectSchema queryPaginationObjectSchema
} from '../../../../tools/database/pagination.js' } from '#src/tools/database/pagination.js'
import { channelSchema } from '../../../../models/Channel.js' import { channelSchema } from '#src/models/Channel.js'
type QuerySchemaType = Static<typeof queryPaginationObjectSchema> type QuerySchemaType = Static<typeof queryPaginationObjectSchema>

View File

@ -1,13 +1,14 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { messageSchema } from '../../../../models/Message.js' import { messageSchema } from '#src/models/Message.js'
import { channelSchema } from '../../../../models/Channel.js' import { channelSchema } from '#src/models/Channel.js'
import { memberSchema } from '../../../../models/Member.js' import { memberSchema } from '#src/models/Member.js'
import { userPublicWithoutSettingsSchema } from '../../../../models/User.js' import { userPublicWithoutSettingsSchema } from '#src/models/User.js'
const parametersSchema = Type.Object({ const parametersSchema = Type.Object({
channelId: channelSchema.id channelId: channelSchema.id

View File

@ -1,15 +1,16 @@
import { Type, Static } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import fastifyMultipart from 'fastify-multipart' import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import fastifyMultipart from '@fastify/multipart'
import prisma from '../../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { messageSchema } from '../../../../../models/Message.js' import { messageSchema } from '#src/models/Message.js'
import { memberSchema } from '../../../../../models/Member.js' import { memberSchema } from '#src/models/Member.js'
import { userPublicWithoutSettingsSchema } from '../../../../../models/User.js' import { userPublicWithoutSettingsSchema } from '#src/models/User.js'
import { channelSchema } from '../../../../../models/Channel.js' import { channelSchema } from '#src/models/Channel.js'
import { uploadFile } from '../../../../../tools/utils/uploadFile.js' import { uploadFile } from '#src/tools/utils/uploadFile.js'
const parametersSchema = Type.Object({ const parametersSchema = Type.Object({
channelId: channelSchema.id channelId: channelSchema.id

View File

@ -1,10 +1,11 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { channelSchema } from '../../../models/Channel.js' import { channelSchema } from '#src/models/Channel.js'
const bodyPutServiceSchema = Type.Object({ const bodyPutServiceSchema = Type.Object({
name: channelSchema.name name: channelSchema.name
@ -55,9 +56,9 @@ export const putChannelService: FastifyPluginAsync = async (fastify) => {
if (request.user == null) { if (request.user == null) {
throw fastify.httpErrors.forbidden() throw fastify.httpErrors.forbidden()
} }
const { user } = request const { user, params, body } = request
const { channelId } = request.params const { channelId } = params
const { name } = request.body const { name } = body
const channelCheck = await prisma.channel.findUnique({ const channelCheck = await prisma.channel.findUnique({
where: { id: channelId } where: { id: channelId }
}) })

View File

@ -1,4 +1,4 @@
import { FastifyPluginAsync } from 'fastify' import type { FastifyPluginAsync } from 'fastify'
import { deleteChannelService } from './[channelId]/delete.js' import { deleteChannelService } from './[channelId]/delete.js'
import { getChannelByIdService } from './[channelId]/get.js' import { getChannelByIdService } from './[channelId]/get.js'

View File

@ -1,18 +1,20 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { memberExample } from '../../../../models/Member.js' import { memberExample } from '#src/models/Member.js'
import { guildExample } from '../../../../models/Guild.js' import { guildExample } from '#src/models/Guild.js'
await tap.test('DELETE /guilds/[guildId]', async (t) => { await test('DELETE /guilds/[guildId]', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds and delete the guild', async (t) => { await t.test('succeeds and delete the guild', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'member').value({ sinon.stub(prisma, 'member').value({
findFirst: async () => { findFirst: async () => {
@ -36,13 +38,13 @@ await tap.test('DELETE /guilds/[guildId]', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.id, guildExample.id) assert.strictEqual(responseJson.id, guildExample.id)
t.equal(responseJson.name, guildExample.name) assert.strictEqual(responseJson.name, guildExample.name)
t.equal(responseJson.description, guildExample.description) assert.strictEqual(responseJson.description, guildExample.description)
}) })
await t.test("fails if the guild doesn't exist", async (t) => { await t.test("fails if the guild doesn't exist", async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'member').value({ sinon.stub(prisma, 'member').value({
findFirst: async () => { findFirst: async () => {
@ -56,10 +58,10 @@ await tap.test('DELETE /guilds/[guildId]', async (t) => {
authorization: `Bearer ${accessToken}` authorization: `Bearer ${accessToken}`
} }
}) })
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
}) })
await t.test('fails if the user is not the owner', async (t) => { await t.test('fails if the user is not the owner', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'member').value({ sinon.stub(prisma, 'member').value({
findFirst: async () => { findFirst: async () => {
@ -78,7 +80,10 @@ await tap.test('DELETE /guilds/[guildId]', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 400) assert.strictEqual(response.statusCode, 400)
t.equal(responseJson.message, 'You should be an owner of the guild') assert.strictEqual(
responseJson.message,
'You should be an owner of the guild'
)
}) })
}) })

View File

@ -1,22 +1,24 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { memberExample } from '../../../../models/Member.js' import { memberExample } from '#src/models/Member.js'
import { guildExample } from '../../../../models/Guild.js' import { guildExample } from '#src/models/Guild.js'
import { userExample } from '../../../../models/User.js' import { userExample } from '#src/models/User.js'
import { channelExample } from '../../../../models/Channel.js' import { channelExample } from '#src/models/Channel.js'
const defaultChannelId = 5 const defaultChannelId = 5
await tap.test('GET /guilds/[guildId]', async (t) => { await test('GET /guilds/[guildId]', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
const { accessToken, user } = await authenticateUserTest() const { accessToken, user } = await authenticateUserTest()
sinon.stub(prisma, 'member').value({ sinon.stub(prisma, 'member').value({
findFirst: async () => { findFirst: async () => {
@ -43,17 +45,17 @@ await tap.test('GET /guilds/[guildId]', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.member.id, memberExample.id) assert.strictEqual(responseJson.member.id, memberExample.id)
t.equal(responseJson.member.isOwner, memberExample.isOwner) assert.strictEqual(responseJson.member.isOwner, memberExample.isOwner)
t.equal(responseJson.member.user.name, user.name) assert.strictEqual(responseJson.member.user.name, user.name)
t.equal(responseJson.member.user.email, null) assert.strictEqual(responseJson.member.user.email, null)
t.equal(responseJson.guild.id, guildExample.id) assert.strictEqual(responseJson.guild.id, guildExample.id)
t.equal(responseJson.guild.name, guildExample.name) assert.strictEqual(responseJson.guild.name, guildExample.name)
t.equal(responseJson.guild.defaultChannelId, defaultChannelId) assert.strictEqual(responseJson.guild.defaultChannelId, defaultChannelId)
}) })
await t.test('fails with not found member/guild', async (t) => { await t.test('fails with not found member/guild', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'member').value({ sinon.stub(prisma, 'member').value({
findFirst: async () => { findFirst: async () => {
@ -68,15 +70,15 @@ await tap.test('GET /guilds/[guildId]', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
t.equal(responseJson.message, 'Member not found') assert.strictEqual(responseJson.message, 'Member not found')
}) })
await t.test('fails with unauthenticated user', async (t) => { await t.test('fails with unauthenticated user', async () => {
const response = await application.inject({ const response = await application.inject({
method: 'GET', method: 'GET',
url: '/guilds/1' url: '/guilds/1'
}) })
t.equal(response.statusCode, 401) assert.strictEqual(response.statusCode, 401)
}) })
}) })

View File

@ -1,23 +1,25 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { memberExample } from '../../../../models/Member.js' import { memberExample } from '#src/models/Member.js'
import { guildExample } from '../../../../models/Guild.js' import { guildExample } from '#src/models/Guild.js'
import { channelExample } from '../../../../models/Channel.js' import { channelExample } from '#src/models/Channel.js'
const defaultChannelId = 5 const defaultChannelId = 5
const newName = 'New guild name' const newName = 'New guild name'
const newDescription = 'New guild description' const newDescription = 'New guild description'
await tap.test('PUT /guilds/[guildId]', async (t) => { await test('PUT /guilds/[guildId]', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds and edit the guild', async (t) => { await t.test('succeeds and edit the guild', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'member').value({ sinon.stub(prisma, 'member').value({
findFirst: async () => { findFirst: async () => {
@ -57,13 +59,13 @@ await tap.test('PUT /guilds/[guildId]', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.name, newName) assert.strictEqual(responseJson.name, newName)
t.equal(responseJson.description, newDescription) assert.strictEqual(responseJson.description, newDescription)
t.equal(responseJson.defaultChannelId, defaultChannelId) assert.strictEqual(responseJson.defaultChannelId, defaultChannelId)
}) })
await t.test("fails if the guild doesn't exist", async (t) => { await t.test("fails if the guild doesn't exist", async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'member').value({ sinon.stub(prisma, 'member').value({
findFirst: async () => { findFirst: async () => {
@ -81,10 +83,10 @@ await tap.test('PUT /guilds/[guildId]', async (t) => {
description: newDescription description: newDescription
} }
}) })
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
}) })
await t.test('fails if the user is not the owner', async (t) => { await t.test('fails if the user is not the owner', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'member').value({ sinon.stub(prisma, 'member').value({
findFirst: async () => { findFirst: async () => {
@ -107,7 +109,10 @@ await tap.test('PUT /guilds/[guildId]', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 400) assert.strictEqual(response.statusCode, 400)
t.equal(responseJson.message, 'You should be an owner of the guild') assert.strictEqual(
responseJson.message,
'You should be an owner of the guild'
)
}) })
}) })

View File

@ -1,19 +1,21 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
import prisma from '../../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { memberExample } from '../../../../../models/Member.js' import { memberExample } from '#src/models/Member.js'
import { guildExample } from '../../../../../models/Guild.js' import { guildExample } from '#src/models/Guild.js'
import { channelExample } from '../../../../../models/Channel.js' import { channelExample } from '#src/models/Channel.js'
await tap.test('GET /guilds/[guildId]/channels', async (t) => { await test('GET /guilds/[guildId]/channels', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'member').value({ sinon.stub(prisma, 'member').value({
findFirst: async () => { findFirst: async () => {
@ -33,14 +35,14 @@ await tap.test('GET /guilds/[guildId]/channels', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.length, 1) assert.strictEqual(responseJson.length, 1)
t.equal(responseJson[0].id, channelExample.id) assert.strictEqual(responseJson[0].id, channelExample.id)
t.equal(responseJson[0].name, channelExample.name) assert.strictEqual(responseJson[0].name, channelExample.name)
t.equal(responseJson[0].guildId, channelExample.guildId) assert.strictEqual(responseJson[0].guildId, channelExample.guildId)
}) })
await t.test('fails with not found member/guild', async (t) => { await t.test('fails with not found member/guild', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'member').value({ sinon.stub(prisma, 'member').value({
findFirst: async () => { findFirst: async () => {
@ -55,15 +57,15 @@ await tap.test('GET /guilds/[guildId]/channels', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
t.equal(responseJson.message, 'Member not found') assert.strictEqual(responseJson.message, 'Member not found')
}) })
await t.test('fails with unauthenticated user', async (t) => { await t.test('fails with unauthenticated user', async () => {
const response = await application.inject({ const response = await application.inject({
method: 'GET', method: 'GET',
url: '/guilds/1/channels' url: '/guilds/1/channels'
}) })
t.equal(response.statusCode, 401) assert.strictEqual(response.statusCode, 401)
}) })
}) })

View File

@ -1,21 +1,23 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
import prisma from '../../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { memberExample } from '../../../../../models/Member.js' import { memberExample } from '#src/models/Member.js'
import { guildExample } from '../../../../../models/Guild.js' import { guildExample } from '#src/models/Guild.js'
import { channelExample } from '../../../../../models/Channel.js' import { channelExample } from '#src/models/Channel.js'
const defaultChannelId = 5 const defaultChannelId = 5
await tap.test('POST /guilds/[guildId]/channels', async (t) => { await test('POST /guilds/[guildId]/channels', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'member').value({ sinon.stub(prisma, 'member').value({
findFirst: async () => { findFirst: async () => {
@ -42,14 +44,14 @@ await tap.test('POST /guilds/[guildId]/channels', async (t) => {
payload: { name: channelExample.name } payload: { name: channelExample.name }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 201) assert.strictEqual(response.statusCode, 201)
t.equal(responseJson.id, channelExample.id) assert.strictEqual(responseJson.id, channelExample.id)
t.equal(responseJson.name, channelExample.name) assert.strictEqual(responseJson.name, channelExample.name)
t.equal(responseJson.guildId, channelExample.guildId) assert.strictEqual(responseJson.guildId, channelExample.guildId)
t.equal(responseJson.defaultChannelId, defaultChannelId) assert.strictEqual(responseJson.defaultChannelId, defaultChannelId)
}) })
await t.test('fails if the member is not found', async (t) => { await t.test('fails if the member is not found', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'member').value({ sinon.stub(prisma, 'member').value({
findFirst: async () => { findFirst: async () => {
@ -64,10 +66,10 @@ await tap.test('POST /guilds/[guildId]/channels', async (t) => {
}, },
payload: { name: channelExample.name } payload: { name: channelExample.name }
}) })
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
}) })
await t.test('fails if the member is not owner', async (t) => { await t.test('fails if the member is not owner', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'member').value({ sinon.stub(prisma, 'member').value({
findFirst: async () => { findFirst: async () => {
@ -85,6 +87,6 @@ await tap.test('POST /guilds/[guildId]/channels', async (t) => {
}, },
payload: { name: channelExample.name } payload: { name: channelExample.name }
}) })
t.equal(response.statusCode, 400) assert.strictEqual(response.statusCode, 400)
}) })
}) })

View File

@ -1,15 +1,16 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { guildSchema } from '../../../../models/Guild.js' import { guildSchema } from '#src/models/Guild.js'
import { channelSchema } from '../../../../models/Channel.js' import { channelSchema } from '#src/models/Channel.js'
import { import {
getPaginationOptions, getPaginationOptions,
queryPaginationObjectSchema queryPaginationObjectSchema
} from '../../../../tools/database/pagination.js' } from '#src/tools/database/pagination.js'
type QuerySchemaType = Static<typeof queryPaginationObjectSchema> type QuerySchemaType = Static<typeof queryPaginationObjectSchema>

View File

@ -1,11 +1,12 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { channelSchema } from '../../../../models/Channel.js' import { channelSchema } from '#src/models/Channel.js'
import { guildSchema } from '../../../../models/Guild.js' import { guildSchema } from '#src/models/Guild.js'
const bodyPostServiceSchema = Type.Object({ const bodyPostServiceSchema = Type.Object({
name: channelSchema.name name: channelSchema.name
@ -56,9 +57,9 @@ export const postChannelService: FastifyPluginAsync = async (fastify) => {
if (request.user == null) { if (request.user == null) {
throw fastify.httpErrors.forbidden() throw fastify.httpErrors.forbidden()
} }
const { user } = request const { user, params, body } = request
const { guildId } = request.params const { guildId } = params
const { name } = request.body const { name } = body
const member = await prisma.member.findFirst({ const member = await prisma.member.findFirst({
where: { guildId, userId: user.current.id } where: { guildId, userId: user.current.id }
}) })

View File

@ -1,10 +1,11 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { guildSchema } from '../../../models/Guild.js' import { guildSchema } from '#src/models/Guild.js'
const parametersSchema = Type.Object({ const parametersSchema = Type.Object({
guildId: guildSchema.id guildId: guildSchema.id

View File

@ -1,13 +1,14 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { guildSchema } from '../../../models/Guild.js' import { guildSchema } from '#src/models/Guild.js'
import { memberSchema } from '../../../models/Member.js' import { memberSchema } from '#src/models/Member.js'
import { userPublicWithoutSettingsSchema } from '../../../models/User.js' import { userPublicWithoutSettingsSchema } from '#src/models/User.js'
import { channelSchema } from '../../../models/Channel.js' import { channelSchema } from '#src/models/Channel.js'
const parametersSchema = Type.Object({ const parametersSchema = Type.Object({
guildId: guildSchema.id guildId: guildSchema.id

View File

@ -1,13 +1,14 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import fastifyMultipart from 'fastify-multipart' import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import fastifyMultipart from '@fastify/multipart'
import authenticateUser from '../../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { fastifyErrors } from '../../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { uploadFile } from '../../../../tools/utils/uploadFile.js' import { uploadFile } from '#src/tools/utils/uploadFile.js'
import { guildSchema } from '../../../../models/Guild.js' import { guildSchema } from '#src/models/Guild.js'
import { channelSchema } from '../../../../models/Channel.js' import { channelSchema } from '#src/models/Channel.js'
const parametersSchema = Type.Object({ const parametersSchema = Type.Object({
guildId: guildSchema.id guildId: guildSchema.id

View File

@ -1,19 +1,21 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
import prisma from '../../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { memberExample } from '../../../../../models/Member.js' import { memberExample } from '#src/models/Member.js'
import { guildExample } from '../../../../../models/Guild.js' import { guildExample } from '#src/models/Guild.js'
import { userExample } from '../../../../../models/User.js' import { userExample } from '#src/models/User.js'
await tap.test('GET /guilds/[guildId]/members', async (t) => { await test('GET /guilds/[guildId]/members', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'member').value({ sinon.stub(prisma, 'member').value({
findFirst: async () => { findFirst: async () => {
@ -31,16 +33,16 @@ await tap.test('GET /guilds/[guildId]/members', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.length, 1) assert.strictEqual(responseJson.length, 1)
t.equal(responseJson[0].id, memberExample.id) assert.strictEqual(responseJson[0].id, memberExample.id)
t.equal(responseJson[0].isOwner, memberExample.isOwner) assert.strictEqual(responseJson[0].isOwner, memberExample.isOwner)
t.equal(responseJson[0].user.id, userExample.id) assert.strictEqual(responseJson[0].user.id, userExample.id)
t.equal(responseJson[0].user.name, userExample.name) assert.strictEqual(responseJson[0].user.name, userExample.name)
t.equal(responseJson[0].user.email, null) assert.strictEqual(responseJson[0].user.email, null)
}) })
await t.test('fails with not found member/guild', async (t) => { await t.test('fails with not found member/guild', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'member').value({ sinon.stub(prisma, 'member').value({
findFirst: async () => { findFirst: async () => {
@ -55,15 +57,15 @@ await tap.test('GET /guilds/[guildId]/members', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
t.equal(responseJson.message, 'Member not found') assert.strictEqual(responseJson.message, 'Member not found')
}) })
await t.test('fails with unauthenticated user', async (t) => { await t.test('fails with unauthenticated user', async () => {
const response = await application.inject({ const response = await application.inject({
method: 'GET', method: 'GET',
url: '/guilds/1/members' url: '/guilds/1/members'
}) })
t.equal(response.statusCode, 401) assert.strictEqual(response.statusCode, 401)
}) })
}) })

View File

@ -1,16 +1,17 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { guildSchema } from '../../../../models/Guild.js' import { guildSchema } from '#src/models/Guild.js'
import { import {
getPaginationOptions, getPaginationOptions,
queryPaginationObjectSchema queryPaginationObjectSchema
} from '../../../../tools/database/pagination.js' } from '#src/tools/database/pagination.js'
import { memberSchema } from '../../../../models/Member.js' import { memberSchema } from '#src/models/Member.js'
import { userPublicWithoutSettingsSchema } from '../../../../models/User.js' import { userPublicWithoutSettingsSchema } from '#src/models/User.js'
type QuerySchemaType = Static<typeof queryPaginationObjectSchema> type QuerySchemaType = Static<typeof queryPaginationObjectSchema>

View File

@ -1,22 +1,24 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
import prisma from '../../../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { memberExample } from '../../../../../../models/Member.js' import { memberExample } from '#src/models/Member.js'
import { guildExample } from '../../../../../../models/Guild.js' import { guildExample } from '#src/models/Guild.js'
import { userExample } from '../../../../../../models/User.js' import { userExample } from '#src/models/User.js'
import { channelExample } from '../../../../../../models/Channel.js' import { channelExample } from '#src/models/Channel.js'
const defaultChannelId = 5 const defaultChannelId = 5
await tap.test('POST /guilds/[guildId]/members/join', async (t) => { await test('POST /guilds/[guildId]/members/join', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'member').value({ sinon.stub(prisma, 'member').value({
findFirst: async () => { findFirst: async () => {
@ -44,17 +46,17 @@ await tap.test('POST /guilds/[guildId]/members/join', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 201) assert.strictEqual(response.statusCode, 201)
t.equal(responseJson.id, memberExample.id) assert.strictEqual(responseJson.id, memberExample.id)
t.equal(responseJson.userId, memberExample.userId) assert.strictEqual(responseJson.userId, memberExample.userId)
t.equal(responseJson.user.name, userExample.name) assert.strictEqual(responseJson.user.name, userExample.name)
t.equal(responseJson.user.email, null) assert.strictEqual(responseJson.user.email, null)
t.equal(responseJson.guild.id, guildExample.id) assert.strictEqual(responseJson.guild.id, guildExample.id)
t.equal(responseJson.guild.name, guildExample.name) assert.strictEqual(responseJson.guild.name, guildExample.name)
t.equal(responseJson.guild.defaultChannelId, channelExample.id) assert.strictEqual(responseJson.guild.defaultChannelId, channelExample.id)
}) })
await t.test('fails if the guild is not found', async (t) => { await t.test('fails if the guild is not found', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'member').value({ sinon.stub(prisma, 'member').value({
findFirst: async () => { findFirst: async () => {
@ -78,10 +80,10 @@ await tap.test('POST /guilds/[guildId]/members/join', async (t) => {
authorization: `Bearer ${accessToken}` authorization: `Bearer ${accessToken}`
} }
}) })
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
}) })
await t.test('fails if the user is already in the guild', async (t) => { await t.test('fails if the user is already in the guild', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'member').value({ sinon.stub(prisma, 'member').value({
findFirst: async () => { findFirst: async () => {
@ -109,7 +111,7 @@ await tap.test('POST /guilds/[guildId]/members/join', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 400) assert.strictEqual(response.statusCode, 400)
t.equal(responseJson.defaultChannelId, defaultChannelId) assert.strictEqual(responseJson.defaultChannelId, defaultChannelId)
}) })
}) })

View File

@ -1,17 +1,14 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { import { fastifyErrors, fastifyErrorsSchema, id } from '#src/models/utils.js'
fastifyErrors, import authenticateUser from '#src/tools/plugins/authenticateUser.js'
fastifyErrorsSchema, import { guildSchema } from '#src/models/Guild.js'
id import { memberSchema } from '#src/models/Member.js'
} from '../../../../../models/utils.js' import { userPublicWithoutSettingsSchema } from '#src/models/User.js'
import authenticateUser from '../../../../../tools/plugins/authenticateUser.js' import { channelSchema } from '#src/models/Channel.js'
import { guildSchema } from '../../../../../models/Guild.js'
import { memberSchema } from '../../../../../models/Member.js'
import { userPublicWithoutSettingsSchema } from '../../../../../models/User.js'
import { channelSchema } from '../../../../../models/Channel.js'
const parametersSchema = Type.Object({ const parametersSchema = Type.Object({
guildId: guildSchema.id guildId: guildSchema.id
@ -61,8 +58,8 @@ export const postMemberService: FastifyPluginAsync = async (fastify) => {
if (request.user == null) { if (request.user == null) {
throw fastify.httpErrors.forbidden() throw fastify.httpErrors.forbidden()
} }
const { user } = request const { user, params } = request
const { guildId } = request.params const { guildId } = params
const guild = await prisma.guild.findUnique({ const guild = await prisma.guild.findUnique({
where: { where: {
id: guildId id: guildId

View File

@ -1,18 +1,20 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
import prisma from '../../../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { memberExample } from '../../../../../../models/Member.js' import { memberExample } from '#src/models/Member.js'
import { guildExample } from '../../../../../../models/Guild.js' import { guildExample } from '#src/models/Guild.js'
await tap.test('DELETE /guilds/[guildId]/members/leave', async (t) => { await test('DELETE /guilds/[guildId]/members/leave', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
const member = { const member = {
...memberExample, ...memberExample,
@ -34,13 +36,13 @@ await tap.test('DELETE /guilds/[guildId]/members/leave', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.id, member.id) assert.strictEqual(responseJson.id, member.id)
t.equal(responseJson.isOwner, member.isOwner) assert.strictEqual(responseJson.isOwner, member.isOwner)
t.equal(responseJson.userId, member.userId) assert.strictEqual(responseJson.userId, member.userId)
}) })
await t.test('fails if the member is not found', async (t) => { await t.test('fails if the member is not found', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'member').value({ sinon.stub(prisma, 'member').value({
findFirst: async () => { findFirst: async () => {
@ -54,10 +56,10 @@ await tap.test('DELETE /guilds/[guildId]/members/leave', async (t) => {
authorization: `Bearer ${accessToken}` authorization: `Bearer ${accessToken}`
} }
}) })
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
}) })
await t.test('fails if the member is owner', async (t) => { await t.test('fails if the member is owner', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
const member = { const member = {
...memberExample, ...memberExample,
@ -75,6 +77,6 @@ await tap.test('DELETE /guilds/[guildId]/members/leave', async (t) => {
authorization: `Bearer ${accessToken}` authorization: `Bearer ${accessToken}`
} }
}) })
t.equal(response.statusCode, 400) assert.strictEqual(response.statusCode, 400)
}) })
}) })

View File

@ -1,11 +1,12 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { guildSchema } from '../../../../../models/Guild.js' import { guildSchema } from '#src/models/Guild.js'
import { memberSchema } from '../../../../../models/Member.js' import { memberSchema } from '#src/models/Member.js'
const parametersSchema = Type.Object({ const parametersSchema = Type.Object({
guildId: guildSchema.id guildId: guildSchema.id
@ -45,8 +46,8 @@ export const deleteMemberService: FastifyPluginAsync = async (fastify) => {
if (request.user == null) { if (request.user == null) {
throw fastify.httpErrors.forbidden() throw fastify.httpErrors.forbidden()
} }
const { user } = request const { user, params } = request
const { guildId } = request.params const { guildId } = params
const member = await prisma.member.findFirst({ const member = await prisma.member.findFirst({
where: { guildId, userId: user.current.id } where: { guildId, userId: user.current.id }
}) })

View File

@ -1,12 +1,13 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { guildSchema } from '../../../models/Guild.js' import { guildSchema } from '#src/models/Guild.js'
import { parseStringNullish } from '../../../tools/utils/parseStringNullish.js' import { parseStringNullish } from '#src/tools/utils/parseStringNullish.js'
import { channelSchema } from '../../../models/Channel.js' import { channelSchema } from '#src/models/Channel.js'
const parametersSchema = Type.Object({ const parametersSchema = Type.Object({
guildId: guildSchema.id guildId: guildSchema.id

View File

@ -1,19 +1,21 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
import prisma from '../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { memberExample } from '../../../models/Member.js' import { memberExample } from '#src/models/Member.js'
import { guildExample } from '../../../models/Guild.js' import { guildExample } from '#src/models/Guild.js'
import { channelExample } from '../../../models/Channel.js' import { channelExample } from '#src/models/Channel.js'
await tap.test('GET /guilds', async (t) => { await test('GET /guilds', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'guild').value({ sinon.stub(prisma, 'guild').value({
findUnique: async () => { findUnique: async () => {
@ -38,10 +40,10 @@ await tap.test('GET /guilds', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.length, 1) assert.strictEqual(responseJson.length, 1)
t.equal(responseJson[0].name, guildExample.name) assert.strictEqual(responseJson[0].name, guildExample.name)
t.equal(responseJson[0].description, guildExample.description) assert.strictEqual(responseJson[0].description, guildExample.description)
t.equal(responseJson[0].defaultChannelId, channelExample.id) assert.strictEqual(responseJson[0].defaultChannelId, channelExample.id)
}) })
}) })

View File

@ -1,20 +1,22 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
import prisma from '../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { memberExample } from '../../../models/Member.js' import { memberExample } from '#src/models/Member.js'
import { guildExample } from '../../../models/Guild.js' import { guildExample } from '#src/models/Guild.js'
import { channelExample } from '../../../models/Channel.js' import { channelExample } from '#src/models/Channel.js'
import { userExample } from '../../../models/User.js' import { userExample } from '#src/models/User.js'
await tap.test('POST /guilds', async (t) => { await test('POST /guilds', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
const { accessToken, user } = await authenticateUserTest() const { accessToken, user } = await authenticateUserTest()
sinon.stub(prisma, 'guild').value({ sinon.stub(prisma, 'guild').value({
create: async () => { create: async () => {
@ -49,21 +51,24 @@ await tap.test('POST /guilds', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 201) assert.strictEqual(response.statusCode, 201)
t.equal(responseJson.guild.id, guildExample.id) assert.strictEqual(responseJson.guild.id, guildExample.id)
t.equal(responseJson.guild.name, guildExample.name) assert.strictEqual(responseJson.guild.name, guildExample.name)
t.equal(responseJson.guild.description, guildExample.description) assert.strictEqual(responseJson.guild.description, guildExample.description)
t.equal(responseJson.guild.members.length, 1) assert.strictEqual(responseJson.guild.members.length, 1)
t.equal(responseJson.guild.members[0].userId, user.id) assert.strictEqual(responseJson.guild.members[0].userId, user.id)
t.equal(responseJson.guild.members[0].user.name, user.name) assert.strictEqual(responseJson.guild.members[0].user.name, user.name)
t.equal(responseJson.guild.members[0].guildId, guildExample.id) assert.strictEqual(responseJson.guild.members[0].guildId, guildExample.id)
t.equal(responseJson.guild.members[0].isOwner, memberExample.isOwner) assert.strictEqual(
t.equal(responseJson.guild.channels.length, 1) responseJson.guild.members[0].isOwner,
t.equal(responseJson.guild.channels[0].id, channelExample.id) memberExample.isOwner
t.equal(responseJson.guild.channels[0].guildId, guildExample.id) )
assert.strictEqual(responseJson.guild.channels.length, 1)
assert.strictEqual(responseJson.guild.channels[0].id, channelExample.id)
assert.strictEqual(responseJson.guild.channels[0].guildId, guildExample.id)
}) })
await t.test('fails with empty name and description', async (t) => { await t.test('fails with empty name and description', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
const response = await application.inject({ const response = await application.inject({
method: 'POST', method: 'POST',
@ -72,6 +77,6 @@ await tap.test('POST /guilds', async (t) => {
authorization: `Bearer ${accessToken}` authorization: `Bearer ${accessToken}`
} }
}) })
t.equal(response.statusCode, 400) assert.strictEqual(response.statusCode, 400)
}) })
}) })

View File

@ -1,14 +1,15 @@
import { Type, Static } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors, id } from '../../models/utils.js' import { fastifyErrors, id } from '#src/models/utils.js'
import authenticateUser from '../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { guildSchema } from '../../models/Guild.js' import { guildSchema } from '#src/models/Guild.js'
import { import {
getPaginationOptions, getPaginationOptions,
queryPaginationObjectSchema queryPaginationObjectSchema
} from '../../tools/database/pagination.js' } from '#src/tools/database/pagination.js'
type QuerySchemaType = Static<typeof queryPaginationObjectSchema> type QuerySchemaType = Static<typeof queryPaginationObjectSchema>

View File

@ -1,4 +1,4 @@
import { FastifyPluginAsync } from 'fastify' import type { FastifyPluginAsync } from 'fastify'
import { getGuilds } from './get.js' import { getGuilds } from './get.js'
import { postGuilds } from './post.js' import { postGuilds } from './post.js'

View File

@ -1,14 +1,15 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { guildSchema } from '../../models/Guild.js' import { guildSchema } from '#src/models/Guild.js'
import { channelSchema } from '../../models/Channel.js' import { channelSchema } from '#src/models/Channel.js'
import { memberSchema } from '../../models/Member.js' import { memberSchema } from '#src/models/Member.js'
import { userPublicWithoutSettingsSchema } from '../../models/User.js' import { userPublicWithoutSettingsSchema } from '#src/models/User.js'
import { parseStringNullish } from '../../tools/utils/parseStringNullish.js' import { parseStringNullish } from '#src/tools/utils/parseStringNullish.js'
const bodyPostServiceSchema = Type.Object({ const bodyPostServiceSchema = Type.Object({
name: guildSchema.name, name: guildSchema.name,

View File

@ -1,17 +1,19 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { guildExample } from '../../../../models/Guild.js' import { guildExample } from '#src/models/Guild.js'
await tap.test('GET /guilds/public', async (t) => { await test('GET /guilds/public', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'guild').value({ sinon.stub(prisma, 'guild').value({
findMany: async () => { findMany: async () => {
@ -31,9 +33,9 @@ await tap.test('GET /guilds/public', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.length, 1) assert.strictEqual(responseJson.length, 1)
t.equal(responseJson[0].name, guildExample.name) assert.strictEqual(responseJson[0].name, guildExample.name)
t.equal(responseJson[0].membersCount, 2) assert.strictEqual(responseJson[0].membersCount, 2)
}) })
}) })

View File

@ -1,14 +1,15 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { guildSchema } from '../../../models/Guild.js' import { guildSchema } from '#src/models/Guild.js'
import { import {
getPaginationOptions, getPaginationOptions,
queryPaginationSchema queryPaginationSchema
} from '../../../tools/database/pagination.js' } from '#src/tools/database/pagination.js'
const querySchema = Type.Object({ const querySchema = Type.Object({
search: Type.Optional(Type.String()), search: Type.Optional(Type.String()),
@ -18,7 +19,8 @@ const querySchema = Type.Object({
export type QuerySchemaType = Static<typeof querySchema> export type QuerySchemaType = Static<typeof querySchema>
const getServiceSchema: FastifySchema = { const getServiceSchema: FastifySchema = {
description: 'GET all the public guilds.', description:
'GET all the public guilds (ordered by descending members count).',
tags: ['guilds'] as string[], tags: ['guilds'] as string[],
security: [ security: [
{ {
@ -55,7 +57,11 @@ export const getGuildsPublic: FastifyPluginAsync = async (fastify) => {
} }
const guildsRequest = await prisma.guild.findMany({ const guildsRequest = await prisma.guild.findMany({
...getPaginationOptions(request.query), ...getPaginationOptions(request.query),
orderBy: { createdAt: 'desc' }, orderBy: {
members: {
_count: 'desc'
}
},
...(request.query.search != null && { ...(request.query.search != null && {
where: { where: {
name: { contains: request.query.search } name: { contains: request.query.search }

View File

@ -1,4 +1,4 @@
import { FastifyPluginAsync } from 'fastify' import type { FastifyPluginAsync } from 'fastify'
import { usersService } from './users/index.js' import { usersService } from './users/index.js'
import { guildsService } from './guilds/index.js' import { guildsService } from './guilds/index.js'

View File

@ -1,20 +1,22 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { messageExample } from '../../../../models/Message.js' import { messageExample } from '#src/models/Message.js'
import { memberExample } from '../../../../models/Member.js' import { memberExample } from '#src/models/Member.js'
import { userExample } from '../../../../models/User.js' import { userExample } from '#src/models/User.js'
import { channelExample } from '../../../../models/Channel.js' import { channelExample } from '#src/models/Channel.js'
await tap.test('DELETE /messsages/[messageId]', async (t) => { await test('DELETE /messsages/[messageId]', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'message').value({ sinon.stub(prisma, 'message').value({
findFirst: async () => { findFirst: async () => {
@ -43,18 +45,18 @@ await tap.test('DELETE /messsages/[messageId]', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.id, messageExample.id) assert.strictEqual(responseJson.id, messageExample.id)
t.equal(responseJson.value, messageExample.value) assert.strictEqual(responseJson.value, messageExample.value)
t.equal(responseJson.type, messageExample.type) assert.strictEqual(responseJson.type, messageExample.type)
t.equal(responseJson.mimetype, messageExample.mimetype) assert.strictEqual(responseJson.mimetype, messageExample.mimetype)
t.equal(responseJson.member.id, memberExample.id) assert.strictEqual(responseJson.member.id, memberExample.id)
t.equal(responseJson.member.isOwner, memberExample.isOwner) assert.strictEqual(responseJson.member.isOwner, memberExample.isOwner)
t.equal(responseJson.member.user.id, userExample.id) assert.strictEqual(responseJson.member.user.id, userExample.id)
t.equal(responseJson.member.user.name, userExample.name) assert.strictEqual(responseJson.member.user.name, userExample.name)
}) })
await t.test('fails if the message is not found', async (t) => { await t.test('fails if the message is not found', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'message').value({ sinon.stub(prisma, 'message').value({
findFirst: async () => { findFirst: async () => {
@ -68,10 +70,10 @@ await tap.test('DELETE /messsages/[messageId]', async (t) => {
authorization: `Bearer ${accessToken}` authorization: `Bearer ${accessToken}`
} }
}) })
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
}) })
await t.test('fails if the member is not found', async (t) => { await t.test('fails if the member is not found', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
sinon.stub(prisma, 'message').value({ sinon.stub(prisma, 'message').value({
findFirst: async () => { findFirst: async () => {
@ -93,10 +95,10 @@ await tap.test('DELETE /messsages/[messageId]', async (t) => {
authorization: `Bearer ${accessToken}` authorization: `Bearer ${accessToken}`
} }
}) })
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
}) })
await t.test('fails if the member is not owner of the message', async (t) => { await t.test('fails if the member is not owner of the message', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
const randomUserIdOwnerOfMessage = 14 const randomUserIdOwnerOfMessage = 14
sinon.stub(prisma, 'message').value({ sinon.stub(prisma, 'message').value({
@ -122,6 +124,6 @@ await tap.test('DELETE /messsages/[messageId]', async (t) => {
authorization: `Bearer ${accessToken}` authorization: `Bearer ${accessToken}`
} }
}) })
t.equal(response.statusCode, 400) assert.strictEqual(response.statusCode, 400)
}) })
}) })

View File

@ -1,20 +1,22 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { messageExample } from '../../../../models/Message.js' import { messageExample } from '#src/models/Message.js'
import { memberExample } from '../../../../models/Member.js' import { memberExample } from '#src/models/Member.js'
import { userExample } from '../../../../models/User.js' import { userExample } from '#src/models/User.js'
import { channelExample } from '../../../../models/Channel.js' import { channelExample } from '#src/models/Channel.js'
await tap.test('PUT /messsages/[messageId]', async (t) => { await test('PUT /messsages/[messageId]', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
const newValue = 'some message' const newValue = 'some message'
sinon.stub(prisma, 'message').value({ sinon.stub(prisma, 'message').value({
@ -48,18 +50,18 @@ await tap.test('PUT /messsages/[messageId]', async (t) => {
payload: { value: newValue } payload: { value: newValue }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.id, messageExample.id) assert.strictEqual(responseJson.id, messageExample.id)
t.equal(responseJson.value, newValue) assert.strictEqual(responseJson.value, newValue)
t.equal(responseJson.type, messageExample.type) assert.strictEqual(responseJson.type, messageExample.type)
t.equal(responseJson.mimetype, messageExample.mimetype) assert.strictEqual(responseJson.mimetype, messageExample.mimetype)
t.equal(responseJson.member.id, memberExample.id) assert.strictEqual(responseJson.member.id, memberExample.id)
t.equal(responseJson.member.isOwner, memberExample.isOwner) assert.strictEqual(responseJson.member.isOwner, memberExample.isOwner)
t.equal(responseJson.member.user.id, userExample.id) assert.strictEqual(responseJson.member.user.id, userExample.id)
t.equal(responseJson.member.user.name, userExample.name) assert.strictEqual(responseJson.member.user.name, userExample.name)
}) })
await t.test('fails if the message is not found', async (t) => { await t.test('fails if the message is not found', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
const newValue = 'some message' const newValue = 'some message'
sinon.stub(prisma, 'message').value({ sinon.stub(prisma, 'message').value({
@ -75,10 +77,10 @@ await tap.test('PUT /messsages/[messageId]', async (t) => {
}, },
payload: { value: newValue } payload: { value: newValue }
}) })
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
}) })
await t.test('fails if the member is not found', async (t) => { await t.test('fails if the member is not found', async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
const newValue = 'some message' const newValue = 'some message'
sinon.stub(prisma, 'message').value({ sinon.stub(prisma, 'message').value({
@ -102,12 +104,12 @@ await tap.test('PUT /messsages/[messageId]', async (t) => {
}, },
payload: { value: newValue } payload: { value: newValue }
}) })
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
}) })
await t.test( await t.test(
'fails if the member is not the owner of the message', 'fails if the member is not the owner of the message',
async (t) => { async () => {
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
const newValue = 'some message' const newValue = 'some message'
const randomUserIdOwnerOfMessage = 14 const randomUserIdOwnerOfMessage = 14
@ -135,7 +137,7 @@ await tap.test('PUT /messsages/[messageId]', async (t) => {
}, },
payload: { value: newValue } payload: { value: newValue }
}) })
t.equal(response.statusCode, 400) assert.strictEqual(response.statusCode, 400)
} }
) )
}) })

View File

@ -1,12 +1,13 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { messageSchema } from '../../../models/Message.js' import { messageSchema } from '#src/models/Message.js'
import { memberSchema } from '../../../models/Member.js' import { memberSchema } from '#src/models/Member.js'
import { userPublicWithoutSettingsSchema } from '../../../models/User.js' import { userPublicWithoutSettingsSchema } from '#src/models/User.js'
const parametersSchema = Type.Object({ const parametersSchema = Type.Object({
messageId: messageSchema.id messageId: messageSchema.id
@ -52,8 +53,8 @@ export const deleteMessageService: FastifyPluginAsync = async (fastify) => {
if (request.user == null) { if (request.user == null) {
throw fastify.httpErrors.forbidden() throw fastify.httpErrors.forbidden()
} }
const { user } = request const { user, params } = request
const { messageId } = request.params const { messageId } = params
const messageCheck = await prisma.message.findFirst({ const messageCheck = await prisma.message.findFirst({
where: { id: messageId }, where: { id: messageId },
include: { include: {

View File

@ -1,12 +1,13 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { messageSchema } from '../../../models/Message.js' import { messageSchema } from '#src/models/Message.js'
import { memberSchema } from '../../../models/Member.js' import { memberSchema } from '#src/models/Member.js'
import { userPublicWithoutSettingsSchema } from '../../../models/User.js' import { userPublicWithoutSettingsSchema } from '#src/models/User.js'
const bodyPutServiceSchema = Type.Object({ const bodyPutServiceSchema = Type.Object({
value: messageSchema.value value: messageSchema.value
@ -60,9 +61,9 @@ export const putMessageService: FastifyPluginAsync = async (fastify) => {
if (request.user == null) { if (request.user == null) {
throw fastify.httpErrors.forbidden() throw fastify.httpErrors.forbidden()
} }
const { user } = request const { user, params, body } = request
const { messageId } = request.params const { messageId } = params
const { value } = request.body const { value } = body
const messageCheck = await prisma.message.findFirst({ const messageCheck = await prisma.message.findFirst({
where: { id: messageId, type: 'text' }, where: { id: messageId, type: 'text' },
include: { include: {

View File

@ -1,4 +1,4 @@
import { FastifyPluginAsync } from 'fastify' import type { FastifyPluginAsync } from 'fastify'
import { deleteMessageService } from './[messageId]/delete.js' import { deleteMessageService } from './[messageId]/delete.js'
import { putMessageService } from './[messageId]/put.js' import { putMessageService } from './[messageId]/put.js'

View File

@ -1,17 +1,19 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../application.js' import { application } from '#src/application.js'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { userExample } from '../../../../models/User.js' import { userExample } from '#src/models/User.js'
import { userSettingsExample } from '../../../../models/UserSettings.js' import { userSettingsExample } from '#src/models/UserSettings.js'
await tap.test('GET /users/[userId]', async (t) => { await test('GET /users/[userId]', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
sinon.stub(prisma, 'guild').value({ sinon.stub(prisma, 'guild').value({
findMany: async () => { findMany: async () => {
return [] return []
@ -32,12 +34,12 @@ await tap.test('GET /users/[userId]', async (t) => {
url: `/users/${userExample.id}` url: `/users/${userExample.id}`
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.user.id, userExample.id) assert.strictEqual(responseJson.user.id, userExample.id)
t.equal(responseJson.user.name, userExample.name) assert.strictEqual(responseJson.user.name, userExample.name)
}) })
await t.test('fails with not found user', async (t) => { await t.test('fails with not found user', async () => {
sinon.stub(prisma, 'userSetting').value({ sinon.stub(prisma, 'userSetting').value({
findFirst: async () => { findFirst: async () => {
return null return null
@ -48,7 +50,7 @@ await tap.test('GET /users/[userId]', async (t) => {
url: `/users/1` url: `/users/1`
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 404) assert.strictEqual(response.statusCode, 404)
t.equal(responseJson.message, 'User not found') assert.strictEqual(responseJson.message, 'User not found')
}) })
}) })

View File

@ -1,10 +1,11 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import { userPublicSchema } from '../../../models/User.js' import { userPublicSchema } from '#src/models/User.js'
import { guildSchema } from '../../../models/Guild.js' import { guildSchema } from '#src/models/Guild.js'
const parametersGetUserSchema = Type.Object({ const parametersGetUserSchema = Type.Object({
userId: userPublicSchema.id userId: userPublicSchema.id
@ -50,6 +51,7 @@ export const getUserById: FastifyPluginAsync = async (fastify) => {
id: true, id: true,
name: true, name: true,
email: settings.isPublicEmail, email: settings.isPublicEmail,
isConfirmed: true,
logo: true, logo: true,
status: true, status: true,
biography: true, biography: true,

View File

@ -1,16 +1,18 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../application.js' import { application } from '#src/application.js'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { userExample } from '../../../../models/User.js' import { userExample } from '#src/models/User.js'
await tap.test('GET /users/confirm-email', async (t) => { await test('GET /users/confirm-email', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
sinon.stub(prisma, 'user').value({ sinon.stub(prisma, 'user').value({
findFirst: async () => { findFirst: async () => {
return userExample return userExample
@ -26,10 +28,10 @@ await tap.test('GET /users/confirm-email', async (t) => {
temporaryToken: userExample.temporaryToken ?? '' temporaryToken: userExample.temporaryToken ?? ''
} }
}) })
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
}) })
await t.test('should fails with invalid `temporaryToken`', async (t) => { await t.test('should fails with invalid `temporaryToken`', async () => {
sinon.stub(prisma, 'user').value({ sinon.stub(prisma, 'user').value({
findFirst: async () => { findFirst: async () => {
return null return null
@ -45,6 +47,6 @@ await tap.test('GET /users/confirm-email', async (t) => {
temporaryToken: userExample.temporaryToken ?? '' temporaryToken: userExample.temporaryToken ?? ''
} }
}) })
t.equal(response.statusCode, 403) assert.strictEqual(response.statusCode, 403)
}) })
}) })

View File

@ -1,9 +1,10 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import { userSchema } from '../../../models/User.js' import { userSchema } from '#src/models/User.js'
const queryGetConfirmEmailSchema = Type.Object({ const queryGetConfirmEmailSchema = Type.Object({
redirectURI: Type.Optional(Type.String({ format: 'uri-reference' })), redirectURI: Type.Optional(Type.String({ format: 'uri-reference' })),
@ -53,7 +54,7 @@ export const getConfirmEmail: FastifyPluginAsync = async (fastify) => {
reply.statusCode = 200 reply.statusCode = 200
return 'Success, your email has been confirmed, you can now signin!' return 'Success, your email has been confirmed, you can now signin!'
} }
await reply.redirect(redirectURI) return await reply.redirect(redirectURI)
} }
}) })
} }

View File

@ -1,15 +1,17 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
await tap.test('GET /users/current', async (t) => { await test('GET /users/current', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async (t) => { await t.test('succeeds', async () => {
const { accessToken, user } = await authenticateUserTest() const { accessToken, user } = await authenticateUserTest()
const response = await application.inject({ const response = await application.inject({
method: 'GET', method: 'GET',
@ -19,16 +21,16 @@ await tap.test('GET /users/current', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.user.name, user.name) assert.strictEqual(responseJson.user.name, user.name)
t.strictSame(responseJson.user.strategies, ['Local']) assert.deepStrictEqual(responseJson.user.strategies, ['Local'])
}) })
await t.test('fails with unauthenticated user', async (t) => { await t.test('fails with unauthenticated user', async () => {
const response = await application.inject({ const response = await application.inject({
method: 'GET', method: 'GET',
url: '/users/current' url: '/users/current'
}) })
t.equal(response.statusCode, 401) assert.strictEqual(response.statusCode, 401)
}) })
}) })

View File

@ -1,16 +1,18 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../application.js' import { application } from '#src/application.js'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { authenticateUserTest } from '../../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
await tap.test('PUT /users/current', async (t) => { await test('PUT /users/current', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds with valid accessToken and valid name', async (t) => { await t.test('succeeds with valid accessToken and valid name', async () => {
const newName = 'John Doe' const newName = 'John Doe'
const { accessToken, user, userStubValue } = await authenticateUserTest() const { accessToken, user, userStubValue } = await authenticateUserTest()
sinon.stub(prisma, 'user').value({ sinon.stub(prisma, 'user').value({
@ -36,11 +38,11 @@ await tap.test('PUT /users/current', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.user.name, newName) assert.strictEqual(responseJson.user.name, newName)
}) })
await t.test('succeeds and only update the status', async (t) => { await t.test('succeeds and only update the status', async () => {
const newStatus = '👀 Working on secret projects...' const newStatus = '👀 Working on secret projects...'
const { accessToken, user, userStubValue } = await authenticateUserTest() const { accessToken, user, userStubValue } = await authenticateUserTest()
sinon.stub(prisma, 'user').value({ sinon.stub(prisma, 'user').value({
@ -66,12 +68,12 @@ await tap.test('PUT /users/current', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.user.name, user.name) assert.strictEqual(responseJson.user.name, user.name)
t.equal(responseJson.user.status, newStatus) assert.strictEqual(responseJson.user.status, newStatus)
}) })
await t.test('fails with name already used', async (t) => { await t.test('fails with name already used', async () => {
const newName = 'John Doe' const newName = 'John Doe'
const { accessToken, user, userStubValue } = await authenticateUserTest() const { accessToken, user, userStubValue } = await authenticateUserTest()
sinon.stub(prisma, 'user').value({ sinon.stub(prisma, 'user').value({
@ -90,10 +92,10 @@ await tap.test('PUT /users/current', async (t) => {
name: newName name: newName
} }
}) })
t.equal(response.statusCode, 400) assert.strictEqual(response.statusCode, 400)
}) })
await t.test('fails with invalid website url', async (t) => { await t.test('fails with invalid website url', async () => {
const newWebsite = 'invalid website url' const newWebsite = 'invalid website url'
const { accessToken } = await authenticateUserTest() const { accessToken } = await authenticateUserTest()
const response = await application.inject({ const response = await application.inject({
@ -106,10 +108,10 @@ await tap.test('PUT /users/current', async (t) => {
website: newWebsite website: newWebsite
} }
}) })
t.equal(response.statusCode, 400) assert.strictEqual(response.statusCode, 400)
}) })
await t.test('succeeds with valid website url', async (t) => { await t.test('succeeds with valid website url', async () => {
const newWebsite = 'https://somerandomwebsite.com' const newWebsite = 'https://somerandomwebsite.com'
const { accessToken, user, userStubValue } = await authenticateUserTest() const { accessToken, user, userStubValue } = await authenticateUserTest()
sinon.stub(prisma, 'user').value({ sinon.stub(prisma, 'user').value({
@ -135,8 +137,8 @@ await tap.test('PUT /users/current', async (t) => {
} }
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.user.name, user.name) assert.strictEqual(responseJson.user.name, user.name)
t.equal(responseJson.user.website, newWebsite) assert.strictEqual(responseJson.user.website, newWebsite)
}) })
}) })

View File

@ -1,9 +1,9 @@
import { FastifyPluginAsync, FastifySchema } from 'fastify' import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { userCurrentSchema } from '../../../models/User.js' import { userCurrentSchema } from '#src/models/User.js'
const getCurrentUserSchema: FastifySchema = { const getCurrentUserSchema: FastifySchema = {
description: 'GET the current connected user', description: 'GET the current connected user',

View File

@ -1,11 +1,11 @@
import { Type } from '@sinclair/typebox' import { Type } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import fastifyMultipart from 'fastify-multipart' import fastifyMultipart from '@fastify/multipart'
import authenticateUser from '../../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { fastifyErrors } from '../../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { uploadFile } from '../../../../tools/utils/uploadFile.js' import { uploadFile } from '#src/tools/utils/uploadFile.js'
const putServiceSchema: FastifySchema = { const putServiceSchema: FastifySchema = {
description: 'Edit the current connected user logo', description: 'Edit the current connected user logo',

View File

@ -1,16 +1,17 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { userCurrentSchema, userSchema } from '../../../models/User.js' import { userCurrentSchema, userSchema } from '#src/models/User.js'
import { sendEmail } from '../../../tools/email/sendEmail.js' import { sendEmail } from '#src/tools/email/sendEmail.js'
import { API_URL } from '../../../tools/configurations/index.js' import { API_URL } from '#src/tools/configurations.js'
import { Language, Theme } from '../../../models/UserSettings.js' import type { Language, Theme } from '#src/models/UserSettings.js'
import { parseStringNullish } from '../../../tools/utils/parseStringNullish.js' import { parseStringNullish } from '#src/tools/utils/parseStringNullish.js'
const bodyPutServiceSchema = Type.Object({ const bodyPutServiceSchema = Type.Object({
name: Type.Optional(userSchema.name), name: Type.Optional(userSchema.name),
@ -136,7 +137,9 @@ export const putCurrentUser: FastifyPluginAsync = async (fastify) => {
}) })
await fastify.io.emitToAuthorizedUsers({ await fastify.io.emitToAuthorizedUsers({
event: 'users', event: 'users',
isAuthorizedCallback: () => true, isAuthorizedCallback: () => {
return true
},
payload: { payload: {
action: 'update', action: 'update',
item: user item: user

View File

@ -1,19 +1,21 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import sinon from 'sinon' import sinon from 'sinon'
import { application } from '../../../../../application.js' import { application } from '#src/application.js'
import { authenticateUserTest } from '../../../../../__test__/utils/authenticateUserTest.js' import { authenticateUserTest } from '#src/__test__/utils/authenticateUserTest.js'
import prisma from '../../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { userSettingsExample } from '../../../../../models/UserSettings.js' import { userSettingsExample } from '#src/models/UserSettings.js'
await tap.test('PUT /users/current/settings', async (t) => { await test('PUT /users/current/settings', async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test( await t.test(
'succeeds and edit the theme, language, isPublicEmail and isPublicGuilds', 'succeeds and edit the theme, language, isPublicEmail and isPublicGuilds',
async (t) => { async () => {
const newSettings = { const newSettings = {
theme: 'light', theme: 'light',
language: 'fr', language: 'fr',
@ -42,15 +44,21 @@ await tap.test('PUT /users/current/settings', async (t) => {
payload: newSettings payload: newSettings
}) })
const responseJson = response.json() const responseJson = response.json()
t.equal(response.statusCode, 200) assert.strictEqual(response.statusCode, 200)
t.equal(responseJson.settings.theme, newSettings.theme) assert.strictEqual(responseJson.settings.theme, newSettings.theme)
t.equal(responseJson.settings.language, newSettings.language) assert.strictEqual(responseJson.settings.language, newSettings.language)
t.equal(responseJson.settings.isPublicEmail, newSettings.isPublicEmail) assert.strictEqual(
t.equal(responseJson.settings.isPublicGuilds, newSettings.isPublicGuilds) responseJson.settings.isPublicEmail,
newSettings.isPublicEmail
)
assert.strictEqual(
responseJson.settings.isPublicGuilds,
newSettings.isPublicGuilds
)
} }
) )
await t.test('fails with invalid language', async (t) => { await t.test('fails with invalid language', async () => {
const newSettings = { const newSettings = {
language: 'somerandomlanguage' language: 'somerandomlanguage'
} }
@ -75,6 +83,6 @@ await tap.test('PUT /users/current/settings', async (t) => {
}, },
payload: newSettings payload: newSettings
}) })
t.equal(response.statusCode, 400) assert.strictEqual(response.statusCode, 400)
}) })
}) })

View File

@ -1,10 +1,11 @@
import { Static, Type } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync, FastifySchema } from 'fastify'
import prisma from '../../../../tools/database/prisma.js' import prisma from '#src/tools/database/prisma.js'
import { fastifyErrors } from '../../../../models/utils.js' import { fastifyErrors } from '#src/models/utils.js'
import authenticateUser from '../../../../tools/plugins/authenticateUser.js' import authenticateUser from '#src/tools/plugins/authenticateUser.js'
import { userSettingsSchema } from '../../../../models/UserSettings.js' import { userSettingsSchema } from '#src/models/UserSettings.js'
const bodyPutServiceSchema = Type.Object({ const bodyPutServiceSchema = Type.Object({
theme: Type.Optional(userSettingsSchema.theme), theme: Type.Optional(userSettingsSchema.theme),

View File

@ -1,4 +1,4 @@
import { FastifyPluginAsync } from 'fastify' import type { FastifyPluginAsync } from 'fastify'
import { postSignupUser } from './signup/post.js' import { postSignupUser } from './signup/post.js'
import { getConfirmEmail } from './confirm-email/get.js' import { getConfirmEmail } from './confirm-email/get.js'

Some files were not shown because too many files have changed in this diff Show More