mitsu30 / Read_me_backend

1 stars 0 forks source link

ユーザー認証(RUNTEQ)ユーザー機能実装 #19

Open mitsu30 opened 1 year ago

mitsu30 commented 1 year ago
import { useRouter } from 'next/router';
import { useSnackbar } from 'notistack';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import axios from "axios";
import { useState } from "react";
import Grid from '@mui/material/Grid';
import TextField from '@mui/material/TextField';
import Avatar from '@mui/material/Avatar';
import nookies from "nookies";

export default function AdditionalInfoPage({ initialData }) {
  const router = useRouter();
  const { enqueueSnackbar } = useSnackbar();
  const { id } = router.query;

  const [username, setUsername] = useState(initialData.name);
  const [avatar, setAvatar] = useState(null);
  const [preview, setPreview] = useState(null);

  const handleUpdateProfile = async () => {
    try {
      const formData = new FormData();
      formData.append('user[name]', username); 
      formData.append('user[avatar]', avatar);

      const cookies = nookies.get(null);
      const config = {
        headers: { 
          'Content-Type': 'multipart/form-data',
          authorization: `Bearer ${cookies.token}` 
        },
      };

      const response = await axios.put(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/users/${id}`, formData, config);

      if (response.status === 200) {
        enqueueSnackbar('プロフィールを更新しました', { variant: 'success' });
        router.push("/");
      } else {
        enqueueSnackbar('プロフィールの更新に失敗しました', { variant: 'error' });
      }
    } catch (error) {
      enqueueSnackbar('エラーが発生しました', { variant: 'error' });
      console.error(error);
    }
  };

  const handleAvatarChange = (e) => {
    setAvatar(e.target.files[0]);
    setPreview(URL.createObjectURL(e.target.files[0]));
  }

  return (
    <Grid container component="main" justifyContent="center">
      <Grid item xs={12} md={6}>
        <Box
          sx={{
            my: 2,
            mx: 4,
            flexDirection: 'column',
            alignItems: 'center',
          }}
        >
          <Box sx={{ display: 'flex', justifyContent: 'center', my: 5 }}>
            <Typography component="h1" variant="h3">
              ユーザー登録
            </Typography>
          </Box>
          <Box sx={{mt: 2 }}>
            <Typography component="h1" variant="h5">
            ニックネームをおしえてね
            </Typography>
          </Box>
          <Box>
            <Typography component="h1" variant="h5">
            (RUNTEQで使っている名前)
            </Typography>
          </Box>
          <Box component="form">
            <TextField
              color="secondary"
              margin="normal"
              fullWidth
              id="username"
              label="ニックネーム"
              name="username"
              autoFocus
              value={username}
              onChange={(e) => setUsername(e.target.value)}
            />
          </Box>
          <Box sx={{mt: 2 }}>
            <Typography component="h1" variant="h5">
              アイコン用の画像をえらんでね
            </Typography>
          </Box>
          <Box>
            <TextField
              color="secondary"
              margin="normal"
              fullWidth
              type="file"
              id="avatar"
              name="avatar"
              accept="image/png, image/jpeg"
              onChange={handleAvatarChange}
              />
          </Box>
          {preview && (
            <Box sx={{ display: 'flex', justifyContent: 'center', my: 2 }}>
              <Avatar alt="アバター画像プレビュー" src={preview} />
            </Box>
          )}
          <Box sx={{ display: 'flex', justifyContent: 'center', my: 2 }}>
            <Button 
              type="submit"
              variant="contained"
              sx={{ 
                mt: 3, 
                mb: 2, 
                width: '30%', 
                backgroundColor: '#FF6699',
                '&:hover': {
                  backgroundColor: '#E60073',
                },
                color: '#white',
                fontWeight: 'bold'  
              }}
              onClick={handleUpdateProfile}
            >
              登録する
            </Button>
          </Box>
        </Box> 
      </Grid>
    </Grid>
  );
}

export async function getServerSideProps(context) {
  const { id } = context.query;
  const cookies = nookies.get(context);
  const config = {
    headers: { authorization: `Bearer ${cookies.token}` },
  };

  const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/users/${id}`);
  const data = await response.json();

  return {
    props: {
      initialData: data.data
    },
  };
}

[バックエンド側] 保存に使用しているのはupdateメソッドです。

class Api::V1::UsersController < ApplicationController
  def update
    user = current_user
    byebug
    if user.update(user_params)
      render json: { status: 'SUCCESS', message: 'Updated the user', data: user }
    else
      render json: { status: 'ERROR', message: 'Not updated', data: user.errors }
    end
  end

  def show
    user = User.find(params[:id])
    if user
      user_data = user.attributes
      user_data[:avatar_url] = rails_blob_url(user.avatar) if user.avatar.attached?
      render json: { status: 'SUCCESS', message: 'Loaded the user', data: user_data }
    else
      render json: { status: 'ERROR', message: 'User not found' }
    end
  end

  private

  def user_params
    params.require(:user).permit(:name, :uid, :role, :is_student, :avatar)
  end
end

ログ

Started PUT "/api/v1/users/f37fa3e9-e2a2-45cc-972f-f0beb301c678" for ::1 at 2023-06-13 10:10:26 +0900
Processing by Api::V1::UsersController#update as HTML
  Parameters: {"user"=>{"name"=>"三つ", "avatar"=>#<ActionDispatch::Http::UploadedFile:0x00000001091ce170 @tempfile=#<Tempfile:/var/folders/tt/swv2glzx5g1074cx7w7sglj40000gn/T/RackMultipart20230613-27957-vbrlj9.png>, @original_filename="西野さん.png", @content_type="image/png", @headers="Content-Disposition: form-data; name=\"user[avatar]\"; filename=\"\xE8\xA5\xBF\xE9\x87\x8E\xE3\x81\x95\xE3\x82\x93.png\"\r\nContent-Type: image/png\r\n">}, "id"=>"f37fa3e9-e2a2-45cc-972f-f0beb301c678"}
Authentication result: {:uid=>"lmERMcJFqgNyJifcfWDxiJEeHDM2"}
  User Load (0.7ms)  SELECT "users".* FROM "users" WHERE "users"."uid" = $1 LIMIT $2  [["uid", "lmERMcJFqgNyJifcfWDxiJEeHDM2"], ["LIMIT", 1]]
  ↳ app/controllers/concerns/application_controller.rb:17:in `block in authenticate_token'
/Users/nishinomitsuhiro/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/byebug-11.1.3/lib/byebug/attacher.rb:14: warning: undefining the allocator of T_DATA class Byebug::ThreadsTable
/Users/nishinomitsuhiro/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/byebug-11.1.3/lib/byebug/attacher.rb:14: warning: undefining the allocator of T_DATA class Byebug::Context

[1, 10] in /Users/nishinomitsuhiro/Desktop/Read_me backend/app/controllers/api/v1/users_controller.rb
    1: class Api::V1::UsersController < ApplicationController
    2:   def update
    3:     user = current_user
    4:     byebug
=>  5:     if user.update(user_params)
    6:       render json: { status: 'SUCCESS', message: 'Updated the user', data: user }
    7:     else
    8:       render json: { status: 'ERROR', message: 'Not updated', data: user.errors }
    9:     end
   10:   end
(byebug) user
#<User id: "f37fa3e9-e2a2-45cc-972f-f0beb301c678", name: "mitsu30", uid: "lmERMcJFqgNyJifcfWDxiJEeHDM2", avatar: nil, role: "general", is_student: true, created_at: "2023-06-13 01:10:00.563011000 +0000", updated_at: "2023-06-13 01:10:00.563011000 +0000">
(byebug) continue
  TRANSACTION (0.2ms)  BEGIN
  ↳ app/controllers/api/v1/users_controller.rb:5:in `update'
  User Exists? (0.7ms)  SELECT 1 AS one FROM "users" WHERE "users"."uid" = $1 AND "users"."id" != $2 LIMIT $3  [["uid", "lmERMcJFqgNyJifcfWDxiJEeHDM2"], ["id", "f37fa3e9-e2a2-45cc-972f-f0beb301c678"], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/users_controller.rb:5:in `update'
  User Update (0.6ms)  UPDATE "users" SET "name" = $1, "updated_at" = $2 WHERE "users"."id" = $3  [["name", "三つ"], ["updated_at", "2023-06-13 01:10:34.516560"], ["id", "f37fa3e9-e2a2-45cc-972f-f0beb301c678"]]
  ↳ app/controllers/api/v1/users_controller.rb:5:in `update'
  ActiveStorage::Blob Load (1.2ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" INNER JOIN "active_storage_attachments" ON "active_storage_blobs"."id" = "active_storage_attachments"."blob_id" WHERE "active_storage_attachments"."record_id" = $1 AND "active_storage_attachments"."record_type" = $2 AND "active_storage_attachments"."name" = $3 LIMIT $4  [["record_id", "f37fa3e9-e2a2-45cc-972f-f0beb301c678"], ["record_type", "User"], ["name", "avatar"], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/users_controller.rb:5:in `update'
  ActiveStorage::Attachment Load (0.3ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = $1 AND "active_storage_attachments"."record_type" = $2 AND "active_storage_attachments"."name" = $3 LIMIT $4  [["record_id", "f37fa3e9-e2a2-45cc-972f-f0beb301c678"], ["record_type", "User"], ["name", "avatar"], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/users_controller.rb:5:in `update'
  ActiveStorage::Blob Create (0.5ms)  INSERT INTO "active_storage_blobs" ("key", "filename", "content_type", "metadata", "service_name", "byte_size", "checksum", "created_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING "id"  [["key", "2k4h6hh147s6d07njb7lu7lkp6bu"], ["filename", "西野さん.png"], ["content_type", "image/png"], ["metadata", "{\"identified\":true}"], ["service_name", "amazon"], ["byte_size", 35273], ["checksum", "vuZfrwebuY3STZ7NTLBMZA=="], ["created_at", "2023-06-13 01:10:34.533433"]]
  ↳ app/controllers/api/v1/users_controller.rb:5:in `update'
  ActiveStorage::Attachment Create (0.5ms)  INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["name", "avatar"], ["record_type", "User"], ["record_id", "f37fa3e9-e2a2-45cc-972f-f0beb301c678"], ["blob_id", "4c13a8df-ef6c-4019-85d7-63507c7c0460"], ["created_at", "2023-06-13 01:10:34.535057"]]
  ↳ app/controllers/api/v1/users_controller.rb:5:in `update'
  User Update (0.1ms)  UPDATE "users" SET "updated_at" = $1 WHERE "users"."id" = $2  [["updated_at", "2023-06-13 01:10:34.536274"], ["id", "f37fa3e9-e2a2-45cc-972f-f0beb301c678"]]
  ↳ app/controllers/api/v1/users_controller.rb:5:in `update'
  TRANSACTION (0.3ms)  COMMIT
  ↳ app/controllers/api/v1/users_controller.rb:5:in `update'
  S3 Storage (1310.3ms) Uploaded file to key: 2k4h6hh147s6d07njb7lu7lkp6bu (checksum: vuZfrwebuY3STZ7NTLBMZA==)
[ActiveJob] Enqueued ActiveStorage::AnalyzeJob (Job ID: 10d753a4-920a-431c-a01d-3efbb14adb23) to Async(default) with arguments: #<GlobalID:0x00000001090a5d48 @uri=#<URI::GID gid://read-me/ActiveStorage::Blob/4c13a8df-ef6c-4019-85d7-63507c7c0460>>
Completed 500 Internal Server Error in 9702ms (ActiveRecord: 10.8ms | Allocations: 143252)

SystemStackError (stack level too deep):

app/controllers/api/v1/users_controller.rb:6:in `update'
  ActiveStorage::Blob Load (104.9ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = $1 LIMIT $2  [["id", "4c13a8df-ef6c-4019-85d7-63507c7c0460"], ["LIMIT", 1]]
[ActiveJob] [ActiveStorage::AnalyzeJob] [10d753a4-920a-431c-a01d-3efbb14adb23] Performing ActiveStorage::AnalyzeJob (Job ID: 10d753a4-920a-431c-a01d-3efbb14adb23) from Async(default) enqueued at 2023-06-13T01:10:35Z with arguments: #<GlobalID:0x0000000107ab80f8 @uri=#<URI::GID gid://read-me/ActiveStorage::Blob/4c13a8df-ef6c-4019-85d7-63507c7c0460>>
[ActiveJob] [ActiveStorage::AnalyzeJob] [10d753a4-920a-431c-a01d-3efbb14adb23]   S3 Storage (834.0ms) Downloaded file from key: 2k4h6hh147s6d07njb7lu7lkp6bu
[ActiveJob] [ActiveStorage::AnalyzeJob] [10d753a4-920a-431c-a01d-3efbb14adb23]   TRANSACTION (104.1ms)  BEGIN
[ActiveJob] [ActiveStorage::AnalyzeJob] [10d753a4-920a-431c-a01d-3efbb14adb23]   ActiveStorage::Blob Update (104.7ms)  UPDATE "active_storage_blobs" SET "metadata" = $1 WHERE "active_storage_blobs"."id" = $2  [["metadata", "{\"identified\":true,\"width\":128,\"height\":128,\"analyzed\":true}"], ["id", "4c13a8df-ef6c-4019-85d7-63507c7c0460"]]
[ActiveJob] [ActiveStorage::AnalyzeJob] [10d753a4-920a-431c-a01d-3efbb14adb23]   TRANSACTION (104.6ms)  COMMIT
[ActiveJob] [ActiveStorage::AnalyzeJob] [10d753a4-920a-431c-a01d-3efbb14adb23] Performed ActiveStorage::AnalyzeJob (Job ID: 10d753a4-920a-431c-a01d-3efbb14adb23) from Async(default) in 3354.01ms
Started GET "/api/v1/users/f37fa3e9-e2a2-45cc-972f-f0beb301c678" for ::1 at 2023-06-13 10:11:24 +0900
Processing by Api::V1::UsersController#show as */*
  Parameters: {"id"=>"f37fa3e9-e2a2-45cc-972f-f0beb301c678"}
  User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", "f37fa3e9-e2a2-45cc-972f-f0beb301c678"], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/users_controller.rb:13:in `show'
  ActiveStorage::Attachment Load (0.6ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = $1 AND "active_storage_attachments"."record_type" = $2 AND "active_storage_attachments"."name" = $3 LIMIT $4  [["record_id", "f37fa3e9-e2a2-45cc-972f-f0beb301c678"], ["record_type", "User"], ["name", "avatar"], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/users_controller.rb:16:in `show'
  ActiveStorage::Blob Load (0.4ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = $1 LIMIT $2  [["id", "4c13a8df-ef6c-4019-85d7-63507c7c0460"], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/users_controller.rb:16:in `show'
Completed 200 OK in 11ms (Views: 0.7ms | ActiveRecord: 1.3ms | Allocations: 3023)
Tsuchiya2 commented 1 year ago

上記ChatGPTに投げてみました。以下共有になります。


SystemStackError (stack level too deep)エラーが発生していることと、S3へのアクセスに時間がかかっていることから、いくつかの可能性が考えられます。

まず、Active Storage と S3 の相性は問題ありません。Active Storage は S3 と一緒に使用するために設計されています。

以下のいくつかの点について確認をお願いします。

ネットワークの速度: ローカル環境からS3へのネットワークの速度が遅いと、ファイルのアップロードに時間がかかることがあります。ネットワークの速度を確認し、可能であれば高速な接続を使用してみてください。

ファイルの大きさ: アップロードするファイルの大きさによってもアップロード時間は変わります。大きなファイルをアップロードしようとしていないか確認してみてください。

コードの問題: SystemStackError (stack level too deep)エラーは通常、無限ループまたは深い再帰などの結果としてスタックオーバーフローが発生した場合に発生します。コード内に無限ルーバックや再帰呼び出しが存在しないか確認してみてください。

Active Storage の設定: Active Storage が正しく設定されているか確認してみてください。例えば、config/storage.yml内のS3設定が正しく行われているかなどを確認します。

Tsuchiya2 commented 1 year ago

ファイルの大きさが関係しているかもしれませんので、上記と合わせてアップロードするファイルの制限 or ファイルサイズを縮小してアップロードするようにしてみると良いかもしれません。

mitsu30 commented 1 year ago

上記のログが出力された時に添付している画像は35KBです。さらに圧縮が必要でしょうか? ファイルサイズの相場観がなく伺いました。

Tsuchiya2 commented 1 year ago

なるほど。そのサイズなら問題ないかと。 他の検討箇所の確認してみていただけますか。

mitsu30 commented 1 year ago

以下のアドバイスを基にファイルを見直してみました。 usersテーブルにavaterカラムを設定していたため、処理に時間がかかっていたようです。 こちらを削除してモデルファイルにhas_one_attached :avatarの記述のみとすることで 不自然に時間がかかるっている状況が改善されました! ありがとうございました!

深い再帰などの結果としてスタックオーバーフローが発生した場合に発生します。