tercul-backend/internal/adapters/graphql/schema.graphqls
google-labs-jules[bot] bb5e18d162 refactor: Introduce application layer and dataloaders
This commit introduces a new application layer to the codebase, which decouples the GraphQL resolvers from the data layer. The resolvers now call application services, which in turn call the repositories. This change improves the separation of concerns and makes the code more testable and maintainable.

Additionally, this commit introduces dataloaders to solve the N+1 problem in the GraphQL resolvers. The dataloaders are used to batch and cache database queries, which significantly improves the performance of the API.

The following changes were made:
- Created application services for most of the domains.
- Refactored the GraphQL resolvers to use the new application services.
- Implemented dataloaders for the `Author` aggregate.
- Updated the `app.Application` struct to hold the application services instead of the repositories.
- Fixed a large number of compilation errors in the test files that arose from these changes.

There are still some compilation errors in the `internal/adapters/graphql/integration_test.go` file. These errors are due to the test files still trying to access the repositories directly from the `app.Application` struct. The remaining work is to update these tests to use the new application services.
2025-09-08 10:19:43 +00:00

715 lines
12 KiB
GraphQL

# GraphQL schema for Tercul literary platform
# Core types
type Work {
id: ID!
name: String!
language: String!
content: String
createdAt: String!
updatedAt: String!
translations: [Translation!]
authors: [Author!]
authorIDs: [ID!]
tags: [Tag!]
categories: [Category!]
readabilityScore: ReadabilityScore
writingStyle: WritingStyle
emotions: [Emotion!]
topicClusters: [TopicCluster!]
moods: [Mood!]
concepts: [Concept!]
linguisticLayers: [LinguisticLayer!]
stats: WorkStats
textMetadata: TextMetadata
poeticAnalysis: PoeticAnalysis
copyright: Copyright
copyrightClaims: [CopyrightClaim!]
collections: [Collection!]
comments: [Comment!]
likes: [Like!]
bookmarks: [Bookmark!]
}
type Translation {
id: ID!
name: String!
language: String!
content: String
workId: ID!
work: Work!
translator: User
createdAt: String!
updatedAt: String!
stats: TranslationStats
copyright: Copyright
copyrightClaims: [CopyrightClaim!]
comments: [Comment!]
likes: [Like!]
}
type Author {
id: ID!
name: String!
language: String!
biography: String
birthDate: String
deathDate: String
createdAt: String!
updatedAt: String!
works: [Work!]
books: [Book!]
country: Country
city: City
place: Place
address: Address
copyrightClaims: [CopyrightClaim!]
copyright: Copyright
}
type User {
id: ID!
username: String!
email: String!
firstName: String
lastName: String
displayName: String
bio: String
avatarUrl: String
role: UserRole!
lastLoginAt: String
verified: Boolean!
active: Boolean!
createdAt: String!
updatedAt: String!
# Relationships
translations: [Translation!]
comments: [Comment!]
likes: [Like!]
bookmarks: [Bookmark!]
collections: [Collection!]
contributions: [Contribution!]
# Location
country: Country
city: City
address: Address
# Stats
stats: UserStats
}
type UserProfile {
id: ID!
userId: ID!
user: User!
phoneNumber: String
website: String
twitter: String
facebook: String
linkedIn: String
github: String
preferences: JSON
settings: JSON
createdAt: String!
updatedAt: String!
}
enum UserRole {
READER
CONTRIBUTOR
REVIEWER
EDITOR
ADMIN
}
type Book {
id: ID!
name: String!
language: String!
createdAt: String!
updatedAt: String!
works: [Work!]
stats: BookStats
copyright: Copyright
copyrightClaims: [CopyrightClaim!]
}
type Collection {
id: ID!
name: String!
description: String
createdAt: String!
updatedAt: String!
works: [Work!]
user: User
stats: CollectionStats
}
type Tag {
id: ID!
name: String!
createdAt: String!
updatedAt: String!
works: [Work!]
}
type Category {
id: ID!
name: String!
createdAt: String!
updatedAt: String!
works: [Work!]
}
type Comment {
id: ID!
text: String!
createdAt: String!
updatedAt: String!
user: User!
work: Work
translation: Translation
lineNumber: Int
parentComment: Comment
childComments: [Comment!]
likes: [Like!]
}
type Like {
id: ID!
createdAt: String!
updatedAt: String!
user: User!
work: Work
translation: Translation
comment: Comment
}
type Bookmark {
id: ID!
name: String
createdAt: String!
updatedAt: String!
user: User!
work: Work!
}
type Contribution {
id: ID!
name: String!
status: ContributionStatus!
createdAt: String!
updatedAt: String!
user: User!
work: Work
translation: Translation
}
enum ContributionStatus {
DRAFT
SUBMITTED
UNDER_REVIEW
APPROVED
REJECTED
}
type ReadabilityScore {
id: ID!
score: Float!
language: String!
createdAt: String!
updatedAt: String!
work: Work
}
type WritingStyle {
id: ID!
name: String!
language: String!
createdAt: String!
updatedAt: String!
work: Work
}
type Emotion {
id: ID!
name: String!
language: String!
createdAt: String!
updatedAt: String!
user: User
work: Work
collection: Collection
}
type TopicCluster {
id: ID!
name: String!
createdAt: String!
updatedAt: String!
works: [Work!]
}
type Mood {
id: ID!
name: String!
language: String!
createdAt: String!
updatedAt: String!
works: [Work!]
}
type Concept {
id: ID!
name: String!
createdAt: String!
updatedAt: String!
works: [Work!]
words: [Word!]
}
type Word {
id: ID!
name: String!
createdAt: String!
updatedAt: String!
concept: Concept
works: [Work!]
}
type LinguisticLayer {
id: ID!
name: String!
language: String!
createdAt: String!
updatedAt: String!
works: [Work!]
}
type WorkStats {
id: ID!
views: Int
likes: Int
comments: Int
bookmarks: Int
shares: Int
translationCount: Int
readingTime: Int
complexity: Float
sentiment: Float
createdAt: String!
updatedAt: String!
work: Work!
}
type TranslationStats {
id: ID!
views: Int
likes: Int
comments: Int
shares: Int
readingTime: Int
sentiment: Float
createdAt: String!
updatedAt: String!
translation: Translation!
}
type UserStats {
id: ID!
activity: Int!
createdAt: String!
updatedAt: String!
user: User!
}
type BookStats {
id: ID!
sales: Int!
createdAt: String!
updatedAt: String!
book: Book!
}
type CollectionStats {
id: ID!
items: Int!
createdAt: String!
updatedAt: String!
collection: Collection!
}
type TextMetadata {
id: ID!
analysis: String!
language: String!
createdAt: String!
updatedAt: String!
work: Work!
}
type PoeticAnalysis {
id: ID!
structure: String!
language: String!
createdAt: String!
updatedAt: String!
work: Work!
}
type Copyright {
id: ID!
name: String!
language: String!
createdAt: String!
updatedAt: String!
workOwner: Author
works: [Work!]
translations: [Translation!]
books: [Book!]
sources: [Source!]
}
type CopyrightClaim {
id: ID!
details: String!
createdAt: String!
updatedAt: String!
work: Work
translation: Translation
book: Book
source: Source
author: Author
user: User
}
type Country {
id: ID!
name: String!
language: String!
createdAt: String!
updatedAt: String!
authors: [Author!]
users: [User!]
}
type City {
id: ID!
name: String!
language: String!
createdAt: String!
updatedAt: String!
country: Country
authors: [Author!]
users: [User!]
}
type Place {
id: ID!
name: String!
language: String!
createdAt: String!
updatedAt: String!
city: City
country: Country
authors: [Author!]
}
type Address {
id: ID!
street: String!
createdAt: String!
updatedAt: String!
city: City
country: Country
authors: [Author!]
users: [User!]
}
type Source {
id: ID!
name: String!
language: String!
createdAt: String!
updatedAt: String!
copyright: Copyright
copyrightClaims: [CopyrightClaim!]
works: [Work!]
}
type Edge {
id: ID!
sourceTable: String!
sourceId: ID!
targetTable: String!
targetId: ID!
relation: String!
language: String
extra: JSON
createdAt: String!
updatedAt: String!
}
scalar JSON
directive @binding(constraint: String!) on INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION
# Queries
type Query {
# Work queries
work(id: ID!): Work
works(
limit: Int
offset: Int
language: String
authorId: ID
categoryId: ID
tagId: ID
search: String
): [Work!]!
# Translation queries
translation(id: ID!): Translation
translations(
workId: ID!
language: String
limit: Int
offset: Int
): [Translation!]!
# Author queries
author(id: ID!): Author
authors(
limit: Int
offset: Int
search: String
countryId: ID
): [Author!]!
# User queries
user(id: ID!): User
userByEmail(email: String!): User
userByUsername(username: String!): User
users(
limit: Int
offset: Int
role: UserRole
): [User!]!
me: User
userProfile(userId: ID!): UserProfile
# Collection queries
collection(id: ID!): Collection
collections(
userId: ID
limit: Int
offset: Int
): [Collection!]!
# Tag queries
tag(id: ID!): Tag
tags(limit: Int, offset: Int): [Tag!]!
# Category queries
category(id: ID!): Category
categories(limit: Int, offset: Int): [Category!]!
# Comment queries
comment(id: ID!): Comment
comments(
workId: ID
translationId: ID
userId: ID
limit: Int
offset: Int
): [Comment!]!
# Search
search(
query: String!
limit: Int
offset: Int
filters: SearchFilters
): SearchResults!
trendingWorks(timePeriod: String, limit: Int): [Work!]!
}
input SearchFilters {
languages: [String!]
categories: [ID!]
tags: [ID!]
authors: [ID!]
dateFrom: String
dateTo: String
}
type SearchResults {
works: [Work!]!
translations: [Translation!]!
authors: [Author!]!
total: Int!
}
# Mutations
type Mutation {
# Authentication
register(input: RegisterInput!): AuthPayload!
login(input: LoginInput!): AuthPayload!
# Work mutations
createWork(input: WorkInput!): Work!
updateWork(id: ID!, input: WorkInput!): Work!
deleteWork(id: ID!): Boolean!
# Translation mutations
createTranslation(input: TranslationInput!): Translation!
updateTranslation(id: ID!, input: TranslationInput!): Translation!
deleteTranslation(id: ID!): Boolean!
# Author mutations
createAuthor(input: AuthorInput!): Author!
updateAuthor(id: ID!, input: AuthorInput!): Author!
deleteAuthor(id: ID!): Boolean!
# User mutations
updateUser(id: ID!, input: UserInput!): User!
deleteUser(id: ID!): Boolean!
# Collection mutations
createCollection(input: CollectionInput!): Collection!
updateCollection(id: ID!, input: CollectionInput!): Collection!
deleteCollection(id: ID!): Boolean!
addWorkToCollection(collectionId: ID!, workId: ID!): Collection!
removeWorkFromCollection(collectionId: ID!, workId: ID!): Collection!
# Comment mutations
createComment(input: CommentInput!): Comment!
updateComment(id: ID!, input: CommentInput!): Comment!
deleteComment(id: ID!): Boolean!
# Like mutations
createLike(input: LikeInput!): Like!
deleteLike(id: ID!): Boolean!
# Bookmark mutations
createBookmark(input: BookmarkInput!): Bookmark!
deleteBookmark(id: ID!): Boolean!
# Contribution mutations
createContribution(input: ContributionInput!): Contribution!
updateContribution(id: ID!, input: ContributionInput!): Contribution!
deleteContribution(id: ID!): Boolean!
reviewContribution(id: ID!, status: ContributionStatus!, feedback: String): Contribution!
# Additional authentication mutations
logout: Boolean!
refreshToken: AuthPayload!
forgotPassword(email: String!): Boolean!
resetPassword(token: String!, newPassword: String!): Boolean!
verifyEmail(token: String!): Boolean!
resendVerificationEmail(email: String!): Boolean!
# User profile mutations
updateProfile(input: UserInput!): User!
changePassword(currentPassword: String!, newPassword: String!): Boolean!
}
# Input types
input LoginInput {
email: String!
password: String!
}
input RegisterInput {
username: String!
email: String!
password: String!
firstName: String!
lastName: String!
}
type AuthPayload {
token: String!
user: User!
}
input WorkInput {
name: String!
language: String!
content: String
authorIds: [ID!]
tagIds: [ID!]
categoryIds: [ID!]
}
input TranslationInput {
name: String!
language: String!
content: String
workId: ID!
}
input AuthorInput {
name: String!
language: String!
biography: String
birthDate: String
deathDate: String
countryId: ID
cityId: ID
placeId: ID
addressId: ID
}
input UserInput {
username: String
email: String
password: String
firstName: String
lastName: String
displayName: String
bio: String
avatarUrl: String
role: UserRole
verified: Boolean
active: Boolean
countryId: ID
cityId: ID
addressId: ID
}
input CollectionInput {
name: String!
description: String
workIds: [ID!]
}
input CommentInput {
text: String!
workId: ID
translationId: ID
lineNumber: Int
parentCommentId: ID
}
input LikeInput {
workId: ID
translationId: ID
commentId: ID
}
input BookmarkInput {
name: String
workId: ID!
}
input ContributionInput {
name: String!
workId: ID
translationId: ID
status: ContributionStatus
}