画像アップロード「Uploadthing」を使って、Nuxt3×Djnagoアプリから画像を削除する

# 完成イメージ
#### 画像をアップロードして...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/e1b7c709-a680-4426-b70b-718f63b92c60.png)

#### こんな感じに画像がアップロードされる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/14097be1-c67b-4c50-ad77-3e82d66e0cda.png)

#### DELETEボタンを押すとメッセージが表示されて....
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/965ab66b-4ca8-4e8a-97b1-00b1443d8b36.png)

#### 画面から画像が削除される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/1b302d1a-2c63-4cf5-97ec-fdbef4250b36.png)

#### uploadthingのダッシュボードにからも削除される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/15b5118f-8bc2-49c6-8021-62a24236a906.png)

以上のサンプルを作っていきます。

# ディレクトリ構造
```
.
└── NuxtDjangoProject/
    ├── backend/
    │   ├── adminuser
    │   ├── api/
    │   │   ├── migrations/
    │   │   │   └── 0001_initial.py
    │   │   ├── serializer.py
    │   │   ├── urls.py
    │   │   └── views.py
    │   └── todoproject/
    │       ├── settings.py
    │       └── urls.py
    └── frontend/
        ├── components/
        │   ├── footer/
        │   │   └── footer.vue
        │   └── navbar/
        │       └── navbar.vue
        ├── pages/
        │   ├── adminUsers/
        │   │   ├── login/
        │   │   │   └── login.vue
        │   │   ├── registration/
        │   │   │   └── registration.vue
        │   │   ├── updateProfile/
        │   │   │   └── updateProfile.vue
        │   │   └── uploadImageFile/
        │   │       └── uploadImageFile.vue
        │   ├── login/
        │   │   └── login.vue
        │   ├── registration/
        │   │   └── registration.vue
        │   └── index.vue
        ├── .env
        ├── server/
        │   ├── api/
        │   │   └── uploadthing/
        │   │       └── delete.ts
        │   └── uploadthing.ts
        ├── app.vue
        ├── nuxtconfig.ts/
        │   └── package.json
        └── tsxconfig.json
```

# サーバ側の処理
サーバ側の処理は下記のとおりです。

```frontend/server/api/uploadthing/delete.ts
import { UTApi } from "uploadthing/server";
import { z } from 'zod'

const utapi = new UTApi({
    apiKey:process.env.UPLOADTHING_TOKEN
});

export default defineEventHandler(async (event)=>{
    try{
        const body = await readBody(event);
        const { key } = z.object({key:z.string()}).parse(body);

        if(!process.env.UPLOADTHING_TOKEN){
            console.log('Please set UPLOADTHING_TOKEN.');
            throw new Error('UPLOADTHING_SECRET is not set.');
        }

        const response = await utapi.deleteFiles([key]);
        console.log('Uploadthing delete response:',response);

        if(response.success){
            return {success:true};
        }else{
            console.log('Uploadthing delete failed:',response.error);
            return {success:false,error:response.error};
        }
    }catch(error:any){
        console.error('サーバーエラー:', error);
        return { success: false, error: error.message };
    }
});
```

# フロント側の処理
フロント側の処理は下記のとおりです。

```frontend/pages/adminUsers/uploadImageFile/uploadImageFile.vue
<script setup lang="ts">
import { useRouter } from 'vue-router';
import { ref } from 'vue'
import { useFetch } from 'nuxt/app'

const router = useRouter();
const alert = (msg: string) => {
  window.alert(msg);
  
};

/** Method for deleting image */
const fileRef = ref<File | null>(null);// Type is set at File or null
const imageKey = ref<string>('');

const setImageInfo = (data:any)=>{
  imageKey.value = file[0].key;
}

const clickImageDelete = async()=>{
  if(!imageKey.value){
    alert('Please select targe image.');
    return;
  }

  const { data,error } = await useFetch('/api/uploadthing/delete',{
    method:'POST',
    body:{
      key:imageKey.value,
    },
  });

  if(error.value){
    console.log('Fail delete image,',error.value);
    alert('Faile delete image.');
    return;
  }

  if(data.value?.success){
    alert('Success to delete target image.');
    fileRef.value = null;
    imageKey.value = '';
  }else{
    alert('Fail to delete.');
  }
}
</script>

<template>
  <div>
    <UploadButton
      :config="{
        endpoint: 'imageUploader',
        onClientUploadComplete: (file) => {
          if(file && file.length > 0){
            console.log('file:', file[0]);//Debug file object.
            console.log('file[0].key:', file[0].key); // Debug file[0].key.
            console.log('Convert String type for file[0].key is :', String(file[0].key)); // Convert type of string.
            fileRef = file[0].url;// Debug file[0].url.
            imageKey = String(file[0].key);
            console.log('imageKey.key:', imageKey); // 追加
            console.log('fileRef after upload:', fileRef); // 追加
          }                    
          alert('Upload complete');
        },
        onUploadError: (error) => {
          console.error(error, error.cause);
          alert('Upload failed');
        },
      }"
    />
    <UploadDropzone
      :config="{
        endpoint: 'imageUploader',
        onClientUploadComplete: (file) => {
          console.log('uploaded', file);
          console.log('uploaded', file[0].url);
          console.log('uploaded', file[0].key);
          fileRef = file[0].url;// Debug file[0].url.
          imageKey = String(file[0].key);
          alert('Upload complete');
        },
        onUploadError: (error) => {
          console.error(error, error.cause);
          alert('Upload failed');
        },
      }"
    />
    <!-- Image thumbnail Area -->
    <div v-if="imageKey" class="flex items-center justify-center">
      <v-avatar size="x-large">
        <v-img
        :src="fileRef"
      ></v-img>
      </v-avatar>
    </div>
    <!-- Image key Area -->
    <div>
      <input type="hidden" :value="imageKey"/>
    </div>
    <!-- Image thumbnail button Area -->
    <div v-if="imageKey" class="flex items-center justify-center mt-8">
      <v-btn
        type="button"
        color="success"
        @click="clickImageDelete"
      >
        Delete
      </v-btn>
    </div>
  </div>
</template>

```

以上です。