Skip to content

Commit

Permalink
First release. ⚡️
Browse files Browse the repository at this point in the history
  • Loading branch information
sandulat committed Sep 23, 2020
1 parent ad4eafc commit 858674d
Show file tree
Hide file tree
Showing 93 changed files with 5,517 additions and 616 deletions.
15 changes: 13 additions & 2 deletions .env
Original file line number Diff line number Diff line change
@@ -1,2 +1,13 @@
# This env file should be checked into source control
# This is the place for default values that should be used in all environments
APP_URL=

DATABASE_URL=

# Mail drivers: SENDGRID, SMTP, PREVIEW
MAIL_DRIVER=PREVIEW
MAIL_HOST=
MAIL_PORT=
MAIL_USER=
MAIL_PASS=
MAIL_FROM_EMAIL=
MAIL_FROM_NAME=
MAIL_SENDGRID_KEY=
155 changes: 32 additions & 123 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
[![Blitz.js](https://raw.githubusercontent.com/blitz-js/art/master/github-cover-photo.png)](https://blitzjs.com)
# **Blitz.js Jobs**

This is a [Blitz.js](https://github.com/blitz-js/blitz) app.

# **name**
The first-ever [Blitz.js job board](https://blitz-jobs.com/). This project is non-commercial, meaning that anyone can post jobs for free.

## Getting Started

Expand All @@ -19,26 +17,38 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the
Ensure the `.env.local` file has required environment variables:

```
APP_URL=http://localhost:3000
DATABASE_URL=postgresql://<YOUR_DB_USERNAME>@localhost:5432/jobboard
MAIL_DRIVER=PREVIEW
MAIL_FROM_EMAIL="[email protected]"
MAIL_FROM_NAME="Blitz.js Jobs"
```

Ensure the `.env.test.local` file has required environment variables:
## Mail

```
DATABASE_URL=postgresql://<YOUR_DB_USERNAME>@localhost:5432/jobboard_test
```
The environment variable `MAIL_DRIVER` can be equal to one of these:

## Tests
- `SMTP` - Basic SMTP driver. Requires these environment variables to be set:
- `MAIL_HOST`
- `MAIL_PORT`
- `MAIL_USER`
- `MAIL_PASS`
- `SENDGRID` - Sendgrid driver. Requires these environment variables to be set:
- `MAIL_SENDGRID_KEY`
- `PREVIEW` - Browser preview driver.

Runs your tests using Jest.
Usage example:

```
blitz test
or
yarn test
```
```js
import { mail } from "app/mail/mail"

Blitz comes with a test setup using [Jest](https://jestjs.io/) and [react-testing-library](https://testing-library.com/).
mail.send({
subject: "Please confirm your email",
to: "[email protected]",
view: "auth/mail/user-confirmation",
variables: { confirmationUrl: "https://confirmation.url/" },
})
```

## Commands

Expand All @@ -58,113 +68,12 @@ Blitz comes with a powerful CLI that is designed to make development easy and fa

You can read more about it on the [CLI Overview](https://blitzjs.com/docs/cli-overview) documentation.

## What's included?

Here is the structure of your app.

```
jobboard
├── app
│ |── auth
│   │   ├── components
│   │   │ └── LoginForm.tsx
│   │   ├── mutations
│   │   │ ├── login.ts
│   │   │ ├── logout.ts
│   │   │ └── signup.ts
│   │   └── pages
│   │   ├── login.tsx
│   │   └── signup.tsx
│   ├── auth-utils.ts
│   ├── validations.ts
│   ├── components
│   │   ├── Form.tsx
│   │   └── LabeledTextField.tsx
│   ├── hooks
│   │   └── useCurrentUser.ts
│   ├── layouts
│   │   └── Layout.tsx
│   │── pages
│   │ ├── _app.tsx
│   │ ├── _document.tsx
│   │ ├── 404.tsx
│   │ ├── index.tsx
│   │ └── index.test.tsx
│   └── users
│   │   └── queries
│   │   └── getCurrentUser.ts
├── db
│   ├── migrations
│   ├── index.ts
│   └── schema.prisma
├── integrations
├── node_modules
├── public
│   ├── favicon.ico
│   └── logo.png
├── test
│   ├── __mocks__
│   │   └── fileMock.js
│   ├── setup.ts
│   └── utils.tsx
├── utils
├── .env
├── .eslintrc.js
├── .gitignore
├── .npmrc
├── .prettierignore
├── babel.config.js
├── blitz.config.js
├── jest.config.js
├── package.json
├── README.md
├── tsconfig.json
└── yarn.lock
```

These files are:

- The `app/` directory is a container for most of your project. This is where you’ll put any pages or API routes.

- `db`/ is where your database configuration goes. If you’re writing models or checking migrations, this is where to go.

- `node_modules/` is where your “dependencies” are stored. This directory is updated by your package manager, so you don’t have to worry too much about it.

- `public/` is a directory where you will put any static assets. If you have images, files, or videos which you want to use in your app, this is where to put them.

- `test/` is a directory where you can put your unit and integration tests.

- `utils/` is a good place to put any shared utility files which you might use across different sections of your app.

- `.babelrc.js`, `.env`, etc. ("dotfiles") are configuration files for various bits of JavaScript tooling.

- `blitz.config.js` is for advanced custom configuration of Blitz. It extends [`next.config.js`](https://nextjs.org/docs/api-reference/next.config.js/introduction).

- `jest.config.js` contains config for Jest tests. You can [customize it if needed](https://jestjs.io/docs/en/configuration).

- `package.json` contains information about your dependencies and devDependencies. If you’re using a tool like `npm` or `yarn`, you won’t have to worry about this much.

- `tsconfig.json` is our recommended setup for TypeScript.

You can read more about it in the [File Structure](https://blitzjs.com/docs/file-structure) section of the documentation.

## Learn more

Read the [Blitz.js Documentation](https://blitzjs.com/docs/getting-started) to learn more.

### The Blitz.js Manifesto

Read the [Blitz Manifesto](https://blitzjs.com/docs/manifesto) to learn the Blitz foundational principles.

Blitz is built on Next.js. For more info on this see [Why use Blitz instead of Next.js](https://blitzjs.com/docs/why-blitz)
## To do

## Get in touch
Contributions and suggestions of any kind are very welcome.

The Blitz community is warm, safe, diverse, inclusive, and fun! Feel free to reach out to us in any of our communication channels.
Here is a list of things that need to be developed:

- [Website](https://blitzjs.com/)
- [Slack](https://slack.blitzjs.com/)
- [Report an issue](https://github.com/blitz-js/blitz/issues/new/choose)
- [Forum discussions](https://github.com/blitz-js/blitz/discussions)
- [Sponsors and donations](https://github.com/blitz-js/blitz#sponsors-and-donations)
- [Contributing Guide](https://blitzjs.com/docs/contributing)
- [ ] Forgot password system
- [ ] Tests
- [ ] Captcha (login, signup, post job)
22 changes: 22 additions & 0 deletions app/admin/mutations/publishJob.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Role } from "app/users/role"
import { IdInput, IdInputType } from "app/validations"
import { SessionContext } from "blitz"
import db from "db"

export default async function publishJob(
input: IdInputType,
ctx: { session?: SessionContext } = {}
) {
ctx.session!.authorize(Role.ADMIN)

const { id } = IdInput.parse(input)

await db.job.update({
where: {
id,
},
data: {
publishedAt: new Date(),
},
})
}
37 changes: 37 additions & 0 deletions app/admin/pages/admin/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { Suspense } from "react"
import { BlitzPage, GetServerSideProps } from "blitz"
import { AppLayout } from "app/layouts/AppLayout"
import { JobsList } from "app/jobs/components/JobsList"
import { Loading } from "app/components/Loading"
import { ensureHasRole } from "app/guards/ensureHasRole"
import { Role } from "app/users/role"
import getUnpublishedJobs from "app/admin/queries/getUnpublishedJobs"

export const getServerSideProps: GetServerSideProps = async (context) =>
await ensureHasRole({ ...context, role: Role.ADMIN })

const Admin: BlitzPage = () => {
return (
<React.Fragment>
<div>
<h3 className="text-lg font-medium leading-6 text-gray-900">Manage All Jobs</h3>
<p className="mt-1 text-sm leading-5 text-gray-500">
Here you can publish all the submitted jobs.
</p>
</div>
<Suspense
fallback={
<div className="flex items-end justify-center h-12">
<Loading className="w-5 h-5 text-indigo-600" />
</div>
}
>
<JobsList query={getUnpublishedJobs} withAdminActions />
</Suspense>
</React.Fragment>
)
}

Admin.getLayout = (page) => <AppLayout title="Dashboard">{page}</AppLayout>

export default Admin
26 changes: 26 additions & 0 deletions app/admin/queries/getUnpublishedJobs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { PaginationInput, PaginationInputType } from "app/validations"
import db from "db"

export default async function getUnpublishedJobs(input: PaginationInputType) {
const { skip, take } = PaginationInput.parse(input)

const jobs = await db.job.findMany({
skip,
take,
where: {
publishedAt: null,
},
})

const count = await db.job.count()

const hasMore = typeof take === "number" ? skip + take < count : false

const nextPage = hasMore ? { take, skip: skip + take! } : null

return {
jobs,
nextPage,
hasMore,
}
}
29 changes: 26 additions & 3 deletions app/auth/auth-utils.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,62 @@
import { AuthenticationError } from "blitz"
import SecurePassword from "secure-password"
import db from "db"
import db, { User } from "db"
import { EmailUsedError } from "app/errors/emailUsed"
import { UnconfirmedEmailError } from "app/errors/unconfirmedEmail"

const SP = new SecurePassword()

export const hashPassword = async (password: string) => {
const hashedBuffer = await SP.hash(Buffer.from(password))

return hashedBuffer.toString("base64")
}

export const verifyPassword = async (hashedPassword: string, password: string) => {
try {
return await SP.verify(Buffer.from(password), Buffer.from(hashedPassword, "base64"))
} catch (error) {
console.error(error)

return false
}
}

export const ensureUserConfirmed = (user: Pick<User, "confirmedAt">) => {
if (!user.confirmedAt) {
throw new UnconfirmedEmailError()
}
}

export const ensureUserEmailNotUsed = async (email: string) => {
const user = await db.user.findOne({ where: { email } })

if (!!user) {
throw new EmailUsedError({ email })
}
}

export const authenticateUser = async (email: string, password: string) => {
const user = await db.user.findOne({ where: { email } })

if (!user || !user.hashedPassword) throw new AuthenticationError()
if (!user || !user.hashedPassword) {
throw new AuthenticationError()
}

switch (await verifyPassword(user.hashedPassword, password)) {
case SecurePassword.VALID:
break
case SecurePassword.VALID_NEEDS_REHASH:
// Upgrade hashed password with a more secure hash
const improvedHash = await hashPassword(password)

await db.user.update({ where: { id: user.id }, data: { hashedPassword: improvedHash } })

break
default:
throw new AuthenticationError()
}

const { hashedPassword, ...rest } = user

return rest
}
14 changes: 14 additions & 0 deletions app/auth/components/AuthHeading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React, { PropsWithoutRef } from "react"

export const AuthHeading = React.forwardRef<
HTMLHeadingElement,
PropsWithoutRef<JSX.IntrinsicElements["h2"]>
>(({ children, ...props }, ref) => (
<h2
ref={ref}
className="mt-6 text-3xl font-extrabold leading-9 text-center text-gray-900"
{...props}
>
{children}
</h2>
))
10 changes: 10 additions & 0 deletions app/auth/components/AuthSubheading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React, { PropsWithoutRef } from "react"

export const AuthSubheading = React.forwardRef<
HTMLHeadingElement,
PropsWithoutRef<JSX.IntrinsicElements["p"]>
>(({ children, ...props }, ref) => (
<p ref={ref} className="mt-2 text-sm leading-5 text-center text-gray-600" {...props}>
{children}
</p>
))
14 changes: 14 additions & 0 deletions app/auth/components/AuthWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React, { PropsWithoutRef } from "react"

export const AuthWrapper = React.forwardRef<
HTMLDivElement,
PropsWithoutRef<JSX.IntrinsicElements["h2"]>
>(({ children, ...props }, ref) => (
<div
ref={ref}
className="flex flex-col justify-center min-h-screen py-12 bg-gray-50 sm:px-6 lg:px-8"
{...props}
>
{children}
</div>
))
Loading

0 comments on commit 858674d

Please sign in to comment.