icon

go new API Template Generator

oohara.yuukia
loading
oohara.yuukia
更新日:

go new API Template Generator

新しい形式のAPIのテンプレをざっくり作るやつ

<html>
  <head>
    <title>%title%</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/phi-jp/meltline@v0.1.15/meltline.min.css">
    %style("main.css")%
    %script("main.js")%
  </head>
  <body>
    <main class="p16">
      <h1 class="fs18 mb16">APIのテンプレを作るやつ</h1>
      <form id=form submit="event.preventDefault();">
        <div class="row gap-16">
          <!-- API 名 -->
          <label class="f fm mb16">
            <span class="mr8">API名:</span>
            <input id="name_of_api" placeholder="User"></input>
          </label>
          <label class="f fm mb16">
            <span class="mr8">メソッド名:</span>
            <input id="name_of_method" placeholder="Get"></input>
          </label>
          <label class="f fm mb16">
            <span class="mr8">出力型パッケージ名:</span>
            <input id="name_of_output_struct_package" placeholder="model" value="model"></input>
          </label>
        </div>
        <div>
          <label class="block mb16">
            <div class="mb8">URL(改行区切り)</div>
            <textarea id="url_params" placeholder="user_id"></textarea>
          </label>
        </div>

        <div class="row gap-16 mb16">
          <!-- ログインについて -->
          <label class="f fm mb4">
            <input name="login" class="mr8" type="radio" value="auth" /> ログイン必須
          </label>
          <label class="f fm mb4">
            <input name="login" class="mr8" type="radio" value="opt" /> ログイン任意
          </label>
          <label class="f fm mb4">
            <input name="login" class="mr8" type="radio" value="no"/> ログイン不要
          </label>
        </div>
        <div class="row gap-16 mb16">
          <label class="f fm mb4">
            <input id=permission class="mr8" type="checkbox" /> Permission
          </label>

          <label class="f fm mb4">
            <input id=after_input class="mr8" type="checkbox" /> AfterInput
          </label>
          <label class="f fm mb4">
            <input id=is_list_check class="mr8" type="checkbox" /> List
          </label>
        </div>
      </form>

      <div id="outputs" class="row gap-16 mb16">
        <div class="w300 flex-fixed">
          <div class="f fbw fm mb4">
            <span>handler</span>
            <button type="button" data-copy=output_handler>コピー</button>
          </div>
          <textarea id="output_handler" readonly class="block w-full h100" ></textarea>
        </div>

        <div class="w300 flex-fixed">
          <div class="f fbw fm mb4">
            <span>service_interface</span>
            <button type="button" data-copy=output_service_interface>コピー</button>
          </div>
          <textarea id="output_service_interface" readonly class="block w-full h100" ></textarea>
        </div>

        <div class="w300 flex-fixed">
          <div class="f fbw fm mb4">
            <span>service_impl</span>
            <button type="button" data-copy=output_service_impl>コピー</button>
          </div>
          <textarea id="output_service_impl" readonly class="block w-full h100" ></textarea>
        </div>


        <div class="w300 flex-fixed">
          <div class="f fbw fm mb4">
            <span>service_input</span>
            <button type="button" data-copy=output_service_input>コピー</button>
          </div>
          <textarea id="output_service_input"  class="block w-full h100" ></textarea>
        </div>

        
        <div class="w300 flex-fixed">
          <div class="f fbw fm mb4">
            <span>service_output</span>
            <button type="button" data-copy=output_service_output>コピー</button>
          </div>
          <textarea id="output_service_output"  class="block w-full h100" ></textarea>
        </div>

        
      </div>
      


    </main>
  </body>
</html>
body {
  margin: 0px;
  background-color: #eee;
}
.w300 {
  width: 300px;
}
input, textarea {
  border: 1px black solid;
  background-color: white;
  border-radius: 4px;
  padding: 2px 6px;
}

button {
  background-color: ghostwhite;
  border: 1px solid;
  border-radius: 4px;
  padding: 2px 4px; 
  font-size: 12px;
}

button:hover {
  background-color: #ddd;
}

.toast {
  position: fixed;
  z-index: 99999;
  top: 16px;
  left: 0;
  right: 0;
  margin: 0 auto;
  background-color: rgba(40, 40, 40, 0.6);
  backdrop-filter: blur(4px);
  color: white;
  border-radius: 4px;
  padding: 4px 8px;
  min-width: 100px;
  max-width: 300px;
  word-break: break-all;
  line-break: anywhere;
  animation: toast 0.2s ease;
  pointer-events: none;
}

@keyframes toast {
  from {
    opacity: 0.5;
    transform: translateY(-30px);
  }
  to {
    opacity: 1;
    transform: translateY(0px);
  }
}
window.addEventListener('load', () => {
  function execCopy(string){

    // 空div 生成
    var tmp = document.createElement("div");
    // 選択用のタグ生成
    var pre = document.createElement('pre');

    // 親要素のCSSで user-select: none だとコピーできないので書き換える
    pre.style.webkitUserSelect = 'auto';
    pre.style.userSelect = 'auto';

    tmp.appendChild(pre).textContent = string;

    // 要素を画面外へ
    var s = tmp.style;
    s.position = 'fixed';
    s.right = '200%';

    // body に追加
    document.body.appendChild(tmp);
    // 要素を選択
    document.getSelection().selectAllChildren(tmp);

    // クリップボードにコピー
    var result = document.execCommand("copy");

    // 要素削除
    document.body.removeChild(tmp);

    return result;
  }

  const toast = (message = '', timeout = 3000) => {
    const t = document.createElement('div');
    t.className = 'toast';
    t.textContent = message;
    document.body.appendChild(t)
    setTimeout(() => {
      document.body.removeChild(t)
    }, timeout);
  };

  const snake_to_upper_camel = v => v.split('_').map(v => v.slice(0, 1).toUpperCase() + v.slice(1)).join('');
  const to_lower_camel = v=>v.slice(0, 1).toLowerCase()+v.slice(1);
  const to_upper_camel = v=>v.slice(0, 1).toUpperCase()+v.slice(1);
    
  const to_plural_form = (word) => {
    if (word.endsWith('s')) {
      return word + 'es';
    }
    else if (word.endsWith('y')) {
      return word.slice(0, -1) + 'ies';
    }
    else {
      return word + 's';
    }
  };


  const handler = (api, method) => 
func (h *${api}) ${method}() rapi.RouterElement {
    return handler.NewHandlerMethod(h.s${api}.${method})
}
;

const handler_after_input = (api, method) => 
func (h *${api}) ${method}() rapi.RouterElement {
    hm := handler.NewHandlerMethod(h.s${api}.${method})
    hm.AfterInput(func(ctx context.Context, r *http.Request, param *service.${api}${method}Input) error {
        if param.Limit == 0 {
            param.Limit = 16
        }
        return nil
    })
    return hm
}
;

const service_method_interface = (api, method) => 
    ${method}(
        ctx context.Context,
        param *${api}${method}Input,
    ) (*${api}${method}Output, error)
;

const service_method_impl = (api, method) => func (s *${to_lower_camel(api)}) ${service_method_interface(api, method).trim()} {;

const param_me_user_id = (auth) => {
  switch(auth) {
    case 'auth':
      return 	MeUserID string \assign:"user_id" validate:"required"\\n;
    case 'opt':
      return 	MeUserID string \assign:"user_id"\\n;
  }

  return '';
};

const param_permission = (use_perm) => use_perm ? 	Permission *model.Permission \assign:"permission" validate:"required"\
 : '';

const param_urls = (urls) => urls.length ? ${urls.map(v => {
  return 	${snake_to_upper_camel(v).replace(/Id$/, 'ID')} string \url:"${v}" validate:"required"\;
}).join('\n')}\n: '';

const service_list_input = (is_list) => is_list ? 	Limit    int             \form:"limit" validate:"max=100"\
    Cursor   string          \form:"cursor"\\n : '';

const service_input = (api, method, auth, use_perm, is_list, urls) => 
type ${api}${method}Input struct {
${param_me_user_id(auth)}${param_permission(use_perm)}${param_urls(urls)}${service_list_input(is_list)}}
;
const service_output_struct = (api, is_list, output_struct_package = 'model') => is_list ? 
    ${to_plural_form(api)}      []*${output_struct_package}.${api} \json:"${to_plural_form(to_lower_camel(api))}"\
    NextCursor string        \json:"next_cursor"\
:
    ${api}  *${output_struct_package}.${api} \json:"${to_lower_camel(api)}"\
;

const service_ouptut = (api, method, is_list, output_struct_package = 'model') => 
type ${api}${method}Output struct {
${service_output_struct(api, is_list, output_struct_package)}
}


  /**
  * 
  * @param {(...args: any) => any} func 
  * @param {number} ms 
  * @returns {((...args: any) => any) & { cancel: () => void }}
  */
  const debounce = (func, ms) => {
    /** @type {NodeJS.Timeout} */
    let id;
    return Object.assign((/** @type {any[]} */ ...args) => {
      clearTimeout(id);
      id = setTimeout(() => func(...args), ms);
    }, {
      cancel: () => clearTimeout(id),
    });
  };

  const d_update = debounce(update, 256);

  form.oninput = () => {
    d_update();
  };

  outputs.onclick = (e) => {
    const target = e.target;
    const c_target = target.dataset.copy;
    if (c_target) {
      const c_target_elm = document.getElementById(c_target);
      if (c_target_elm) {
        if (execCopy(c_target_elm.value)) {
          toast('コピーしました');
        }
        else {
          toast('コピーに失敗しました。選択してコピーしてください');
        }
      }
    }
  };

  function update() {
    let [api, method, output_struct_package] = [name_of_api, name_of_method, name_of_output_struct_package].map(e=>e.value.trim());
    api = to_upper_camel(api);
    method = to_upper_camel(method);
    const urls = url_params.value.split('\n').map(v => v.trim()).filter(Boolean);
    const [use_perm, use_after_input, is_list] = [permission, after_input, is_list_check].map(e=>e.checked);
    const auth = form.login.value;
    let handler_func;
    if (use_after_input) {
      handler_func = handler_after_input;
    }
    else {
      handler_func = handler;
    }
    [
      [output_handler, handler_func(api, method)],
      [output_service_interface, service_method_interface(api, method)],
      [output_service_impl, service_method_impl(api, method)],
      [output_service_input, service_input(api, method, auth, use_perm, is_list, urls)],
      [output_service_output, service_ouptut(api, method, is_list, output_struct_package)]
    ].forEach(([elm, value]) => {
      elm.value = value;
    });
  }
});
oohara.yuukia
a