Development/Projects

μ΅œμ’… ν”„λ‘œμ νŠΈ GAIA μ†Œκ°œ

πŸ“ μž‘μ„± : 2021.07.18  ⏱ μˆ˜μ • : 
λ°˜μ‘ν˜•

http://www.gaia.best by team seed   πŸŒ±

λ°œν‘œμ˜μƒκ³Ό ν…ŒμŠ€νŠΈμ˜μƒμ„ ν™•μΈν•΄λ³΄μ„Έμš”

GAIAλŠ” 기쑴의 Project Management Systemλ“€μ˜ μ–΄λ €μš΄ μ‚¬μš©λ²•κ³Ό 높은 μ§„μž…μž₯벽을 ν•΄κ²°ν•˜κΈ° μœ„ν•΄ κΈ°νšλ˜μ—ˆμŠ΅λ‹ˆλ‹€.

μ•„λž˜μ˜ λͺ¨λ“ˆλ“€μ„ 톡해 ν”„λ‘œμ νŠΈ 관리와 κ°œλ°œμžκ°„μ˜ ν˜‘μ—…μ„ λ•μŠ΅λ‹ˆλ‹€.

  • 이슈 νŠΈλž˜ν‚Ή ( Milestone, Issue )
  • ν”„λ‘œμ νŠΈ 일정 관리 ( Calendar, Gantt )
  • 칸반 λ³΄λ“œ
  • μœ„ν‚€
  • λ‰΄μŠ€
  • μΈμŠ€ν„΄νŠΈ λ©”μ‹ μ €
  • ν”„λ‘œμ νŠΈ 톡계
  • 톡합 검색


πŸ“š Technology Stack



πŸ‘©‍πŸ‘©‍πŸ‘¦‍πŸ‘¦ Team members

drawing

@Shane-Park Shane(PL)
@JeonghoonWon Josh(DA)
@KrGil Eisen(TA)



πŸ† 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 역할을 λŒ€μ²΄ν•  수 μžˆλ„λ‘ κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.

path

예λ₯Ό λ“€μ–΄ μœ„μ˜ 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

main

GAIA의 메인 ν™”λ©΄ μž…λ‹ˆλ‹€.

login

우츑 상단 둜그인 λ²„νŠΌμ„ 눌러 둜그인 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

provider

νšŒμ›κ°€μž…μ—μ„œ μ—°ν•„ λ²„νŠΌμ„ 클릭 ν•˜λ©΄ μˆ¨κ²¨μ§„ μ„œλ²„ κ΄€λ¦¬μž λͺ¨λ“œλ‘œ μ§„μž… ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

provider

ν•΄λ‹Ή νŽ˜μ΄μ§€μ—μ„œλŠ” μš΄μ˜μ€‘μΈ μ„œλ²„μ˜ λ‹€μ–‘ν•œ 정보λ₯Ό μ‹€μ‹œκ°„μœΌλ‘œ 확인 ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ§€κΈˆμ€ Oracle cloud의 Ubuntu instanceμ—μ„œ μ„œλ²„λ₯Ό κ΅¬λ™μ€‘μ΄λΌμ„œ OS information에 LINUX둜 λ‚˜μ˜€λŠ” 것이 ν™•μΈλ©λ‹ˆλ‹€.



3.User

overview

처음 둜그인 ν–ˆμ„λ•Œ νŽ˜μ΄μ§€ μž…λ‹ˆλ‹€. μ’ŒμΈ‘μ—λŠ” λ‚΄κ°€ μ†ν•΄μžˆλŠ” ν”„λ‘œμ νŠΈλ“€μ΄, μ€‘μ•™λΆ€μ—λŠ” λ‚˜μ—κ²Œ ν• λ‹Ήλœ μ΄μŠˆλ“€μ΄ λ³΄μ—¬μ§‘λ‹ˆλ‹€.

push

λˆ„κ΅°κ°€κ°€ 접속을 ν•˜κ±°λ‚˜ λ‚΄κ°€ μ“΄ μ΄μŠˆμ— λŒ“κΈ€μ„ 달면 push μ•ŒλžŒμ„ λ°›μŠ΅λ‹ˆλ‹€. 우츑 상단에 ν‘œμ‹œλ©λ‹ˆλ‹€.

alarms

μ’… λͺ¨μ–‘ μ•„μ΄μ½˜μ„ ν΄λ¦­ν•΄μ„œ μ•ŒλžŒλ“€μ„ 확인 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

profile

ν”„λ‘œν•„ 정보λ₯Ό μˆ˜μ • ν•  수 있으며

setting

이름과 λΉ„λ°€λ²ˆν˜Έλ₯Ό λ³€κ²½ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

log

접속 이λ ₯은 elastic search의 λΉ„κ΄€κ³„ν˜• λ°μ΄ν„°λ² μ΄μŠ€λ‘œ κ΄€λ¦¬λ©λ‹ˆλ‹€.



4.Chatting

chat

κ°„λ‹¨ν•œ μ±„νŒ… κΈ°λŠ₯도 μ€€λΉ„λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.



5.Project

1) Code

code

ν”„λ‘œμ νŠΈμ— λ“€μ–΄κ°€λ©΄ 첫 νŽ˜μ΄μ§€μΈ Code νŽ˜μ΄μ§€ μž…λ‹ˆλ‹€. Github에 μžˆλŠ” repository와 μ—°λ™ν•΄μ„œ Code와 readme νŒŒμΌμ„ κ°€μ Έμ˜΅λ‹ˆλ‹€. μš°μΈ‘μ—λŠ” ν”„λ‘œμ νŠΈμ— λŒ€ν•œ μ„€λͺ…κ³Ό 멀버듀 λͺ©λ‘μ΄ λ‚˜μ˜΅λ‹ˆλ‹€.

2) Keyboard Shortcuts

shortcut

생산성 ν–₯상을 μœ„ν•œ 단좕킀 κΈ°λŠ₯을 μ œκ³΅ν•©λ‹ˆλ‹€. Ctrl + '/' ν‚€λ‘œ 단좕킀 λͺ©λ‘μ„ 확인 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

3) Search

search

톡합 검색 κΈ°λŠ₯을 μ œκ³΅ν•©λ‹ˆλ‹€. Keyλ₯Ό μž…λ ₯ν•  λ•Œ λ§ˆλ‹€ λ°”λ‘œλ°”λ‘œ 검색 ν•΄ μ€λ‹ˆλ‹€. Logstash둜 Oracle μ„œλ²„λ₯Ό Elastic Search에 인덱싱 ν•΄μ„œ κ΅¬ν˜„ ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

4) Multi languages

languages

λ‹€κ΅­μ–΄ 메뉴λ₯Ό μ§€μ›ν•©λ‹ˆλ‹€. λ©”λ‰΄λŠ” ν•˜λ“œμ½”λ”© λ˜μ–΄ μžˆμ§€ μ•Šκ³  Database μ—μ„œ λ°›μ•„μ˜€κΈ° λ•Œλ¬Έμ— κ°„λ‹¨ν•˜κ²Œ 메뉴λ₯Ό μΆ”κ°€ν•˜κ±°λ‚˜ μ–Έμ–΄λ₯Ό μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

5) Milestone

milestone1

λ§ˆμΌμŠ€ν†€ λͺ©λ‘μ„ 확인 ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 각각 λ§ˆμΌμŠ€ν†€μ˜ 진행도λ₯Ό ν•œλˆˆμ— 확인 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

milestone2

κ°œλ³„ λ§ˆμΌμŠ€ν†€μ„ 쑰회 ν•˜λ©΄ ν•΄λ‹Ή λ§ˆμΌμŠ€ν†€μ— μ†ν•œ μ΄μŠˆλ“€μ„ 쑰회 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

6) Issue

issue

이슈 λͺ©λ‘ νŽ˜μ΄μ§€μ—μ„œλŠ” 각 ν•„ν„°λ³„λ‘œ μ΄μŠˆλ“€μ„ 필터링 ν•΄μ„œ 쑰회 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

assignee

λ‹΄λ‹Ήμž λͺ©λ‘μ— 마우슀λ₯Ό 올리면 νŽΌμ³μ„œ λ³΄μ—¬μ€λ‹ˆλ‹€. css둜 κ΅¬ν˜„ ν–ˆμŠ΅λ‹ˆλ‹€.

newissue

μƒˆλ‘œμš΄ 이슈λ₯Ό μž‘μ„± ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

issueedit

κ°„λ‹¨ν•˜κ²Œ μˆ˜μ •λ„ ν•  수 있고 λŒ“κΈ€λ„ μž‘μ„± ν•©λ‹ˆλ‹€.

ro

상황에따라 둜/으둜 을/λ₯Όκ³Ό 같은 쑰사λ₯Ό κ΅¬λΆ„ν•©λ‹ˆλ‹€. μ•„λž˜μ˜ μ½”λ“œλ‘œ κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.

// 받침이 μžˆλŠ” λ¬ΈμžμΈμ§€ ν…ŒμŠ€νŠΈ ν•΄μ£ΌλŠ” ν•¨μˆ˜ μž…λ‹ˆλ‹€.
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

gantt

8) Calendar

calendar

9) Kanban

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;
        }
    }

μ œκ°€ μ›Œλ‚™μ— μΉΈλ°˜μ„ μ’‹μ•„ν•΄μ„œ νŒ€μ—μ„œλ„ ν¬μŠ€νŠΈμž‡μ„ ν™œμš©ν•œ μΉΈλ°˜μ„ 적극적으둜 ν™œμš© ν–ˆμŠ΅λ‹ˆλ‹€.

kanban2

10)News

news

λ‰΄μŠ€ νŽ˜μ΄μ§€ μž…λ‹ˆλ‹€. λ¬΄ν•œ 슀크둀둜 νŽ˜μ΄μ§• 처리 ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

11)Wiki

wiki

μœ„ν‚€ νŽ˜μ΄μ§€μ—μ„œλŠ” 각 μœ„ν‚€λ³„ μˆ˜μ • λ‚΄μ—­λ˜ν•œ 쑰회 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

12)Analytics

analytics

ν”„λ‘œμ νŠΈμ˜ 각쒅 톡계λ₯Ό 확인 ν•  수 μžˆλŠ” νŽ˜μ΄μ§€ μž…λ‹ˆλ‹€.

13)Member

member

멀버 관리 νŽ˜μ΄μ§€μ—μ„œλŠ” μ†Œμ†λœ 멀버듀을 μ‘°νšŒν•˜κ³ , λ©€λ²„μ˜ κΆŒν•œμ„ λΆ€μ—¬ ν•  수 있으며 μ΄ˆλŒ€ ν˜Ήμ€ νƒˆν‡΄λ₯Ό μ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.

14)Management

management

ν”„λ‘œμ νŠΈλ₯Ό κ΄€λ¦¬ν•˜λŠ” νŽ˜μ΄μ§€ μž…λ‹ˆλ‹€.

label

μƒˆλ‘œμš΄ 라벨을 생성할 λ•ŒλŠ” μ»€μŠ€ν„°λ§ˆμ΄μ§• ν•  수 μžˆλ„λ‘ ν–ˆμŠ΅λ‹ˆλ‹€.

binary

μ‚¬μš©λͺ¨λ“ˆ, μ΄μŠˆμ€‘μš”λ„, 그리고 κΆŒν•œμ˜ κ²½μš°μ—λŠ” μ»¬λŸΌμ„ μ—¬λŸ¬κ°œ λ§Œλ“€ ν•„μš” 없도둝 μ΄μ§„μˆ˜ ν˜•νƒœλ‘œ 값을 μ €μž₯ν•˜λ„λ‘ κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.

 

μ΄μƒμž…λ‹ˆλ‹€. ν•΄λ‹Ή 글은 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

 

λ°˜μ‘ν˜•