Let's Go تمرین‌های راهنما › اضافه کردن صفحه 'حساب کاربری' به برنامه
قبلی · فهرست · بعدی
فصل 16.4.

اضافه کردن صفحه 'حساب کاربری' به برنامه (Adding an Account Page to the Application)

هدف شما در این تمرین اضافه کردن یک صفحه جدید 'حساب کاربری شما' به برنامه است. این صفحه باید به یک مسیر جدید GET /account/view متصل شود و نام، آدرس ایمیل و تاریخ ثبت‌نام کاربر احراز هویت شده فعلی را نمایش دهد، مشابه این:

16.04-01.png

مرحله 1 (Step 1)

در فایل internal/models/users.go یک متد جدید UserModel.Get() ایجاد کنید. این متد باید شناسه یک کاربر را به عنوان پارامتر بپذیرد و یک ساختار User حاوی تمام اطلاعات این کاربر (به جز رمز عبور هش شده آنها، که نیازی به آن نداریم) را برگرداند. اگر هیچ کاربری با این شناسه پیدا نشد، باید یک خطای ErrNoRecord برگرداند.

همچنین، نوع UserModelInterface را به‌روزرسانی کنید تا این متد جدید Get() را شامل شود و یک متد متناظر به mocks.UserModel ما اضافه کنید تا همچنان با رابط کاربری سازگار باشد.

نمایش کد پیشنهادی

مرحله 2 (Step 2)

یک مسیر GET /account/view ایجاد کنید که به یک هندلر جدید accountView متصل شود. این مسیر باید فقط برای کاربران احراز هویت شده محدود شود.

نمایش کد پیشنهادی

مرحله 3 (Step 3)

هندلر accountView را به‌روزرسانی کنید تا "authenticatedUserID" را از جلسه دریافت کند، جزئیات کاربر مربوطه را از پایگاه داده (با استفاده از متد جدید UserModel.Get()) دریافت کند و آنها را در یک پاسخ HTTP متنی ساده نمایش دهد. اگر هیچ کاربری مطابق با "authenticatedUserID" از جلسه پیدا نشد، کاربر را به GET /user/login هدایت کنید تا مجدداً احراز هویت شود.

وقتی به عنوان یک کاربر احراز هویت شده به https://localhost:4000/account/view در مرورگر خود مراجعه می‌کنید، باید پاسخی مشابه این دریافت کنید:

16.04-02.png

نمایش کد پیشنهادی

مرحله 4 (Step 4)

یک فایل جدید ui/html/pages/account.tmpl ایجاد کنید که اطلاعات کاربر را در یک جدول نمایش دهد. سپس هندلر accountView را به‌روزرسانی کنید تا این قالب جدید را رندر کند و جزئیات کاربر را از طریق ساختار templateData منتقل کند.

نمایش کد پیشنهادی

مرحله 5 (Step 5)

علاوه بر این، نوار ناوبری اصلی سایت را به‌روزرسانی کنید تا یک لینک به صفحه مشاهده حساب کاربری (فقط برای کاربران احراز هویت شده قابل مشاهده) اضافه شود. سپس بررسی کنید که صفحه جدید و ناوبری همانطور که انتظار می‌رود کار می‌کند، با مراجعه به https://localhost:4000/account/view در مرورگر خود در حالی که وارد شده‌اید.

نمایش کد پیشنهادی

کد پیشنهادی

کد پیشنهادی برای مرحله 1

File: internal/models/users.go
package models

...

type UserModelInterface interface {
    Insert(name, email, password string) error
    Authenticate(email, password string) (int, error)
    Exists(id int) (bool, error)
    Get(id int) (User, error)
}

...

func (m *UserModel) Get(id int) (User, error) {
    var user User

    stmt := `SELECT id, name, email, created FROM users WHERE id = ?`

    err := m.DB.QueryRow(stmt, id).Scan(&user.ID, &user.Name, &user.Email, &user.Created)
    if err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            return User{}, ErrNoRecord
        } else {
            return User{}, err
        }
    }

    return user, nil
}
File: internal/models/mocks/users.go
package mocks

import (
    "time" // New import

    "snippetbox.alexedwards.net/internal/models"
)

...

func (m *UserModel) Get(id int) (models.User, error) {
    if id == 1 {
        u := models.User{
            ID:      1,
            Name:    "Alice",
            Email:   "alice@example.com",
            Created: time.Now(),
        }

        return u, nil
    }

    return models.User{}, models.ErrNoRecord
}

کد پیشنهادی برای مرحله 2

File: cmd/web/handlers.go
...

func (app *application) accountView(w http.ResponseWriter, r *http.Request) {
    // Some code will go here later...
}
File: cmd/web/routes.go
package main

...

func (app *application) routes() http.Handler {
    mux := http.NewServeMux()

    mux.Handle("GET /static/", http.FileServerFS(ui.Files))

    mux.HandleFunc("GET /ping", ping)

    dynamic := alice.New(app.sessionManager.LoadAndSave, noSurf, app.authenticate)

    mux.Handle("GET /{$}", dynamic.ThenFunc(app.home))
    mux.Handle("GET /about", dynamic.ThenFunc(app.about))
    mux.Handle("GET /snippet/view/{id}", dynamic.ThenFunc(app.snippetView))
    mux.Handle("GET /user/signup", dynamic.ThenFunc(app.userSignup))
    mux.Handle("POST /user/signup", dynamic.ThenFunc(app.userSignupPost))
    mux.Handle("GET /user/login", dynamic.ThenFunc(app.userLogin))
    mux.Handle("POST /user/login", dynamic.ThenFunc(app.userLoginPost))

    protected := dynamic.Append(app.requireAuthentication)

    mux.Handle("GET /snippet/create", protected.ThenFunc(app.snippetCreate))
    mux.Handle("POST /snippet/create", protected.ThenFunc(app.snippetCreatePost))
    // Add the view account route, using the protected middleware chain.
    mux.Handle("GET /account/view", protected.ThenFunc(app.accountView))
    mux.Handle("POST /user/logout", protected.ThenFunc(app.userLogoutPost))

    standard := alice.New(app.recoverPanic, app.logRequest, commonHeaders)
    return standard.Then(mux)
}

کد پیشنهادی برای مرحله 3

File: cmd/web/handlers.go
...

func (app *application) accountView(w http.ResponseWriter, r *http.Request) {
    userID := app.sessionManager.GetInt(r.Context(), "authenticatedUserID")

    user, err := app.users.Get(userID)
    if err != nil {
        if errors.Is(err, models.ErrNoRecord) {
            http.Redirect(w, r, "/user/login", http.StatusSeeOther)
        } else {
            app.serverError(w, r, err)
        }
        return
    }

    fmt.Fprintf(w, "%+v", user)
}

کد پیشنهادی برای مرحله 4

File: cmd/web/templates.go
...

type templateData struct {
    CurrentYear     int
    Snippet         models.Snippet
    Snippets        []models.Snippet
    Form            any
    Flash           string
    IsAuthenticated bool
    CSRFToken       string
    User            models.User
}

...
File: ui/html/pages/account.tmpl
{{define "title"}}Your Account{{end}}

{{define "main"}}
    <h2>Your Account</h2>
    {{with .User}}
     <table>
        <tr>
            <th>Name</th>
            <td>{{.Name}}</td>
        </tr>
        <tr>
            <th>Email</th>
            <td>{{.Email}}</td>
        </tr>
        <tr>
            <th>Joined</th>
            <td>{{humanDate .Created}}</td>
        </tr>
    </table>
    {{end }}
{{end}}
File: cmd/web/handlers.go
...

func (app *application) accountView(w http.ResponseWriter, r *http.Request) {
    userID := app.sessionManager.GetInt(r.Context(), "authenticatedUserID")

    user, err := app.users.Get(userID)
    if err != nil {
        if errors.Is(err, models.ErrNoRecord) {
            http.Redirect(w, r, "/user/login", http.StatusSeeOther)
        } else {
            app.serverError(w, r, err)
        }
        return
    }

    data := app.newTemplateData(r)
    data.User = user

    app.render(w, r, http.StatusOK, "account.tmpl", data)
}

کد پیشنهادی برای مرحله 5

File: ui/html/partials/nav.tmpl
{{define "nav"}}
<nav>
    <div>
        <a href='/'>Home</a>
        <a href='/about'>About</a>
         {{if .IsAuthenticated}}
            <a href='/snippet/create'>Create snippet</a>
        {{end}}
    </div>
    <div>
        {{if .IsAuthenticated}}
            <!-- Add the view account link for authenticated users -->
            <a href='/account/view'>Account</a>
            <form action='/user/logout' method='POST'>
                <input type='hidden' name='csrf_token' value='{{.CSRFToken}}'>
                <button>Logout</button>
            </form>
        {{else}}
            <a href='/user/signup'>Signup</a>
            <a href='/user/login'>Login</a>
        {{end}}
    </div>
</nav>
{{end}}

واژه‌نامه اصطلاحات فنی

اصطلاح فارسی معادل انگلیسی توضیح
صفحه جدید New Page صفحه تازه اضافه شده به برنامه
مسیر جدید New Route مسیر دسترسی جدید در برنامه
نام Name نام کاربر در سیستم
آدرس ایمیل Email Address آدرس پست الکترونیکی کاربر
تاریخ ثبت‌نام Registration Date تاریخ ایجاد حساب کاربری
کاربر احراز هویت شده Authenticated User کاربری که وارد سیستم شده است
متد جدید New Method تابع جدید اضافه شده به کلاس
شناسه کاربر User ID شماره منحصر به فرد کاربر
پارامتر Parameter ورودی تابع
ساختار Structure نوع داده ترکیبی
اطلاعات کاربر User Information داده‌های مربوط به کاربر
رمز عبور هش شده Hashed Password رمز عبور رمزنگاری شده
خطا Error پیام خطا در برنامه
هندلر جدید New Handler تابع پردازش درخواست
جلسه Session نشست کاربری
جزئیات کاربر User Details اطلاعات جزئی کاربر
پایگاه داده Database محل ذخیره داده‌ها
پاسخ HTTP متنی ساده Plain Text HTTP Response پاسخ متنی بدون قالب‌بندی
فایل قالب جدید New Template File فایل قالب‌بندی صفحه
جدول Table نمایش داده در قالب سطر و ستون
قالب جدید New Template قالب تازه ایجاد شده
رندر Render نمایش محتوا در قالب
ساختار داده قالب Template Data Structure ساختار داده برای قالب
نوار ناوبری Navigation Bar منوی اصلی سایت
پیوند Link لینک به صفحه دیگر