# [KB] Knowledgebase ROOV
## Radioplus repository development cycle
### Development
```plantuml
actor Developer as dev
actor Reviewer as rev
participant Bitbucket as bitbucket
participant Gitlab as gitlab
participant "Dev environment" as devenv
note over dev
origin:
git@bitbucket.org:rctiplus/radioplus-webssr.git
end note
note over rev
origin:
git@bitbucket.org:rctiplus/radioplus-webssr.git
end note
dev ->o bitbucket: Push to "development"
rev --> rev: Change remote origin url to "gitlab"
note right
git remote set-url origin https://mnc-repo.mncdigital.com/roov/radioplus.git
end note
note over rev
origin:
https://mnc-repo.mncdigital.com/roov/radioplus.git
end note
rev --> rev: Fetch origin "development"
note right
git fetch origin development
end note
rev --> gitlab: Force push to development
note right
git push -f origin development
end note
gitlab --> devenv: Deployment to Dev (CI/CD)
rev --> rev: Revert back remote origin url to "bitbucket"
note right
git remote set-url origin git@bitbucket.org:rctiplus/radioplus-webssr.git
end note
note over rev
origin:
git@bitbucket.org:rctiplus/radioplus-webssr.git
end note
```
```plantuml
@startuml
note "moderationStatusID = {\n\tPENDING: 1\n\tAPPROVE: 2\n\tREJECT: 3\n}" as moderationStatus
PENDING: 1
APPROVE: 2
REJECT: 3
[*] --> PENDING
PENDING --> APPROVE
PENDING --> REJECT
APPROVE --> PENDING
REJECT --> PENDING
```
## Auth Client Scheme
```plantuml
actor Client as client
participant AuthAPI as auth
group Authenticate client
client -> auth: Authenticate client `GET /clients`
note right
Headers: {
"Authrorization": "Basic ${CLIENT_ID}:${CLIENT_SECRET}"
}
end note
auth --> client: Authenticate client responses`
note right
Headers: `X-Client-Token, X-Client-Token-Expired-At
end note
end
group Authenticate session
client -> auth: Authenticate sessions: `POST /sessions`
note right
Headers: {
"Authrorization": "Bearer ${X-Client-Token}"
}
end note
auth --> client: Authenticate session responses
note right
Headers: `X-Access-Token, X-Access-Token-Expired-At, X-Refresh-Access-Token, X-Refresh-Access-Token-Expired-At`
end note
end
group Refresh session
client -> client: X-Acces-Token expired
else Refresh session not expired
client -> auth: Refresh sessions: `PUT /sessions`
note right
Headers: {
"Authrorization": "Bearer ${X-Refresh-Access-Token}"
}
end note
auth --> client: Authenticate session responses
note right
Headers: `X-Access-Token, X-Access-Token-Expired-At, X-Refresh-Access-Token, X-Refresh-Access-Token-Expired-At`
end note
else Refresh token expired
client -> client: X-Refresh-Access-Token expired
note right
Repeat Authenticate session
end note
end
```
## Entity Relationship Diagram (ERD)
### SQL Database
```plantuml
entity AuthProvider {
ID: smallint <<PK>>
--
slug: varchar(255) <<UNIQUE>>
metadata: JSON
createdAt: datetime
updatedAt: datetime
modifiedBy: JSON
version: bigint
}
note left
metadata = {
saltRounds
providerURL
environment
}
end note
entity ClientType {
ID: smallint <<PK>>
--
name: varchar(255)
createdAt: datetime
updatedAt: datetime
modifiedBy: JSON
version: bigint
}
entity ClientAuth {
ID: smallint <<PK>>
--
typeID: smallint
clientID: varchar(8)
credentials: JSON
metadata: JSON
createdAt: datetime
updatedAt: datetime
modifiedBy: JSON
}
note right
credentails = {
clientSecret
clientLifetime
}
end note
entity AdminAuth {
ID: bigint <<PK, AI>>
--
userID: bigint <<FK>>
username: varchar(255) <<UNIQUE>>
authProviderID: smallint
credential: tinytext <<NULLABLE>>
statusID: smallint
metadata: JSON
createdAt: datetime
updatedAt: datetime
modifiedBy: JSON
version: bigint
}
note right
metadata = {
super: bool
}
end note
ClientAuth "typeID"||--||"ID"ClientType
AdminAuth "authProviderID"||--||"ID" AuthProvider
note "statusID = {\n\tINACTIVE: 1\n\tACTIVE: 2\n}" as status
entity ContentType {
ID: smallint <<PK>>
--
name: varchar(255)
createdAt: datetime
updatedAt: datetime
modifiedBy: JSON
version: bigint
}
note top
ContentType = {
LIVE: 1
AOD: 2
VOD: 3
}
end note
entity ContentCategory {
ID: smallint <<PK>>
--
slug: varchar(255) <<UNIQUE>>
name: varchar(255)
imageURL: varchar(511) <<NULLABLE>>
statusID: smallint
metadata: JSON
createdAt: datetime
updatedAt: datetime
modifiedBy: JSON
version: bigint
}
note top
metadata = {
description
}
ContentCategory = [slug][int] {
comedy: 1
horror: 2
lifestyle: 4
stories: 5
audio-series: 6
music: 7
business: 9
kids-family: 10
health: 11
sports: 12
religion: 13
audiobook: 14
religion-islam: 15
religion-kristen: 16
religion-buddha = 17
religion-hindu = 18
religion-kong-hu-cu = 19
religion-katolik = 20
video = 21
}
end note
entity UserAuth {
ID: bigint <<PK, AI>>
--
extID: bigint <<UNIQUE>>
userID: bigint <<FK>>
authProviderID: smallint
credential: tinytext
statusID: smallint
metadata: JSON
createdAt: datetime
updatedAt: datetime
modifiedBy: JSON
version: bigint
}
entity User {
ID: bigint <<PK, AI>>
--
email: varchar(255) <<NULLABLE>>
fullName: varchar(255)
avatarURL: varchar(511) <<NULLABLE>>
metadata: JSON
createdAt: datetime
updatedAt: datetime
modifiedBy: JSON
version: bigint
}
note top
metadata = {
gender
phoneNumber
DOB
address
}
end note
entity Content {
ID: bigint <<PK, AI>>
--
extID: varchar(12) <<UNIQUE>>
slug: varhcar(255) <<UNIQUE>>
name: varchar(255)
userID: bigint <<FK>>
statusID: smallint
typeID: smallint
cityID: smallint
sort: bigint
metadata: JSON
highlight: bool
createdAt: datetime
updatedAt: datetime
modifiedBy: JSON
version: bigint
}
note right
metadata = {
description
bannerURL
thumbnailURL
extBannerURL
liveMetadata
}
end note
entity ContentData {
ID: bigint <<PK, AI>>
--
extID: varchar(12) <<UNIQUE>>
slug: varhcar(255) <<UNIQUE>>
title: varchar(255)
contentID: bigint <<FK>>
statusID: smallint
dataURL: varchar(511)
moderationStatusID: smallint
sort: bigint
highlight: bool
metadata: JSON
createdAt: datetime
updatedAt: datetime
modifiedBy: JSON
version: bigint
}
note right
metadata = {
description
bannerURL
thumbnailURL
}
end note
entity ContentHasCategory {
ID: bigint <<PK, AI>>
--
contentID: bigint <<FK>>
categoryID: smallint <<FK>>
createdAt: datetime
}
entity Template {
ID: bigint <<PK, AI>>
--
name: varchar(255)
typeID: smallint
statusID: smallint
dataURL: varhcar(511) <<NULLABLE>>
metadata: JSON
createdAt: datetime
updatedAt: datetime
modifiedBy: JSON
version: bigint
}
note right
typeID = {
BACKGROUND-MUSIC: 1
COVER: 2
}
end note
entity City {
ID: smallint <<PK>>
--
slug: varhcar(255) <<UNIQUE>>
name: varchar(255)
createdAt: datetime
updatedAt: datetime
modifiedBy: JSON
version: bigint
}
User "ID"||--|>"userID" Content
ContentCategory "ID"<|--||"categoryID" ContentHasCategory
Content "ID"<|--||"contentID" ContentHasCategory
ContentType "ID"||--||"typeID"Content
Content "ID"||--|>"contentID"ContentData
City "ID"||--|| "cityID" Content
User "ID" ||--||"userID" UserAuth
User "ID" ||--||"userID" AdminAuth
@enduml
@enduml
```
```plantuml
entity News {
ID: bigint <<PK, AI>>
--
slug: varchar(255)
title: varchar(255)
sort: bigint
categoryID: smallint
content: text
statusID: smallint
metadata: JSON
createdAt: datetime
updatedAt: datetime
modifiedBy: JSON
version: bigint
}
note left
metadata = {
bannerURL
imageURL
videoURL
embedURL
}
end note
entity NewsCategory {
ID: smallint <<PK, AI>>
--
slug: varchar(255) <<UNIQUE>>
name: varchar(255)
imageURL: varchar(511) <<NULLABLE>>
statusID: smallint
createdAt: datetime
updatedAt: datetime
modifiedBy: JSON
version: bigint
}
entity NewsTag {
ID: smallint <<PK>>
--
name: varchar(255)
statusID: smallint
createdAt: datetime
updatedAt: datetime
modifiedBy: JSON
version: bigint
}
entity NewsHasTag {
ID: bigint <<PK, AI>>
--
tagID: smallint
newsID: bigint
createdAt: datetime
}
entity Campaign {
ID: bigint <<PK, AI>>
--
typeID: smallint
name: varchar(255)
metadata: JSON
statusID: smallint
sort: bigint
createdAt: datetime
updatedAt: datetime
modifiedBy: JSON
version: bigint
}
note right
typeID = {
PROMOTION: 1
TOP_ARTIST: 2
}
metadata = {
bannerURL
content
}
end note
News "categoryID"||--||"ID" NewsCategory
NewsHasTag "newsID"||--|>"ID" News
NewsHasTag "tagID"||--|>"ID" NewsTag
```
### NoSQL
```plantuml
entity Settings {
ID: Objecttd
--
extID: int
key: string
description: string
value: string | []any
isArray: bool
createdAt: date
updatedAt: date
}
entity Privilege {
ID: ObjectId
--
extID: string
ability: string
createdAt: date
updatedAt: date
}
entity SubjectPrivileges {
ID: ObjectId
--
subjectExtID: string
name: string
privilegeExtID: string
createdAt: date
privilege: Privilege
}
```
## Migrations
```plantuml
entity Template {}
note right
from backsounds:
name = backsounds.name
statusID = backsounds.active
dataURL = backsounds.url
metadata = {
duration: backsounds.duration
}
end note
```
```plantuml
entity UserAuth {}
note right
extID = userdetail.UDSso_id
statusID = UDStatus
metadata = {
SSOUnique: userdetail.UDSsoUnique
}
end note
```
```plantuml
entity User {}
note right
email = userdetail.UDEmail
fullName = userdetail.UDFirstname+userdetail.UDLastname
avatarURL = userdetail.UDUrl_profile
metadata = {
phoneNumber: userdetail.UDPhoneNumber
address: userdetail.UDAddress
DOB: userdetail.UDDateOfBirth
gender: userdetail.UDGender
}
end note
```
```plantuml
entity Content {}
note right
podcasts:
slug = kebabCase(podcasts.title)
name = podcasts.title
userID = podcasts.created_by
typeID = AOD
statusID = podcasts.status
metadata = {
description: podcasts.description
bannerURL: podcasts.image_banner
thumbnailURL: podcasts.image_name
}
sort = podcasts.sort
radios:
slug = kebabCase(radios.name)
name = radios.name
userID = radios.created_by
typeID = LIVE
statusID = radios.status
metadata = {
bannerURL: radios.image_banner
thumbnailURL: radios.image_name
extBannerURL: radios.image_banner_radio_partner
liveMetadata: {
frequency: radios.frequency
city: radios.city
address: radios.address
geoPoint: {
latitude: radios.latitude
longitude: radios.longitude
}
phoneNumber: radios.phone
website: radios.website
fax: radios.fax
email: radios.email
socialMedia: {
whatsapp: radios.whatsapp
facebook: radios.facebook
twitter: radios.twitter
instagram: radios.instagram
}
}
}
sort = radios.sort_network
videos:
slug = kebabCase(videos.title)
name = videos.title
userID = videos.created_by
typeID = VIDEO
statusID = videos.status
metadata = {
description: videos.description
thumbnailURL: videos.image_name
}
sort = videos.sort
spirituals:
slug = kebabCase(spirituals.title)
name = sprituals.title
userID = spirituals.created_by
typeID = AOD
statusID = spirituals.status
metadata = {
description: spirituals.description
thumbnailURL: spirituals.image_name
bannerURL: spirituals.image_banner
}
sort = spirituals.sort
end note
```
```plantuml
entity ContentHasCategory {}
note right
podcasts:
categoryID = podcasts.categoryPodcasts_id
sprituals:
categoryID = spirituals.category_id
radios:
categoryID = Music for radios.statusRadios == 'rMusic'
end note
```
```plantuml
entity ContentData {}
note right
content_podcasts:
slug = kebabCase(content_podcasts.title)
title = content_podcasts.title
contentID = content_podcasts.podcasts_id
statusID = content_podcasts.status
dataURL = content_podcasts.embed_url OR content_podcasts.filename
moderationStatusID = content_podcasts.moderation_status
metadata = {
description: content_podcasts.description
thumbnailURL: content_podcasts.image_name
bannerURL: content_podcasts.image_url
}
sort: content_podcasts.sort
content_spirituals:
slug = kebabCase(content_spirituals.title)
title = content_spirituals.title
contentID = spritiuals_having_content.content_id == content_spirituals.id
statusID = content_spirituals.status
dataURL = content_spirituals.embed_url OR content_spirituals.filename
moderationStatusID = APPROVE
metadata = {
thumbnailURL: content_spirituals.image_name
bannerURL: content_spirituals.image_banner
}
radios:
slug = kebabCase(radios.name)
title = radios.name
contentID = Content.slug == kebabCase(radios.name)
statusID = radios.status
dataURL = radios.audio_url
moderationStatusID = APPROVE
highlight = radios.statusRadios == 'rBanner'
metadata = {
bannerURL: radios.image_banner
thumbnailURL: radios.image_name
}
sort: radios.sort_banner || radios.sort_music
videos:
slug = kebabCase(videos.title)
title = videos.title
contentID = Content.slug == kebabCase(videos.title)
statusID = videos.status
dataURL = videos.embed_url OR videos.filename
moderationStatusID = APPROVE
metadata = {
thumbnailURL: videos.image_name
}
sort: videos.sort
end note
```
```plantuml
entity News {}
note right
news:
slug = kebabCase(news.title)
title = news.title
sort = news.sort
category_id = news.category_id
content = news.content
metadata = {
bannerURL: news.image_banner
imageURL: news.image_content
videoURL: news.videos_content
embedURL: news.embed_url
}
end note
```
```plantuml
entity NewsHasTag {}
note right
news_having_tag:
news_id: news_id == news.id
tag_id: tag_id == news_tag.id
end note
```
```plantuml
entity Campaign {}
note right
content_topartists:
typeID = TOP_ARTIST
name = title
sort = sort
metadata = {
bannerURL: image_banner
content: content
}
statusID = status
end note
```
### Content/Content Data ExtID
```json=
CONTENT_extID_PREFIX = {
PODCAST: 'P-',
RADIO: 'R-',
SPIRITUAL: 'S-',
VIDEO: 'V-',
NEWS: 'N-',
}
```
## Development
### Credentials
```json
> CMS App Client credential. ClientId = DIZD9JPG ClientSecret = oatfUPrKt3onkif28103c6DnHGTkyOIo
> Web App Client credential. ClientId = ODOLLDGT ClientSecret = YSwDkSK1XikqIFPihyfvj8t9QmmNC8hC
> Mobile App Client credential. ClientId = G1UP424T ClientSecret = ptouROa31JE81sacnRZo5x8a2oF5VHqU
> H2H Client credential. ClientId = CEP3UDOL ClientSecret = aezeen7chunievoh6ar1mohk0Lae6pah
> Super Administrator credential. Username = superadmin Password = 9djqf2MWl29j1jvMr5tHWDfYS4TCYcTb
```
### Mobile Integration
#### Home Curation
```json=
VIEW_TYPE = {
UNKNOWN: 0,
SQUARE_GRID: 1,
HORIZONTAL_LIST: 2,
TITLE: 3,
CAROUSEL: 4,
MINI_PLAYER: 5,
VERTICAL_LIST: 6,
PORTRAIT_GRID: 7,
LANDSCAPE_GRID: 8,
COMMENT_LIST: 9,
BANNER: 10,
SPECIAL_BANNER: 11,
COMMENT_FORM: 12,
LANDSCAPE_TILE: 13
}
DATA_TYPE = {
OBJECT: 1,
ARRAY: 2,
STRING: 3,
INTEGER: 4,
DECIMAL: 5,
BOOLEAN: 6,
ARRAY_CURRATOR_OBJECT: 7,
ARRAY_STRING: 8,
ARRAY_INTEGER: 9,
ARRAY_DECIMAL: 10,
ARRAY_BOOLEAN: 11,
}
```
#### API Response Body
```json=
BODY = {
data: Object
meta: Object
status: Object
}
```
##### CURRATOR OBJECT
```json=
CURRATOR_OBJECT = {
viewTypeID,
dataTypeID,
data,
callback: {
url,
method,
query,
},
}
```
#### Mobile response convention
**These responses is mapped based on VIEW_TYPE**
##### LANDSCAPE | PORTRAIT | SQUARE_GRID
```json=
{
"ID": number,
"title": string,
"subtitle": string,
"imageURL": string
}
```
###### PORTRAIT GRID

###### LANDSCAPE_GRID

###### SQURE_GRID

##### SPECIAL_BANNER
```json=
{
"imageURL": string,
"title": string,
}
```

##### CAROUSEL
```json=
{
"ID": number,
"title": string,
"subtitle": string
}
```

##### MINI_PLAYER
```json=
{
"ID": number,
"dataURL": string,
"title": string,
"subtitle": string
}
```

##### BANNER | TITLE
`string` on field `data`
###### BANNER

###### TITLE

##### COMMENT_LIST
```json=
{
"ID": number,
"createdAt": number,
"likes": number,
"replies": [
"ID": number,
"createdAt": number,
"user": {
"ID": number,
"fullName": string,
"avatarURL": string
},
"message": string
],
"user": {
"ID": number,
"fullName": string,
"avatarURL": string
},
"message": string
}
```

##### VERTICAL_LIST
```json=
{
"ID": number,
"imageURL": string,
"title": string,
"subtitle": string,
"intro": string
}
```

##### COMMENT_FORM

##### LANDSCAPE_TILE
```json=
{
"ID": number,
"title": string,
"imageURL": string
}
```
