π TaskTracker - νκ³
π― νλ‘μ νΈ κ°μ λ³Έ νλ‘μ νΈκ° μ§ν₯νλ λ°λ λ€μκ³Ό κ°μλ€. νλ‘μ νΈμ νμμ±μ λ³ΈμΈ μ€μ€λ‘ λ©λν μ μμ κ² Firebaseλ₯Ό ν΅ν Authenticationκ³Ό CRUDμ μ κ³Όμ μ κ²½νν κ² Redux-Toolkitμ ν΅ν΄ μ μ μν κ΄λ¦¬λ₯Ό κ²½νν κ² >
https://tasktracker-livid.vercel.app/home
π― νλ‘μ νΈ κ°μ
λ³Έ νλ‘μ νΈκ° μ§ν₯νλ λ°λ λ€μκ³Ό κ°μλ€.
1. νλ‘μ νΈμ νμμ±μ λ³ΈμΈ μ€μ€λ‘ λ©λν μ μμ κ² 2. Firebaseλ₯Ό ν΅ν Authenticationκ³Ό CRUDμ μ κ³Όμ μ κ²½νν κ² 3. Redux-Toolkitμ ν΅ν΄ μ μ μν κ΄λ¦¬λ₯Ό κ²½νν κ²
β νλ‘μ νΈμ νμμ±
λΆνΈνλ€. κ³νμ μΈμ°λ©΄ μ§ν€μ§ μκ³ , μΈμ°μ§ μμΌλ©΄ λΆμνλ€. κ·Έλ¦¬κ³ λͺ¨λ κ²μ μμΈ‘ν λ§νΌ λλνμ§ μμλ€. λ§€λ² μννλ €λ μμ μ λμ΄λλ μμ΄νκ³ , μμΈ‘ν μ μλ 무μΈκ°λ₯Ό μμΈ‘νλ € νκΈ°μ λΉμ°ν μ€ν¨νλ©΄μ, 무μμΈκ° μλͺ»λλ€λ μκ°μ΄ λ€μλ€.
μμΈκ³Ό κ²°κ³Όκ° λ°λλ μκ°μ΄ λΆλͺ ν μλ€. κ·Έλ¬λκΉ, κ³νκ³Ό μ€νμ λ¬Έμ κ° μμ΄μ λΆμν΄μ§λ κ²μ΄ μλλΌ, λΆμνκΈ° λλ¬Έμ κ³νκ³Ό μ€νμ΄ λ§κ°μ§λ μν©μ΄ μλ€λ κ²μ΄λ€. λΆμμ΄ μμΈμ΄λΌλ©΄ κ·Έκ²μ κ²½κ°νλ €λ μͺ½μ΄ ν΄κ²°μ κ°κΉκ² λ€.
λ½λͺ¨λλ‘ κΈ°λ²μ κ½€λ μ λͺ ν μκ°κ΄λ¦¬ λ°©λ²λ‘ μ€ νλμκ³ , λ²νΌμ ν΄λ¦νλ μκ° λ΄κ° μ€μ ν Taskμ μκ°μ μ¨μ ν μ§μ€νκ² λλ€λ μ μ΄ λΆμ κ΄λ¦¬μ μλΉν μ μ©νλ€λ μκ°μ΄ λ€μλ€.
νλ‘μ νΈλ μ¬μ€μ κ»λ°κΈ°μ λΆκ³Όνλ€. νλ‘μ νΈλΌλ λ°μ΄λ리 μμμ μ΄λ ν κΈ°μ μ μ΅λνκ³ μλ ¨νμΌλ©° λ¬΄μ¨ μ΄μΌκΈ°λ₯Ό λ΄μλμ§κ° ν¨μ¬ μ€μνκ³ , μ΄λ¬ν μμ μ μ¬μ ν μ΄λ ΅μ§λ§, κ³ λ―Όμ μ΄λμ΄ νλ‘μ νΈμ΄κΈ° λλ¬Έμ κ³ λ―Όμ΄ μ μ λμ§ μμ νλ‘μ νΈλ₯Ό μνν κ²μ΄λΌλ©΄, κ·Έλ₯ λκ°μ λ Έλ κ² ν볡μ λ μ 리ν κ²μ΄λΌλ κ²μ΄ λ΄ μκ°μ΄μλ€. μμ€μ μ΄λ€.
β Authentication
1. createUserWithEmailAndPassword(νμ κ°μ ) βοΈ
// νμκ°μ
μ μ²λ¦¬νλ ν¨μ
const handleSignUp = async (e) => {
e.preventDefault();
if (Object.values(errors).some((error) => error)) {
alert("Please check your information again.");
return;
}
await dispatch(
signupUser({
email: userInfo?.email,
password: userInfo?.password,
imgFile,
displayName: userInfo?.username,
})
).unwrap();
alert("Sign up successful!");
navigate("/signin");
};export const signupUser = createAsyncThunk(
"user/signupUser",
async (userInfo) => {
try {
// Firebaseλ₯Ό ν΅ν΄, μ΄λ©μΌκ³Ό λΉλ°λ²νΈλ‘ μ¬μ©μλ₯Ό μμ±
await createUserWithEmailAndPassword(
auth,
userInfo?.email,
userInfo?.password
);
// νμ¬ μ¬μ©μκ° μλ κ²½μ°(=firebaseμ νμ λ±λ‘μ΄ λ κ²½μ°)
if (auth.currentUser) {
// λμμ μ΄λ―Έμ§ νμΌμ΄ μλ κ²½μ°
if (userInfo?.imgFile) {
// firebaseμ μ΄λ―Έμ§λ₯Ό μ
λ‘λ
const storageRef = ref(
storage,
new Date().getTime() + userInfo?.imgFile?.name
);
const uploadTask = uploadBytesResumable(
storageRef,
userInfo?.imgFile
);
uploadTask.on(
"state_changed",
// μ
λ‘λ μνκ° λ³κ²½λ λ μ€νλ μ½λ°± ν¨μ
(snapshot) => {
// μ
λ‘λ μ§νλ₯ μ κ³μ°νλ λ‘μ§
const progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log("Upload is " + progress + "% done");
switch (snapshot.state) {
case "paused":
console.log("Upload is paused");
break;
case "running":
console.log("Upload is running");
break;
}
},
// μ
λ‘λ μ€ μλ¬κ° λ°μν κ²½μ°
(error) => {
alert("Failed to upload image. Please try again later.");
},
// μ
λ‘λκ° μλ£λ κ²½μ°
() => {
// μ
λ‘λλ νμΌμ λ€μ΄λ‘λ URLμ κ°μ Έμ΄
getDownloadURL(uploadTask.snapshot.ref).then(
async (downloadURL) => {
// μ¬μ©μ νλ‘νμ μ
λ°μ΄νΈ
await updateProfile(auth.currentUser, {
displayName: userInfo?.displayName,
photoURL: downloadURL,
});
}
);
}
);
}
// μ΄λ―Έμ§ νμΌμ΄ μλ κ²½μ°
else {
await updateProfile(auth.currentUser, {
displayName: userInfo?.displayName,
photoURL: "",
});
}
}
} catch (error) {
// μμΈκ° λ°μν κ²½μ°
switch (error.code) {
case "auth/email-already-in-use":
alert("Email already in use.");
break;
case "auth/weak-password":
alert("Password should be at least 6 characters.");
break;
case "auth/network-request-failed":
alert("Network request failed.");
break;
case "auth/invalid-email":
alert("Invalid email format.");
break;
case "auth/internal-error":
alert("Internal error.");
break;
default:
alert("Sign up failed.");
}
}
}
);μ½λλ₯Ό μ£Όμ μ£Όμ μ€λͺ νλ κ²μ ν¬κ² μλ―Έκ° μμ κ² κ°λ€. Firebaseμμ μ 곡νλ createUserWithEmailAndPassword ν¨μμ μ μ ν κ°μ μ λ¬νλ©΄ νμ κ°μ μ μλ£λλ€.
κ·Έλ°λ° μ¬λ£κ° 무μμΈμ§ κ·Έ μμ±μ΄ μ΄λ νμ§λ λͺ νν μΈμ§ν΄μΌ νλ€. ν₯νμ React-Queryλ Supabase λ± λ€μν κΈ°μ μ νλ‘μ νΈμ μ μ©ν μμ μΈλ°, λ΄κ° νμ©ν μ μλ λ°μ΄ν°μλ 무μμ΄ μμΌλ©°, κ·Έ λ°μ΄ν°λ€μ΄ κ°μ§λ μμ±μ΄ 무μμΈμ§ μμ§ λͺ»νλ©΄, μ λλ‘ λ μ½λλ₯Ό μμ±ν μ μλ€.
λΌμ§κ³ κΈ°κ° μνλλ©΄ ν©νμ΄λλ₯Ό κ±°μ³ μλ―Έλ Έμ°μΌλ‘ λ°λλλ°, μ΄λ νμν κ²μ΄ λ¨λ°±μ§ λΆν΄ ν¨μμΈ 'νλ‘ν μμ 'κ³ , μμ°μ μ΄ λ°ν¨λλ λμ μμ±λλ νλ‘ν μμ λ μΌμ’ μ μνμ μν μ νκΈ°μ, μΌλ°μ μΌλ‘ μλκ΅μ κ°μ μμ°μ μΌλ‘ νλ€.
μ½λ©μ μ§μμ΄ μλμ μΌλ‘ λ§μ, κΉλ€λ‘μ΄ μλ(like λ°±μ’ μ μ μλ)μκ² μμ¬λ₯Ό λμ νλ κ³Όμ κ³Ό κ°μμ, λ΄κ° μ¬μ©νλ μ¬λ£λ₯Ό λͺ νν μκ³ μμ΄μΌλ§ νλ€.
2. signInWithEmailAndPassword(λ‘κ·ΈμΈ) βοΈ
// λ‘κ·ΈμΈμ μ²λ¦¬νλ ν¨μ
const handleSignIn = async () => {
try {
await dispatch(
signInUser({ email: signinInfo?.email, password: signinInfo?.password })
).unwrap();
navigate("/home");
} catch (error) {
console.error(error);
}
};export const signInUser = createAsyncThunk(
"user/signInUser",
async (userInfo) => {
const userCredential = await signInWithEmailAndPassword(
auth,
userInfo?.email,
userInfo?.password
);
const result = {
uid: userCredential?.user?.uid,
displayName: userCredential?.user?.displayName,
email: userCredential?.user?.email,
};
localStorage.setItem("user", JSON.stringify(result));
return result;
}
);Firebaseμμ μ 곡νλ ν¨μλ₯Ό μ¬μ©νλ€λ μ μ μ μΈνκ³ νΉμ§μ μΈ κ²μ, μ μ μ 보λ₯Ό localstorageμ μ μ₯νλ€λ μ μ΄λ€.
μ¬λ¬ μ₯λ¨μ μ΄ μκ² μ§λ§, λΈλΌμ°μ μ λ°μ΄ν°λ₯Ό μ μ₯νλ©΄, μ μ κ° νμ΄μ§λ₯Ό μλ‘κ³ μΉ¨νκ±°λ λΈλΌμ°μ λ₯Ό λ«μλ€κ° λ€μ μ΄μ΄λ, localstorageμ μ μ₯λ λ°μ΄ν°λ κ·Έλλ‘ μ μ§λκΈ°μ, λ‘κ·ΈμΈ μν μ μ§ λ± μΈμ κ΄λ ¨ κΈ°λ₯μ ꡬννλ λ° μ μ©νκΈ° λλ¬Έμ μ¬μ©νλ€.
3. onAuthStateChanged(νμ μ μ§) βοΈ
useEffect(() => {
dispatch(checkLoginUser(JSON.parse(localStorage.getItem("user"))));
}, [dispatch]);// μ μ μ 보λ₯Ό μ€μ νλ λΉλκΈ° ν¨μ
export const checkLoginUser = createAsyncThunk(
"user/checkLoginUser",
async (user) => {
console.log(user);
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
console.log(user);
localStorage.setItem("user", JSON.stringify(user));
});
return user;
}
);
Appbarμμλ ν΄λΉ useEffectλ₯Ό μ€ννλ€. μ¦, Appbarκ° μ μλλ λͺ¨λ μμμμ κ³μν΄μ μ μ μ 보λ₯Ό checkLoginUser ν¨μλ‘ dispatch νλ€λ κ²μ΄λ€.
Firebaseμμ μ 곡νλ onAuthStateChanged ν¨μλ₯Ό ν΅ν΄ κ³μν΄μ μ μ μ 보λ₯Ό κ°±μ λ° μ μ§ν μ μκ² λλ€.
β CRUD
1. Add Task λ²νΌ βοΈ
λμ: μ¬μ©μκ° μ λ ₯ν μλ‘μ΄ νμ€ν¬ μ΄λ¦μ 리μ€νΈμ μΆκ°νλ€. μλ₯Ό λ€μ΄, μ¬μ©μκ° "μ΄λνκΈ°"λΌλ νμ€ν¬λ₯Ό μ λ ₯νλ©΄, μ΄ νμ€ν¬κ° 리μ€νΈμ μΆκ°λλ€.
2. Go to Archive λ²νΌ βοΈ
λμ: νμ¬ νλ©΄μμ μμΉ΄μ΄λΈ νμ΄μ§λ‘ μ΄λνλ€. μ΄λ νμλ ν νμ΄μ§μ μλ λͺ¨λ complete νμ€ν¬λ€μ νμΈν μ μλ€.
3. All Clear λ²νΌ βοΈ
λμ: μμΉ΄μ΄λΈ νμ΄μ§μ μλ λͺ¨λ complete νμ€ν¬λ€μ μμ νλ€. λ¨, ν νμ΄μ§μ μΆκ°λ λ΄μ©μ completeκ° μλκΈ°μ μμ λμ§ μλλ€.
4. Go Home λ²νΌ βοΈ
λμ: μμΉ΄μ΄λΈ νμ΄μ§μμ ν νμ΄μ§λ‘ λλμκ°λ€. μ΄λ νμλ λ€μ ν νμ΄μ§μ μΆκ°λμ΄ μλ νμ€ν¬λ€κ³Ό μμΉ΄μ΄λΈ λ²νΌμ νμΈν μ μλ€.
5. Start λ²νΌ βοΈ
λμ: μ νν νΉμ νμ€ν¬μ νμ΄λ¨Έλ₯Ό μμνλ€. μλ₯Ό λ€μ΄, "μ΄λνκΈ°" νμ€ν¬μ νμ΄λ¨Έλ₯Ό μμνλ©΄, ν΄λΉ νμ€ν¬λ 'pending' μνλ‘ νμλλ©° μκ°μ΄ μ§νλλ€.
6. Pause λ²νΌ βοΈ
λμ: νμ¬ μ§ν μ€μΈ νμ€ν¬μ νμ΄λ¨Έλ₯Ό μΌμ μ μ§νλ€. μλ₯Ό λ€μ΄, "μ΄λνκΈ°" νμ€ν¬μ νμ΄λ¨Έκ° μΌμ μ μ§λλ©΄, ν΄λΉ νμ€ν¬λ 'paused' μνλ‘ λ³κ²½λλ©° μ μ§λ κΈ°λ‘μΌλ‘ ν΄λΉ νμ€ν¬κ° μλ²μ μ μ₯λλ€.
7. Delete λ²νΌ βοΈ
λμ: μ νν νΉμ νμ€ν¬λ₯Ό 리μ€νΈμμ μμ νλ€. μλ₯Ό λ€μ΄, "μ΄λνκΈ°" νμ€ν¬λ₯Ό μμ νλ©΄, μ΄ νμ€ν¬λ 리μ€νΈμμ μ κ±°λμ΄ UIμμ 보μ΄μ§ μκ² λλ€.
8. λν μ½λ βοΈ
const handleAddTask = () => {
if (!task.trim()) {
return alert("νμ€ν¬λ₯Ό μ
λ ₯ν΄μ£ΌμΈμ.");
}
const newTask = {
createdAt: Date.now(),
task,
status: "pending",
initialTime: time,
pausedTime: "",
userId: user.uid,
};
dispatch(addTask(newTask));
setTask("");
setTime(300);
};import { createSlice } from "@reduxjs/toolkit";
import { createAsyncThunk } from "@reduxjs/toolkit";
import {
addDoc,
getDocs,
deleteDoc,
doc,
collection,
updateDoc,
} from "firebase/firestore";
import { db } from "../firebase";
// λͺ¨λ νμ€ν¬ κ°μ Έμ€κΈ°
export const getAllTasks = createAsyncThunk(
"tasks/getAllTasks",
async (userId) => {
const taskCollection = await getDocs(collection(db, "tasks"));
const allTasks = [
...taskCollection.docs.map((doc) => ({ ...doc.data(), id: doc.id })),
].filter((task) => task.userId === userId);
return allTasks;
}
);
// νμ€ν¬ μΆκ°νκΈ°
export const addTask = createAsyncThunk("tasks/addTask", async (data) => {
const result = await addDoc(collection(db, "tasks"), {
userId: data?.userId,
task: data?.task,
initialTime: data?.initialTime,
pausedTime: "",
createdAt: data?.createdAt,
status: data.status,
});
const taskId = result.path.split("/")[1];
return { ...data, id: taskId };
});
// νμ€ν¬ μμ νκΈ°
export const deleteTask = createAsyncThunk(
"tasks/deleteTask",
async (taskId) => {
console.log(taskId);
await deleteDoc(doc(db, "tasks", taskId));
return taskId;
}
);
// λ©μΆ νμ€ν¬ μΆκ°νκΈ°
export const putTask = createAsyncThunk("tasks/putTask", async (task) => {
if (task.pausedTime !== "") {
await updateDoc(doc(db, "tasks", task.id), {
pausedTime: task?.pausedTime,
status: task.status,
});
}
return task;
});
// μλ£λ νμ€ν¬ μΆκ°νκΈ°
export const completedTask = createAsyncThunk(
"tasks/completedTask",
async (task) => {
if (task?.status === "complete") {
await updateDoc(doc(db, "tasks", task.id), {
status: task?.status,
});
}
return task;
}
);
// λͺ¨λ 'complete task' μμ νκΈ°
export const allClear = createAsyncThunk(
"tasks/allClearTask",
async (userId) => {
const taskCollection = await getDocs(collection(db, "tasks"));
const tasksToDelete = taskCollection.docs.filter(
(doc) => doc.data().userId === userId && doc.data().status === "complete"
);
const deletePromises = tasksToDelete.map((task) =>
deleteDoc(doc(db, "tasks", task.id))
);
await Promise.all(deletePromises);
return tasksToDelete.map((task) => task.id);
}
);
const initialState = {
tasks: [],
loading: false,
error: null,
};
const tasksSlice = createSlice({
name: "tasks",
initialState,
extraReducers: (builder) => {
builder.addCase(getAllTasks.pending, (state) => {
state.loading = true;
});
builder.addCase(getAllTasks.fulfilled, (state, action) => {
state.loading = false;
state.tasks = action.payload;
});
builder.addCase(getAllTasks.rejected, (state) => {
state.loading = false;
});
builder.addCase(addTask.pending, (state) => {
state.loading = true;
});
builder.addCase(addTask.fulfilled, (state, action) => {
state.loading = false;
state.tasks.push(action.payload);
});
builder.addCase(addTask.rejected, (state) => {
state.loading = false;
});
builder.addCase(deleteTask.pending, (state) => {
state.loading = true;
});
builder.addCase(deleteTask.fulfilled, (state, action) => {
state.loading = false;
state.tasks = state.tasks.filter((task) => task.id !== action.payload);
});
builder.addCase(deleteTask.rejected, (state) => {
state.loading = false;
});
builder.addCase(putTask.pending, (state) => {
state.loading = true;
});
builder.addCase(putTask.fulfilled, (state, action) => {
state.loading = false;
state.tasks = state.tasks.map((task) =>
task.id === action.payload.id ? action.payload : task
);
});
builder.addCase(putTask.rejected, (state) => {
state.loading = false;
});
builder.addCase(completedTask.pending, (state) => {
state.loading = true;
});
builder.addCase(completedTask.fulfilled, (state, action) => {
state.loading = false;
state.tasks = state.tasks.filter((task) => task.id !== action.payload.id);
window.alert("Task saved successfully π");
});
builder.addCase(completedTask.rejected, (state) => {
state.loading = false;
});
builder.addCase(allClear.pending, (state) => {
state.loading = true;
});
builder.addCase(allClear.fulfilled, (state, action) => {
state.loading = false;
state.tasks = state.tasks.filter((task) => !action.payload.some(task.id));
});
builder.addCase(allClear.rejected, (state) => {
state.loading = false;
});
},
});
export default tasksSlice.reducer;
λμκ² νμν λ°μ΄ν°μ νλ‘νΌν°λ₯Ό λͺ νν ν¨μΌλ‘μ¨, dbμ λΆμ°μ μ€μ¬, ν¨μ¨μ μΈ μν κ΄λ¦¬λ₯Ό μννκ² λ€λ κ²μ΄ ν΅μ¬μ΄λ€.
β μμΈμ²λ¦¬
1. νμ κ°μ μ νμν μμΈμ²λ¦¬λ₯Ό utilλ‘ λΆλ¦¬ βοΈ
export const validateUsername = (username) => {
const usernameRegex = /^[a-zA-Z0-9_]{3,16}$/;
return usernameRegex.test(username)
? ""
: "3μ μ΄μ 16μ μ΄ν, μλ¬Έμ, μ«μ, λ°μ€(_)λ§ νμ©λ©λλ€.";
};
export const validateEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email) ? "" : "μλͺ»λ μ΄λ©μΌ νμμ
λλ€.";
};
export const validatePassword = (password) => {
const passwordRegex =
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
return passwordRegex.test(password)
? ""
: "μ΅μ 8μ μ΄μ, λλ¬Έμ, μλ¬Έμ, μ«μ, νΉμλ¬Έμλ₯Ό ν¬ν¨ν΄μΌ ν©λλ€.";
};
2. λ‘κ·ΈμΈ μ¬λΆμ λ°λ₯Έ λΌμ°ν μμΈ μ²λ¦¬ βοΈ
import { useEffect } from "react";
import { useNavigate, useLocation } from "react-router-dom";
const usePreventAuth = () => {
const navigate = useNavigate();
const { pathname } = useLocation();
useEffect(() => {
if (JSON.parse(localStorage.getItem("user"))) {
navigate("/home");
if (pathname === "/archive") {
navigate("/archive");
}
} else {
if (pathname === "/home" || pathname === "/archive") {
navigate("/");
}
}
}, []);
return;
};
export default usePreventAuth;3. loadingμ ν΅ν, (API ν΅μ μ ) Button disabled μ²λ¦¬ βοΈ
<Button disabled={loading} onClick={handleSignIn}>
Sign In
</Button>const Button = styled.button`
display: flex;
align-items: center;
justify-content: center;
width: 300px;
padding: 1rem 0;
margin: 1rem;
font-size: 1.5rem;
color: black;
background: white;
border: 2px solid white;
border-radius: 50px;
box-shadow: 0px 4px 15px rgba(255, 255, 255, 0.2);
transition: all 0.3s ease-in-out;
cursor: pointer;
opacity: 0;
animation: fadeIn 1.5s ease forwards;
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
}
}
&:hover {
background: #ffffff;
color: black;
transform: translateY(-5px);
box-shadow: 0px 8px 20px rgba(255, 255, 255, 0.3);
}
&:active {
transform: translateY(0);
box-shadow: 0px 4px 15px rgba(255, 255, 255, 0.2);
}
&:disabled {
background-color: gray;
}
`;4. .envλ₯Ό ν΅ν νκ²½λ³μ μ²λ¦¬ βοΈ
import { initializeApp } from "firebase/app";
import { getAuth, onAuthStateChanged } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
import { getStorage } from "firebase/storage";
// Your web app's Firebase configuration
const firebaseConfig = {
apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
authDomain: "task-tracker3.firebaseapp.com",
projectId: "task-tracker3",
storageBucket: "task-tracker3.appspot.com",
messagingSenderId: "977756658453",
appId: "1:977756658453:web:588402c61ac23db27c671c",
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const storage = getStorage(app); // Initialize storage
export { auth, db, storage, onAuthStateChanged };β νκ³
μ€λ¬΄ μ΄μ΄ κ° λμμ λ μ κΉ ν리λ°κ²λ¨μμ μλ₯΄λ°μ΄νΈλ₯Ό ν μ μ΄ μλ€. ν ν λ¨Έλκ° λ°λ―ν λ¬Όμ μ€ μ μλκ³ μ¬μ€λ³΄μ μ λ¨κ±°μ΄ λ¬Όμ μ°¬λ¬Όμ μ’ νκ³ μμλλ°, μ€μ₯λμ΄ μ°¬λ¬Όμ μ λΆλκ³ νΈν΅ μλ νΈν΅μ μ³€λ€. κ·Έλ°λ° ν λ¨Έλμ μμΌμ ¨λ€. 'λ¨κ±°μ΄' λ¬Όμ΄ μλλΌ, 'λ°λ―ν' λ¬Όμ λ¬λΌκ³ νμΌλ λ΄κ° λ§λ€κ³ λ§μνμ ¨λ€. λ¬Όλ‘ μ€μ₯λλ μ¬κ³Όνμ ¨λ€.
κ°λμ λν μΌμμ μ¨λ€. μ¬λ°λ μ μ, κ·Έ λν μΌμ΄λΌλ κ²μ λλ¨ν νλ¨μ΄ μλλΌ μΈμ¬ν κ΄μ°°λ‘λΆν° μ»μ μ μλ€. λ¨Έλ¦¬νΈ λΉ μ§ λκΉμ§ μ³λ€λ³Έ νλ‘λνΈλ ν리ν°κ° μ’μ μλ°μ μκ³ , μλ§λ μ¬μ©μμκ² κ°λμΌλ‘ λ€κ°κ° κ²μ΄λ€. λ¬Όλ‘ κ·Έλ μ§ μλλΌλ λΉμ°ν λ§μ΄ κ΄μ°°ν΄μΌ λλ€. κΈ°λ³Έμ΄λκΉ. 'λΆμ§λ°ν¨'μ λν κ²½κ°μ¬μ΄ μ΄λ² νλ‘μ νΈμ μ΅λ κ΅ν μλμμκΉ.
More to read
Amazon VPC Architecture μ΄ν΄νκΈ°
μλ‘μ΄ νλ‘μ νΈλ₯Ό κΈ°ννλ©°, κ°λ°μμ 무μμ κ°μ₯ λ¨Όμ κ³ λ―Όν΄μΌ νλμ§ λ€μ λμλ³΄κ² λμμ΅λλ€.νλλ νλ‘ νΈμλκ° λͺ¨λ μ€κ³μ μΆλ°μ μ΄λΌκ³ λ―Ώμμ΅λλ€. μ μ κ° λ¬΄μμ λ³΄κ³ , μ΄λ€ νλ¦μμ 머무λ₯΄κ³ μ΄ννλμ§μ λν μ΄ν΄ μμ΄ μλΉμ€λ₯Ό λ§λ λ€λ 건 λΆκ°λ₯νλ€κ³ μκ°νκΈ°
'μμ¬μ΄νΈ'νλ‘ νΈμλ κ΄μ μΌλ‘ μκ³ λ¦¬μ¦ μ΄ν΄νκΈ°
μ€λλ§μ λ°©λ²λ‘ μ κ΄ν κΈμ μ°κ² λμμ΅λλ€. μ΅κ·Ό μν©μ μ΄λ μ΅λλ€. SSAFYμμλ ν루μ μμ²λ μμ μκ³ λ¦¬μ¦ λ¬Έμ λ€μ κ³Όμ λ‘ μννκ² λ©λλ€. κ·Έ κ³Όμ μμ, 'ꡬνλ ₯'μ΄ λ§€μ° λ¨μ΄μ§λ€λ μκ°μ΄ λ€μμ΅λλ€. μμ ν μ΄λ €μ΄ λ¬Έμ λΌλ©΄ 'μμ¬μ'μ΄λΌλ κ°μ μ‘°μ°¨ λλΌμ§
SubnetVPC μ€κ³μ μμ: IPμ Subnet
λ°λ³΅λλ λ£¨ν΄ μμμ μ»μ μμ κ°μ λ°ν μΌμ, μ΄μ λ κΈ°μ μ μ€ννΈλΌμ λνκΈ° μν κ°μΈ νλ‘μ νΈμ μ°©μνκ³ μ ν©λλ€.μ΄λ² νλ‘μ νΈμ λͺ©νλ λ¨μν ν¬νΈν΄λ¦¬μ€ ꡬμΆμ λμ΄, μ€μ μλΉμ€ μμ€μ λΈλ‘κ·Έ μμ€ν ꡬνκ³Ό λ€κ΅μ΄ μ²λ¦¬ μ μ© λ± μ€λ¬΄μ κ°κΉμ΄ μλμ ν λ¨κ³