Add support for only viewing hints after unlocking another hint (#2074)

* Add support for only viewing hints after unlocking another hint
* Closes #2007
This commit is contained in:
Kevin Chung
2022-04-04 23:17:01 -04:00
committed by GitHub
parent eb8461cf2f
commit 24bf5302c4
9 changed files with 118 additions and 18 deletions

View File

@@ -120,6 +120,33 @@ class Hint(Resource):
user = get_current_user()
hint = Hints.query.filter_by(id=hint_id).first_or_404()
if hint.requirements:
requirements = hint.requirements.get("prerequisites", [])
# Get the IDs of all hints that the user has unlocked
all_unlocks = HintUnlocks.query.filter_by(account_id=user.account_id).all()
unlock_ids = {unlock.id for unlock in all_unlocks}
# Filter out hint IDs that don't exist
all_hint_ids = {h.id for h in Hints.query.with_entities(Hints.id).all()}
prereqs = set(requirements).intersection(all_hint_ids)
# If the user has the necessary unlocks or is admin we should allow them to view
if unlock_ids >= prereqs or is_admin():
pass
else:
return (
{
"success": False,
"errors": {
"requirements": [
"You must unlock other hints before accessing this hint"
]
},
},
403,
)
view = "unlocked"
if hint.cost:
view = "locked"

View File

@@ -25,7 +25,7 @@
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label class="text-muted">
<label>
Hint<br />
<small>Markdown &amp; HTML are supported</small>
</label>
@@ -50,6 +50,31 @@
v-model.lazy="cost"
/>
</div>
<div class="form-group">
<label>
Requirements<br />
<small
>Hints that must be unlocked before unlocking this
hint</small
>
</label>
<div
class="form-check"
v-for="hint in hints"
:key="hint.id"
>
<label class="form-check-label cursor-pointer">
<input
class="form-check-input"
type="checkbox"
:value="hint.id"
v-model="selectedHints"
/>
{{ hint.cost }} - {{ hint.id }}
</label>
</div>
</div>
<input type="hidden" id="hint-id-for-hint" name="id" />
</div>
</div>
@@ -74,11 +99,13 @@
export default {
name: "HintCreationForm",
props: {
challenge_id: Number
challenge_id: Number,
hints: Array
},
data: function() {
return {
cost: 0
cost: 0,
selectedHints: []
};
},
methods: {
@@ -89,11 +116,11 @@ export default {
return this.$refs.content.value;
},
submitHint: function() {
console.log(this.co);
let params = {
challenge_id: this.$props.challenge_id,
content: this.getContent(),
cost: this.getCost()
cost: this.getCost(),
requirements: { prerequisites: this.selectedHints }
};
CTFd.fetch("/api/v1/hints", {
method: "POST",

View File

@@ -25,7 +25,7 @@
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label class="text-muted">
<label>
Hint<br />
<small>Markdown &amp; HTML are supported</small>
</label>
@@ -52,6 +52,31 @@
v-model.lazy="cost"
/>
</div>
<div class="form-group">
<label>
Requirements<br />
<small
>Hints that must be unlocked before unlocking this
hint</small
>
</label>
<div
class="form-check"
v-for="hint in otherHints"
:key="hint.id"
>
<label class="form-check-label cursor-pointer">
<input
class="form-check-input"
type="checkbox"
:value="hint.id"
v-model="selectedHints"
/>
{{ hint.content }} - {{ hint.cost }}
</label>
</div>
</div>
</div>
</div>
</div>
@@ -78,14 +103,25 @@ import { bindMarkdownEditor } from "../../styles";
export default {
name: "HintEditForm",
props: {
hint_id: Number
challenge_id: Number,
hint_id: Number,
hints: Array
},
data: function() {
return {
cost: 0,
content: null
content: null,
selectedHints: []
};
},
computed: {
// Get all hints besides the current one
otherHints: function() {
return this.hints.filter(hint => {
return hint.id !== this.$props.hint_id;
});
}
},
watch: {
hint_id: {
immediate: true,
@@ -114,6 +150,7 @@ export default {
let hint = response.data;
this.cost = hint.cost;
this.content = hint.content;
this.selectedHints = hint.requirements?.prerequisites || [];
// Wait for Vue to update the DOM
this.$nextTick(() => {
// Wait a little longer because we need the modal to appear.
@@ -138,7 +175,8 @@ export default {
let params = {
challenge_id: this.$props.challenge_id,
content: this.getContent(),
cost: this.getCost()
cost: this.getCost(),
requirements: { prerequisites: this.selectedHints }
};
CTFd.fetch(`/api/v1/hints/${this.$props.hint_id}`, {

View File

@@ -4,6 +4,7 @@
<HintCreationForm
ref="HintCreationForm"
:challenge_id="challenge_id"
:hints="hints"
@refreshHints="refreshHints"
/>
</div>
@@ -11,7 +12,9 @@
<div>
<HintEditForm
ref="HintEditForm"
:challenge_id="challenge_id"
:hint_id="editing_hint_id"
:hints="hints"
@refreshHints="refreshHints"
/>
</div>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -420,6 +420,11 @@ const displayUnlock = id => {
const loadHint = id => {
CTFd.api.get_hint({ hintId: id }).then(response => {
if (!response.success) {
let msg = Object.values(response.errors).join("\n");
alert(msg);
return;
}
if (response.data.content) {
displayHint(response.data);
return;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long