http://www.gaia.best by team seed ๐ฑ
๋ฐํ์์๊ณผ ํ ์คํธ์์์ ํ์ธํด๋ณด์ธ์
GAIA๋ ๊ธฐ์กด์ Project Management System๋ค์ ์ด๋ ค์ด ์ฌ์ฉ๋ฒ๊ณผ ๋์ ์ง์
์ฅ๋ฒฝ์ ํด๊ฒฐํ๊ธฐ ์ํด ๊ธฐํ๋์์ต๋๋ค.
์๋์ ๋ชจ๋๋ค์ ํตํด ํ๋ก์ ํธ ๊ด๋ฆฌ์ ๊ฐ๋ฐ์๊ฐ์ ํ์
์ ๋์ต๋๋ค.
- ์ด์ ํธ๋ํน ( Milestone, Issue )
- ํ๋ก์ ํธ ์ผ์ ๊ด๋ฆฌ ( Calendar, Gantt )
- ์นธ๋ฐ ๋ณด๋
- ์ํค
- ๋ด์ค
- ์ธ์คํดํธ ๋ฉ์ ์
- ํ๋ก์ ํธ ํต๊ณ
- ํตํฉ ๊ฒ์
๐ Technology Stack
๐ฉ๐ฉ๐ฆ๐ฆ Team members
๐ Award
๋๋์ธ์ฌ๊ฐ๋ฐ์ ํด๋น ๊ธฐ์ ์ต์ฐ์ ํ๋ก์ ํธ๋ก ์ ์ ๋์์ต๋๋ค.๐๐
Gaia ์๊ฐ
1.Gaia
- Single Page Application
- URL Structure
- Elastic Search
2.Main
- index
- login
- admin
3.User
- overview
- notification
- alarm
- profile
- setting
- log
4.Chatting
- chat
5.Project
- code
- keyboard shortcut
- search
- multi languages
- milestone
- issue
- gantt
- calendar
- kanban
- news
- wiki
- analytics
- setting - member
- setting - management
1.Gaia
1) Single Page Application
gaia๋ ์ฑ๊ธ ํ์ด์ง ์ดํ๋ฆฌ์ผ์ด์ ์ ๋๋ค.
ํ์ํ ๊ฐ์ข ํจ์๋ค์ ๋ชจ๋ํ ์ํค๊ณ ๊ธฐ์ด๋ถํฐ ํ๋์ฉ ์ค๊ณํ๋ค๋ณด๋ ์ฝ์ง ์์ ๊ณผ์ ์ด์์ง๋ง ํด๋ผ ์ ์์์ต๋๋ค. gaia์์์ ๋ชจ๋ ์์ฒญ์ ๋น๋๊ธฐ๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค.
// ๋ค๋ก๊ฐ๊ธฐ ์ด๋ฒคํธ binding ํ๊ธฐ
$(window).bind("popstate", function(event) {
var data = event.originalEvent.state;
if(data){ // ์ด์ ํ์ด์ง ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด ajax๋ก ๋ค์ ์์ฒญํด ํ๋ฉด ๋ ๋๋ง.
if(data.startsWith('member-')){
memberMovePage(data.substring('member-'.length));
}else{
movePage(data);
}
}else{ // ํ์คํ ๋ฆฌ์ ์ ๋ณด๊ฐ ์์๊ฒฝ์ฐ ๋ฉ์ธํ๋ฉด์ผ๋ก ์ด๋์ํค๊ธฐ.
var url = getContextPath();
$(location).attr('href',url);
}
})
// ๋ค๋ก๊ฐ๊ธฐ ์ํฉ์ ์ ์ธํ๊ณ ๋ pushState๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ์์์ผํฉ๋๋ค.
const movePageHistory = function(pageParam){
var url = getContextPath()
+'/'+manager_id+'/'+project_title
+ (pageParam!='code' ? '/'+pageParam : '');
history.pushState(pageParam, null, url);
movePage(pageParam);
}
pushState๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ์๊ณ popState๋ฅผ binding ํด์ ๋ค๋ก๊ฐ๊ธฐ์ ์์ผ๋ก ๊ฐ๊ธฐ ์ํฉ์ ํด๊ฒฐํ์ต๋๋ค.
2) URL Structure
๋ํ pathvariable์ ์ ๊ทน์ ์ผ๋ก ํ์ฉํด URL ๊ทธ ์์ฒด๊ฐ navigation ์ญํ ์ ๋์ฒดํ ์ ์๋๋ก ๊ตฌํํ์ต๋๋ค.
์๋ฅผ ๋ค์ด ์์ url์ธ kkobuk/ddit302/issue/9 ์ ๊ฒฝ์ฐ์๋ kkobuk์ด ์์ฑํ ddit302 ๋ผ๋ ํ๋ก์ ํธ์ 9๋ฒ์งธ ์ด์๋ฅผ ๋ป ํฉ๋๋ค.
@Controller
@RequestMapping("{manager_id:^(?:(?!admin$|view$|restapi$).)*$}/{project_title:^(?:(?!overview$|help$|chat$|setting$).)*$}")
public class ProjectUrlMapper {
@Inject
private ProjectService service;
@Inject
private ProjectDao dao;
@Inject
private WebApplicationContext container;
private ServletContext application;
@PostConstruct
public void init() {
application = container.getServletContext();
}
private static final Logger logger = LoggerFactory.getLogger(ProjectUrlMapper.class);
@GetMapping({"","{pageParam}", "{pageParam}/{paramNo}"})
public String projectMenuOverview(
@PathVariable String manager_id
,@PathVariable String project_title
,@PathVariable Optional<String> pageParam
,@PathVariable Optional<String> paramNo
,Authentication authentication
,HttpSession session
,Model model
,HttpServletResponse resp
) {
// ์ ์์ค์ธ ํ๋ก์ ํธ์ ๋ํ ์ฒ๋ฆฌ๋ฅผ ๋จผ์ ํ๋ค.
loadProjectProcessor(manager_id, project_title, authentication, session, resp);
// paramNo ๊ฐ ์กด์ฌํ ๋๋ pageParam์ ๋ถ์ฌ์ค๋ค.
if(paramNo.isPresent()) {
pageParam = Optional.of(String.format("%s/%s", pageParam.get(),paramNo.get()));
}
model.addAttribute("pageParam", pageParam.isPresent() ? pageParam.get() : "code");
model.addAttribute("manager_id", manager_id);
model.addAttribute("project_title", project_title);
return "view/template/project";
}
}
PathVariable๊ณผ ์ ๊ท์์ ํ์ฉํด ๊ตฌํ ํ์ต๋๋ค.
3) Elastic Search
@Component
public class ElasticUtil {
private String hostname;
private int port;
public String getHostname() {
return hostname;
}
public int getPort() {
return port;
}
private RestClientBuilder restClientBuilder;
private ElasticUtil() {
Properties properties = new Properties();
try { // dbinfo.properties์์ ์ ์ ์ ๋ณด ๋ฐ์์ต๋๋ค.
properties.load(Resources.getResourceAsReader("best/gaia/db/dbinfo.properties"));
} catch (IOException e) {}
hostname = properties.getProperty("el.url");
port = Integer.parseInt(properties.getProperty("el.port"));
HttpHost host = new HttpHost(hostname, port);
restClientBuilder = RestClient.builder(host);
};
/**
* @param index
* @param query Map<String, Object> key๋ ํ๋กํผํฐ๋ช
, object๋ value ์กฐ๊ฑด
* @param sort Map<String, SortOrder>
* @param size (null ๋ฃ์ ์ ์์ต๋๋ค. size null์ผ ๊ฒฝ์ฐ ๋ชจ๋ ๋ฐ์์ด)
* @return
*/
public List<Map<String,Object>> simpleSearch(
String index
, Map<String,Object> query
, Map<String,SortOrder> sort
, Integer size
){
// search์ index ์กฐ๊ฑด ๊ฑธ๊ธฐ
SearchRequest searchRequest = new SearchRequest(index);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// query์ ์๋ ์
์ฟผ๋ฆฌ ์กฐ๊ฑด์ผ๋ก ๊ฑธ๊ธฐ
for(String key : query.keySet()) {
searchSourceBuilder.query(QueryBuilders.matchQuery(key, query.get(key)));
}
// sort ์ ์๋ ์
์ ์ ๋ ฌ ์กฐ๊ฑด์ผ๋ก ๊ฑธ๊ธฐ
for(String key : sort.keySet()) {
searchSourceBuilder.sort(new FieldSortBuilder(key).order(sort.get(key)));
}
if(size != null) {
searchSourceBuilder.size(size);
}else {
searchSourceBuilder.size(200);
}
searchRequest.source(searchSourceBuilder);
List<Map<String,Object>> list = new ArrayList<>();
try(RestHighLevelClient client = new RestHighLevelClient(restClientBuilder)) {
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHits searchHits = response.getHits();
for(SearchHit hit : searchHits) {
Map<String, Object> sourceMap = hit.getSourceAsMap();
list.add(sourceMap);
}
} catch (IOException e) {}
return list;
}
public int insert(String index, Map<String, Object> data ){
IndexResponse response = null;
try(RestHighLevelClient client = new RestHighLevelClient(restClientBuilder)) {
data.put("date", LocalDateTime.now());
XContentBuilder xContent = XContentFactory.jsonBuilder().map(data);
String jsonBody = Strings.toString(xContent);
// id ์์ด ์ฝ์
์ ์๋ UID๊ฐ ์์ฑ๋ฉ๋๋ค.
String id = null;
IndexRequest indexRequest = new IndexRequest(index).id(id).source(jsonBody, XContentType.JSON);
response = client.index(indexRequest, RequestOptions.DEFAULT);
} catch (IOException e) {}
return response.getShardInfo().getSuccessful();
}
}
Elastic Search์ highlevel java client API๋ฅผ ํ์ฉํ ๋ชจ๋์ ๋ง๋ค์ด์ ์ฝ๊ฒ ์ฌ์ฉํ์ต๋๋ค.
2.Main
GAIA์ ๋ฉ์ธ ํ๋ฉด ์ ๋๋ค.
์ฐ์ธก ์๋จ ๋ก๊ทธ์ธ ๋ฒํผ์ ๋๋ฌ ๋ก๊ทธ์ธ ํ ์ ์์ต๋๋ค.
ํ์๊ฐ์ ์์ ์ฐํ ๋ฒํผ์ ํด๋ฆญ ํ๋ฉด ์จ๊ฒจ์ง ์๋ฒ ๊ด๋ฆฌ์ ๋ชจ๋๋ก ์ง์ ํ ์ ์์ต๋๋ค.
ํด๋น ํ์ด์ง์์๋ ์ด์์ค์ธ ์๋ฒ์ ๋ค์ํ ์ ๋ณด๋ฅผ ์ค์๊ฐ์ผ๋ก ํ์ธ ํ ์ ์์ต๋๋ค. ์ง๊ธ์ Oracle cloud์ Ubuntu instance์์ ์๋ฒ๋ฅผ ๊ตฌ๋์ค์ด๋ผ์ OS information์ LINUX๋ก ๋์ค๋ ๊ฒ์ด ํ์ธ๋ฉ๋๋ค.
3.User
์ฒ์ ๋ก๊ทธ์ธ ํ์๋ ํ์ด์ง ์ ๋๋ค. ์ข์ธก์๋ ๋ด๊ฐ ์ํด์๋ ํ๋ก์ ํธ๋ค์ด, ์ค์๋ถ์๋ ๋์๊ฒ ํ ๋น๋ ์ด์๋ค์ด ๋ณด์ฌ์ง๋๋ค.
๋๊ตฐ๊ฐ๊ฐ ์ ์์ ํ๊ฑฐ๋ ๋ด๊ฐ ์ด ์ด์์ ๋๊ธ์ ๋ฌ๋ฉด push ์๋์ ๋ฐ์ต๋๋ค. ์ฐ์ธก ์๋จ์ ํ์๋ฉ๋๋ค.
์ข ๋ชจ์ ์์ด์ฝ์ ํด๋ฆญํด์ ์๋๋ค์ ํ์ธ ํ ์ ์์ต๋๋ค.
ํ๋กํ ์ ๋ณด๋ฅผ ์์ ํ ์ ์์ผ๋ฉฐ
์ด๋ฆ๊ณผ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ณ๊ฒฝ ํ ์ ์์ต๋๋ค.
์ ์ ์ด๋ ฅ์ elastic search์ ๋น๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ก ๊ด๋ฆฌ๋ฉ๋๋ค.
4.Chatting
๊ฐ๋จํ ์ฑํ ๊ธฐ๋ฅ๋ ์ค๋น๋์ด ์์ต๋๋ค.
5.Project
1) Code
ํ๋ก์ ํธ์ ๋ค์ด๊ฐ๋ฉด ์ฒซ ํ์ด์ง์ธ Code ํ์ด์ง ์ ๋๋ค. Github์ ์๋ repository์ ์ฐ๋ํด์ Code์ readme ํ์ผ์ ๊ฐ์ ธ์ต๋๋ค. ์ฐ์ธก์๋ ํ๋ก์ ํธ์ ๋ํ ์ค๋ช ๊ณผ ๋ฉค๋ฒ๋ค ๋ชฉ๋ก์ด ๋์ต๋๋ค.
2) Keyboard Shortcuts
์์ฐ์ฑ ํฅ์์ ์ํ ๋จ์ถํค ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. Ctrl + '/' ํค๋ก ๋จ์ถํค ๋ชฉ๋ก์ ํ์ธ ํ ์ ์์ต๋๋ค.
3) Search
ํตํฉ ๊ฒ์ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. Key๋ฅผ ์ ๋ ฅํ ๋ ๋ง๋ค ๋ฐ๋ก๋ฐ๋ก ๊ฒ์ ํด ์ค๋๋ค. Logstash๋ก Oracle ์๋ฒ๋ฅผ Elastic Search์ ์ธ๋ฑ์ฑ ํด์ ๊ตฌํ ํ์์ต๋๋ค.
4) Multi languages
๋ค๊ตญ์ด ๋ฉ๋ด๋ฅผ ์ง์ํฉ๋๋ค. ๋ฉ๋ด๋ ํ๋์ฝ๋ฉ ๋์ด ์์ง ์๊ณ Database ์์ ๋ฐ์์ค๊ธฐ ๋๋ฌธ์ ๊ฐ๋จํ๊ฒ ๋ฉ๋ด๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ ์ธ์ด๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค.
5) Milestone
๋ง์ผ์คํค ๋ชฉ๋ก์ ํ์ธ ํ ์ ์์ต๋๋ค. ๊ฐ๊ฐ ๋ง์ผ์คํค์ ์งํ๋๋ฅผ ํ๋์ ํ์ธ ํ ์ ์์ต๋๋ค.
๊ฐ๋ณ ๋ง์ผ์คํค์ ์กฐํ ํ๋ฉด ํด๋น ๋ง์ผ์คํค์ ์ํ ์ด์๋ค์ ์กฐํ ํ ์ ์์ต๋๋ค.
6) Issue
์ด์ ๋ชฉ๋ก ํ์ด์ง์์๋ ๊ฐ ํํฐ๋ณ๋ก ์ด์๋ค์ ํํฐ๋ง ํด์ ์กฐํ ํ ์ ์์ต๋๋ค.
๋ด๋น์ ๋ชฉ๋ก์ ๋ง์ฐ์ค๋ฅผ ์ฌ๋ฆฌ๋ฉด ํผ์ณ์ ๋ณด์ฌ์ค๋๋ค. css๋ก ๊ตฌํ ํ์ต๋๋ค.
์๋ก์ด ์ด์๋ฅผ ์์ฑ ํ ์ ์์ต๋๋ค.
๊ฐ๋จํ๊ฒ ์์ ๋ ํ ์ ์๊ณ ๋๊ธ๋ ์์ฑ ํฉ๋๋ค.
์ํฉ์๋ฐ๋ผ ๋ก/์ผ๋ก ์/๋ฅผ๊ณผ ๊ฐ์ ์กฐ์ฌ๋ฅผ ๊ตฌ๋ถํฉ๋๋ค. ์๋์ ์ฝ๋๋ก ๊ตฌํํ์ต๋๋ค.
// ๋ฐ์นจ์ด ์๋ ๋ฌธ์์ธ์ง ํ
์คํธ ํด์ฃผ๋ ํจ์ ์
๋๋ค.
const isSingleCharacter = function(text) {
var strGa = 44032; // ๊ฐ
var strHih = 55203; // ํฃ
var lastStrCode = text.charCodeAt(text.length-1);
if(lastStrCode < strGa || lastStrCode > strHih) {
return false; //ํ๊ธ์ด ์๋ ๊ฒฝ์ฐ false ๋ฐํ
}
return (( lastStrCode - strGa ) % 28 == 0)
}
// '๋ก' ๊ฐ ๋ถ์ด์ผ ํ๋์ง '์ผ๋ก'๊ฐ ๋ถ์ด์ผ ํ๋์ง ์ฒดํฌํด์ฃผ๋ ํจ์
const roChecker = function(text){
return text + (isSingleCharacter(text)? '๋ก' : '์ผ๋ก');
}
// '๋ฅผ' ์ด ๋ถ์ด์ผ ํ๋์ง '์'์ด ๋ถ์ด์ผ ํ๋์ง๋ฅผ ์ฒดํฌํด์ฃผ๋ ํจ์
const rulChecker = function(text){
return text + (isSingleCharacter(text)? '๋ฅผ' : '์');
}
ํ๊ธ ์ข ์ฑ์ด ์ด 28๊ฐ ์ธ ๊ฒ์ ํ์ฉํด ์ฝ๋๋ฅผ ์์ฑ ํ์ต๋๋ค.
7) Gantt
8) Calendar
9) Kanban
์นธ๋ฐ ๊ธฐ๋ฅ๋ ๊ตฌํํ์ต๋๋ค. ๊ฐ๊ฐ์ ์นธ๋ฐ Column๊ณผ Card๋ค์ Singly linked list๋ก ์ฐ๊ฒฐ ๋์ด ์์ต๋๋ค. ํด๋น ๋น์ฆ๋์ค ๋ก์ง์ ์๋์ ๊ฐ์ต๋๋ค.
@Override
@Transactional
public ServiceResult moveCard(Integer droppedCardNo, Integer newColumnNo, Integer nextCardNo) {
// ๋๋๋ ์นด๋ ์ ๋ณด ๋ฐ์์ค๊ธฐ
KanbanCardVO droppedCard = kanbanDao.selectCard(droppedCardNo);
KanbanCardVO previousNextCard = null;
KanbanCardVO currentNextCard = null;
// ๋๋๋ ์นด๋์ previousNextCard ์ ๋ณด
if (droppedCard.getKb_card_next_no() != null) {
previousNextCard = kanbanDao.selectCard(droppedCard.getKb_card_next_no());
}
// ๋๋๋ ์นด๋์ ํ์ฌ ๋ค์ ์นด๋ ์ ๋ณด
if (nextCardNo != null) {
currentNextCard = kanbanDao.selectCard(nextCardNo);
}
if (previousNextCard != null) {
// previousNextCard๊ฐ droppedCard์ priv_no๋ฅผ priv_no ๋ก ๊ฐ์ง๋ค.
previousNextCard.setKb_card_priv_no(droppedCard.getKb_card_priv_no());
}
// droppedCard์ ์ด์ ์นด๋ ์ ๋ณด๋ฅผ ํ์ฌ ๋ค์ ์นด๋์ ์ด์ ์นด๋ ๋๋ฒ์์ ๊ฐ์ ธ์ ์์ ํ๋ค.
if (currentNextCard == null) {
// ์๋ก์ด ์๋ฆฌ์ ๋ค์ ์นด๋๊ฐ ์๋ค๋ฉด, ์ด์ฌ ์จ ์ปฌ๋ผ์ ๋ง์ง๋ง ์นด๋๋ฅผ priv_no ๋ก ๊ฐ๋๋ค.
Integer lastCardNo = kanbanDao.getLastCardNo(newColumnNo);
droppedCard.setKb_card_priv_no(lastCardNo);
} else {
// ์๋ก์ด ์๋ฆฌ์ ๋ค์ ์นด๋๊ฐ ์์ผ๋ฉด ํด๋น ์นด๋์ ์ด์ ์นด๋ ๋ฒํธ๋ฅผ ๋บ์ด์จ๋ค.
droppedCard.setKb_card_priv_no(currentNextCard.getKb_card_priv_no());
// ํ์ฌ ๋ค์ ์นด๋์ ์ด์ ์นด๋ ์ ๋ณด๋ฅผ dropped card no ๋ก ์์ ํ๋ค.
currentNextCard.setKb_card_priv_no(droppedCardNo);
}
// droppedCard์ column ๊ฐ์ ํ์ฌ ๋ค์ ์นด๋์ column ๊ฐ์ผ๋ก ์์ ํ๋ค.
droppedCard.setKb_col_no(newColumnNo);
// ๋ณ๊ฒฝ์ด ์์๋ ์ธ ๊ฐ์ ์นธ๋ฐ ์นด๋ ์ ๋ณด๋ฅผ ๋ชจ๋ ์
๋ฐ์ดํธ ํ๋ค.
// ์ฟผ๋ฆฌ๋ฅผ ์ธ๋ฒ ์์ง๋ง, ํ๋์ ํธ๋์ญ์
์ผ๋ก ๊ด๋ฆฌ
int validChecker = 1;
if (previousNextCard != null) {
validChecker *= kanbanDao.updateCard(previousNextCard);
}
validChecker *= kanbanDao.updateCard(droppedCard);
if (currentNextCard != null) {
validChecker *= kanbanDao.updateCard(currentNextCard);
}
if (validChecker == 1) {
return ServiceResult.OK;
} else {
return ServiceResult.FAIL;
}
}
์ ๊ฐ ์๋์ ์นธ๋ฐ์ ์ข์ํด์ ํ์์๋ ํฌ์คํธ์์ ํ์ฉํ ์นธ๋ฐ์ ์ ๊ทน์ ์ผ๋ก ํ์ฉ ํ์ต๋๋ค.
10)News
๋ด์ค ํ์ด์ง ์ ๋๋ค. ๋ฌดํ ์คํฌ๋กค๋ก ํ์ด์ง ์ฒ๋ฆฌ ํ์์ต๋๋ค.
11)Wiki
์ํค ํ์ด์ง์์๋ ๊ฐ ์ํค๋ณ ์์ ๋ด์ญ๋ํ ์กฐํ ํ ์ ์์ต๋๋ค.
12)Analytics
ํ๋ก์ ํธ์ ๊ฐ์ข ํต๊ณ๋ฅผ ํ์ธ ํ ์ ์๋ ํ์ด์ง ์ ๋๋ค.
13)Member
๋ฉค๋ฒ ๊ด๋ฆฌ ํ์ด์ง์์๋ ์์๋ ๋ฉค๋ฒ๋ค์ ์กฐํํ๊ณ , ๋ฉค๋ฒ์ ๊ถํ์ ๋ถ์ฌ ํ ์ ์์ผ๋ฉฐ ์ด๋ ํน์ ํํด๋ฅผ ์ํฌ ์ ์์ต๋๋ค.
14)Management
ํ๋ก์ ํธ๋ฅผ ๊ด๋ฆฌํ๋ ํ์ด์ง ์ ๋๋ค.
์๋ก์ด ๋ผ๋ฒจ์ ์์ฑํ ๋๋ ์ปค์คํฐ๋ง์ด์ง ํ ์ ์๋๋ก ํ์ต๋๋ค.
์ฌ์ฉ๋ชจ๋, ์ด์์ค์๋, ๊ทธ๋ฆฌ๊ณ ๊ถํ์ ๊ฒฝ์ฐ์๋ ์ปฌ๋ผ์ ์ฌ๋ฌ๊ฐ ๋ง๋ค ํ์ ์๋๋ก ์ด์ง์ ํํ๋ก ๊ฐ์ ์ ์ฅํ๋๋ก ๊ตฌํํ์ต๋๋ค.
์ด์์ ๋๋ค. ํด๋น ๊ธ์ readme ํ์ผ ์์ฑ์ ์ํด markdown ์ผ๋ก ์์ฑ ๋์์ต๋๋ค.
Github ์์ readme ํ์ผ์ ๊พธ์คํ ์ ๋ก๋ ํ ์์ ์ด์ง๋ง ํฐ์คํ ๋ฆฌ์์์ markdown ์ง์์ด ๋น์ฝํด์ ํด๋น ํฌ์คํ ์ ๋์ด์ ์ ๋ฐ์ดํธ ํ์ง ์์ต๋๋ค. ์๋ ๋งํฌ์์ ์ต์ ์ readme ํ์ผ์ ํ์ธํ ์ ์์ต๋๋ค.
https://github.com/ddit301/gaia
ddit301/gaia
final project gaia. Contribute to ddit301/gaia development by creating an account on GitHub.
github.com
'Development > Projects' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
์ต์ข ํ๋ก์ ํธ GAIA ์๊ฐ (0) | 2021.07.18 |
---|---|
Google Analytics ๋ฐ์ดํฐ java ํตํด ๋ฐ์์ค๊ธฐ (1) | 2021.06.22 |
Ajax ๋น๋๊ธฐ ์์ฒญ ๋ฐ์์ ๋ก๋ฉ ์ด๋ฏธ์ง (๋ก๋ฉ ๋ฐ) ๋ง๋ค๊ธฐ. (0) | 2021.06.13 |
Github REST API ์ฌ์ฉํ๊ธฐ (1) | 2021.06.11 |
GAIA ์๋ ์์คํ ์ ๋ง๋ค๊ธฐ ์ํด ๊ตฌ์ถํ ์ฌ๋ฌ๊ฐ์ง ๋ชจ๋ ์๊ฐ์ ๊ณผ์ (0) | 2021.06.09 |
Google Analytics ๊ตฌ๊ธ ์ ๋๋ฆฌํฑ์ค ํ์ฉํ๊ธฐ - ์น ์ดํ๋ฆฌ์ผ์ด์ ์ ์ ์ฉ (3) | 2021.05.23 |