diff --git a/README.md b/README.md
index c7a2a7d2..07c09450 100644
--- a/README.md
+++ b/README.md
@@ -11,8 +11,6 @@ Build your own AI real-time collaborative markdown editor in just 5 minutes.
-
-
## Overview
CodePair is an open-source real-time collaborative markdown editor with AI intelligence, built using React, NestJS, and LangChain.
@@ -32,62 +30,84 @@ This repository contains multiple packages/modules that make up our project. Eac
## Getting Started with Development
-### Configuration and Setup
+### 1. Set Up GitHub OAuth Key
+
+For the Social Login feature, you need to obtain a GitHub OAuth key before running the project. Please refer to [this document](./docs/1_Set_Up_GitHub_OAuth_Key.md) for guidance.
+
+After completing this step, you should have the `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` values.
+
+### 2. Choose Running Mode
+
+We offer two options. Choose the one that best suits your needs:
+
+- **Frontend Development Only Mode**: Use this option if you only want to develop the frontend.
+- **Full Stack Development Mode**: Use this option if you want to develop both the frontend and backend together.
+
+### 3-1. Frontend Development Only Mode
-Before running the Frontend and Backend applications, you need to fill in the required API Keys.
-Follow these steps:
+1. Update your `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` to `./backend/docker/docker-compose-full.yml`.
+
+ ```bash
+ vi ./backend/docker/docker-compose-full.yml
+
+ # In the file, update the following values:
+ # GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET
+ GITHUB_CLIENT_ID: "your_github_client_id_here"
+ GITHUB_CLIENT_SECRET: "your_github_client_secret_here"
+ ```
+
+2. Run `./backend/dockerdocker-compose-full.yml`.
+
+ ```bash
+ docker-compose -f ./backend/docker/docker-compose-full.yml up -d
+ ```
-**Frontend Environment Configuration**
+3. Run the Frontend application:
-1. Navigate to the `frontend` directory.
-
```bash
cd frontend
+ npm install
+ npm run dev
```
-2. Copy the `.env.example` file to create a `.env.development` file.
+
+4. Visit http://localhost:5173 to enjoy your CodePair.
+
+### 3-2. Full Stack Development Mode
+
+1. Update your `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` to `./backend/.env.development`.
+
```bash
- cp .env.example .env.development
+ vi ./backend/.env.development
+
+ # In the file, update the following values:
+ # GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET
+ GITHUB_CLIENT_ID=your_github_client_id_here
+ GITHUB_CLIENT_SECRET=your_github_client_secret_here
```
-3. Edit the `.env.development` file and fill in the necessary environment variable values. Refer to the comments for the meaning and examples of each value.
-**Backend Environment Configuration**
+2. Run `.backend/docker/docker-compose.yml`.
-1. Navigate to the `frontend` directory.
-
```bash
- cd backend
+ docker-compose -f ./backend/docker/docker-compose.yml up -d
```
-2. Copy the `.env.example` file to create a `.env.development` file.
+
+3. Run the Backend application:
+
```bash
- cp .env.example .env.development
+ cd backend
+ npm install
+ npm run start:dev
```
-3. Edit the `.env.development` file and fill in the necessary environment variable values. Refer to the comments for the meaning and examples of each value.
-
-### Run Application
-
-1. Run the Dockerfile for MongoDB, the database used by CodePair:
-
- ```bash
- docker-compose up -f ./backend/docker/mongodb_replica/docker-compose.yml -d
- ```
-
-2. Run the Backend application:
-
- ```bash
- cd backend
- npm install
- npm run start:dev
- ```
-3. Run the Frontend application:
-
- ```bash
- cd frontend
- npm install
- npm run dev
- ```
+4. Run the Frontend application:
-4. Visit http://localhost:5173 to enjoy your CodePair.
+ ```bash
+ cd ../frontend
+ npm install
+ npm run dev
+ ```
+
+5. Visit http://localhost:5173 to enjoy your CodePair.
## Contributing
diff --git a/backend/.env.example b/backend/.env.development
similarity index 75%
rename from backend/.env.example
rename to backend/.env.development
index c92061fe..236e5076 100644
--- a/backend/.env.example
+++ b/backend/.env.development
@@ -1,10 +1,7 @@
-# This file serves as a template for configuring environment variables.
-# Copy this file to `.env` and fill in the necessary values.
-
# DATABASE_URL: URL for connecting to the database.
# Format: mongodb://:@:/
# Example: mongodb://localhost:27017/codepair (For development mode)
-DATABASE_URL=your_mongodb_database_url_here
+DATABASE_URL=mongodb://localhost:27017/codepair
# GITHUB_CLIENT_ID: Client ID for authenticating with GitHub.
# To obtain a client ID, create an OAuth app at: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app
@@ -15,29 +12,34 @@ GITHUB_CLIENT_SECRET=your_github_client_secret_here
# GITHUB_CALLBACK_URL: Callback URL for handling GitHub authentication response.
# Format: https:///auth/login/github
# Example: http://localhost:3000/auth/login/github (For development mode)
-GITHUB_CALLBACK_URL=your_github_callback_url_here
+GITHUB_CALLBACK_URL=http://localhost:3000/auth/login/github
# JWT_AUTH_SECRET: Secret key for JWT authentication.
# This key is used to sign and verify JWT tokens.
-JWT_AUTH_SECRET=your_jwt_auth_secret_here
+JWT_AUTH_SECRET=you_should_change_this_secret_key_in_production
# FRONTEND_BASE_URL: Base URL of the frontend application.
# This URL is used for redirecting after authentication, etc.
# Example: http://localhost:5173 (For development mode)
-FRONTEND_BASE_URL=your_frontend_base_url_here
-
+FRONTEND_BASE_URL=http://localhost:5173
# YORKIE_API_ADDR: URL of the Yorkie Server
# This URL is used for using collaborative editing CRDT server
-# Example: https://api.yorkie.dev (For development mode)
-YORKIE_API_ADDR=your_yorkie_api_addr_here
+# Example: http://localhost:8080 (For development mode)
+YORKIE_API_ADDR=http://localhost:8080
# YORKIE_PROJECT_NAME: Name of the Yorkie project
# Create Yorkie project at: https://yorkie.dev
-YORKIE_PROJECT_NAME=your_yorkie_project_name_here
+# Example: default (For development mode)
+YORKIE_PROJECT_NAME=default
# YORKIE_PROJECT_SECRET_KEY: Secret key of the Yorkie project
# To obtain a project secret key, visit your project dashboard: https://yorkie.dev/dashboard/projects
-YORKIE_PROJECT_SECRET_KEY=your_yorkie_project_secret_key_here
+# You can leave this empty if you are using the default project
+YORKIE_PROJECT_SECRET_KEY=""
+# YORKIE_INTELLIGENCE: Whether to enable Yorkie Intelligence for collaborative editing.
+# Set to true if Yorkie Intelligence is required.
+# If set to false, OPENAI_API_KEY is not required.
+YORKIE_INTELLIGENCE=false
# OPENAI_API_KEY: API key for using the gpt-3.5-turbo model by Yorkie Intelligence.
# To obtain an API key, visit OpenAI: https://help.openai.com/en/articles/4936850-where-do-i-find-my-api-key
OPENAI_API_KEY=your_openai_api_key_here
@@ -45,12 +47,12 @@ OPENAI_API_KEY=your_openai_api_key_here
# LANGCHAIN_TRACING_V2: Whether LangSmith monitoring for YorkieIntelligence is needed.
# Set to true if LangSmith monitoring is required.
# If set to false, LANGCHAIN_ENDPOINT, LANGCHAIN_API_KEY, and LANGCHAIN_PROJECT are not required.
-LANGCHAIN_TRACING_V2=true
+LANGCHAIN_TRACING_V2=false
# LANGCHAIN_ENDPOINT: LangSmith API URL.
# This URL is used to communicate with LangSmith.
# Example: https://api.smith.langchain.com
# To obtain the LangSmith endpoint, visit LangSmith: https://www.langchain.com/langsmith
-LANGCHAIN_ENDPOINT=your_langsmith_api_key_here
+LANGCHAIN_ENDPOINT=https://www.langchain.com/langsmith
# LANGCHAIN_API_KEY: LangSmith API Key.
# This key is required for authenticating with LangSmith.
# To obtain an API key, visit LangSmith: https://www.langchain.com/langsmith
@@ -60,6 +62,11 @@ LANGCHAIN_API_KEY=your_langsmith_api_key_here
# To create a LangSmith project, visit LangSmith: https://www.langchain.com/langsmith
LANGCHAIN_PROJECT=your_langsmith_project_name_here
+
+# FILE_UPLOAD: Whether to enable file upload to storage
+# Set to true if file upload is required.
+# If set to false, AWS_S3_BUCKET_NAME is not required.
+FILE_UPLOAD=false
# AWS_S3_BUCKET_NAME: S3 Bucket name
# This is the name of the S3 Bucket
AWS_S3_BUCKET_NAME="your_s3_bucket_name"
\ No newline at end of file
diff --git a/backend/README.md b/backend/README.md
index ccb70ed5..9db6d20d 100644
--- a/backend/README.md
+++ b/backend/README.md
@@ -4,25 +4,38 @@ This project is the backend part of the CodePair service developed using NestJS.
## Getting Started
-1. Navigate to the project directory.
+1. Set Up GitHub OAuth Key
+
+ For the Social Login feature, you need to obtain a GitHub OAuth key before running the project. Please refer to [this document](../docs/1_Set_Up_GitHub_OAuth_Key.md) for guidance.
+
+ After completing this step, you should have the `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` values.
+
+2. Update your `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` to `./backend/.env.development`.
```bash
- cd backend
+ vi ./backend/.env.development
+
+ # In the file, update the following values:
+ # GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET
+ GITHUB_CLIENT_ID=your_github_client_id_here
+ GITHUB_CLIENT_SECRET=your_github_client_secret_here
```
-2. Install dependencies.
+3. Run `.backend/docker/docker-compose.yml`.
```bash
- npm install
+ docker-compose -f ./backend/docker/docker-compose.yml up -d
```
-3. Start the server in development mode.
+4. Run the Backend application:
```bash
+ cd backend
+ npm install
npm run start:dev
```
-4. Open [http://localhost:3000](http://localhost:3000) in your browser to access the backend.
+5. Visit http://localhost:3000 to enjoy your CodePair.
## API Specification
@@ -92,4 +105,4 @@ backend/
## Contributing
Please see the [CONTRIBUTING.md](../CONTRIBUTING.md) file for details on how to contribute to this project.
-If you are interested in internal design, please refer to the [Design Document](./design/).
\ No newline at end of file
+If you are interested in internal design, please refer to the [Design Document](./design/).
diff --git a/backend/docker/docker-compose-full.yml b/backend/docker/docker-compose-full.yml
index 77e6730d..5bec6f56 100644
--- a/backend/docker/docker-compose-full.yml
+++ b/backend/docker/docker-compose-full.yml
@@ -7,14 +7,22 @@ services:
environment:
DATABASE_URL: "mongodb://mongo:27017/codepair"
# Environment variables need to be passed to the container
- GITHUB_CLIENT_ID: "GITHUB_CLIENT_ID"
- GITHUB_CLIENT_SECRET: "GITHUB_CLIENT_SECRET"
- GITHUB_CALLBACK_URL: "/auth/login/github"
- JWT_AUTH_SECRET: "JWT_AUTH_SECRET"
- FRONTEND_BASE_URL: "FRONTEND_BASE_URL"
- YORKIE_API_ADDR: "http://localhost:8080"
- YORKIE_PROJECT_NAME: "admin" # If you want to use the other project, you should change this value
- YORKIE_PROJECT_SECRET_KEY: "admin" # If you want to use the other project, you should change this value
+ GITHUB_CLIENT_ID: "your_github_client_id_here"
+ GITHUB_CLIENT_SECRET: "your_github_client_secret_here"
+ GITHUB_CALLBACK_URL: "http://localhost:3000/auth/login/github"
+ JWT_AUTH_SECRET: "you_should_change_this_secret_key_in_production"
+ FRONTEND_BASE_URL: "http://localhost:5173"
+ YORKIE_API_ADDR: "http://yorkie:8080"
+ YORKIE_PROJECT_NAME: "default"
+ YORKIE_PROJECT_SECRET_KEY: ""
+ YORKIE_PROJECT_TOKEN: ""
+ YORKIE_INTELLIGENCE: false
+ OPENAI_API_KEY: "your_openai_api_key_here"
+ LANGCHAIN_ENDPOINT: "https://www.langchain.com/langsmith"
+ LANGCHAIN_API_KEY: "your_langsmith_api_key_here"
+ LANGCHAIN_PROJECT: "your_langsmith_project_name_here"
+ FILE_UPLOAD: false
+ AWS_S3_BUCKET_NAME: "your_s3_bucket_name"
ports:
- "3000:3000"
depends_on:
@@ -22,6 +30,7 @@ services:
restart: unless-stopped
links:
- "mongo:mongo"
+ - "yorkie:yorkie"
yorkie:
image: "yorkieteam/yorkie:latest"
diff --git a/backend/docker/docker-compose.yml b/backend/docker/docker-compose.yml
index a99bc9ac..7b191618 100644
--- a/backend/docker/docker-compose.yml
+++ b/backend/docker/docker-compose.yml
@@ -1,21 +1,30 @@
version: "3.8"
services:
- codepair-backend:
+ yorkie:
+ image: "yorkieteam/yorkie:latest"
+ command: ["server", "--enable-pprof"]
+ restart: always
+ ports:
+ - "8080:8080"
+ - "8081:8081"
+
+ mongo:
build:
- context: ../
+ context: ./mongodb_replica
+ args:
+ MONGO_VERSION: 4
environment:
- # Environment variables need to be passed to the container
- DATABASE_URL: "DATABASE_URL"
- GITHUB_CLIENT_ID: "GITHUB_CLIENT_ID"
- GITHUB_CLIENT_SECRET: "GITHUB_CLIENT_SECRET"
- GITHUB_CALLBACK_URL: "/auth/login/github"
- JWT_AUTH_SECRET: "JWT_AUTH_SECRET"
- FRONTEND_BASE_URL: "FRONTEND_BASE_URL"
- YORKIE_API_ADDR: "YORKIE_API_ADDR"
- YORKIE_PROJECT_NAME: "YORKIE_PROJECT_NAME"
- YORKIE_PROJECT_SECRET_KEY: "YORKIE_PROJECT_SECRET_KEY"
- AWS_S3_BUCKET_NAME: "YOUR_S3_BUCKET_NAME"
+ MONGO_REPLICA_HOST: 127.0.0.1
+ MONGO_REPLICA_PORT: 27017
+ MONGO_INITDB_DATABASE: "codepair"
+ MONGO_COMMAND: "mongo"
ports:
- - "3000:3000"
+ - "27017:27017"
restart: unless-stopped
+ healthcheck:
+ test:
+ ["CMD", "mongo", "admin", "--port", "27017", "--eval", "db.adminCommand('ping').ok"]
+ interval: 5s
+ timeout: 2s
+ retries: 20
diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts
index 3897794e..688bf78d 100644
--- a/backend/src/app.module.ts
+++ b/backend/src/app.module.ts
@@ -13,10 +13,15 @@ import { CheckModule } from "./check/check.module";
import { IntelligenceModule } from "./intelligence/intelligence.module";
import { LangchainModule } from "./langchain/langchain.module";
import { FilesModule } from "./files/files.module";
+import { SettingsModule } from "./settings/settings.module";
@Module({
imports: [
- ConfigModule.forRoot({ isGlobal: true }),
+ ConfigModule.forRoot({
+ isGlobal: true,
+ envFilePath:
+ process.env.NODE_ENV === "production" ? ".env.production" : ".env.development",
+ }),
UsersModule,
AuthModule,
WorkspacesModule,
@@ -27,6 +32,8 @@ import { FilesModule } from "./files/files.module";
IntelligenceModule,
LangchainModule,
FilesModule,
+ ConfigModule,
+ SettingsModule,
],
controllers: [],
providers: [
diff --git a/backend/src/settings/settings.controller.spec.ts b/backend/src/settings/settings.controller.spec.ts
new file mode 100644
index 00000000..02670cb8
--- /dev/null
+++ b/backend/src/settings/settings.controller.spec.ts
@@ -0,0 +1,18 @@
+import { Test, TestingModule } from "@nestjs/testing";
+import { SettingsController } from "./settings.controller";
+
+describe("SettingsController", () => {
+ let controller: SettingsController;
+
+ beforeEach(async () => {
+ const module: TestingModule = await Test.createTestingModule({
+ controllers: [SettingsController],
+ }).compile();
+
+ controller = module.get(SettingsController);
+ });
+
+ it("should be defined", () => {
+ expect(controller).toBeDefined();
+ });
+});
diff --git a/backend/src/settings/settings.controller.ts b/backend/src/settings/settings.controller.ts
new file mode 100644
index 00000000..eb18926b
--- /dev/null
+++ b/backend/src/settings/settings.controller.ts
@@ -0,0 +1,22 @@
+import { Controller, Get } from "@nestjs/common";
+import { SettingsService } from "./settings.service";
+import { ApiFoundResponse, ApiOperation, ApiTags } from "@nestjs/swagger";
+import { GetSettingsResponse } from "./types/get-settings-response.type";
+import { Public } from "src/utils/decorators/auth.decorator";
+
+@ApiTags("Settings")
+@Controller("settings")
+export class SettingsController {
+ constructor(private settingsService: SettingsService) {}
+
+ @Public()
+ @Get()
+ @ApiOperation({
+ summary: "Get Settings",
+ description: "Get Settings of CodePair",
+ })
+ @ApiFoundResponse({ type: GetSettingsResponse })
+ async getSettings(): Promise {
+ return this.settingsService.getSettings();
+ }
+}
diff --git a/backend/src/settings/settings.module.ts b/backend/src/settings/settings.module.ts
new file mode 100644
index 00000000..4b8e6847
--- /dev/null
+++ b/backend/src/settings/settings.module.ts
@@ -0,0 +1,9 @@
+import { Module } from "@nestjs/common";
+import { SettingsController } from "./settings.controller";
+import { SettingsService } from "./settings.service";
+
+@Module({
+ controllers: [SettingsController],
+ providers: [SettingsService],
+})
+export class SettingsModule {}
diff --git a/backend/src/settings/settings.service.spec.ts b/backend/src/settings/settings.service.spec.ts
new file mode 100644
index 00000000..a4961c0b
--- /dev/null
+++ b/backend/src/settings/settings.service.spec.ts
@@ -0,0 +1,18 @@
+import { Test, TestingModule } from "@nestjs/testing";
+import { SettingsService } from "./settings.service";
+
+describe("SettingsService", () => {
+ let service: SettingsService;
+
+ beforeEach(async () => {
+ const module: TestingModule = await Test.createTestingModule({
+ providers: [SettingsService],
+ }).compile();
+
+ service = module.get(SettingsService);
+ });
+
+ it("should be defined", () => {
+ expect(service).toBeDefined();
+ });
+});
diff --git a/backend/src/settings/settings.service.ts b/backend/src/settings/settings.service.ts
new file mode 100644
index 00000000..a2532705
--- /dev/null
+++ b/backend/src/settings/settings.service.ts
@@ -0,0 +1,23 @@
+import { Injectable } from "@nestjs/common";
+import { ConfigService } from "@nestjs/config";
+import { generateFeatureList } from "src/utils/constants/yorkie-intelligence";
+import { GetSettingsResponse } from "./types/get-settings-response.type";
+
+@Injectable()
+export class SettingsService {
+ constructor(private configService: ConfigService) {}
+
+ async getSettings(): Promise {
+ return {
+ yorkieIntelligence: {
+ enable: this.configService.get("YORKIE_INTELLIGENCE") === "true",
+ config: {
+ features: generateFeatureList(this.configService),
+ },
+ },
+ fileUpload: {
+ enable: this.configService.get("FILE_UPLOAD") === "true",
+ },
+ };
+ }
+}
diff --git a/backend/src/settings/types/get-settings-response.type.ts b/backend/src/settings/types/get-settings-response.type.ts
new file mode 100644
index 00000000..56cc9986
--- /dev/null
+++ b/backend/src/settings/types/get-settings-response.type.ts
@@ -0,0 +1,28 @@
+import { ApiProperty } from "@nestjs/swagger";
+
+class YorkieIntelligenceConfig {
+ @ApiProperty({ type: Boolean, description: "Enable Yorkie Intelligence" })
+ enable: boolean;
+
+ @ApiProperty({ type: Object, description: "Yorkie Intelligence Config" })
+ config: {
+ features: Array<{
+ title: string;
+ icon: string;
+ feature: string;
+ }>;
+ };
+}
+
+class FileUploadConfig {
+ @ApiProperty({ type: Boolean, description: "Enable File Upload" })
+ enable: boolean;
+}
+
+export class GetSettingsResponse {
+ @ApiProperty({ type: YorkieIntelligenceConfig, description: "Yorkie Intelligence Config" })
+ yorkieIntelligence: YorkieIntelligenceConfig;
+
+ @ApiProperty({ type: FileUploadConfig, description: "File Upload Config" })
+ fileUpload: FileUploadConfig;
+}
diff --git a/backend/src/utils/constants/yorkie-intelligence.ts b/backend/src/utils/constants/yorkie-intelligence.ts
new file mode 100644
index 00000000..e7a40999
--- /dev/null
+++ b/backend/src/utils/constants/yorkie-intelligence.ts
@@ -0,0 +1,25 @@
+import { ConfigService } from "@nestjs/config";
+
+export enum IntelligenceFeature {
+ GITHUB_ISSUE = "github-issue",
+ GITHUB_PR = "github-pr",
+}
+
+export const generateFeatureList = (configService: ConfigService) => {
+ const generateIconUrl = (icon: string) => {
+ return `${configService.get("FRONTEND_BASE_URL")}/yorkie_intelligence/${icon}`;
+ };
+
+ return [
+ {
+ title: "Write GitHub Issue",
+ icon: generateIconUrl("github.svg"),
+ feature: "github-issue",
+ },
+ {
+ title: "Write GitHub Pull Request",
+ icon: generateIconUrl("github.svg"),
+ feature: "github-pr",
+ },
+ ];
+};
diff --git a/docs/1_Set_Up_GitHub_OAuth_Key.md b/docs/1_Set_Up_GitHub_OAuth_Key.md
new file mode 100644
index 00000000..a6abc447
--- /dev/null
+++ b/docs/1_Set_Up_GitHub_OAuth_Key.md
@@ -0,0 +1,36 @@
+# Set Up GitHub OAuth
+
+For the Social Login feature, you need to obtain a GitHub OAuth key before running the project. After completing this step, you should have the `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` values.
+
+## 1. Visit GitHub and Sign In
+
+Visit [GitHub](https://github.com/) and sign in to your account.
+You should have a GitHub account to create an OAuth application.
+
+## 2.Visit Developer Settings
+
+You can access the Developer Setting by [this link](https://github.com/settings/apps).
+
+Or, you can access the Developer Settings page by clicking on your profile icon in the top right corner of the page and selecting `Settings`. Then, click on the `Developer settings` tab.
+
+## 3. Create a New GitHub App
+
+![Create a New GitHub App](./images/create_new_github_app.png)
+
+Click on the `New GitHub App` button to create a new OAuth application.
+
+## 4. Fill Out the Form
+
+![Fill Out the Form](./images/github_form.png)
+
+You should fill out the form with the following information (In Development Mode):
+
+- Authorization callback URL: `http://localhost:3000/auth/login/github`
+
+Other fields can be filled out according to your needs.
+
+## 5. Get Your Client ID and Client Secret
+
+![Get Your Client ID and Client Secret](./images/get_your_key.png)
+
+After creating the application, you will see your `Client ID` and `Client Secret`. Copy these values and save them in a safe place. Paste the `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` values into the `backend/.env.development` or `backend/docker/docker-compose-full.yml` file.
diff --git a/docs/images/create_new_github_app.png b/docs/images/create_new_github_app.png
new file mode 100644
index 00000000..97537a80
Binary files /dev/null and b/docs/images/create_new_github_app.png differ
diff --git a/docs/images/create_new_oauth_app.png b/docs/images/create_new_oauth_app.png
new file mode 100644
index 00000000..d0f2f86d
Binary files /dev/null and b/docs/images/create_new_oauth_app.png differ
diff --git a/docs/images/get_your_key.png b/docs/images/get_your_key.png
new file mode 100644
index 00000000..8aaf617f
Binary files /dev/null and b/docs/images/get_your_key.png differ
diff --git a/docs/images/github_form.png b/docs/images/github_form.png
new file mode 100644
index 00000000..8e968437
Binary files /dev/null and b/docs/images/github_form.png differ
diff --git a/frontend/.env.development b/frontend/.env.development
index be08dbc1..855fc6af 100644
--- a/frontend/.env.development
+++ b/frontend/.env.development
@@ -1,3 +1,3 @@
VITE_API_ADDR="http://localhost:3000"
-VITE_YORKIE_API_ADDR="https://api.yorkie.dev"
-VITE_YORKIE_API_KEY="cmftp10ksk14av0kc7gg"
+VITE_YORKIE_API_ADDR="http://localhost:8080"
+VITE_YORKIE_API_KEY=""
diff --git a/frontend/.env.example b/frontend/.env.example
deleted file mode 100644
index ccc70e05..00000000
--- a/frontend/.env.example
+++ /dev/null
@@ -1,15 +0,0 @@
-# This file serves as a template for configuring environment variables.
-# Copy this file to `.env` and fill in the necessary values.
-
-# VITE_API_ADDR: URL of backend application
-# This URL is used for backend API
-# Example: http://localhost:3000 (For development mode)
-VITE_API_ADDR=your_api_addr_here
-
-# YORKIE_API_ADDR: URL of the Yorkie Server
-# This URL is used for using collaborative editing CRDT server
-# Example: https://api.yorkie.dev (For development mode)
-VITE_YORKIE_API_ADDR=your_yorkie_api_addr_here
-# VITE_YORKIE_API_KEY: API key of the Yorkie project
-# To obtain a API key, visit your project dashboard: https://yorkie.dev/dashboard/projects
-VITE_YORKIE_API_KEY=your_yorkie_api_key_here
diff --git a/frontend/README.md b/frontend/README.md
index ed562d10..2442ffc0 100755
--- a/frontend/README.md
+++ b/frontend/README.md
@@ -4,25 +4,38 @@ This project is the frontend part of the CodePair service developed using Vite a
## Getting Started
-1. Navigate to the project directory.
+1. Set Up GitHub OAuth Key
-```bash
-cd frontend
-```
+ For the Social Login feature, you need to obtain a GitHub OAuth key before running the project. Please refer to [this document](../docs/1_Set_Up_GitHub_OAuth_Key.md) for guidance.
-2. Install dependencies.
+ After completing this step, you should have the `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` values.
-```bash
-npm install
-```
+2. Update your `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` to `./backend/dockerdocker-compose-full.yml`.
-3. Start the development server.
+ ```bash
+ vi ./backend/docker/docker-compose-full.yml
-```bash
-npm run dev
-```
+ # In the file, update the following values:
+ # GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET
+ GITHUB_CLIENT_ID: "your_github_client_id_here"
+ GITHUB_CLIENT_SECRET: "your_github_client_secret_here"
+ ```
+
+3. Run `./backend/dockerdocker-compose-full.yml`.
+
+ ```bash
+ docker-compose -f ./backend/docker/docker-compose-full.yml up -d
+ ```
+
+4. Run the Frontend application:
+
+ ```bash
+ cd frontend
+ npm install
+ npm run dev
+ ```
-4. Open [http://localhost:5173](http://localhost:5173) in your browser to view the app.
+5. Visit [http://localhost:5173](http://localhost:5173) to enjoy your CodePair.
## Commands
diff --git a/frontend/public/yorkie_intelligence/github.svg b/frontend/public/yorkie_intelligence/github.svg
new file mode 100644
index 00000000..326755c1
--- /dev/null
+++ b/frontend/public/yorkie_intelligence/github.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/frontend/src/components/common/PrivateRoute.tsx b/frontend/src/components/common/PrivateRoute.tsx
index ba56620d..cc60b859 100644
--- a/frontend/src/components/common/PrivateRoute.tsx
+++ b/frontend/src/components/common/PrivateRoute.tsx
@@ -2,6 +2,7 @@ import { ReactNode, useContext } from "react";
import { useLocation, Navigate } from "react-router-dom";
import { AuthContext } from "../../contexts/AuthContext";
import { Backdrop, CircularProgress } from "@mui/material";
+import { useGetSettingsQuery } from "../../hooks/api/settings";
interface PrivateRouteProps {
children?: ReactNode;
@@ -12,6 +13,8 @@ const PrivateRoute = (props: PrivateRouteProps) => {
const { isLoggedIn, isLoading } = useContext(AuthContext);
const location = useLocation();
+ useGetSettingsQuery();
+
if (isLoading) {
return (
diff --git a/frontend/src/components/editor/Editor.tsx b/frontend/src/components/editor/Editor.tsx
index eb019248..825b0515 100644
--- a/frontend/src/components/editor/Editor.tsx
+++ b/frontend/src/components/editor/Editor.tsx
@@ -14,12 +14,14 @@ import { imageUploader } from "../../utils/imageUploader";
import { useCreateUploadUrlMutation, useUploadFileMutation } from "../../hooks/api/file";
import { selectWorkspace } from "../../store/workspaceSlice";
import { ScrollSyncPane } from "react-scroll-sync";
+import { selectSetting } from "../../store/settingSlice";
function Editor() {
const dispatch = useDispatch();
const themeMode = useCurrentTheme();
const [element, setElement] = useState();
const editorStore = useSelector(selectEditor);
+ const settingStore = useSelector(selectSetting);
const workspaceStore = useSelector(selectWorkspace);
const { mutateAsync: createUploadUrl } = useCreateUploadUrlMutation();
const { mutateAsync: uploadFile } = useUploadFileMutation();
@@ -31,7 +33,12 @@ function Editor() {
useEffect(() => {
let view: EditorView | undefined = undefined;
- if (!element || !editorStore.doc || !editorStore.client) {
+ if (
+ !element ||
+ !editorStore.doc ||
+ !editorStore.client ||
+ typeof settingStore.fileUpload?.enable !== "boolean"
+ ) {
return;
}
@@ -62,7 +69,9 @@ function Editor() {
EditorView.lineWrapping,
keymap.of([indentWithTab]),
intelligencePivot,
- imageUploader(handleUploadImage, editorStore.doc),
+ ...(settingStore.fileUpload.enable
+ ? [imageUploader(handleUploadImage, editorStore.doc)]
+ : []),
],
});
@@ -85,6 +94,7 @@ function Editor() {
workspaceStore.data,
createUploadUrl,
uploadFile,
+ settingStore.fileUpload?.enable,
]);
return (
diff --git a/frontend/src/components/editor/YorkieIntelligenceFeature.tsx b/frontend/src/components/editor/YorkieIntelligenceFeature.tsx
index f45c280d..405cbfa9 100644
--- a/frontend/src/components/editor/YorkieIntelligenceFeature.tsx
+++ b/frontend/src/components/editor/YorkieIntelligenceFeature.tsx
@@ -10,7 +10,7 @@ import {
Typography,
useTheme,
} from "@mui/material";
-import { INTELLIGENCE_FOOTER_ID, IntelligenceFeature } from "../../constants/intelligence";
+import { INTELLIGENCE_FOOTER_ID } from "../../constants/intelligence";
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
import RefreshIcon from "@mui/icons-material/Refresh";
import { FormContainer, TextFieldElement, useForm } from "react-hook-form-mui";
@@ -27,7 +27,7 @@ import { selectEditor } from "../../store/editorSlice";
interface YorkieIntelligenceFeatureProps {
title: string;
- feature: IntelligenceFeature;
+ feature: string;
onClose: () => void;
}
diff --git a/frontend/src/components/editor/YorkieIntelligenceFeatureList.tsx b/frontend/src/components/editor/YorkieIntelligenceFeatureList.tsx
index feb9c640..8ac46502 100644
--- a/frontend/src/components/editor/YorkieIntelligenceFeatureList.tsx
+++ b/frontend/src/components/editor/YorkieIntelligenceFeatureList.tsx
@@ -1,32 +1,30 @@
-import { ListItemIcon, ListItemText, MenuItem, MenuList, Stack, TextField } from "@mui/material";
+import {
+ Icon,
+ ListItemIcon,
+ ListItemText,
+ MenuItem,
+ MenuList,
+ Stack,
+ TextField,
+} from "@mui/material";
import { useMemo, useState } from "react";
-import GitHubIcon from "@mui/icons-material/GitHub";
import { matchSorter } from "match-sorter";
-import { IntelligenceFeature } from "../../constants/intelligence";
-
-const featureInfoList = [
- {
- title: "Write GitHub Issue",
- icon: ,
- feature: IntelligenceFeature.GITHUB_ISSUE,
- },
- {
- title: "Write GitHub Pull Request",
- icon: ,
- feature: IntelligenceFeature.GITHUB_PR,
- },
-];
+import { selectSetting } from "../../store/settingSlice";
+import { useSelector } from "react-redux";
interface YorkieIntelligenceFeatureListProps {
- onSelectFeature: (feature: IntelligenceFeature, title: string) => void;
+ onSelectFeature: (feature: string, title: string) => void;
}
function YorkieIntelligenceFeatureList(props: YorkieIntelligenceFeatureListProps) {
const { onSelectFeature } = props;
+ const settingStore = useSelector(selectSetting);
const [featureText, setFeatureText] = useState("");
const filteredFeatureInfoList = useMemo(() => {
- return matchSorter(featureInfoList, featureText, { keys: ["title", "feature"] });
- }, [featureText]);
+ return matchSorter(settingStore.yorkieIntelligence?.config.features ?? [], featureText, {
+ keys: ["title", "feature"],
+ });
+ }, [featureText, settingStore.yorkieIntelligence?.config.features]);
const handleFeatureTextChange: React.ChangeEventHandler<
HTMLInputElement | HTMLTextAreaElement
@@ -51,7 +49,11 @@ function YorkieIntelligenceFeatureList(props: YorkieIntelligenceFeatureListProps
key={featureInfo.feature}
onClick={() => onSelectFeature(featureInfo.feature, featureInfo.title)}
>
- {featureInfo.icon}
+
+
+
+
+
{featureInfo.title}
))}
diff --git a/frontend/src/components/editor/YorkieIntelligenceFooter.tsx b/frontend/src/components/editor/YorkieIntelligenceFooter.tsx
index 3f757c40..33d62e32 100644
--- a/frontend/src/components/editor/YorkieIntelligenceFooter.tsx
+++ b/frontend/src/components/editor/YorkieIntelligenceFooter.tsx
@@ -1,7 +1,6 @@
import { Box, Card, Popover, useTheme } from "@mui/material";
import YorkieIntelligenceFeatureList from "./YorkieIntelligenceFeatureList";
import { useEffect, useMemo, useRef, useState } from "react";
-import { IntelligenceFeature } from "../../constants/intelligence";
import YorkieIntelligenceFeature from "./YorkieIntelligenceFeature";
import { useSelector } from "react-redux";
import { selectEditor } from "../../store/editorSlice";
@@ -17,7 +16,7 @@ function YorkieIntelligenceFooter(props: YorkieIntelligenceFooterProps) {
const editorStore = useSelector(selectEditor);
const anchorRef = useRef(null);
const [selectedTitle, setSelectedTitle] = useState(null);
- const [selectedFeature, setSelectedFeature] = useState(null);
+ const [selectedFeature, setSelectedFeature] = useState(null);
const [anchorEl, setAnchorEl] = useState();
const [closeModalOpen, setCloseModalOpen] = useState(false);
const cardRef = useRef(null);
@@ -37,7 +36,7 @@ function YorkieIntelligenceFooter(props: YorkieIntelligenceFooterProps) {
};
}, []);
- const handleSelectFeature = (feature: IntelligenceFeature, title: string) => {
+ const handleSelectFeature = (feature: string, title: string) => {
setSelectedFeature(feature);
setSelectedTitle(title);
};
diff --git a/frontend/src/constants/intelligence.ts b/frontend/src/constants/intelligence.ts
index fc278995..c73c0b97 100644
--- a/frontend/src/constants/intelligence.ts
+++ b/frontend/src/constants/intelligence.ts
@@ -1,7 +1,2 @@
export const INTELLIGENCE_HEADER_ID = "yorkie-intelligence-header";
export const INTELLIGENCE_FOOTER_ID = "yorkie-intelligence-footer";
-
-export enum IntelligenceFeature {
- GITHUB_ISSUE = "github-issue",
- GITHUB_PR = "github-pr",
-}
diff --git a/frontend/src/hooks/api/settings.ts b/frontend/src/hooks/api/settings.ts
new file mode 100644
index 00000000..884d9bf8
--- /dev/null
+++ b/frontend/src/hooks/api/settings.ts
@@ -0,0 +1,34 @@
+import { useQuery } from "@tanstack/react-query";
+import axios from "axios";
+import { GetSettingsResponse } from "./types/settings";
+import { useDispatch, useSelector } from "react-redux";
+import { selectSetting, setFileUpload, setYorkieIntelligence } from "../../store/settingSlice";
+import { useEffect } from "react";
+
+export const generateGetSettingsQueryKey = () => {
+ return ["settings"];
+};
+
+export const useGetSettingsQuery = () => {
+ const dispatch = useDispatch();
+ const settingStore = useSelector(selectSetting);
+ const query = useQuery({
+ queryKey: generateGetSettingsQueryKey(),
+ enabled: settingStore.yorkieIntelligence === null && settingStore.fileUpload === null,
+ queryFn: async () => {
+ const res = await axios.get("/settings");
+ return res.data;
+ },
+ staleTime: 1000 * 60 * 60 * 24, // 24 hours
+ });
+
+ useEffect(() => {
+ if (!query.isSuccess) return;
+
+ const data = query.data;
+ dispatch(setYorkieIntelligence(data.yorkieIntelligence));
+ dispatch(setFileUpload(data.fileUpload));
+ }, [dispatch, query.data, query.isSuccess]);
+
+ return query;
+};
diff --git a/frontend/src/hooks/api/types/settings.d.ts b/frontend/src/hooks/api/types/settings.d.ts
new file mode 100644
index 00000000..8e590d7a
--- /dev/null
+++ b/frontend/src/hooks/api/types/settings.d.ts
@@ -0,0 +1,20 @@
+class YorkieIntelligenceSetting {
+ enable: boolean;
+ config: {
+ features: Array<{
+ title: string;
+ icon: string;
+ feature: string;
+ }>;
+ };
+}
+
+class FileUploadSetting {
+ enable: boolean;
+}
+
+export class GetSettingsResponse {
+ yorkieIntelligence: YorkieIntelligenceSetting;
+
+ fileUpload: FileUploadSetting;
+}
diff --git a/frontend/src/pages/workspace/document/Index.tsx b/frontend/src/pages/workspace/document/Index.tsx
index afc2de2d..6ee3ca29 100644
--- a/frontend/src/pages/workspace/document/Index.tsx
+++ b/frontend/src/pages/workspace/document/Index.tsx
@@ -9,11 +9,13 @@ import { useGetWorkspaceQuery } from "../../../hooks/api/workspace";
import DocumentView from "../../../components/editor/DocumentView";
import { useYorkieDocument } from "../../../hooks/useYorkieDocument";
import YorkieIntelligence from "../../../components/editor/YorkieIntelligence";
+import { selectSetting } from "../../../store/settingSlice";
function DocumentIndex() {
const dispatch = useDispatch();
const params = useParams();
const userStore = useSelector(selectUser);
+ const settingStore = useSelector(selectSetting);
const { data: workspace } = useGetWorkspaceQuery(params.workspaceSlug);
const { data: document } = useGetDocumentQuery(workspace?.id, params.documentId);
const { doc, client } = useYorkieDocument(document?.yorkieDocumentId, userStore.data?.nickname);
@@ -33,7 +35,7 @@ function DocumentIndex() {
return (
-
+ {settingStore.yorkieIntelligence?.enable && }
);
}
diff --git a/frontend/src/store/settingSlice.ts b/frontend/src/store/settingSlice.ts
new file mode 100644
index 00000000..d4754766
--- /dev/null
+++ b/frontend/src/store/settingSlice.ts
@@ -0,0 +1,47 @@
+import { createSlice } from "@reduxjs/toolkit";
+import type { PayloadAction } from "@reduxjs/toolkit";
+import { RootState } from "./store";
+
+interface YorkieIntelligenceSetting {
+ enable: boolean;
+ config: {
+ features: Array<{
+ title: string;
+ icon: string;
+ feature: string;
+ }>;
+ };
+}
+
+interface FileUploadSetting {
+ enable: boolean;
+}
+
+export interface SettingState {
+ yorkieIntelligence: YorkieIntelligenceSetting | null;
+ fileUpload: FileUploadSetting | null;
+}
+
+const initialState: SettingState = {
+ yorkieIntelligence: null,
+ fileUpload: null,
+};
+
+export const settingSlice = createSlice({
+ name: "setting",
+ initialState,
+ reducers: {
+ setYorkieIntelligence: (state, action: PayloadAction) => {
+ state.yorkieIntelligence = action.payload;
+ },
+ setFileUpload: (state, action: PayloadAction) => {
+ state.fileUpload = action.payload;
+ },
+ },
+});
+
+export const { setYorkieIntelligence, setFileUpload } = settingSlice.actions;
+
+export const selectSetting = (state: RootState) => state.setting;
+
+export default settingSlice.reducer;
diff --git a/frontend/src/store/store.ts b/frontend/src/store/store.ts
index 79710107..81fb07f7 100644
--- a/frontend/src/store/store.ts
+++ b/frontend/src/store/store.ts
@@ -7,6 +7,7 @@ import authSlice from "./authSlice";
import userSlice from "./userSlice";
import workspaceSlice from "./workspaceSlice";
import documentSlice from "./documentSlice";
+import settingSlice from "./settingSlice";
const reducers = combineReducers({
// Persistence
@@ -17,6 +18,7 @@ const reducers = combineReducers({
editor: editorSlice,
workspace: workspaceSlice,
document: documentSlice,
+ setting: settingSlice,
});
const persistConfig = {