Added login page
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing

This commit is contained in:
Dominic Grimm 2022-02-02 15:38:36 +01:00
parent 1b2fcf7ba8
commit 0da7f11239
18 changed files with 166 additions and 78 deletions

View file

@ -51,10 +51,11 @@ type: docker
name: frontend
steps:
- name: prettier
image: elnebuloso/prettier
image: node:alpine
commands:
- cd docker/frontend/
- prettier . -c
- yarn global add prettier eslint
- yarn lint
- name: build
image: tmaier/docker-compose
volumes:

View file

@ -54,6 +54,9 @@ services:
- default
- db
- redis
depends_on:
- db
- redis
environment:
URL: ${URL}
BACKEND_DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_USER}
@ -65,9 +68,6 @@ services:
BACKEND_SMTP_NAME: ${BACKEND_SMTP_NAME}
BACKEND_SMTP_USERNAME: ${BACKEND_SMTP_USERNAME}
BACKEND_SMTP_PASSWORD: ${BACKEND_SMTP_PASSWORD}
depends_on:
- db
- redis
frontend:
build:
@ -80,6 +80,8 @@ services:
- default
depends_on:
- backend
environment:
NODE_ENV: production
networks:
db:

View file

@ -1,10 +1,10 @@
FROM crystallang/crystal:latest-alpine as deps
FROM crystallang/crystal:1.3-alpine as deps
WORKDIR /app
RUN apk add curl --no-cache
COPY ./shard.yml ./shard.lock ./
RUN shards install --production
FROM crystallang/crystal:latest-alpine as builder
FROM crystallang/crystal:1.3-alpine as builder
ARG BUILD_ENV
WORKDIR /app
COPY --from=deps /app/shard.yml /app/shard.lock ./

View file

@ -22,7 +22,7 @@ module Backend
JWT.encode({"data" => data.to_h, "exp" => expiration}, SAFE_ENV["BACKEND_JWT_SECRET"], JWT::Algorithm::HS256)
end
def create_user_jwt(user_id : Int, expiration : Int = (Time.utc + Time::Span.new(hours: 6)).to_unix) : String
def create_user_jwt(user_id : Int, expiration : Int = (Time.utc + Time::Span.new(days: 1)).to_unix) : String
create_jwt({user_id: user_id}, expiration)
end

View file

@ -1,12 +1,16 @@
require "CrystalEmail"
module Backend
module API
module Schema
@[GraphQL::Object]
class Mutation < GraphQL::BaseMutation
@[GraphQL::Field]
def login(input : LoginInput) : LoginPayload
user = Db::User.find_by(email: input.email)
raise "Auth failed" unless user && Auth.verify_password?(input.password, user.password)
def login(email : String, password : String) : LoginPayload
raise "Auth failed" if email.empty? || password.empty? || !CrystalEmail::Rfc5322::Public.validates?(email)
user = Db::User.find_by(email: email)
raise "Auth failed" unless user && Auth.verify_password?(password, user.password)
LoginPayload.new(
user: User.new(user),

View file

@ -102,18 +102,18 @@ module Backend
end
end
@[GraphQL::InputObject]
class LoginInput < GraphQL::BaseInputObject
getter email
getter password
# @[GraphQL::InputObject]
# class LoginInput < GraphQL::BaseInputObject
# getter email
# getter password
@[GraphQL::Field]
def initialize(
@email : String,
@password : String
)
end
end
# @[GraphQL::Field]
# def initialize(
# @email : String,
# @password : String
# )
# end
# end
@[GraphQL::Object]
class LoginPayload < GraphQL::BaseObject

View file

@ -2,7 +2,10 @@ module Backend
extend self
def run : Nil
Log.info { "Starting backend services..." }
{% if flag?(:development) %}
Log.warn { "Backend is running in development mode! Do not use this in production!" }
{% end %}
Log.info { "Starting services..." }
channel = Channel(Nil).new(SERVICES.size)

View file

@ -1 +0,0 @@
v16.13.2

View file

@ -1,5 +1,5 @@
# Install dependencies only when needed
FROM node:14-alpine AS deps
FROM node:16-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
@ -7,27 +7,24 @@ COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
# Rebuild the source code only when needed
FROM node:14-alpine AS builder
FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN yarn build
# Production image, copy all the files and run next
FROM node:14-alpine AS runner
FROM node:16-alpine AS runner
WORKDIR /app
ARG BUILD_ENV
ENV NODE_ENV ${BUILD_ENV}
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# You only need to copy next.config.js if you are NOT using the default configuration
# COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
USER nextjs
@ -39,4 +36,7 @@ EXPOSE 3000
# Uncomment the following line in case you want to disable telemetry.
ENV NEXT_TELEMETRY_DISABLED 1
ARG BUILD_ENV
ENV NODE_ENV ${BUILD_ENV}
CMD ["yarn", "start"]

View file

@ -0,0 +1,29 @@
import Cookies from "js-cookie";
import Link from "next/link";
function Navbar(): JSX.Element {
const isLoggedIn = !!Cookies.get("mentorenwahl_bearer");
function handleLogout(event: MouseEvent): void {
event.preventDefault();
Cookies.remove("mentorenwahl_bearer");
}
return (
<nav>
<ul>
<li>
{isLoggedIn ? (
<button onClick={handleLogout as any}>Logout</button>
) : (
<Link href="/login" passHref>
<button>Login</button>
</Link>
)}
</li>
</ul>
</nav>
);
}
export default Navbar;

View file

@ -0,0 +1,16 @@
import Navbar from "../components/navbar";
interface MainLayoutProps {
children: React.ReactNode;
}
function MainLayout({ children }: MainLayoutProps): JSX.Element {
return (
<div>
<Navbar />
<main>{children}</main>
</div>
);
}
export default MainLayout;

View file

@ -1,6 +0,0 @@
import { ApolloClient, InMemoryCache } from "@apollo/client";
export const client = new ApolloClient({
uri: "/graphql",
cache: new InMemoryCache(),
});

View file

@ -0,0 +1,2 @@
export const _BASE = "mentorenwahl_";
export const TOKEN = _BASE + "token";

View file

@ -10,11 +10,13 @@
"dependencies": {
"@apollo/client": "^3.5.8",
"graphql": "^16.3.0",
"js-cookie": "^3.0.1",
"next": "12.0.9",
"react": "17.0.2",
"react-dom": "17.0.2"
},
"devDependencies": {
"@types/js-cookie": "^3.0.1",
"@types/node": "17.0.13",
"@types/react": "17.0.38",
"eslint": "8.8.0",

View file

@ -1,9 +1,22 @@
import type { AppProps } from "next/app";
import { ApolloProvider, ApolloClient, InMemoryCache } from "@apollo/client";
import MainLayout from "../layouts/main";
import "../styles/globals.css";
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
const client = new ApolloClient({
uri: "/graphql",
cache: new InMemoryCache(),
});
function MyApp({ Component, pageProps }: AppProps): JSX.Element {
return (
<ApolloProvider client={client}>
<MainLayout>
<Component {...pageProps} />
</MainLayout>
</ApolloProvider>
);
}
export default MyApp;

View file

@ -1,11 +1,22 @@
import type { NextPage } from "next";
import Cookies from "js-cookie";
import Router from "next/router";
import { useEffect } from "react";
const Home: NextPage = () => {
return (
<div>
<h1>Willkommen zur Mentorenwahl!</h1>
</div>
);
};
import * as cookieNames from "../lib/cookieNames";
function Home(): JSX.Element {
const token = Cookies.get(cookieNames.TOKEN);
useEffect(() => {
if (!token) {
Router.push("/login");
}
}, [token]);
if (!token) {
return <></>;
}
return <p>Du bist eingeloggt!</p>;
}
export default Home;

View file

@ -1,39 +1,40 @@
import type { NextPage } from "next";
import type { FormEvent } from "react";
import { gql } from "@apollo/client";
import type { FormEvent, FormEventHandler } from "react";
import { gql, useMutation } from "@apollo/client";
import Cookies from "js-cookie";
import Router from "next/router";
import { client } from "../lib/client";
import * as cookieNames from "../lib/cookieNames";
const Login: NextPage = () => {
async function loginUser(event: FormEvent): Promise<void> {
const LOGIN = gql`
mutation Login($email: String!, $password: String!) {
login(email: $email, password: $password) {
token
}
}
`;
function Login(): JSX.Element {
const [login, { error }] = useMutation(LOGIN);
const loginUser: FormEventHandler = async (
event: FormEvent & { target: HTMLFormElement }
): Promise<void> => {
event.preventDefault();
const input = {
email: (event.target as HTMLFormElement).email.value,
password: (event.target as HTMLFormElement).password.value,
email: event.target.email.value as string,
password: event.target.password.value as string,
};
console.log(input);
// client
// .mutate({
// mutation: gql`
// mutation Login($input: LoginInput!) {
// login(input: $input) {
// user {
// id
// firstname
// lastname
// email
// }
// bearer
// }
// }
// `,
// })
// .then((res) => {
// console.log(res);
// });
}
const data = (
await login({
variables: { email: input.email, password: input.password },
})
).data;
if (data) {
Cookies.set(cookieNames.TOKEN, data.login.token, { expires: 1 });
Router.push("/");
}
};
return (
<div>
@ -54,8 +55,9 @@ const Login: NextPage = () => {
<br />
<button type="submit">Login</button>
</form>
{error && <p style={{ color: "red" }}>{error.message}</p>}
</div>
);
};
}
export default Login;

View file

@ -162,6 +162,11 @@
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz#7f698254aadf921e48dda8c0a6b304026b8a9323"
integrity sha512-JLo+Y592QzIE+q7Dl2pMUtt4q8SKYI5jDrZxrozEQxnGVOyYE+GWK9eLkwTaeN9DDctlaRAQ3TBmzZ1qdLE30A==
"@types/js-cookie@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.1.tgz#04aa743e2e0a85a22ee9aa61f6591a8bc19b5d68"
integrity sha512-7wg/8gfHltklehP+oyJnZrz9XBuX5ZPP4zB6UsI84utdlkRYLnOm2HfpLXazTwZA+fpGn0ir8tGNgVnMEleBGQ==
"@types/json5@^0.0.29":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
@ -1090,6 +1095,11 @@ isexe@^2.0.0:
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
js-cookie@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.1.tgz#9e39b4c6c2f56563708d7d31f6f5f21873a92414"
integrity sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"