mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Return pinnedAt date from pinPost resolver/clean up
- it's good to return the pinnedAt date for ordering - move test to a better describe block - remove unneeded outdated variables from graphql/PostQuery UpdatePost - fix indentation in Post.gql - fix pinnedAt to return pinned.createdAt, not post.createdAt Co-authored-by: Mike Aono <aonomike@gmail.com>
This commit is contained in:
parent
411bbabcd5
commit
be0c8044e8
@ -86,6 +86,7 @@ export default function Resolver(type, options = {}) {
|
|||||||
}
|
}
|
||||||
return resolvers
|
return resolvers
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
...undefinedToNullResolver(undefinedToNull),
|
...undefinedToNullResolver(undefinedToNull),
|
||||||
...booleanResolver(boolean),
|
...booleanResolver(boolean),
|
||||||
|
|||||||
@ -226,7 +226,7 @@ export default {
|
|||||||
return emoted
|
return emoted
|
||||||
},
|
},
|
||||||
pinPost: async (_parent, params, context, _resolveInfo) => {
|
pinPost: async (_parent, params, context, _resolveInfo) => {
|
||||||
let pinnedPost
|
let pinnedPostWithNestedAttributes
|
||||||
const { driver, user } = context
|
const { driver, user } = context
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
const { id: userId } = user
|
const { id: userId } = user
|
||||||
@ -248,19 +248,27 @@ export default {
|
|||||||
`
|
`
|
||||||
MATCH (user:User {id: $userId}) WHERE user.role = 'admin'
|
MATCH (user:User {id: $userId}) WHERE user.role = 'admin'
|
||||||
MATCH (post:Post {id: $params.id})
|
MATCH (post:Post {id: $params.id})
|
||||||
MERGE (user)-[:PINNED {createdAt: toString(datetime())}]->(post)
|
MERGE (user)-[pinned:PINNED {createdAt: toString(datetime())}]->(post)
|
||||||
RETURN post
|
RETURN post, pinned.createdAt as pinnedAt
|
||||||
`,
|
`,
|
||||||
{ userId, params },
|
{ userId, params },
|
||||||
)
|
)
|
||||||
return pinPostTransactionResponse.records.map(record => record.get('post').properties)
|
return pinPostTransactionResponse.records.map(record => ({
|
||||||
|
pinnedPost: record.get('post').properties,
|
||||||
|
pinnedAt: record.get('pinnedAt'),
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
try {
|
try {
|
||||||
;[pinnedPost] = await writeTxResultPromise
|
const [transactionResult] = await writeTxResultPromise
|
||||||
|
const { pinnedPost, pinnedAt } = transactionResult
|
||||||
|
pinnedPostWithNestedAttributes = {
|
||||||
|
...pinnedPost,
|
||||||
|
pinnedAt
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
return pinnedPost
|
return pinnedPostWithNestedAttributes
|
||||||
},
|
},
|
||||||
unpinPost: async (_parent, params, context, _resolveInfo) => {
|
unpinPost: async (_parent, params, context, _resolveInfo) => {
|
||||||
let unpinnedPost
|
let unpinnedPost
|
||||||
|
|||||||
@ -581,6 +581,7 @@ describe('UpdatePost', () => {
|
|||||||
}
|
}
|
||||||
createdAt
|
createdAt
|
||||||
updatedAt
|
updatedAt
|
||||||
|
pinnedAt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@ -607,7 +608,7 @@ describe('UpdatePost', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('moderator cannot pin posts', () => {
|
describe('moderators cannot pin posts', () => {
|
||||||
let moderator
|
let moderator
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
moderator = await user.update({ role: 'moderator', updatedAt: new Date().toISOString() })
|
moderator = await user.update({ role: 'moderator', updatedAt: new Date().toISOString() })
|
||||||
@ -664,6 +665,22 @@ describe('UpdatePost', () => {
|
|||||||
expected,
|
expected,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('sets createdAt date for PINNED', async () => {
|
||||||
|
variables = { ...variables, id: 'created-and-pinned-by-same-admin' }
|
||||||
|
const expected = {
|
||||||
|
data: {
|
||||||
|
pinPost: {
|
||||||
|
id: 'created-and-pinned-by-same-admin',
|
||||||
|
pinnedAt: expect.any(String)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
|
}
|
||||||
|
await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject(
|
||||||
|
expected,
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('post created by another admin', () => {
|
describe('post created by another admin', () => {
|
||||||
@ -742,20 +759,13 @@ describe('UpdatePost', () => {
|
|||||||
variables = { ...variables, id: 'only-pinned-post' }
|
variables = { ...variables, id: 'only-pinned-post' }
|
||||||
await mutate({ mutation: pinPostMutation, variables })
|
await mutate({ mutation: pinPostMutation, variables })
|
||||||
pinnedPost = await neode.cypher(
|
pinnedPost = await neode.cypher(
|
||||||
`MATCH ()-[relationship:PINNED]->(post:Post) RETURN post, relationship`,
|
`MATCH ()-[pinned:PINNED]->(post:Post) RETURN post, pinned`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('leaves only one pinned post at a time', async () => {
|
it('leaves only one pinned post at a time', async () => {
|
||||||
expect(pinnedPost.records).toHaveLength(1)
|
expect(pinnedPost.records).toHaveLength(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sets createdAt date for PINNED', () => {
|
|
||||||
const [pinnedPostCreatedAt] = pinnedPost.records.map(record => {
|
|
||||||
return record.get('relationship').properties.createdAt
|
|
||||||
})
|
|
||||||
expect(pinnedPostCreatedAt).toEqual(expect.any(String))
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('PostOrdering', () => {
|
describe('PostOrdering', () => {
|
||||||
@ -787,7 +797,7 @@ describe('UpdatePost', () => {
|
|||||||
name: 'Admin',
|
name: 'Admin',
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
})
|
})
|
||||||
await admin.relateTo(pinnedPost, 'pinned', { createdAt: newDate.toISOString() })
|
await admin.relateTo(pinnedPost, 'pinned')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('pinned post appear first even when created before other posts', async () => {
|
it('pinned post appear first even when created before other posts', async () => {
|
||||||
@ -870,7 +880,7 @@ describe('UpdatePost', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('moderator cannot unpin posts', () => {
|
describe('moderators cannot unpin posts', () => {
|
||||||
let moderator
|
let moderator
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
moderator = await user.update({ role: 'moderator', updatedAt: new Date().toISOString() })
|
moderator = await user.update({ role: 'moderator', updatedAt: new Date().toISOString() })
|
||||||
|
|||||||
@ -17,7 +17,7 @@ type Post {
|
|||||||
updatedAt: String
|
updatedAt: String
|
||||||
language: String
|
language: String
|
||||||
pinnedAt: String @cypher(
|
pinnedAt: String @cypher(
|
||||||
statement: "MATCH (this)<-[pinned:PINNED]-(:User) WHERE NOT this.deleted = true AND NOT this.disabled = true RETURN this.createdAt"
|
statement: "MATCH (this)<-[pinned:PINNED]-(:User) WHERE NOT this.deleted = true AND NOT this.disabled = true RETURN pinned.createdAt"
|
||||||
)
|
)
|
||||||
pinnedBy: User @relation(name:"PINNED", direction: "IN")
|
pinnedBy: User @relation(name:"PINNED", direction: "IN")
|
||||||
relatedContributions: [Post]!
|
relatedContributions: [Post]!
|
||||||
|
|||||||
@ -1,180 +1,180 @@
|
|||||||
type User {
|
type User {
|
||||||
id: ID!
|
id: ID!
|
||||||
actorId: String
|
actorId: String
|
||||||
name: String
|
name: String
|
||||||
email: String! @cypher(statement: "MATCH (this)-[: PRIMARY_EMAIL]->(e: EmailAddress) RETURN e.email")
|
email: String! @cypher(statement: "MATCH (this)-[: PRIMARY_EMAIL]->(e: EmailAddress) RETURN e.email")
|
||||||
slug: String!
|
slug: String!
|
||||||
avatar: String
|
avatar: String
|
||||||
coverImg: String
|
coverImg: String
|
||||||
deleted: Boolean
|
deleted: Boolean
|
||||||
disabled: Boolean
|
disabled: Boolean
|
||||||
disabledBy: User @relation(name: "DISABLED", direction: "IN")
|
disabledBy: User @relation(name: "DISABLED", direction: "IN")
|
||||||
role: UserGroup!
|
role: UserGroup!
|
||||||
publicKey: String
|
publicKey: String
|
||||||
invitedBy: User @relation(name: "INVITED", direction: "IN")
|
invitedBy: User @relation(name: "INVITED", direction: "IN")
|
||||||
invited: [User] @relation(name: "INVITED", direction: "OUT")
|
invited: [User] @relation(name: "INVITED", direction: "OUT")
|
||||||
|
|
||||||
location: Location @cypher(statement: "MATCH (this)-[: IS_IN]->(l: Location) RETURN l")
|
location: Location @cypher(statement: "MATCH (this)-[: IS_IN]->(l: Location) RETURN l")
|
||||||
locationName: String
|
locationName: String
|
||||||
about: String
|
about: String
|
||||||
socialMedia: [SocialMedia]! @relation(name: "OWNED_BY", direction: "IN")
|
socialMedia: [SocialMedia]! @relation(name: "OWNED_BY", direction: "IN")
|
||||||
|
|
||||||
# createdAt: DateTime
|
# createdAt: DateTime
|
||||||
# updatedAt: DateTime
|
# updatedAt: DateTime
|
||||||
createdAt: String
|
createdAt: String
|
||||||
updatedAt: String
|
updatedAt: String
|
||||||
|
|
||||||
termsAndConditionsAgreedVersion: String
|
termsAndConditionsAgreedVersion: String
|
||||||
termsAndConditionsAgreedAt: String
|
termsAndConditionsAgreedAt: String
|
||||||
|
|
||||||
allowEmbedIframes: Boolean
|
allowEmbedIframes: Boolean
|
||||||
|
|
||||||
friends: [User]! @relation(name: "FRIENDS", direction: "BOTH")
|
friends: [User]! @relation(name: "FRIENDS", direction: "BOTH")
|
||||||
friendsCount: Int! @cypher(statement: "MATCH (this)<-[: FRIENDS]->(r: User) RETURN COUNT(DISTINCT r)")
|
friendsCount: Int! @cypher(statement: "MATCH (this)<-[: FRIENDS]->(r: User) RETURN COUNT(DISTINCT r)")
|
||||||
|
|
||||||
following: [User]! @relation(name: "FOLLOWS", direction: "OUT")
|
following: [User]! @relation(name: "FOLLOWS", direction: "OUT")
|
||||||
followingCount: Int! @cypher(statement: "MATCH (this)-[: FOLLOWS]->(r: User) RETURN COUNT(DISTINCT r)")
|
followingCount: Int! @cypher(statement: "MATCH (this)-[: FOLLOWS]->(r: User) RETURN COUNT(DISTINCT r)")
|
||||||
|
|
||||||
followedBy: [User]! @relation(name: "FOLLOWS", direction: "IN")
|
followedBy: [User]! @relation(name: "FOLLOWS", direction: "IN")
|
||||||
followedByCount: Int! @cypher(statement: "MATCH (this)<-[: FOLLOWS]-(r: User) RETURN COUNT(DISTINCT r)")
|
followedByCount: Int! @cypher(statement: "MATCH (this)<-[: FOLLOWS]-(r: User) RETURN COUNT(DISTINCT r)")
|
||||||
|
|
||||||
# Is the currently logged in user following that user?
|
# Is the currently logged in user following that user?
|
||||||
followedByCurrentUser: Boolean! @cypher(
|
followedByCurrentUser: Boolean! @cypher(
|
||||||
statement: """
|
statement: """
|
||||||
MATCH (this)<-[: FOLLOWS]-(u: User { id: $cypherParams.currentUserId})
|
MATCH (this)<-[: FOLLOWS]-(u: User { id: $cypherParams.currentUserId})
|
||||||
RETURN COUNT(u) >= 1
|
RETURN COUNT(u) >= 1
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
isBlocked: Boolean! @cypher(
|
isBlocked: Boolean! @cypher(
|
||||||
statement: """
|
statement: """
|
||||||
MATCH (this)<-[: BLOCKED]-(u: User { id: $cypherParams.currentUserId})
|
MATCH (this)<-[: BLOCKED]-(u: User { id: $cypherParams.currentUserId})
|
||||||
RETURN COUNT(u) >= 1
|
RETURN COUNT(u) >= 1
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
# contributions: [WrittenPost]!
|
# contributions: [WrittenPost]!
|
||||||
# contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]!
|
# contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]!
|
||||||
# @cypher(
|
# @cypher(
|
||||||
# statement: "MATCH (this)-[w:WROTE]->(p:Post) RETURN p as Post, w.timestamp as timestamp"
|
# statement: "MATCH (this)-[w:WROTE]->(p:Post) RETURN p as Post, w.timestamp as timestamp"
|
||||||
# )
|
# )
|
||||||
contributions: [Post]! @relation(name: "WROTE", direction: "OUT")
|
contributions: [Post]! @relation(name: "WROTE", direction: "OUT")
|
||||||
contributionsCount: Int! @cypher(
|
contributionsCount: Int! @cypher(
|
||||||
statement: """
|
statement: """
|
||||||
MATCH (this)-[: WROTE]->(r: Post)
|
MATCH (this)-[: WROTE]->(r: Post)
|
||||||
WHERE NOT r.deleted = true AND NOT r.disabled = true
|
WHERE NOT r.deleted = true AND NOT r.disabled = true
|
||||||
RETURN COUNT(r)
|
RETURN COUNT(r)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
comments: [Comment]! @relation(name: "WROTE", direction: "OUT")
|
comments: [Comment]! @relation(name: "WROTE", direction: "OUT")
|
||||||
commentedCount: Int! @cypher(statement: "MATCH (this)-[: WROTE]->(: Comment)-[: COMMENTS]->(p: Post) WHERE NOT p.deleted = true AND NOT p.disabled = true RETURN COUNT(DISTINCT(p))")
|
commentedCount: Int! @cypher(statement: "MATCH (this)-[: WROTE]->(: Comment)-[: COMMENTS]->(p: Post) WHERE NOT p.deleted = true AND NOT p.disabled = true RETURN COUNT(DISTINCT(p))")
|
||||||
|
|
||||||
shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT")
|
shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT")
|
||||||
shoutedCount: Int! @cypher(statement: "MATCH (this)-[: SHOUTED]->(r: Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)")
|
shoutedCount: Int! @cypher(statement: "MATCH (this)-[: SHOUTED]->(r: Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)")
|
||||||
|
|
||||||
categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT")
|
categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT")
|
||||||
|
|
||||||
badges: [Badge]! @relation(name: "REWARDED", direction: "IN")
|
badges: [Badge]! @relation(name: "REWARDED", direction: "IN")
|
||||||
badgesCount: Int! @cypher(statement: "MATCH (this)<-[: REWARDED]-(r: Badge) RETURN COUNT(r)")
|
badgesCount: Int! @cypher(statement: "MATCH (this)<-[: REWARDED]-(r: Badge) RETURN COUNT(r)")
|
||||||
|
|
||||||
emotions: [EMOTED]
|
emotions: [EMOTED]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
input _UserFilter {
|
input _UserFilter {
|
||||||
AND: [_UserFilter!]
|
AND: [_UserFilter!]
|
||||||
OR: [_UserFilter!]
|
OR: [_UserFilter!]
|
||||||
name_contains: String
|
name_contains: String
|
||||||
about_contains: String
|
about_contains: String
|
||||||
slug_contains: String
|
slug_contains: String
|
||||||
id: ID
|
id: ID
|
||||||
id_not: ID
|
id_not: ID
|
||||||
id_in: [ID!]
|
id_in: [ID!]
|
||||||
id_not_in: [ID!]
|
id_not_in: [ID!]
|
||||||
id_contains: ID
|
id_contains: ID
|
||||||
id_not_contains: ID
|
id_not_contains: ID
|
||||||
id_starts_with: ID
|
id_starts_with: ID
|
||||||
id_not_starts_with: ID
|
id_not_starts_with: ID
|
||||||
id_ends_with: ID
|
id_ends_with: ID
|
||||||
id_not_ends_with: ID
|
id_not_ends_with: ID
|
||||||
friends: _UserFilter
|
friends: _UserFilter
|
||||||
friends_not: _UserFilter
|
friends_not: _UserFilter
|
||||||
friends_in: [_UserFilter!]
|
friends_in: [_UserFilter!]
|
||||||
friends_not_in: [_UserFilter!]
|
friends_not_in: [_UserFilter!]
|
||||||
friends_some: _UserFilter
|
friends_some: _UserFilter
|
||||||
friends_none: _UserFilter
|
friends_none: _UserFilter
|
||||||
friends_single: _UserFilter
|
friends_single: _UserFilter
|
||||||
friends_every: _UserFilter
|
friends_every: _UserFilter
|
||||||
following: _UserFilter
|
following: _UserFilter
|
||||||
following_not: _UserFilter
|
following_not: _UserFilter
|
||||||
following_in: [_UserFilter!]
|
following_in: [_UserFilter!]
|
||||||
following_not_in: [_UserFilter!]
|
following_not_in: [_UserFilter!]
|
||||||
following_some: _UserFilter
|
following_some: _UserFilter
|
||||||
following_none: _UserFilter
|
following_none: _UserFilter
|
||||||
following_single: _UserFilter
|
following_single: _UserFilter
|
||||||
following_every: _UserFilter
|
following_every: _UserFilter
|
||||||
followedBy: _UserFilter
|
followedBy: _UserFilter
|
||||||
followedBy_not: _UserFilter
|
followedBy_not: _UserFilter
|
||||||
followedBy_in: [_UserFilter!]
|
followedBy_in: [_UserFilter!]
|
||||||
followedBy_not_in: [_UserFilter!]
|
followedBy_not_in: [_UserFilter!]
|
||||||
followedBy_some: _UserFilter
|
followedBy_some: _UserFilter
|
||||||
followedBy_none: _UserFilter
|
followedBy_none: _UserFilter
|
||||||
followedBy_single: _UserFilter
|
followedBy_single: _UserFilter
|
||||||
followedBy_every: _UserFilter
|
followedBy_every: _UserFilter
|
||||||
role_in: [UserGroup!]
|
role_in: [UserGroup!]
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
User(
|
User(
|
||||||
id: ID
|
id: ID
|
||||||
email: String
|
email: String
|
||||||
actorId: String
|
actorId: String
|
||||||
name: String
|
name: String
|
||||||
slug: String
|
slug: String
|
||||||
avatar: String
|
avatar: String
|
||||||
coverImg: String
|
coverImg: String
|
||||||
role: UserGroup
|
role: UserGroup
|
||||||
locationName: String
|
locationName: String
|
||||||
about: String
|
about: String
|
||||||
createdAt: String
|
createdAt: String
|
||||||
updatedAt: String
|
updatedAt: String
|
||||||
friendsCount: Int
|
friendsCount: Int
|
||||||
followingCount: Int
|
followingCount: Int
|
||||||
followedByCount: Int
|
followedByCount: Int
|
||||||
followedByCurrentUser: Boolean
|
followedByCurrentUser: Boolean
|
||||||
contributionsCount: Int
|
contributionsCount: Int
|
||||||
commentedCount: Int
|
commentedCount: Int
|
||||||
shoutedCount: Int
|
shoutedCount: Int
|
||||||
badgesCount: Int
|
badgesCount: Int
|
||||||
first: Int
|
first: Int
|
||||||
offset: Int
|
offset: Int
|
||||||
orderBy: [_UserOrdering]
|
orderBy: [_UserOrdering]
|
||||||
filter: _UserFilter
|
filter: _UserFilter
|
||||||
): [User]
|
): [User]
|
||||||
|
|
||||||
blockedUsers: [User]
|
blockedUsers: [User]
|
||||||
currentUser: User
|
currentUser: User
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
UpdateUser (
|
UpdateUser (
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String
|
name: String
|
||||||
email: String
|
email: String
|
||||||
slug: String
|
slug: String
|
||||||
avatar: String
|
avatar: String
|
||||||
coverImg: String
|
coverImg: String
|
||||||
avatarUpload: Upload
|
avatarUpload: Upload
|
||||||
locationName: String
|
locationName: String
|
||||||
about: String
|
about: String
|
||||||
termsAndConditionsAgreedVersion: String
|
termsAndConditionsAgreedVersion: String
|
||||||
termsAndConditionsAgreedAt: String
|
termsAndConditionsAgreedAt: String
|
||||||
allowEmbedIframes: Boolean
|
allowEmbedIframes: Boolean
|
||||||
): User
|
): User
|
||||||
|
|
||||||
DeleteUser(id: ID!, resource: [Deletable]): User
|
DeleteUser(id: ID!, resource: [Deletable]): User
|
||||||
|
|
||||||
|
|
||||||
block(id: ID!): User
|
block(id: ID!): User
|
||||||
unblock(id: ID!): User
|
unblock(id: ID!): User
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,8 +34,6 @@ export default () => {
|
|||||||
$imageUpload: Upload
|
$imageUpload: Upload
|
||||||
$categoryIds: [ID]
|
$categoryIds: [ID]
|
||||||
$image: String
|
$image: String
|
||||||
$pinned: Boolean
|
|
||||||
$unpinned: Boolean
|
|
||||||
) {
|
) {
|
||||||
UpdatePost(
|
UpdatePost(
|
||||||
id: $id
|
id: $id
|
||||||
@ -45,8 +43,6 @@ export default () => {
|
|||||||
imageUpload: $imageUpload
|
imageUpload: $imageUpload
|
||||||
categoryIds: $categoryIds
|
categoryIds: $categoryIds
|
||||||
image: $image
|
image: $image
|
||||||
pinned: $pinned
|
|
||||||
unpinned: $unpinned
|
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user