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

 

๋ฐ˜์‘ํ˜•