Skip to content

Commit 5d872dc

Browse files
committed
Implemented Users in Backend
1 parent 2c3a598 commit 5d872dc

File tree

24 files changed

+350
-48
lines changed

24 files changed

+350
-48
lines changed

code/client/src/domain/editor/fugue/Fugue.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export class Fugue {
118118
*/
119119
deleteLocal(selection: Selection): DeleteOperation[] {
120120
const nodes = Array.from(this.traverseBySelection(selection));
121-
const cursor = {...selection.start}
121+
const cursor = { ...selection.start };
122122
return nodes.map(node => {
123123
if (node.value === '\n') {
124124
cursor.line++;

code/client/src/domain/editor/slate/operations/input.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Editor } from 'slate';
22
import { ReactEditor } from 'slate-react';
33
import CustomEditor from '@domain/editor/slate/CustomEditor';
4-
import {isEqual, min} from 'lodash';
4+
import { isEqual, min } from 'lodash';
55
import { getKeyFromInputEvent } from '@domain/editor/slate/utils/domEvents';
66
import { getSelection, isSelected } from '@domain/editor/slate/utils/selection';
77
import { Cursor, emptyCursor } from '@domain/editor/cursor';
@@ -20,13 +20,13 @@ export default (editor: Editor, connector: InputConnector, onFormat: (mark: Inli
2020
if (!key) return;
2121

2222
const selection = getSelection(editor);
23-
console.log("Selection: ", selection)
24-
const cursor = min([{...selection.start}, {...selection.end}]) as Cursor; // always use the start of the selection
25-
console.log("Initial Cursor: ", cursor);
23+
console.log('Selection: ', selection);
24+
const cursor = min([{ ...selection.start }, { ...selection.end }]) as Cursor; // always use the start of the selection
25+
console.log('Initial Cursor: ', cursor);
2626
// if there is a selection, delete the selected text
2727
if (isSelected(editor)) connector.deleteSelection(selection);
28-
console.log("Selection after deletion: ", getSelection(editor));
29-
console.log("Cursor after deletion: ", cursor);
28+
console.log('Selection after deletion: ', getSelection(editor));
29+
console.log('Cursor after deletion: ', cursor);
3030
switch (key) {
3131
case 'Enter':
3232
onEnter(cursor);
@@ -81,7 +81,7 @@ export default (editor: Editor, connector: InputConnector, onFormat: (mark: Inli
8181
*/
8282
function onKey(key: string, cursor: Cursor) {
8383
const styles = CustomEditor.getMarks(editor) as InlineStyle[];
84-
console.log("Cursor: ", cursor)
84+
console.log('Cursor: ', cursor);
8585
connector.insertCharacter(key, cursor, styles);
8686
}
8787

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { UserData } from '@notespace/shared/src/users/types';
2+
import { HttpCommunication } from '@services/communication/http/httpCommunication';
3+
4+
function authService(http: HttpCommunication) {
5+
async function registerUser(id: string, data: UserData) {
6+
await http.post('/users', { id, ...data });
7+
}
8+
9+
async function registerUserOAuth(id: string, data: UserData) {
10+
await http.post('/users?oauth=true', { id, ...data });
11+
}
12+
13+
async function getUser(id: string) {
14+
return await http.get(`/users/${id}`);
15+
}
16+
17+
async function updateUser(id: string, newProps: Partial<UserData>) {
18+
await http.put(`/users/${id}`, { id, ...newProps });
19+
}
20+
21+
async function deleteUser(id: string) {
22+
await http.delete(`/users/${id}`);
23+
}
24+
25+
return {
26+
registerUser,
27+
registerUserOAuth,
28+
getUser,
29+
updateUser,
30+
deleteUser,
31+
};
32+
}
33+
34+
export default authService;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { useMemo } from 'react';
2+
import { useCommunication } from '@ui/contexts/communication/useCommunication';
3+
import authService from '@services/auth/authService';
4+
5+
function useAuthService() {
6+
const { http } = useCommunication();
7+
return useMemo(() => authService(http), [http]);
8+
}
9+
10+
export default useAuthService;

code/client/src/services/communication/http/httpCommunication.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export interface HttpCommunication {
44
post: (url: string, data?: any) => Promise<any>;
55
get: (url: string) => Promise<any>;
66
put: (url: string, data?: any) => Promise<any>;
7-
delete: (url: string) => Promise<any>;
7+
delete: (url: string, data?: any) => Promise<any>;
88
}
99

1010
async function get(url: string) {
@@ -19,8 +19,8 @@ async function put(url: string, body: any) {
1919
return request(url, 'PUT', body);
2020
}
2121

22-
async function _delete(url: string) {
23-
return request(url, 'DELETE');
22+
async function del(url: string, body: any) {
23+
return request(url, 'DELETE', body);
2424
}
2525

2626
const request = async (url: string, method: string, body?: any) => {
@@ -43,5 +43,5 @@ export const httpCommunication: HttpCommunication = {
4343
post,
4444
get,
4545
put,
46-
delete: _delete,
46+
delete: del,
4747
};

code/client/src/ui/components/header/Header.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
import './Header.scss';
22
import { useAuth } from '@ui/contexts/auth/useAuth';
3+
import { Link } from 'react-router-dom';
34

45
function Header() {
56
const { currentUser, logout } = useAuth();
67
return (
78
<header className="header">
89
<p></p>
910
<div>
10-
<p>{currentUser?.email}</p>
11-
<button onClick={logout}>Logout</button>
11+
{currentUser ? (
12+
<>
13+
<p>{currentUser?.email}</p>
14+
<button onClick={logout}>Logout</button>
15+
</>
16+
) : (
17+
<Link to="/login">Login</Link>
18+
)}
1219
</div>
1320
</header>
1421
);

code/client/src/ui/contexts/auth/AuthContext.tsx

Lines changed: 52 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,33 @@
1-
import { auth, googleAuthProvider, githubAuthProvider } from '@config';
1+
import { auth, githubAuthProvider, googleAuthProvider } from '@config';
22
import { createContext, ReactNode, useEffect, useState } from 'react';
33
import {
4-
signInWithPopup,
54
createUserWithEmailAndPassword,
65
signInWithEmailAndPassword,
6+
signInWithPopup,
77
signOut,
88
updateProfile,
99
User,
10-
UserCredential,
1110
} from 'firebase/auth';
11+
import { validateEmail, validatePassword, validateUsername } from '@ui/contexts/auth/utils';
12+
import useError from '@ui/contexts/error/useError';
13+
import useAuthService from '@services/auth/useAuthService';
1214

1315
export type AuthContextType = {
1416
currentUser: User | null;
15-
login: (email: string, password: string) => Promise<UserCredential>;
16-
loginWithGoogle: () => Promise<UserCredential>;
17-
loginWithGithub: () => Promise<UserCredential>;
18-
signup: (email: string, password: string) => Promise<UserCredential>;
17+
login: (email: string, password: string) => Promise<void>;
18+
loginWithGoogle: () => Promise<void>;
19+
loginWithGithub: () => Promise<void>;
20+
signup: (username: string, email: string, password: string) => Promise<void>;
1921
logout: () => Promise<void>;
2022
updateUserProfile: (user: User, profile: UserProfile) => Promise<void>;
2123
};
2224

2325
export const AuthContext = createContext<AuthContextType>({
2426
currentUser: null,
25-
login: async () => ({}) as UserCredential,
26-
loginWithGoogle: async () => ({}) as UserCredential,
27-
loginWithGithub: async () => ({}) as UserCredential,
28-
signup: async () => ({}) as UserCredential,
27+
login: async () => {},
28+
loginWithGoogle: async () => {},
29+
loginWithGithub: async () => {},
30+
signup: async () => {},
2931
logout: async () => {},
3032
updateUserProfile: async () => {},
3133
});
@@ -42,30 +44,51 @@ type UserProfile = {
4244
export function AuthProvider({ children }: AuthProviderProps) {
4345
const [currentUser, setCurrentUser] = useState<User | null>(null);
4446
const [loading, setLoading] = useState(true);
47+
const { publishError } = useError();
48+
const { registerUser, registerUserOAuth } = useAuthService();
4549

46-
function signup(email: string, password: string) {
47-
return createUserWithEmailAndPassword(auth, email, password);
48-
}
50+
const handleAsyncAction = async (action: () => Promise<any>) => {
51+
try {
52+
return await action();
53+
} catch (e) {
54+
console.log(e);
55+
publishError(e as Error);
56+
}
57+
};
4958

50-
function login(email: string, password: string) {
51-
return signInWithEmailAndPassword(auth, email, password);
52-
}
59+
const signup = (username: string, email: string, password: string) =>
60+
handleAsyncAction(async () => {
61+
validateUsername(username);
62+
validateEmail(email);
63+
validatePassword(password);
64+
const { user } = await createUserWithEmailAndPassword(auth, email, password);
65+
await registerUser(user.uid, { username, email });
66+
});
5367

54-
function logout() {
55-
return signOut(auth);
56-
}
68+
const login = (email: string, password: string) =>
69+
handleAsyncAction(async () => {
70+
return signInWithEmailAndPassword(auth, email, password)
71+
.catch(() => {
72+
throw new Error('Invalid credentials');
73+
})
74+
.then(console.log);
75+
});
5776

58-
function updateUserProfile(user: User, profile: UserProfile) {
59-
return updateProfile(user, profile);
60-
}
77+
const logout = () => handleAsyncAction(() => signOut(auth));
6178

62-
function loginWithGoogle() {
63-
return signInWithPopup(auth, googleAuthProvider);
64-
}
79+
const updateUserProfile = (user: User, profile: UserProfile) => updateProfile(user, profile);
6580

66-
function loginWithGithub() {
67-
return signInWithPopup(auth, githubAuthProvider);
68-
}
81+
const loginWithGoogle = () =>
82+
handleAsyncAction(async () => {
83+
const { user } = await signInWithPopup(auth, googleAuthProvider);
84+
await registerUserOAuth(user.uid, { username: user.displayName!, email: user.email! });
85+
});
86+
87+
const loginWithGithub = () =>
88+
handleAsyncAction(async () => {
89+
const { user } = await signInWithPopup(auth, githubAuthProvider);
90+
await registerUserOAuth(user.uid, { username: user.displayName!, email: user.email! });
91+
});
6992

7093
useEffect(() => {
7194
return auth.onAuthStateChanged(user => {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
export function validateUsername(username: string) {
2+
const usernameRegex = /^[a-zA-Z0-9_]{3,20}$/;
3+
if (!usernameRegex.test(username)) {
4+
throw new Error('Invalid username');
5+
}
6+
}
7+
8+
export function validateEmail(email: string) {
9+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
10+
if (!emailRegex.test(email)) {
11+
throw new Error('Invalid email');
12+
}
13+
}
14+
15+
export function validatePassword(password: string) {
16+
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
17+
if (!passwordRegex.test(password)) {
18+
throw new Error(
19+
'Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one digit, and one special character.'
20+
);
21+
}
22+
}

code/client/src/ui/pages/auth/signup/Signup.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { FormEvent } from 'react';
12
import CssBaseline from '@mui/material/CssBaseline';
23
import TextField from '@mui/material/TextField';
34
import OAuth from '@ui/pages/auth/components/OAuth';
@@ -7,10 +8,9 @@ import Grid from '@mui/material/Grid';
78
import Box from '@mui/material/Box';
89
import Typography from '@mui/material/Typography';
910
import Container from '@mui/material/Container';
10-
import { FormEvent, useState } from 'react';
11-
import '../Auth.scss';
1211
import { useAuth } from '@ui/contexts/auth/useAuth';
1312
import useError from '@ui/contexts/error/useError';
13+
import '../Auth.scss';
1414

1515
function Signup() {
1616
const navigate = useNavigate();

code/server/sql/clean_tables.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
begin;
22

3+
delete from "user";
34
delete from resource;
45
delete from workspace;
56

0 commit comments

Comments
 (0)