Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,29 @@ tolgee:
file-storage-url: http://localhost:8080
```

To enable authentication, add following properties:

```yaml
tolgee:
authentication:
enabled: true
initial-username: <YOUR_REAL_EMAIL>
initial-password: admin
```

You can check `application-e2e.yaml` for further inspiration.
To learn more about externalized configuration in Spring boot, read [the docs](https://docs.spring.io/spring-boot/3.4/reference/features/external-config.html).

Since we set the active profile to `dev`, Spring uses the `application-dev.yaml` configuration file.

## API schema changes

After you change the API schema, you need to run the webapp schema script to update Frontend:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should also mention that the backend must be running for the schema script to work.


```bash
cd webapp && npm run schema
```

## Updating the database changelog

Tolgee uses Liquibase to handle the database migration. The migrations are run on every app startup. To update the changelog, run:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.tolgee.api.v2.controllers
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import io.tolgee.constants.Message
import io.tolgee.dtos.queryResults.UserAccountView
import io.tolgee.dtos.queryResults.organization.OrganizationView
import io.tolgee.exceptions.BadRequestException
import io.tolgee.hateoas.organization.OrganizationModel
Expand Down Expand Up @@ -50,7 +51,7 @@ class AdministrationController(
private val organizationModelAssembler: OrganizationModelAssembler,
private val authenticationFacade: AuthenticationFacade,
private val userAccountService: UserAccountService,
private val pagedResourcesAssembler: PagedResourcesAssembler<UserAccount>,
private val pagedResourcesAssembler: PagedResourcesAssembler<UserAccountView>,
private val userAccountModelAssembler: UserAccountModelAssembler,
private val jwtService: JwtService,
) : IController {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import io.tolgee.dtos.Avatar
import io.tolgee.model.UserAccount
import org.springframework.hateoas.RepresentationModel
import org.springframework.hateoas.server.core.Relation
import java.util.Date

@Relation(collectionRelation = "users", itemRelation = "user")
data class UserAccountModel(
Expand All @@ -14,6 +15,7 @@ data class UserAccountModel(
val avatar: Avatar?,
val globalServerRole: UserAccount.Role,
val mfaEnabled: Boolean,
val lastActivity: Date?,
val deleted: Boolean,
val disabled: Boolean,
) : RepresentationModel<UserAccountModel>()
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.tolgee.hateoas.userAccount

import io.tolgee.api.isMfaEnabled
import io.tolgee.api.v2.controllers.V2UserController
import io.tolgee.dtos.queryResults.UserAccountView
import io.tolgee.model.UserAccount
import io.tolgee.service.AvatarService
import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport
Expand All @@ -10,23 +11,24 @@ import org.springframework.stereotype.Component
@Component
class UserAccountModelAssembler(
private val avatarService: AvatarService,
) : RepresentationModelAssemblerSupport<UserAccount, UserAccountModel>(
) : RepresentationModelAssemblerSupport<UserAccountView, UserAccountModel>(
V2UserController::class.java,
UserAccountModel::class.java,
) {
override fun toModel(entity: UserAccount): UserAccountModel {
val avatar = avatarService.getAvatarLinks(entity.avatarHash)
override fun toModel(view: UserAccountView): UserAccountModel {
val avatar = avatarService.getAvatarLinks(view.avatarHash)

return UserAccountModel(
id = entity.id,
username = entity.username,
name = entity.name,
emailAwaitingVerification = entity.emailVerification?.newEmail,
id = view.id,
username = view.username,
name = view.name,
emailAwaitingVerification = view.emailAwaitingVerification,
avatar = avatar,
globalServerRole = entity.role ?: UserAccount.Role.USER,
mfaEnabled = entity.isMfaEnabled,
deleted = entity.deletedAt != null,
disabled = entity.disabledAt != null,
globalServerRole = view.role ?: UserAccount.Role.USER,
mfaEnabled = view.isMfaEnabled,
deleted = view.deletedAt != null,
disabled = view.disabledAt != null,
lastActivity = view.lastActivity,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.tolgee.dtos.queryResults
import io.tolgee.api.IUserAccount
import io.tolgee.model.UserAccount
import io.tolgee.model.enums.ThirdPartyAuthType
import java.util.*

class UserAccountView(
val id: Long,
Expand All @@ -15,9 +16,12 @@ class UserAccountView(
val role: UserAccount.Role?,
override var isInitialUser: Boolean,
override val totpKey: ByteArray?,
val deletedAt: Date?,
val disabledAt: Date?,
val lastActivity: Date?
) : IUserAccount {
companion object {
fun fromEntity(entity: UserAccount): UserAccountView {
fun fromEntity(entity: UserAccount, lastActivity: Date?): UserAccountView {
return UserAccountView(
id = entity.id,
username = entity.username,
Expand All @@ -29,6 +33,9 @@ class UserAccountView(
role = entity.role ?: UserAccount.Role.USER,
isInitialUser = entity.isInitialUser,
totpKey = entity.totpKey,
deletedAt = entity.deletedAt,
disabledAt = entity.disabledAt,
lastActivity = lastActivity,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,10 @@ interface UserAccountRepository : JpaRepository<UserAccount, Long> {
ua.thirdPartyAuthType,
ua.role,
ua.isInitialUser,
ua.totpKey
ua.totpKey,
ua.deletedAt,
ua.disabledAt,
null
) from UserAccount ua
left join ua.emailVerification ev
where ua.id = :userAccountId and ua.deletedAt is null and ua.disabledAt is null
Expand Down Expand Up @@ -258,7 +261,29 @@ interface UserAccountRepository : JpaRepository<UserAccount, Long> {

@Query(
"""
with lastActivityCTE as (
select ar.authorId as authorId, max(ar.timestamp) as lastActivity
from ActivityRevision ar
group by ar.authorId
)
select new io.tolgee.dtos.queryResults.UserAccountView(
userAccount.id,
userAccount.username,
userAccount.name,
case when ev is not null then coalesce(ev.newEmail, userAccount.username) else null end,
userAccount.avatarHash,
userAccount.accountType,
userAccount.thirdPartyAuthType,
userAccount.role,
userAccount.isInitialUser,
userAccount.totpKey,
userAccount.deletedAt,
userAccount.disabledAt,
la.lastActivity
)
from UserAccount userAccount
left join userAccount.emailVerification ev
left join lastActivityCTE la on la.authorId = userAccount.id
where ((lower(userAccount.name)
like lower(concat('%', cast(:search as text),'%'))
or lower(userAccount.username) like lower(concat('%', cast(:search as text),'%'))) or cast(:search as text) is null)
Expand All @@ -268,7 +293,7 @@ interface UserAccountRepository : JpaRepository<UserAccount, Long> {
fun findAllWithDisabledPaged(
search: String?,
pageable: Pageable,
): Page<UserAccount>
): Page<UserAccountView>

@Query(
value = """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@ class UserAccountService(
fun findAllWithDisabledPaged(
pageable: Pageable,
search: String?,
): Page<UserAccount> {
): Page<UserAccountView> {
return userAccountRepository.findAllWithDisabledPaged(search, pageable)
}

Expand Down
61 changes: 2 additions & 59 deletions webapp/src/service/apiSchema.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -936,9 +936,6 @@ export interface paths {
/** Returns initial data required by the UI to load */
get: operations["get_17"];
};
"/v2/public/llm/prompt": {
post: operations["prompt"];
};
"/v2/public/machine-translation-providers": {
/** Get machine translation providers */
get: operations["getInfo_4"];
Expand Down Expand Up @@ -3406,16 +3403,6 @@ export interface components {
/** Format: int64 */
untranslatedWordCount: number;
};
LlmMessage: {
image?: string;
text?: string;
type: "TEXT" | "IMAGE";
};
LlmParams: {
messages: components["schemas"]["LlmMessage"][];
priority: "LOW" | "HIGH";
shouldOutputJson: boolean;
};
LlmProviderModel: {
apiKey?: string;
apiUrl?: string;
Expand Down Expand Up @@ -4478,13 +4465,6 @@ export interface components {
/** Format: int64 */
outputTokens?: number;
};
PromptResult: {
parsedJson?: components["schemas"]["JsonNode"];
/** Format: int32 */
price: number;
response: string;
usage?: components["schemas"]["PromptResponseUsageDto"];
};
PromptRunDto: {
basicPromptOptions?: (
| "KEY_NAME"
Expand Down Expand Up @@ -5945,6 +5925,8 @@ export interface components {
globalServerRole: "USER" | "ADMIN";
/** Format: int64 */
id: number;
/** Format: date-time */
lastActivity?: string;
mfaEnabled: boolean;
name?: string;
username: string;
Expand Down Expand Up @@ -19320,45 +19302,6 @@ export interface operations {
};
};
};
prompt: {
responses: {
/** OK */
200: {
content: {
"application/json": components["schemas"]["PromptResult"];
};
};
/** Bad Request */
400: {
content: {
"application/json": string;
};
};
/** Unauthorized */
401: {
content: {
"application/json": string;
};
};
/** Forbidden */
403: {
content: {
"application/json": string;
};
};
/** Not Found */
404: {
content: {
"application/json": string;
};
};
};
requestBody: {
content: {
"application/json": components["schemas"]["LlmParams"];
};
};
};
/** Get machine translation providers */
getInfo_4: {
responses: {
Expand Down
16 changes: 14 additions & 2 deletions webapp/src/views/administration/AdministrationUsers.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from 'react';
import { useTranslate } from '@tolgee/react';
import { Box, Chip, ListItem, ListItemText, styled } from '@mui/material';
import { Box, Chip, ListItem, ListItemText, Typography, styled } from '@mui/material';

Check failure on line 3 in webapp/src/views/administration/AdministrationUsers.tsx

View workflow job for this annotation

GitHub Actions / Frontend static check 🪲

Replace `·Box,·Chip,·ListItem,·ListItemText,·Typography,·styled·` with `⏎··Box,⏎··Chip,⏎··ListItem,⏎··ListItemText,⏎··Typography,⏎··styled,⏎`

import { PaginatedHateoasList } from 'tg.component/common/list/PaginatedHateoasList';
import { DashboardPage } from 'tg.component/layout/DashboardPage';
Expand Down Expand Up @@ -71,7 +71,19 @@
sx={{ display: 'grid', gridTemplateColumns: '1fr auto' }}
>
<ListItemText>
{u.name} | {u.username} <Chip size="small" label={u.id} />
<Typography variant="body1" component="div">
{u.name} | {u.username} <Chip size="small" label={u.id} />
</Typography>
<Typography

Check failure on line 77 in webapp/src/views/administration/AdministrationUsers.tsx

View workflow job for this annotation

GitHub Actions / Frontend static check 🪲

Delete `·`
variant="caption"

Check failure on line 78 in webapp/src/views/administration/AdministrationUsers.tsx

View workflow job for this annotation

GitHub Actions / Frontend static check 🪲

Delete `·`
color="text.secondary"
sx={{ fontStyle: 'italic', display: 'block' }}
>
{u.lastActivity

Check failure on line 82 in webapp/src/views/administration/AdministrationUsers.tsx

View workflow job for this annotation

GitHub Actions / Frontend static check 🪲

Delete `·`
? `Last Activity: ${new Date(u.lastActivity).toLocaleString()}`

Check failure on line 83 in webapp/src/views/administration/AdministrationUsers.tsx

View workflow job for this annotation

GitHub Actions / Frontend static check 🪲

Replace `u.lastActivity).toLocaleString()}`·` with `⏎··························u.lastActivity⏎························).toLocaleString()}``
: "No activity yet"

Check failure on line 84 in webapp/src/views/administration/AdministrationUsers.tsx

View workflow job for this annotation

GitHub Actions / Frontend static check 🪲

Replace `"No·activity·yet"⏎····················` with `'No·activity·yet'`
}
</Typography>
</ListItemText>
<Box display="flex" justifyContent="center" gap={1}>
{u.mfaEnabled && <MfaBadge />}
Expand Down
Loading