Updating the user profile is the same as updating any other entity. Before doing so, we need to update the User entity and add a few more properties:
📁 Entities/User.ts
import { Column, HasMany, Options, Relation } from '@Typetron/Database'
import { User as Authenticatable } from '@Typetron/Framework/Auth'
import { Tweet } from 'App/Entities/Tweet'
import { Like } from 'App/Entities/Like'
@Options({
table: 'users'
})
export class User extends Authenticatable {
@Column()
name: string
@Column()
username: string
@Column()
bio?: string
@Column()
photo: string
@Column()
cover: string
@Relation(() => Like, 'user')
likes: HasMany<Like>
@Relation(() => Tweet, 'user')
tweets: HasMany<Tweet>
}
We added the username property, which is the handle of a user. We will use this later in order to mention people in our tweets using the ‘@’ symbol. The bio will be a short description for a user. The photo and cover will contain a path to an image. The photo is the avatar image of the user, and the cover is the image that will show up when you will visit a user’s profile.
Before adding the endpoint that will update the user’s information, we need to add a form that will collect and validate the data before we make use of it. Let’s create the UserForm for this:
📁 Forms/UserForm.ts
import { Field, Form, Rules } from '@Typetron/Forms'
import { File } from '@Typetron/Storage'
import { Required } from '@Typetron/Validation'
export class UserForm extends Form {
@Field()
@Rules(Required)
name: string
@Field()
@Rules(Required)
username: string
@Field()
bio?: string
@Field()
photo?: File
@Field()
cover?: File
}
As you probably saw, for the photo and cover properties we accept a File. This is because we will handle the case when you can make a request with photo and cover properties being the old images paths.
Now we can create the endpoint that will update the user’s profile information. Let’s add this in a new controller called UsersController and also update the User model:
📁 Controllers/Http/UsersController.ts
import { Controller, Middleware, Put } from '@Typetron/Router'
import { Inject } from '@Typetron/Container'
import { AuthUser } from '@Typetron/Framework/Auth'
import { User } from 'App/Entities/User'
import { User as UserModel } from 'App/Models/User'
import { AuthMiddleware } from '@Typetron/Framework/Middleware'
import { UserForm } from 'App/Forms/UserForm'
import { Storage } from '@Typetron/Storage'
@Controller('users')
@Middleware(AuthMiddleware)
export class UsersController {
@AuthUser()
user: User
@Inject()
storage: Storage
@Put()
async update(form: UserForm) {
if (form.photo) {
await this.storage.delete(`public/${this.user.photo}`)
form.photo = await this.storage.save(form.photo, 'public')
}
if (form.cover) {
await this.storage.delete(`public/${this.user.cover}`)
form.cover = await this.storage.save(form.cover, 'public')
}
await this.user.save(form)
return UserModel.from(this.user)
}
}
📁 Models/User.ts
import { Field, Model } from '@Typetron/Models'
export class User extends Model {
@Field()
id: number
@Field()
username: string
@Field()
name: string
@Field()
photo: string
@Field()
cover: string
@Field()
bio?: string
}
We upload an image in the public directory only when we receive an instance of a File in the request, but not before deleting the old ones, so we don’t end up with images on disk that are not used.
Let’s make a form-data request to update the user’s profile:
🌐 [Put] /users
{
"username": "joe",
"name": "John Doe",
"bio": "Building apps with Typetron",
"photo": imageFile,
"cover": imageFile
}
One last thing we need to fix is to restrict the username to have only letters, numbers and/or the ‘_’ character. We cannot allow for spaces since it won’t be possible to mention users in tweets using the ‘@’ character. Let’s add a custom validator in a Validators directory to validate the username for these characters and them update the UserForm to use it:
📁 Validators/IsUsername.ts
import { Rule } from '@Typetron/Validation'
export class IsUsername extends Rule {
identifier = 'isUsername'
passes(attribute: string, value: string): boolean {
return Boolean(value.match(/^[0-9a-zA-Z_]+$/))
}
message(attribute: string): string {
return `The username can only contain numbers, letters and '_'`
}
}
📁 Forms/UserForm.ts
import { Field, Form, Rules } from '@Typetron/Forms'
import { File } from '@Typetron/Storage'
import { Required } from '@Typetron/Validation'
import { IsUsername } from 'App/Validators/IsUsername'
export class UserForm extends Form {
@Field()
@Rules(Required)
name: string
@Field()
@Rules(Required, IsUsername)
username: string
@Field()
bio?: string
@Field()
photo?: File
@Field()
cover?: File
}
Now, if we make a request that contains an invalid username, we should get an error back.