Skip to content

Commit

Permalink
feat: basic sign-in and sign-up
Browse files Browse the repository at this point in the history
refs: #7
  • Loading branch information
sungmin-park committed Nov 29, 2021
1 parent 8931d39 commit 173716d
Show file tree
Hide file tree
Showing 11 changed files with 216 additions and 30 deletions.
3 changes: 2 additions & 1 deletion backend/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
kotlin("jvm") version "1.5.31"
kotlin("plugin.serialization") version "1.5.31"
application
}

Expand All @@ -20,7 +21,7 @@ tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach

dependencies {
// kenet
val kenetVersion = "792258caa1"
val kenetVersion = "9cc72ddcf7"
implementation("com.github.kotlin-everywhere.kenet:kenet-server:$kenetVersion")
implementation("com.github.kotlin-everywhere.kenet:kenet-server-engine-http:$kenetVersion")
implementation("com.github.kotlin-everywhere.kenet:kenet-gen-typescript:$kenetVersion")
Expand Down
67 changes: 62 additions & 5 deletions backend/src/main/kotlin/org/kotlin/everywhere/realworld/api.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,72 @@
package org.kotlin.everywhere.realworld

import kotlinx.serialization.Serializable
import org.kotlin.everywhere.net.Kenet
import org.kotlin.everywhere.net.invoke
import java.util.*

class Api : Kenet() {
val greeting by c<String, String>()
val signUp by c<SignUpReq, SignUpRes>()
val signIn by c<SignInReq, SignInRes>()
}

@Serializable
class SignUpReq(val name: String, val email: String, val password: String)

@Serializable
class SignUpRes(val errors: List<String> = listOf())

@Serializable
class SignInReq(val email: String, val password: String)

@Serializable
data class Res<T : Any>(val errors: List<String>, val data: T? = null)

@Serializable
class SignInRes(val errors: List<String> = listOf(), val data: Data? = null) {
@Serializable
class Data(
val name: String,
val email: String,
val note: String,
val profilePictureUrl: String,
val accessToken: String,
)
}

fun Api.init() {
greeting {
"Hello, named = $it!"
signUp { req ->
val emailTaken = users.any { it.email == req.email }
if (emailTaken) {
return@signUp SignUpRes(errors = listOf("That email is already taken"))
}
users.add(User(name = req.name, email = req.email, password = req.password))
SignUpRes()
}
println("greeting = $greeting")
}

signIn { req ->
val user = users.firstOrNull { it.email == req.email && it.password == req.password }
?: return@signIn SignInRes(errors = listOf("Email or password is worng"))

val accessToken = UUID.randomUUID().toString()
user.accessTokens.add(accessToken)
SignInRes(data = SignInRes.Data(
name = user.name,
email = user.email,
note = user.note,
profilePictureUrl = user.profilePictureUrl,
accessToken = accessToken,
))
}
}

data class User(
val name: String,
val email: String,
val password: String,
val accessTokens: MutableList<String> = mutableListOf(),
val note: String = "",
val profilePictureUrl: String = "",
)

val users = mutableListOf<User>()
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.kotlin.everywhere.realworld

import org.kotlin.everywhere.net.HttpServerEngine
import org.kotlin.everywhere.net.createServer
import org.kotlin.everywhere.net.gen.typescript.TypeScript
import org.kotlin.everywhere.net.gen.typescript.generate
import java.nio.file.Path

Expand All @@ -12,7 +13,7 @@ suspend fun main(args: Array<String>) {

when (command) {
is Generate -> {
generate(api, Path.of(command.destination), "api")
generate(api, Path.of(command.destination), "api", TypeScript)
}
is Serve -> {
createServer(api, HttpServerEngine()).launch(command.port)
Expand Down
15 changes: 15 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",
"@types/jest": "^26.0.24",
"@types/lodash": "^4.14.177",
"@types/node": "^12.20.33",
"@types/react": "^17.0.30",
"@types/react-dom": "^17.0.9",
"mobx": "^6.3.8",
"mobx-react-lite": "^3.2.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^5.3.0",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import "./App.css";
import { HashRouter, Link, Route, Switch } from "react-router-dom";
import { HashRouter, Route, Switch } from "react-router-dom";
import { HomePage } from "./page/home";
import { NotFoundPage } from "./page/not-found";
import { SignInPage } from "./page/sign-in";
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/api.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Api } from "./api/api";

export const api = new Api("http://localhost:5000");
22 changes: 22 additions & 0 deletions frontend/src/model.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { makeAutoObservable } from "mobx";

export class SiteModel {
user: SiteUser | null = null;

constructor() {
makeAutoObservable(this);
}

setUser(user: SiteUser) {
this.user = user;
}
}

interface SiteUser {
accessToken: string;
profilePictureUrl: string;
name: string;
note: string;
}

export const siteModel = new SiteModel();
36 changes: 28 additions & 8 deletions frontend/src/page/sign-in.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
import { ReactElement, useState } from "react";
import { Link } from "react-router-dom";
import { Api } from "../api/api";
import { Link, useHistory } from "react-router-dom";
import { api } from "../api";
import { siteModel } from "../model";

export function SignInPage(): ReactElement {
const history = useHistory();

const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [errors, setErrors] = useState<string[]>([]);

const onSignIn = async () => {
const api = new Api("http://localhost:5000");
const greeting = await api.greeting(email);
alert(greeting);
const res = await api.signIn({ email, password });
if (res.errors.length) {
setErrors(res.errors);
return;
}
if (!res.data) {
return;
}

siteModel.setUser(res.data);
history.replace({ pathname: "/" });
};

return (
Expand All @@ -20,9 +34,13 @@ export function SignInPage(): ReactElement {
<Link to="/register">Don't have an account yet? Sign Up</Link>
</p>

<ul className="error-messages">
<li>That email is already taken</li>
</ul>
{errors.map((error, index) => {
return (
<ul className="error-messages" key={index}>
<li>{error}</li>
</ul>
);
})}

<form>
<fieldset className="form-group">
Expand All @@ -39,6 +57,8 @@ export function SignInPage(): ReactElement {
className="form-control form-control-lg"
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</fieldset>
<button
Expand Down
71 changes: 63 additions & 8 deletions frontend/src/page/sign-up.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,45 @@
import { ReactElement } from "react";
import { Link } from "react-router-dom";
import { Link, Router, useHistory } from "react-router-dom";
import { useState } from "react";
import _ from "lodash";
import { api } from "../api";

export const SignUpPage = () => {
const history = useHistory();

const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [passwordConfirm, setPasswordConfirm] = useState("");
const [errors, setErrors] = useState<string[]>([]);

const onSignUp = async () => {
const errs = [];
if (!name.length) {
errs.push("Input a name");
}
if (!email.length) {
errs.push("Input an email");
}
if (!password.length) {
errs.push("Input a password");
}
if (!_.eq(password, passwordConfirm)) {
errs.push("password and password confirm dose not matched");
}
setErrors(errs);
if (errs.length) {
return;
}

const res = await api.signUp({ email, name, password });
if (res.errors.length) {
setErrors(res.errors);
return;
}

history.replace({ pathname: "/" });
};

export function SignUpPage(): ReactElement {
return (
<div className="auth-page">
<div className="container page">
Expand All @@ -12,37 +50,54 @@ export function SignUpPage(): ReactElement {
<Link to="/login">Have an account?</Link>
</p>

<ul className="error-messages">
<li>That email is already taken</li>
</ul>
{errors.map((error, index) => {
return (
<ul className="error-messages" key={index}>
<li>{error}</li>
</ul>
);
})}

<form>
<form
onSubmit={(e) => {
e.preventDefault();
onSignUp();
}}
>
<fieldset className="form-group">
<input
className="form-control form-control-lg"
type="text"
placeholder="Your Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</fieldset>
<fieldset className="form-group">
<input
className="form-control form-control-lg"
type="text"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</fieldset>
<fieldset className="form-group">
<input
className="form-control form-control-lg"
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</fieldset>
<fieldset className="form-group">
<input
className="form-control form-control-lg"
type="password"
placeholder="Password confirm"
value={passwordConfirm}
onChange={(e) => setPasswordConfirm(e.target.value)}
/>
</fieldset>
<button className="btn btn-lg btn-primary pull-xs-right">
Expand All @@ -54,4 +109,4 @@ export function SignUpPage(): ReactElement {
</div>
</div>
);
}
};
Loading

0 comments on commit 173716d

Please sign in to comment.