diff --git a/.gitignore b/.gitignore index 294fde54e..af915f9c4 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,7 @@ locale/**/*.js dumped.sql /utils/data/patrons.json + +# cypress +cypress/videos/* +cypress/screenshots/* diff --git a/README.md b/README.md index eaf988147..add8a532a 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,13 @@ With the following steps you can access a few pages that don't need a database. 1. Clone the project 2. Run `npm i` to install dependencies 3. Run `npm run compile` to compile translation files. -4. Run `npm run dev` to start the development server at http://localhost:3000/ +4. Run `npm run dev` to start the development server at http://localhost:3000/. (To stop the server at any time, type `Ctrl+C`.) + +If you do not intend to perform any additional setup steps, you will also need to create an empty list of patrons in `utils/data/patrons.json`: + +``` +[] +``` ### Access rest of the pages @@ -59,18 +65,19 @@ DATABASE_URL=postgresql://sendou@localhost:5432 _You can see [Prisma's guide on how to set up a PostgreSQL database running locally](https://www.prisma.io/dataguide/postgresql/setting-up-a-local-postgresql-database) for more info._ 6. Use `npm run migrate` to get the database formatted with the right tables. -7. There should be a seeding script but this doesn't exist yet. If anyone is interested in contributing this is probably a good starting point (see issue #197). +7. Run `npm run prebuild` to generate a few necessary JSON configuration files. +8. Seed some example data in the database by running `npm run seed`. (This seed data is incomplete – see issue #197 if you would like to improve the seed data!) ### Enable logging in In addition to the steps above the steps below enable logging in. -7. Create a file called `.env.local` in the root folder. In it you need following variables: +9. Create a file called `.env.local` in the root folder. In it you need following variables: ``` -DISCORD_CLIENT_ID= -DISCORD_CLIENT_SECRET= -JWT_SECRET= +DISCORD_CLIENT_ID="" +DISCORD_CLIENT_SECRET="" +JWT_SECRET="" ``` a) Go to https://discord.com/developers/applications @@ -79,7 +86,12 @@ c) Go to your newly generated application d) On the "General Information" tab both "CLIENT ID" and "CLIENT SECRET" can be found. e) On the "OAuth2" tab add `http://localhost:3000/api/auth/callback/discord` in the list of redirects. -`JWT_SECRET` can be any randomly generated reasonably long string. +For `JWT_SECRET`, use a long, cryptographically random string. You can use `node` to generate such a string as follows: +``` +node -e "require('crypto').randomBytes(64, function(ex, buf) { console.log(buf.toString('base64')) })" +``` + +Make sure to restart your server after setting these new values (`Ctrl+C` + `npm run dev`). ## Using API @@ -89,4 +101,8 @@ Using other endpoints isn't advised as I change those as I feel to suit the need ## Contributing -If you are interested in contributing come say hello on Discord! For any feature requests or bug reports you can either leave an issue or use the #feedback channel on Discord. +Any kind of contributions are most welcome! If you notice a problem with the website or have a feature request then you can submit an issue for it if one doesn't already exist. + +I label [issues that should be the most approachable to contribute towards with the help wanted label](https://github.com/Sendouc/sendou.ink/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22). That doesn't mean you couldn't work on other issues just ask if you need extra help with them. If you want to work on something that isn't an issue yet then just make one first so we can discuss it before you start. + +If you have any questions you can either make an issue with the label question or ask it on the [Discord server](https://discord.gg/sendou) (there is a channel called `#💻-development` for this purpose). diff --git a/cypress/support/index.ts b/cypress/support/index.ts index 328571d58..065ebc68a 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -13,5 +13,6 @@ Cypress.Commands.add("login", (user: "sendou" | "nzap") => { }); beforeEach(() => { - cy.exec("npm run seed"); + // TODO: use database transactions, instead of dropping and recreating the database with each individual test + cy.exec("npm run migrate:reset -- --force"); }); diff --git a/package-lock.json b/package-lock.json index 2cff0f5bd..030ad6b99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,6 +62,7 @@ "@types/uuid": "^8.3.0", "cross-env": "^7.0.3", "cypress": "^6.8.0", + "fishery": "^1.2.0", "prettier": "^2.2.1", "prisma": "^2.19.0", "ts-node": "^9.1.1", @@ -4024,6 +4025,15 @@ "node": ">=8" } }, + "node_modules/fishery": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fishery/-/fishery-1.2.0.tgz", + "integrity": "sha512-0GG029KHF3p8Q0NiAl/ZOK1fvyAprOiHdtRWUNS46x9QXuQhMwzcGLNDbZ7XIEEBowwBmMsw7StkaU0ek9dSbg==", + "dev": true, + "dependencies": { + "lodash.mergewith": "^4.6.2" + } + }, "node_modules/focus-lock": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.8.1.tgz", @@ -12164,6 +12174,15 @@ "path-exists": "^4.0.0" } }, + "fishery": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fishery/-/fishery-1.2.0.tgz", + "integrity": "sha512-0GG029KHF3p8Q0NiAl/ZOK1fvyAprOiHdtRWUNS46x9QXuQhMwzcGLNDbZ7XIEEBowwBmMsw7StkaU0ek9dSbg==", + "dev": true, + "requires": { + "lodash.mergewith": "^4.6.2" + } + }, "focus-lock": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.8.1.tgz", diff --git a/package.json b/package.json index f96905373..82a0b3eb9 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "start": "next start", "migrate": "prisma migrate deploy --preview-feature", "migrate:save": "prisma migrate dev --create-only --preview-feature", + "migrate:reset": "prisma migrate reset", "gen": "npx prisma generate", "prebuild": "ts-node prisma/scripts/preBuild.ts", "mongo": "ts-node prisma/scripts/dataFromMongo.ts", @@ -77,6 +78,7 @@ "@types/uuid": "^8.3.0", "cross-env": "^7.0.3", "cypress": "^6.8.0", + "fishery": "^1.2.0", "prettier": "^2.2.1", "prisma": "^2.19.0", "ts-node": "^9.1.1", diff --git a/prisma/factories/user.ts b/prisma/factories/user.ts new file mode 100644 index 000000000..ffa8217df --- /dev/null +++ b/prisma/factories/user.ts @@ -0,0 +1,21 @@ +import { Factory } from "fishery"; +import { User } from "@prisma/client"; +import prisma from "../client"; + +export default Factory.define(({ sequence, onCreate }) => { + onCreate(user => { + return prisma.user.create({ data: user }); + }); + + return { + id: sequence, + discordId: sequence.toString().padStart(17, '0'), + discordAvatar: null, + discriminator: sequence.toString().padStart(4, '0'), + username: `User${sequence}`, + patreonTier: 0, + canPostEvents: false, + teamId: null, + ladderTeamId: null + }; +}); diff --git a/prisma/mocks/user.ts b/prisma/mocks/user.ts deleted file mode 100644 index e333a139f..000000000 --- a/prisma/mocks/user.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Prisma } from "@prisma/client"; - -export const getUsersData = (): Prisma.UserCreateManyInput[] => { - return [ - ...new Array(10).fill(null).map((_, i) => ({ - id: i + 1, - discordId: padWithZero(i, 17), - discriminator: padWithZero(i, 4), - username: `User${i + 1}`, - })), - { - id: 11, - discordId: "79237403620945920", - username: "Sendou", - discriminator: "4059", - patreonTier: 1, - discordAvatar: "1e0968214a6ea74aebce4bbd699d6aae", - }, - { - id: 12, - discordId: "455039198672453645", - username: "NZAP", - discriminator: "6227", - discordAvatar: "f809176af93132c3db5f0a5019e96339", - }, - ]; -}; - -function padWithZero(root: number, totalLength: number) { - let result = "" + root; - while (result.length < totalLength) { - result += "0"; - } - - return result; -} diff --git a/prisma/scripts/preBuild.ts b/prisma/scripts/preBuild.ts index 36ff782d0..c11f208ad 100644 --- a/prisma/scripts/preBuild.ts +++ b/prisma/scripts/preBuild.ts @@ -2,6 +2,9 @@ import fs from "fs"; import path from "path"; import prisma from "../client"; +// Include Prisma's .env file as well, so we can fetch the DATABASE_URL +require('dotenv').config({path: 'prisma/.env'}); + const main = async () => { const patrons = await prisma.user.findMany({ where: { patreonTier: { not: null } }, diff --git a/prisma/seed.ts b/prisma/seed.ts index 7c21449e8..e50dba669 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -6,29 +6,11 @@ import { getPlusSuggestionsData, getPlusStatusesData, } from "./mocks/plus"; -import { getUsersData } from "./mocks/user"; +import userFactory from "./factories/user" async function main() { throwIfNotLocalhost(); - - await prisma.profile.deleteMany({}); - await prisma.build.deleteMany({}); - await prisma.salmonRunRecord.deleteMany({}); - await prisma.freeAgentPost.deleteMany({}); - await prisma.team.deleteMany({}); - await prisma.ladderPlayerTrueSkill.deleteMany({}); - await prisma.ladderMatchPlayer.deleteMany({}); - await prisma.plusVotingSummary.deleteMany({}); - await prisma.plusSuggestion.deleteMany({}); - await prisma.plusStatus.deleteMany({}); - await prisma.user.deleteMany({}); - - await prisma.user.createMany({ data: getUsersData() }); - await prisma.plusStatus.createMany({ data: getPlusStatusesData() }); - await prisma.plusSuggestion.createMany({ data: getPlusSuggestionsData() }); - await prisma.plusVotingSummary.createMany({ - data: getPlusVotingSummaryData(), - }); + await seedNewData(); } function throwIfNotLocalhost() { @@ -54,6 +36,27 @@ function throwIfNotLocalhost() { ); } +async function seedNewData() { + await seedUsers(); + await prisma.plusStatus.createMany({ data: getPlusStatusesData() }); + await prisma.plusSuggestion.createMany({ data: getPlusSuggestionsData() }); + await prisma.plusVotingSummary.createMany({ + data: getPlusVotingSummaryData(), + }); +} + +async function seedUsers() { + const randomUsers = [...Array(10)].map((_, _i) => { + return userFactory.build(); + }) + + await prisma.user.createMany({data: [ + ...randomUsers, + userFactory.build({username: "Sendou", patreonTier: 1}), + userFactory.build({username: "NZAP"}) + ]}) +} + main() .catch((e) => { console.error(e);