project settings backend

This commit is contained in:
Jake Potrebic 2021-03-17 23:10:12 -07:00
parent 9795d8b9a0
commit fd370dbcd9
No known key found for this signature in database
GPG Key ID: 7C58557EC9C421F8
13 changed files with 275 additions and 201 deletions

View File

@ -162,6 +162,11 @@ const msgs: LocaleMessageObject = {
nameExists: 'A project with this name already exists',
slugExists: 'A project with this slug already exists',
invalidName: 'This name contains invalid characters',
tooLongName: 'Project name is too long',
tooLongDesc: 'Project description is too long',
tooManyKeywords: 'Project has too many keywords',
noCategory: 'Project must have a category',
noDescription: 'Project must have a description',
},
},
sendForApproval: 'Send for approval',

View File

@ -4,7 +4,7 @@
<v-card>
<v-card-title class="sticky">
{{ $t('project.settings.title') }}
<v-btn class="flex-right" color="success" @click="save">
<v-btn class="flex-right" color="success" :loading="loading.save" @click="save">
<v-icon left>mdi-check</v-icon>
{{ $t('project.settings.save') }}
</v-btn>
@ -30,7 +30,7 @@
</h2>
<p>{{ $t('project.settings.keywordsSub') }}</p>
<v-combobox
v-model="form.keywords"
v-model="form.settings.keywords"
small-chips
deletable-chips
multiple
@ -47,7 +47,7 @@
{{ $t('project.settings.homepage') }}&nbsp;<small>{{ $t('project.settings.optional') }}</small>
</h2>
<p>{{ $t('project.settings.homepageSub') }}</p>
<v-text-field v-model.trim="form.links.homepage" dense hide-details filled prepend-inner-icon="mdi-home-search" />
<v-text-field v-model.trim="form.settings.homepage" dense hide-details filled prepend-inner-icon="mdi-home-search" />
</div>
<v-divider />
<div>
@ -55,7 +55,7 @@
{{ $t('project.settings.issues') }}&nbsp;<small>{{ $t('project.settings.optional') }}</small>
</h2>
<p>{{ $t('project.settings.issuesSub') }}</p>
<v-text-field v-model.trim="form.links.issues" dense hide-details filled prepend-inner-icon="mdi-bug" />
<v-text-field v-model.trim="form.settings.issues" dense hide-details filled prepend-inner-icon="mdi-bug" />
</div>
<v-divider />
<div>
@ -63,7 +63,7 @@
{{ $t('project.settings.source') }}&nbsp;<small>{{ $t('project.settings.optional') }}</small>
</h2>
<p>{{ $t('project.settings.sourceSub') }}</p>
<v-text-field v-model.trim="form.links.source" dense hide-details filled prepend-inner-icon="mdi-source-branch" />
<v-text-field v-model.trim="form.settings.source" dense hide-details filled prepend-inner-icon="mdi-source-branch" />
</div>
<v-divider />
<div>
@ -71,10 +71,11 @@
{{ $t('project.settings.support') }}&nbsp;<small>{{ $t('project.settings.optional') }}</small>
</h2>
<p>{{ $t('project.settings.supportSub') }}</p>
<v-text-field v-model.trim="form.links.support" dense hide-details filled prepend-inner-icon="mdi-face-agent" />
<v-text-field v-model.trim="form.settings.support" dense hide-details filled prepend-inner-icon="mdi-face-agent" />
</div>
<v-divider />
<div>
<!--TODO license stuff is outta whack. Different object schema on request and post-->
<h2>
{{ $t('project.settings.license') }}&nbsp;<small>{{ $t('project.settings.optional') }}</small>
</h2>
@ -82,7 +83,7 @@
<v-row>
<v-col cols="12" :md="isCustomLicense ? 4 : 6">
<v-select
v-model="form.license.type"
v-model="form.settings.license.type"
dense
hide-details
filled
@ -92,10 +93,16 @@
/>
</v-col>
<v-col v-if="isCustomLicense" cols="12" md="8">
<v-text-field v-model.trim="form.license.customName" dense hide-details filled :label="$t('project.settings.licenceCustom')" />
<v-text-field
v-model.trim="form.settings.license.customName"
dense
hide-details
filled
:label="$t('project.settings.licenceCustom')"
/>
</v-col>
<v-col cols="12" :md="isCustomLicense ? 12 : 6">
<v-text-field v-model.trim="form.license.url" dense hide-details filled :label="$t('project.settings.licenceUrl')" />
<v-text-field v-model.trim="form.settings.license.url" dense hide-details filled :label="$t('project.settings.licenceUrl')" />
</v-col>
</v-row>
</div>
@ -107,7 +114,7 @@
<p>{{ $t('project.settings.forumSub') }}</p>
</v-col>
<v-col cols="12" md="4">
<v-switch v-model="form.forumSync"></v-switch>
<v-switch v-model="form.settings.forumSync"></v-switch>
</v-col>
</v-row>
</div>
@ -189,7 +196,7 @@
<v-divider />
</v-card-text>
<v-card-actions>
<v-btn color="success" @click="save">
<v-btn color="success" :loading="loading.save" @click="save">
<v-icon left>mdi-check</v-icon>
{{ $t('project.settings.save') }}
</v-btn>
@ -211,6 +218,7 @@
<script lang="ts">
import { Component } from 'nuxt-property-decorator';
import { ProjectSettingsForm } from 'hangar-internal';
import { ProjectPermission } from '~/utils/perms';
import { NamedPermission, ProjectCategory } from '~/types/enums';
import { RootState } from '~/store';
@ -224,33 +232,32 @@ import { HangarProjectMixin } from '~/components/mixins';
export default class ProjectManagePage extends HangarProjectMixin {
apiKey = '';
newName = '';
form = {
keywords: [] as string[],
links: {
homepage: null as string | null,
issues: null as string | null,
source: null as string | null,
support: null as string | null,
form: ProjectSettingsForm = {
settings: {
homepage: null,
issues: null,
source: null,
support: null,
keywords: [],
license: {
type: '',
url: '',
customName: '',
},
forumSync: false,
},
forumSync: false,
description: '',
license: {
type: '',
url: '',
customName: '',
},
category: ProjectCategory.UNDEFINED,
};
loading = {
save: false,
};
created() {
this.form.keywords = this.project.settings.keywords;
this.form.links.homepage = this.project.settings.homepage;
this.form.links.issues = this.project.settings.issues;
this.form.links.source = this.project.settings.sources;
this.form.links.support = this.project.settings.support;
Object.assign(this.form.settings, this.project.settings);
Object.assign(this.form.settings.license, this.project.settings.license);
this.form.description = this.project.description;
this.form.forumSync = this.project.settings.forumSync;
Object.assign(this.form.license, this.project.settings.license);
this.form.category = this.project.category;
}
@ -259,7 +266,7 @@ export default class ProjectManagePage extends HangarProjectMixin {
}
get isCustomLicense() {
return this.form.license.type === '(custom)';
return this.form.settings.license.type === '(custom)';
}
// TODO do we want to get those from the server? Jake: I think so, it'd be nice to admins to be able to configure default licenses, but not needed for MVP
@ -268,7 +275,20 @@ export default class ProjectManagePage extends HangarProjectMixin {
}
// TODO implement
save() {}
save() {
this.loading.save = true;
this.$api
.requestInternal(`projects/project/${this.$route.params.author}/${this.$route.params.slug}/settings`, true, 'post', {
...this.form,
})
.then(() => {
this.$nuxt.refresh();
})
.catch(this.$util.handleRequestError)
.finally(() => {
this.loading.save = false;
});
}
rename() {}

View File

@ -104,7 +104,7 @@
<v-row>
<v-col cols="12">
<v-text-field
v-model.trim="form.links.homepage"
v-model.trim="form.settings.homepage"
dense
hide-details
filled
@ -114,7 +114,7 @@
</v-col>
<v-col cols="12">
<v-text-field
v-model.trim="form.links.issues"
v-model.trim="form.settings.issues"
dense
hide-details
filled
@ -124,7 +124,7 @@
</v-col>
<v-col cols="12">
<v-text-field
v-model.trim="form.links.source"
v-model.trim="form.settings.source"
dense
hide-details
filled
@ -134,7 +134,7 @@
</v-col>
<v-col cols="12">
<v-text-field
v-model.trim="form.links.support"
v-model.trim="form.settings.support"
dense
hide-details
filled
@ -151,7 +151,7 @@
<v-row>
<v-col cols="12" :md="isCustomLicense ? 4 : 6">
<v-select
v-model="form.license.type"
v-model="form.settings.license.type"
dense
hide-details
filled
@ -161,10 +161,16 @@
/>
</v-col>
<v-col v-if="isCustomLicense" cols="12" md="8">
<v-text-field v-model.trim="form.license.customName" dense hide-details filled :label="$t('project.new.step3.customName')" />
<v-text-field
v-model.trim="form.settings.license.customName"
dense
hide-details
filled
:label="$t('project.new.step3.customName')"
/>
</v-col>
<v-col cols="12" :md="isCustomLicense ? 12 : 6">
<v-text-field v-model.trim="form.license.url" dense hide-details filled :label="$t('project.new.step3.url')" />
<v-text-field v-model.trim="form.settings.license.url" dense hide-details filled :label="$t('project.new.step3.url')" />
</v-col>
</v-row>
<div class="text-h6 pt-5">
@ -175,7 +181,7 @@
<v-row>
<v-col cols="12">
<v-combobox
v-model="form.keywords"
v-model="form.settings.keywords"
small-chips
deletable-chips
multiple
@ -230,31 +236,17 @@
<script lang="ts">
import { Component, Vue, Watch } from 'nuxt-property-decorator';
import { Context } from '@nuxt/types';
import { ProjectOwner } from 'hangar-internal';
import { ProjectOwner, ProjectSettingsForm } from 'hangar-internal';
import { AxiosError } from 'axios';
import { TranslateResult } from 'vue-i18n';
import StepperStepContent from '~/components/steppers/StepperStepContent.vue';
import { RootState } from '~/store';
import { ProjectCategory } from '~/types/enums';
interface NewProjectForm {
interface NewProjectForm extends ProjectSettingsForm {
ownerId: ProjectOwner['userId'];
name: string;
description: string;
category: ProjectCategory;
pageContent: string | null;
links: {
homepage: string | null;
issues: string | null;
source: string | null;
support: string | null;
};
license: {
type: string | null;
url: string | null;
customName: string | null;
};
keywords: string[];
}
@Component({
@ -272,12 +264,13 @@ export default class NewPage extends Vue {
projectError = false;
projectOwners!: ProjectOwner[];
error = null as string | null;
form = ({
form: NewProjectForm = {
category: ProjectCategory.ADMIN_TOOLS,
links: {},
license: {},
keywords: [],
} as unknown) as NewProjectForm;
settings: ({
license: {} as ProjectSettingsForm['settings']['license'],
keywords: [],
} as unknown) as ProjectSettingsForm['settings'],
} as NewProjectForm;
nameErrors: TranslateResult[] = [];
@ -294,7 +287,7 @@ export default class NewPage extends Vue {
}
get isCustomLicense() {
return this.form.license.type === '(custom)';
return this.form.settings.license.type === '(custom)';
}
get noBasicSettingsError() {

View File

@ -1,6 +1,7 @@
declare module 'hangar-internal' {
import { Table, FlagReason } from 'hangar-internal';
import { FlagReason, Table } from 'hangar-internal';
import { Project, Role, User } from 'hangar-api';
import { ProjectCategory } from '~/types/enums';
interface ProjectOwner {
id: number;
@ -64,4 +65,22 @@ declare module 'hangar-internal' {
message: string;
user: User;
}
interface ProjectSettingsForm {
settings: {
homepage: string | null;
issues: string | null;
source: string | null;
support: string | null;
keywords: string[];
license: {
type: string;
url: string;
customName: string;
};
forumSync: false;
};
category: ProjectCategory;
description: string;
}
}

View File

@ -1,11 +1,15 @@
package io.papermc.hangar.controller.internal;
import io.papermc.hangar.controller.HangarController;
import io.papermc.hangar.model.common.NamedPermission;
import io.papermc.hangar.model.common.Permission;
import io.papermc.hangar.model.common.PermissionType;
import io.papermc.hangar.model.db.projects.ProjectTable;
import io.papermc.hangar.model.internal.HangarProject;
import io.papermc.hangar.model.internal.api.requests.projects.NewProject;
import io.papermc.hangar.model.internal.api.requests.projects.NewProjectForm;
import io.papermc.hangar.model.internal.api.requests.projects.ProjectSettingsForm;
import io.papermc.hangar.model.internal.api.responses.PossibleProjectOwner;
import io.papermc.hangar.security.annotations.permission.PermissionRequired;
import io.papermc.hangar.security.annotations.unlocked.Unlocked;
import io.papermc.hangar.security.annotations.visibility.VisibilityRequired;
import io.papermc.hangar.security.annotations.visibility.VisibilityRequired.Type;
@ -64,7 +68,7 @@ public class ProjectController extends HangarController {
@Unlocked
@PostMapping(value = "/create", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> createProject(@RequestBody @Valid NewProject newProject) {
public ResponseEntity<String> createProject(@RequestBody @Valid NewProjectForm newProject) {
ProjectTable projectTable = projectFactory.createProject(newProject);
return ResponseEntity.ok(projectTable.getUrl());
}
@ -76,6 +80,15 @@ public class ProjectController extends HangarController {
return ResponseEntity.ok(projectService.getHangarProject(author, slug));
}
@Unlocked
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.EDIT_SUBJECT_SETTINGS, args = "{#author, #slug}")
@PostMapping(path = "/project/{author}/{slug}/settings", consumes = MediaType.APPLICATION_JSON_VALUE)
public void saveProjectSettings(@PathVariable String author, @PathVariable String slug, @Valid @RequestBody ProjectSettingsForm settingsForm) {
projectService.saveSettings(author, slug, settingsForm);
}
@Unlocked
@VisibilityRequired(type = Type.PROJECT, args = "{#projectId}")
@PostMapping("/project/{id}/star/{state}")
@ResponseStatus(HttpStatus.OK)
@ -83,6 +96,7 @@ public class ProjectController extends HangarController {
userService.toggleStarred(projectId, state);
}
@Unlocked
@VisibilityRequired(type = Type.PROJECT, args = "{#projectId}")
@PostMapping("/project/{id}/watch/{state}")
@ResponseStatus(HttpStatus.OK)

View File

@ -1,15 +1,33 @@
package io.papermc.hangar.model.api.project;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonCreator.Mode;
import io.papermc.hangar.util.StringUtils;
import org.jdbi.v3.core.mapper.reflect.JdbiConstructor;
import java.util.Map;
public class ProjectLicense {
private final String name;
private final String url;
@JdbiConstructor
public ProjectLicense(String name, String url) {
this.name = name;
this.url = url;
}
@JsonCreator(mode = Mode.DELEGATING)
public ProjectLicense(Map<String, String> map) {
String licenseName = StringUtils.stringOrNull(map.get("customName"));
if (licenseName == null) {
licenseName = map.get("type");
}
this.name = licenseName;
this.url = map.get("url");
}
public String getName() {
return name;
}

View File

@ -1,8 +1,11 @@
package io.papermc.hangar.model.api.project;
import com.fasterxml.jackson.annotation.JsonCreator;
import io.papermc.hangar.controller.validations.Validate;
import org.jdbi.v3.core.mapper.Nested;
import org.jetbrains.annotations.Nullable;
import javax.validation.constraints.NotNull;
import java.util.Collection;
public class ProjectSettings {
@ -12,9 +15,12 @@ public class ProjectSettings {
private final String source;
private final String support;
private final ProjectLicense license;
@NotNull
@Validate(SpEL = "#root.size le @hangarConfig.projects.maxKeywords", message = "project.new.error.tooManyKeywords")
private final Collection<String> keywords;
private final boolean forumSync;
@JsonCreator
public ProjectSettings(@Nullable String homepage, @Nullable String issues, @Nullable String source, @Nullable String support, @Nullable @Nested("license") ProjectLicense license, Collection<String> keywords, boolean forumSync) {
this.homepage = homepage;
this.issues = issues;

View File

@ -7,7 +7,7 @@ import io.papermc.hangar.model.common.projects.Category;
import io.papermc.hangar.model.common.projects.Visibility;
import io.papermc.hangar.model.db.ProjectIdentified;
import io.papermc.hangar.model.db.Table;
import io.papermc.hangar.model.internal.api.requests.projects.NewProject;
import io.papermc.hangar.model.internal.api.requests.projects.NewProjectForm;
import io.papermc.hangar.util.StringUtils;
import org.jdbi.v3.core.enums.EnumByOrdinal;
import org.jdbi.v3.core.mapper.reflect.JdbiConstructor;
@ -36,21 +36,21 @@ public class ProjectTable extends Table implements Visitable, ModelVisible, Proj
private String licenseUrl;
private boolean forumSync;
public ProjectTable(ProjectOwner projectOwner, NewProject newProject) {
this.name = newProject.getName();
public ProjectTable(ProjectOwner projectOwner, NewProjectForm form) {
this.name = form.getName();
this.slug = StringUtils.slugify(this.name);
this.ownerName = projectOwner.getName();
this.ownerId = projectOwner.getUserId();
this.category = newProject.getCategory();
this.description = newProject.getDescription();
this.category = form.getCategory();
this.description = form.getDescription();
this.visibility = Visibility.NEW;
this.homepage = newProject.getHomepageUrl();
this.issues = newProject.getIssuesUrl();
this.source = newProject.getSourceUrl();
this.support = newProject.getSupportUrl();
this.keywords = newProject.getKeywords();
this.licenseName = newProject.getLicenseName();
this.licenseUrl = newProject.getLicenseUrl();
this.homepage = form.getSettings().getHomepage();
this.issues = form.getSettings().getIssues();
this.source = form.getSettings().getSource();
this.support = form.getSettings().getSupport();
this.keywords = form.getSettings().getKeywords();
this.licenseName = form.getSettings().getLicense().getName();
this.licenseUrl = form.getSettings().getLicense().getUrl();
}
protected ProjectTable(ProjectTable other) {

View File

@ -1,121 +0,0 @@
package io.papermc.hangar.model.internal.api.requests.projects;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.papermc.hangar.controller.validations.Validate;
import io.papermc.hangar.model.common.projects.Category;
import io.papermc.hangar.util.StringUtils;
import javax.validation.constraints.NotNull;
import java.util.Map;
import java.util.Set;
public class NewProject {
private final long ownerId;
@NotNull
@Validate(SpEL = "#root.length le @hangarConfig.projects.maxNameLen", message = "Project name too long") // TODO i18n
@Validate(SpEL = "#root matches @hangarConfig.projects.nameRegex", message = "Invalid project name") // TODO i18n
private final String name;
@NotNull
@Validate(SpEL = "#root.length le @hangarConfig.projects.maxDescLen", message = "Project description too long") // TODO i18n
private final String description;
@NotNull
private final Category category;
private final String pageContent;
private final String homepageUrl;
private final String issuesUrl;
private final String sourceUrl;
private final String supportUrl;
private final String licenseName;
private final String licenseUrl;
@NotNull
@Validate(SpEL = "#root.size le @hangarConfig.projects.maxKeywords", message = "Too many keywords") // TODO i18n
private final Set<String> keywords;
@JsonCreator
public NewProject(long ownerId, @NotNull String name, @NotNull String description, @NotNull Category category, String pageContent, @JsonProperty("links") Map<String, String> linkMap, @JsonProperty("license") Map<String, String> licenseMap, @NotNull Set<String> keywords) {
this.ownerId = ownerId;
this.name = StringUtils.compact(name);
this.description = description;
this.category = category;
this.pageContent = pageContent;
this.homepageUrl = linkMap.get("homepage");
this.issuesUrl = linkMap.get("issues");
this.sourceUrl = linkMap.get("source");
this.supportUrl = linkMap.get("support");
String licenseName = StringUtils.stringOrNull(licenseMap.get("customName"));
if (licenseName == null) {
licenseName = licenseMap.get("type");
}
this.licenseName = licenseName;
this.licenseUrl = licenseMap.get("url");
this.keywords = keywords;
}
public long getOwnerId() {
return ownerId;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public Category getCategory() {
return category;
}
public String getPageContent() {
return pageContent;
}
public String getHomepageUrl() {
return homepageUrl;
}
public String getIssuesUrl() {
return issuesUrl;
}
public String getSourceUrl() {
return sourceUrl;
}
public String getSupportUrl() {
return supportUrl;
}
public String getLicenseName() {
return licenseName;
}
public String getLicenseUrl() {
return licenseUrl;
}
public Set<String> getKeywords() {
return keywords;
}
@Override
public String toString() {
return "NewProject{" +
"ownerId=" + ownerId +
", name='" + name + '\'' +
", description='" + description + '\'' +
", category=" + category +
", pageContent='" + pageContent + '\'' +
", homepageUrl='" + homepageUrl + '\'' +
", issuesUrl='" + issuesUrl + '\'' +
", sourceUrl='" + sourceUrl + '\'' +
", supportUrl='" + supportUrl + '\'' +
", licenseName='" + licenseName + '\'' +
", licenseUrl='" + licenseUrl + '\'' +
", keywords=" + keywords +
'}';
}
}

View File

@ -0,0 +1,45 @@
package io.papermc.hangar.model.internal.api.requests.projects;
import io.papermc.hangar.controller.validations.Validate;
import io.papermc.hangar.model.api.project.ProjectSettings;
import io.papermc.hangar.model.common.projects.Category;
import javax.validation.constraints.NotNull;
public class NewProjectForm extends ProjectSettingsForm {
private final long ownerId;
@NotNull(message = "project.new.error.invalidName")
@Validate(SpEL = "#root.length le @hangarConfig.projects.maxNameLen", message = "project.new.error.tooLongName")
@Validate(SpEL = "#root matches @hangarConfig.projects.nameRegex", message = "project.new.error.invalidName")
private final String name;
private final String pageContent;
public NewProjectForm(ProjectSettings settings, Category category, String description, long ownerId, String name, String pageContent) {
super(settings, category, description);
this.ownerId = ownerId;
this.name = name;
this.pageContent = pageContent;
}
public long getOwnerId() {
return ownerId;
}
public String getName() {
return name;
}
public String getPageContent() {
return pageContent;
}
@Override
public String toString() {
return "NewProjectForm{" +
"ownerId=" + ownerId +
", name='" + name + '\'' +
", pageContent='" + pageContent + '\'' +
"} " + super.toString();
}
}

View File

@ -0,0 +1,48 @@
package io.papermc.hangar.model.internal.api.requests.projects;
import com.fasterxml.jackson.annotation.JsonCreator;
import io.papermc.hangar.controller.validations.Validate;
import io.papermc.hangar.model.api.project.ProjectSettings;
import io.papermc.hangar.model.common.projects.Category;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
public class ProjectSettingsForm {
@Valid
private final ProjectSettings settings;
@NotNull(message = "project.new.error.noCategory")
private final Category category;
@NotNull(message = "project.new.error.noDescription")
@Validate(SpEL = "#root.length le @hangarConfig.projects.maxDescLen", message = "project.new.error.tooLongDesc")
private final String description;
@JsonCreator
public ProjectSettingsForm(ProjectSettings settings, Category category, String description) {
this.settings = settings;
this.category = category;
this.description = description;
}
public ProjectSettings getSettings() {
return settings;
}
public Category getCategory() {
return category;
}
public String getDescription() {
return description;
}
@Override
public String toString() {
return "ProjectSettingsForm{" +
"settings=" + settings +
", category=" + category +
", description='" + description + '\'' +
'}';
}
}

View File

@ -7,7 +7,7 @@ import io.papermc.hangar.model.common.roles.ProjectRole;
import io.papermc.hangar.model.db.members.ProjectMemberTable;
import io.papermc.hangar.model.db.projects.ProjectOwner;
import io.papermc.hangar.model.db.projects.ProjectTable;
import io.papermc.hangar.model.internal.api.requests.projects.NewProject;
import io.papermc.hangar.model.internal.api.requests.projects.NewProjectForm;
import io.papermc.hangar.service.HangarService;
import io.papermc.hangar.service.api.UsersApiService;
import io.papermc.hangar.service.internal.roles.MemberService;
@ -37,7 +37,7 @@ public class ProjectFactory extends HangarService {
this.usersApiService = usersApiService;
}
public ProjectTable createProject(NewProject newProject) {
public ProjectTable createProject(NewProjectForm newProject) {
ProjectOwner projectOwner = projectService.getProjectOwner(newProject.getOwnerId());
if (projectOwner == null) {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "error.project.ownerNotFound");

View File

@ -1,9 +1,12 @@
package io.papermc.hangar.service.internal.projects;
import io.papermc.hangar.db.customtypes.LoggedActionType;
import io.papermc.hangar.db.customtypes.LoggedActionType.ProjectContext;
import io.papermc.hangar.db.dao.HangarDao;
import io.papermc.hangar.db.dao.internal.HangarProjectsDAO;
import io.papermc.hangar.db.dao.internal.HangarUsersDAO;
import io.papermc.hangar.db.dao.internal.table.projects.ProjectsDAO;
import io.papermc.hangar.exceptions.HangarApiException;
import io.papermc.hangar.model.api.project.Project;
import io.papermc.hangar.model.common.Permission;
import io.papermc.hangar.model.db.OrganizationTable;
@ -15,6 +18,7 @@ import io.papermc.hangar.model.internal.HangarProject;
import io.papermc.hangar.model.internal.HangarProject.HangarProjectInfo;
import io.papermc.hangar.model.internal.HangarProjectFlag;
import io.papermc.hangar.model.internal.HangarProjectPage;
import io.papermc.hangar.model.internal.api.requests.projects.ProjectSettingsForm;
import io.papermc.hangar.model.internal.user.JoinableMember;
import io.papermc.hangar.service.HangarService;
import io.papermc.hangar.service.VisibilityService.ProjectVisibilityService;
@ -23,6 +27,7 @@ import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import java.util.List;
@ -89,6 +94,28 @@ public class ProjectService extends HangarService {
return new HangarProject(project.getRight(), project.getLeft(), projectOwner, members, "", "", info, pages.values());
}
public void saveSettings(String author, String slug, ProjectSettingsForm settingsForm) {
ProjectTable projectTable = getProjectTable(author, slug);
if (projectTable == null) {
throw new HangarApiException(HttpStatus.NOT_FOUND);
}
projectTable.setCategory(settingsForm.getCategory());
projectTable.setKeywords(settingsForm.getSettings().getKeywords());
projectTable.setHomepage(settingsForm.getSettings().getHomepage());
projectTable.setIssues(settingsForm.getSettings().getIssues());
projectTable.setSource(settingsForm.getSettings().getSource());
projectTable.setSupport(settingsForm.getSettings().getSupport());
projectTable.setLicenseName(settingsForm.getSettings().getLicense().getName());
projectTable.setLicenseUrl(settingsForm.getSettings().getLicense().getUrl());
projectTable.setForumSync(settingsForm.getSettings().isForumSync());
projectTable.setDescription(settingsForm.getDescription());
projectsDAO.update(projectTable);
// TODO is icon change?
// TODO role updates
refreshHomeProjects();
userActionLogService.project(LoggedActionType.PROJECT_SETTINGS_CHANGED.with(ProjectContext.of(projectTable.getId())), "", "");
}
// TODO implement flag view
public List<HangarProjectFlag> getHangarProjectFlags(String author, String slug) {
return hangarProjectsDAO.getHangarProjectFlags(author, slug);