Partilhar: WhatsApp
aulify
UC UC02834 · T. Desenv. Software

Ficha 02 · API REST, BD e autenticação

Endpoints REST, SQLAlchemy, login
Versão · Aluno
Tempo · 60 minutos
Cotação · 100 pontos
Aluno(a)
Turma
Data
Objectivos da ficha

Parte I · API REST

Exercício 1 · Desenhar endpoints (15 pts)

Para um recurso "tarefa", desenha os endpoints REST (método + rota + código de sucesso):

a) Listar todas. ___

b) Obter uma. ___

c) Criar. ___

d) Actualizar. ___

e) Apagar. ___

Operação Método + rota Código
Listar GET /api/tarefas 200
Obter uma GET /api/tarefas/<id> 200 / 404
Criar POST /api/tarefas 201
Actualizar PUT /api/tarefas/<id> (ou PATCH) 200 / 404
Apagar DELETE /api/tarefas/<id> 204 / 404

Recursos no plural, sem verbos na URL.

Exercício 2 · Implementar API (20 pts)

Implementa em Flask os endpoints GET (listar), GET (um), POST (criar) para "tarefa" (id, titulo, feita). Trata 404 e validação.

from flask import Flask, jsonify, request

app = Flask(__name__)
tarefas = []
proximo_id = 1

@app.route("/api/tarefas", methods=["GET"])
def listar():
    return jsonify(tarefas)

@app.route("/api/tarefas/<int:id>", methods=["GET"])
def obter(id):
    t = next((x for x in tarefas if x["id"] == id), None)
    if t is None:
        return jsonify({"erro": "Não encontrada"}), 404
    return jsonify(t)

@app.route("/api/tarefas", methods=["POST"])
def criar():
    global proximo_id
    dados = request.get_json()
    if not dados or not dados.get("titulo"):
        return jsonify({"erro": "titulo obrigatório"}), 400
    t = {"id": proximo_id, "titulo": dados["titulo"], "feita": False}
    tarefas.append(t)
    proximo_id += 1
    return jsonify(t), 201

Exercício 3 · Códigos de estado (10 pts)

Que código HTTP devolver em cada situação?

a) Recurso criado com sucesso. ___

b) Recurso apagado com sucesso (sem conteúdo a devolver). ___

c) Pedido com JSON inválido. ___

d) Recurso não existe. ___

e) Utilizador não autenticado. ___

a) 201 Created. b) 204 No Content. c) 400 Bad Request. d) 404 Not Found. e) 401 Unauthorized (ou 403 Forbidden se autenticado mas sem permissão).

Parte II · Base de dados

Exercício 4 · Modelo + CRUD (20 pts)

Define um modelo Flask-SQLAlchemy Tarefa(id, titulo, feita) e escreve as 4 operações CRUD.

from flask_sqlalchemy import SQLAlchemy

app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db"
db = SQLAlchemy(app)

class Tarefa(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    titulo = db.Column(db.String(200), nullable=False)
    feita = db.Column(db.Boolean, default=False)

with app.app_context():
    db.create_all()

# CREATE
t = Tarefa(titulo="Estudar")
db.session.add(t)
db.session.commit()

# READ
todas = Tarefa.query.all()
uma = Tarefa.query.get(1)
pendentes = Tarefa.query.filter_by(feita=False).all()

# UPDATE
t = Tarefa.query.get(1)
t.feita = True
db.session.commit()

# DELETE
db.session.delete(t)
db.session.commit()

Exercício 5 · Rota com BD (10 pts)

Escreve uma rota POST /tarefas que cria uma tarefa na BD a partir de um formulário e redirecciona para a lista.

from flask import request, redirect, url_for

@app.route("/tarefas", methods=["POST"])
def criar_tarefa():
    titulo = request.form.get("titulo", "").strip()
    if not titulo:
        flash("Título obrigatório")
        return redirect(url_for("listar_tarefas"))
    db.session.add(Tarefa(titulo=titulo))
    db.session.commit()
    return redirect(url_for("listar_tarefas"))

@app.route("/tarefas")
def listar_tarefas():
    tarefas = Tarefa.query.order_by(Tarefa.id.desc()).all()
    return render_template("tarefas.html", tarefas=tarefas)

Parte III · Autenticação

Exercício 6 · Hash de senha (15 pts)

a) Porque não se guarda a senha em texto plano? (5 pts)

b) Escreve código de registo (hash) e login (verificar) com werkzeug. (10 pts)

a) Se a BD for comprometida (fuga de dados), senhas em texto plano expõem todas as contas — e como utilizadores reutilizam senhas, expõem contas noutros serviços. Hash (com salt) torna inviável recuperar a senha original.

b)

from werkzeug.security import generate_password_hash, check_password_hash

# Registo
def registar(email, senha):
    if len(senha) < 10:
        raise ValueError("Senha demasiado curta")
    user = User(
        email=email,
        pwd_hash=generate_password_hash(senha)
    )
    db.session.add(user)
    db.session.commit()

# Login
def autenticar(email, senha):
    user = User.query.filter_by(email=email).first()
    if user and check_password_hash(user.pwd_hash, senha):
        return user
    return None

generate_password_hash aplica salt automaticamente e usa algoritmo lento (scrypt/pbkdf2).

Exercício 7 · Proteger rota (10 pts)

Com Flask-Login, escreve uma rota /painel acessível só a utilizadores autenticados, mostrando o email do utilizador actual.

from flask_login import login_required, current_user

@app.route("/painel")
@login_required
def painel():
    return render_template("painel.html", utilizador=current_user)
<!-- painel.html -->
{% extends "base.html" %}
{% block conteudo %}
  <h1>Olá, {{ utilizador.email }}</h1>
{% endblock %}

@login_required redirecciona automaticamente para o login se não autenticado. current_user é o utilizador da sessão. A verificação é no servidor — esconder o link no frontend não basta.